agenthub-multiagent-mcp 1.51.0 → 1.53.0
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 +192 -192
- package/dist/captureHookInstall.d.ts +127 -0
- package/dist/captureHookInstall.d.ts.map +1 -0
- package/dist/captureHookInstall.js +256 -0
- package/dist/captureHookInstall.js.map +1 -0
- package/dist/captureHookInstall.test.d.ts +10 -0
- package/dist/captureHookInstall.test.d.ts.map +1 -0
- package/dist/captureHookInstall.test.js +325 -0
- package/dist/captureHookInstall.test.js.map +1 -0
- package/dist/channel.js +99 -3
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +80 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +197 -4
- package/dist/client.js.map +1 -1
- package/dist/client.unit.test.js +50 -0
- package/dist/client.unit.test.js.map +1 -1
- package/dist/daemon.js +217 -3
- package/dist/daemon.js.map +1 -1
- package/dist/hooks/periodicReminder.js +9 -9
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/session-capture.sh +410 -0
- package/dist/sessionCapture.d.ts +163 -0
- package/dist/sessionCapture.d.ts.map +1 -0
- package/dist/sessionCapture.js +279 -0
- package/dist/sessionCapture.js.map +1 -0
- package/dist/sessionCapture.test.d.ts +9 -0
- package/dist/sessionCapture.test.d.ts.map +1 -0
- package/dist/sessionCapture.test.js +366 -0
- package/dist/sessionCapture.test.js.map +1 -0
- package/dist/sessionCaptureScript.test.d.ts +16 -0
- package/dist/sessionCaptureScript.test.d.ts.map +1 -0
- package/dist/sessionCaptureScript.test.js +92 -0
- package/dist/sessionCaptureScript.test.js.map +1 -0
- package/dist/setup.js +56 -56
- package/dist/steering.test.d.ts +2 -0
- package/dist/steering.test.d.ts.map +1 -0
- package/dist/steering.test.js +223 -0
- package/dist/steering.test.js.map +1 -0
- package/dist/streamingRuntimeLib.d.ts +391 -0
- package/dist/streamingRuntimeLib.d.ts.map +1 -0
- package/dist/streamingRuntimeLib.js +628 -0
- package/dist/streamingRuntimeLib.js.map +1 -0
- package/dist/streamingRuntimeLib.test.d.ts +2 -0
- package/dist/streamingRuntimeLib.test.d.ts.map +1 -0
- package/dist/streamingRuntimeLib.test.js +233 -0
- package/dist/streamingRuntimeLib.test.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +149 -13
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/openspec-viz/diagram-generator.js +54 -54
- package/dist/tools/openspec-viz/html-generator.js +503 -503
- package/dist/tools/sessionControl.d.ts +53 -0
- package/dist/tools/sessionControl.d.ts.map +1 -0
- package/dist/tools/sessionControl.js +265 -0
- package/dist/tools/sessionControl.js.map +1 -0
- package/dist/tools/sessionControl.test.d.ts +14 -0
- package/dist/tools/sessionControl.test.d.ts.map +1 -0
- package/dist/tools/sessionControl.test.js +298 -0
- package/dist/tools/sessionControl.test.js.map +1 -0
- package/dist/tools/tools.test.js +5 -2
- package/dist/tools/tools.test.js.map +1 -1
- package/dist/tools/zohoAttachments.test.d.ts +10 -0
- package/dist/tools/zohoAttachments.test.d.ts.map +1 -0
- package/dist/tools/zohoAttachments.test.js +204 -0
- package/dist/tools/zohoAttachments.test.js.map +1 -0
- package/dist/worker.js +10 -10
- package/native/agenthub-memvid/agenthub-memvid.linux-x64-gnu.node +0 -0
- package/native/agenthub-memvid/index.d.ts +58 -58
- package/native/agenthub-memvid/index.js +319 -319
- package/package.json +77 -77
- package/skills/catalog.json +76 -76
- package/skills/commands/close-session.md +144 -144
- package/skills/manifest.json +50 -50
- package/skills/skills/code-brain-hook/SKILL.md +61 -61
- package/skills/skills/file-bug/SKILL.md +75 -75
- package/skills/skills/multiagent-brainstorm/SKILL.md +142 -142
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consent-gated install of the session-capture hook for **human interactive**
|
|
3
|
+
* Claude Code sessions (add-multiplayer-sessions §3.2/§3.3).
|
|
4
|
+
*
|
|
5
|
+
* The daemon path (§1.5) never comes through here: it passes an ephemeral
|
|
6
|
+
* `--settings` file to the process it spawns, so no user-owned file is ever
|
|
7
|
+
* touched. An interactive session has no such seam — the person launches
|
|
8
|
+
* `claude` themselves — so the hook has to be registered in their own
|
|
9
|
+
* `~/.claude/settings.json`.
|
|
10
|
+
*
|
|
11
|
+
* That makes consent the whole design, not a feature of it:
|
|
12
|
+
*
|
|
13
|
+
* - Nothing is written until the **server** confirms that *this user*
|
|
14
|
+
* consented. The MCP does not decide; it asks and obeys.
|
|
15
|
+
* - What gets written is the hook registration plus a grant file. The hook is
|
|
16
|
+
* inert without the grant, and the grant is only ever produced by a
|
|
17
|
+
* confirmed consent, so a stale settings entry cannot capture anything.
|
|
18
|
+
* - When consent is absent or withdrawn, this module **removes** both. Running
|
|
19
|
+
* it is how a revocation reaches the machine, not merely how a grant does.
|
|
20
|
+
* - Every failure — network, parse, permission — leaves capture off. There is
|
|
21
|
+
* no path where "we could not tell" results in recording.
|
|
22
|
+
*
|
|
23
|
+
* `mergeCaptureHook` (sessionCapture.ts) does the settings merge, so the
|
|
24
|
+
* non-destructive behaviour proven there is the behaviour here: other
|
|
25
|
+
* PostToolUse entries, other hook events, and unrelated settings keys survive.
|
|
26
|
+
*/
|
|
27
|
+
import * as fs from "fs";
|
|
28
|
+
import * as path from "path";
|
|
29
|
+
import { CAPTURE_HOOK_MARKER, captureHookCommand, mergeCaptureHook, resolveCaptureHookScript, } from "./sessionCapture.js";
|
|
30
|
+
/** Default gap between in-session recording notices, in seconds. */
|
|
31
|
+
export const DEFAULT_NOTICE_INTERVAL_SEC = 900;
|
|
32
|
+
/** Timeout on the consent lookup. Startup must not hang on it. */
|
|
33
|
+
export const CONSENT_TIMEOUT_MS = 4000;
|
|
34
|
+
/**
|
|
35
|
+
* Remove the capture hook from a settings object, preserving everything else.
|
|
36
|
+
*
|
|
37
|
+
* The inverse of `mergeCaptureHook`, and the reason revocation is effective on
|
|
38
|
+
* the *next* MCP start rather than only server-side. A PostToolUse entry is
|
|
39
|
+
* dropped only if every command in it is ours; an entry a user has added other
|
|
40
|
+
* commands to keeps those, so this can never eat a hook we did not install.
|
|
41
|
+
*/
|
|
42
|
+
export function removeCaptureHook(base) {
|
|
43
|
+
const merged = { ...base };
|
|
44
|
+
const hooksIn = (base.hooks ?? {});
|
|
45
|
+
if (!Array.isArray(hooksIn.PostToolUse))
|
|
46
|
+
return merged;
|
|
47
|
+
const hooks = { ...hooksIn };
|
|
48
|
+
const kept = [];
|
|
49
|
+
for (const entry of hooksIn.PostToolUse) {
|
|
50
|
+
const commands = entry?.hooks ?? [];
|
|
51
|
+
const survivors = commands.filter((h) => !(typeof h?.command === "string" && h.command.includes(CAPTURE_HOOK_MARKER)));
|
|
52
|
+
if (survivors.length > 0)
|
|
53
|
+
kept.push({ ...entry, hooks: survivors });
|
|
54
|
+
}
|
|
55
|
+
hooks.PostToolUse = kept;
|
|
56
|
+
merged.hooks = hooks;
|
|
57
|
+
return merged;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Strip the client's `/api` suffix. `AgentHubClient` appends it for its own
|
|
61
|
+
* calls, but the hook builds `<root>/api/...` itself, and a doubled `/api/api`
|
|
62
|
+
* 404s with an HTML body — a failure that shows up as "capture silently never
|
|
63
|
+
* worked" rather than as an error.
|
|
64
|
+
*/
|
|
65
|
+
export function captureApiRoot(clientBaseUrl) {
|
|
66
|
+
return clientBaseUrl.replace(/\/+$/, "").replace(/\/api$/, "");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Bring the local machine into line with a consent decision, and report what
|
|
70
|
+
* changed.
|
|
71
|
+
*
|
|
72
|
+
* Consented → write the grant, register the hook.
|
|
73
|
+
* Not → delete the grant and its per-session state, deregister the hook.
|
|
74
|
+
*
|
|
75
|
+
* Both directions are idempotent and safe to run on every MCP start.
|
|
76
|
+
*/
|
|
77
|
+
export function applyCaptureConsent(consented, opts, deps = {}) {
|
|
78
|
+
const exists = deps.exists ?? fs.existsSync;
|
|
79
|
+
const readFile = deps.readFile ?? ((p) => fs.readFileSync(p, "utf8"));
|
|
80
|
+
const writeFile = deps.writeFile ?? ((p, d) => fs.writeFileSync(p, d));
|
|
81
|
+
const mkdir = deps.mkdir ?? ((p) => fs.mkdirSync(p, { recursive: true }));
|
|
82
|
+
const removeFile = deps.removeFile ?? fs.unlinkSync;
|
|
83
|
+
const removeDir = deps.removeDir ?? ((p) => fs.rmSync(p, { recursive: true, force: true }));
|
|
84
|
+
const chmod = deps.chmod ?? fs.chmodSync;
|
|
85
|
+
const now = deps.now ?? (() => new Date());
|
|
86
|
+
// Read the user's settings first. A settings file we cannot parse is left
|
|
87
|
+
// strictly alone — rewriting it would destroy configuration we do not
|
|
88
|
+
// understand, and the cost of not installing is only that capture stays off.
|
|
89
|
+
let settings = {};
|
|
90
|
+
if (exists(opts.settingsPath)) {
|
|
91
|
+
try {
|
|
92
|
+
settings = JSON.parse(readFile(opts.settingsPath));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return "skipped: ~/.claude/settings.json is not valid JSON";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!consented) {
|
|
99
|
+
// Revocation reaching the machine. Remove the credential first: if the
|
|
100
|
+
// process dies between the two writes, the leftover is a hook with no
|
|
101
|
+
// grant, which captures nothing.
|
|
102
|
+
let removed = false;
|
|
103
|
+
for (const target of [opts.grantPath]) {
|
|
104
|
+
if (exists(target)) {
|
|
105
|
+
try {
|
|
106
|
+
removeFile(target);
|
|
107
|
+
removed = true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* best effort — the server refuses the events regardless */
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
removeDir(captureStateDir(opts.grantPath));
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* never existed */
|
|
119
|
+
}
|
|
120
|
+
const stripped = removeCaptureHook(settings);
|
|
121
|
+
if (JSON.stringify(stripped) !== JSON.stringify(settings)) {
|
|
122
|
+
try {
|
|
123
|
+
mkdir(path.dirname(opts.settingsPath));
|
|
124
|
+
writeFile(opts.settingsPath, JSON.stringify(stripped, null, 2));
|
|
125
|
+
removed = true;
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
return `not consented; could not deregister hook: ${errText(e)}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return removed
|
|
132
|
+
? "not consented — capture hook and grant removed"
|
|
133
|
+
: "not consented — capture is off (nothing to remove)";
|
|
134
|
+
}
|
|
135
|
+
if (!opts.hookScript) {
|
|
136
|
+
return "consented, but session-capture.sh was not found — capture not installed";
|
|
137
|
+
}
|
|
138
|
+
if (!opts.connectToken) {
|
|
139
|
+
// Without a credential the hook cannot bootstrap, and a grant file with an
|
|
140
|
+
// empty token would look installed while doing nothing.
|
|
141
|
+
return "consented, but no connect token is available yet — capture not installed";
|
|
142
|
+
}
|
|
143
|
+
const grant = {
|
|
144
|
+
api_url: opts.apiRoot,
|
|
145
|
+
user_id: opts.userId,
|
|
146
|
+
agent_id: opts.agentId,
|
|
147
|
+
connect_token: opts.connectToken,
|
|
148
|
+
consented_at: now().toISOString(),
|
|
149
|
+
notice_interval_sec: opts.noticeIntervalSec ?? DEFAULT_NOTICE_INTERVAL_SEC,
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
mkdir(path.dirname(opts.grantPath));
|
|
153
|
+
writeFile(opts.grantPath, JSON.stringify(grant, null, 2));
|
|
154
|
+
// The grant carries a credential, so it is owner-read/write only. A no-op
|
|
155
|
+
// on Windows; still correct to ask for on every platform that honours it.
|
|
156
|
+
try {
|
|
157
|
+
chmod(opts.grantPath, 0o600);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* filesystem does not support it */
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return `consented, but the capture grant could not be written: ${errText(e)}`;
|
|
165
|
+
}
|
|
166
|
+
const command = captureHookCommand(opts.hookScript);
|
|
167
|
+
const alreadyRegistered = JSON.stringify(mergeCaptureHook(settings, command)) === JSON.stringify(settings);
|
|
168
|
+
if (alreadyRegistered)
|
|
169
|
+
return "consented — capture hook already installed, grant refreshed";
|
|
170
|
+
try {
|
|
171
|
+
mkdir(path.dirname(opts.settingsPath));
|
|
172
|
+
writeFile(opts.settingsPath, JSON.stringify(mergeCaptureHook(settings, command), null, 2));
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
return `consented, but the hook could not be registered: ${errText(e)}`;
|
|
176
|
+
}
|
|
177
|
+
return "consented — capture hook installed";
|
|
178
|
+
}
|
|
179
|
+
/** Where the hook keeps its per-Claude-session bootstrap cache. */
|
|
180
|
+
export function captureStateDir(grantPath) {
|
|
181
|
+
return path.join(path.dirname(grantPath), "agenthub-capture-state");
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Ask the server whether the calling identity's owner has consented.
|
|
185
|
+
*
|
|
186
|
+
* Returns `null` on any failure — unreachable server, non-2xx, unparseable
|
|
187
|
+
* body. `null` is not "unknown, carry on"; every caller treats it as "do not
|
|
188
|
+
* capture", so a server we cannot reach can never be the reason someone gets
|
|
189
|
+
* recorded.
|
|
190
|
+
*/
|
|
191
|
+
export async function fetchCaptureConsent(clientBaseUrl, connectToken, fetchImpl, timeoutMs = CONSENT_TIMEOUT_MS) {
|
|
192
|
+
const doFetch = fetchImpl ?? globalThis.fetch;
|
|
193
|
+
if (!doFetch || !connectToken)
|
|
194
|
+
return null;
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
197
|
+
try {
|
|
198
|
+
const res = await doFetch(`${clientBaseUrl.replace(/\/+$/, "")}/session-capture/consent`, {
|
|
199
|
+
method: "GET",
|
|
200
|
+
headers: { "X-Connect-Token": connectToken },
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok)
|
|
204
|
+
return null;
|
|
205
|
+
const body = (await res.json());
|
|
206
|
+
return {
|
|
207
|
+
consented: body?.consent?.consented === true,
|
|
208
|
+
userId: typeof body?.consent?.user_id === "string" ? body.consent.user_id : "",
|
|
209
|
+
streamingEnabled: body?.streaming_enabled === true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Startup entry point: ask, then apply. Never throws — a capture concern must
|
|
221
|
+
* not be able to stop the MCP from starting.
|
|
222
|
+
*/
|
|
223
|
+
export async function installCaptureHook(opts) {
|
|
224
|
+
try {
|
|
225
|
+
const settingsPath = path.join(opts.homeDir, ".claude", "settings.json");
|
|
226
|
+
const grantPath = path.join(opts.homeDir, ".claude", "agenthub-capture.json");
|
|
227
|
+
const status = await fetchCaptureConsent(opts.clientBaseUrl, opts.connectToken, opts.fetchImpl);
|
|
228
|
+
// Unreachable or unauthenticated ⇒ treat exactly like "not consented", so
|
|
229
|
+
// an offline start also tears down a grant left by a previous one.
|
|
230
|
+
const consented = status?.consented === true;
|
|
231
|
+
const applied = applyCaptureConsent(consented, {
|
|
232
|
+
settingsPath,
|
|
233
|
+
grantPath,
|
|
234
|
+
hookScript: resolveCaptureHookScript(opts.moduleDir, opts.deps?.exists ?? fs.existsSync),
|
|
235
|
+
apiRoot: captureApiRoot(opts.clientBaseUrl),
|
|
236
|
+
userId: status?.userId ?? "",
|
|
237
|
+
agentId: opts.agentId,
|
|
238
|
+
connectToken: opts.connectToken,
|
|
239
|
+
}, opts.deps);
|
|
240
|
+
if (consented && status && !status.streamingEnabled) {
|
|
241
|
+
// Worth saying out loud: the person agreed, and nothing will be recorded
|
|
242
|
+
// anyway. Silence here reads as "it is working".
|
|
243
|
+
return `${applied} (server-side session streaming is disabled, so nothing will be captured yet)`;
|
|
244
|
+
}
|
|
245
|
+
if (status === null)
|
|
246
|
+
return `${applied} (consent could not be confirmed with the server)`;
|
|
247
|
+
return applied;
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
return `skipped: ${errText(e)}`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function errText(e) {
|
|
254
|
+
return e instanceof Error ? e.message : String(e);
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=captureHookInstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureHookInstall.js","sourceRoot":"","sources":["../src/captureHookInstall.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAmB7B,oEAAoE;AACpE,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C,kEAAkE;AAClE,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEvC;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAA6B;IAC7D,MAAM,MAAM,GAA4B,EAAE,GAAG,IAAI,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAgC,CAAC;IAClE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC;QAAE,OAAO,MAAM,CAAC;IAEvD,MAAM,KAAK,GAAgC,EAAE,GAAG,OAAO,EAAE,CAAC;IAC1D,MAAM,IAAI,GAAgB,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAC/B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CACpF,CAAC;QACF,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,OAAO,MAAM,CAAC;AAChB,CAAC;AAYD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,aAAqB;IAClD,OAAO,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AACjE,CAAC;AA2BD;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,SAAkB,EAClB,IAA2B,EAC3B,OAA2B,EAAE;IAE7B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,UAAU,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAClF,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,UAAU,CAAC;IACpD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACpG,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAE3C,0EAA0E;IAC1E,sEAAsE;IACtE,6EAA6E;IAC7E,IAAI,QAAQ,GAA4B,EAAE,CAAC;IAC3C,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAA4B,CAAC;QAChF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,oDAAoD,CAAC;QAC9D,CAAC;IACH,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,uEAAuE;QACvE,sEAAsE;QACtE,iCAAiC;QACjC,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACtC,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACnB,IAAI,CAAC;oBACH,UAAU,CAAC,MAAM,CAAC,CAAC;oBACnB,OAAO,GAAG,IAAI,CAAC;gBACjB,CAAC;gBAAC,MAAM,CAAC;oBACP,4DAA4D;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,SAAS,CAAC,eAAe,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,mBAAmB;QACrB,CAAC;QAED,MAAM,QAAQ,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC7C,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1D,IAAI,CAAC;gBACH,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;gBACvC,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;gBAChE,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,6CAA6C,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,CAAC;QACH,CAAC;QACD,OAAO,OAAO;YACZ,CAAC,CAAC,gDAAgD;YAClD,CAAC,CAAC,oDAAoD,CAAC;IAC3D,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACrB,OAAO,yEAAyE,CAAC;IACnF,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;QACvB,2EAA2E;QAC3E,wDAAwD;QACxD,OAAO,0EAA0E,CAAC;IACpF,CAAC;IAED,MAAM,KAAK,GAAiB;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,OAAO,EAAE,IAAI,CAAC,MAAM;QACpB,QAAQ,EAAE,IAAI,CAAC,OAAO;QACtB,aAAa,EAAE,IAAI,CAAC,YAAY;QAChC,YAAY,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE;QACjC,mBAAmB,EAAE,IAAI,CAAC,iBAAiB,IAAI,2BAA2B;KAC3E,CAAC;IAEF,IAAI,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACpC,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC1D,0EAA0E;QAC1E,0EAA0E;QAC1E,IAAI,CAAC;YACH,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,0DAA0D,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,CAAC;IAED,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,iBAAiB,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC3G,IAAI,iBAAiB;QAAE,OAAO,6DAA6D,CAAC;IAE5F,IAAI,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QACvC,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7F,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,oDAAoD,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,CAAC;IACD,OAAO,oCAAoC,CAAC;AAC9C,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,wBAAwB,CAAC,CAAC;AACtE,CAAC;AAUD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,aAAqB,EACrB,YAAoB,EACpB,SAAwB,EACxB,SAAS,GAAG,kBAAkB;IAE9B,MAAM,OAAO,GAAG,SAAS,IAAK,UAAU,CAAC,KAAiC,CAAC;IAC3E,IAAI,CAAC,OAAO,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE3C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,0BAA0B,EAAE;YACxF,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,iBAAiB,EAAE,YAAY,EAAE;YAC5C,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAG7B,CAAC;QACF,OAAO;YACL,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI;YAC5C,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YAC9E,gBAAgB,EAAE,IAAI,EAAE,iBAAiB,KAAK,IAAI;SACnD,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAYD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAA+B;IACtE,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC;QACzE,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,uBAAuB,CAAC,CAAC;QAC9E,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAEhG,0EAA0E;QAC1E,mEAAmE;QACnE,MAAM,SAAS,GAAG,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;QAE7C,MAAM,OAAO,GAAG,mBAAmB,CACjC,SAAS,EACT;YACE,YAAY;YACZ,SAAS;YACT,UAAU,EAAE,wBAAwB,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC,UAAU,CAAC;YACxF,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC;YAC3C,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE;YAC5B,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,YAAY,EAAE,IAAI,CAAC,YAAY;SAChC,EACD,IAAI,CAAC,IAAI,CACV,CAAC;QAEF,IAAI,SAAS,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;YACpD,yEAAyE;YACzE,iDAAiD;YACjD,OAAO,GAAG,OAAO,+EAA+E,CAAC;QACnG,CAAC;QACD,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,GAAG,OAAO,mDAAmD,CAAC;QAC1F,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,YAAY,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAClC,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,CAAU;IACzB,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* add-multiplayer-sessions §3.2/§3.3/§3.4 — consent-gated install of the
|
|
3
|
+
* interactive capture hook.
|
|
4
|
+
*
|
|
5
|
+
* The load-bearing assertions here are the negative ones: with no consent, or
|
|
6
|
+
* with a consent lookup that failed, nothing is written to the user's machine
|
|
7
|
+
* that could cause their session to be recorded.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=captureHookInstall.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureHookInstall.test.d.ts","sourceRoot":"","sources":["../src/captureHookInstall.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* add-multiplayer-sessions §3.2/§3.3/§3.4 — consent-gated install of the
|
|
3
|
+
* interactive capture hook.
|
|
4
|
+
*
|
|
5
|
+
* The load-bearing assertions here are the negative ones: with no consent, or
|
|
6
|
+
* with a consent lookup that failed, nothing is written to the user's machine
|
|
7
|
+
* that could cause their session to be recorded.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect } from "vitest";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
import { applyCaptureConsent, captureApiRoot, captureStateDir, fetchCaptureConsent, installCaptureHook, removeCaptureHook, } from "./captureHookInstall.js";
|
|
12
|
+
import { CAPTURE_HOOK_MARKER } from "./sessionCapture.js";
|
|
13
|
+
/** In-memory filesystem so the tests never touch a real ~/.claude. */
|
|
14
|
+
function fakeFs(seed = {}) {
|
|
15
|
+
const files = new Map(Object.entries(seed));
|
|
16
|
+
const dirs = new Set();
|
|
17
|
+
const removedDirs = [];
|
|
18
|
+
const modes = new Map();
|
|
19
|
+
const deps = {
|
|
20
|
+
exists: (p) => files.has(p),
|
|
21
|
+
readFile: (p) => {
|
|
22
|
+
const v = files.get(p);
|
|
23
|
+
if (v === undefined)
|
|
24
|
+
throw new Error("ENOENT " + p);
|
|
25
|
+
return v;
|
|
26
|
+
},
|
|
27
|
+
writeFile: (p, d) => void files.set(p, d),
|
|
28
|
+
mkdir: (p) => void dirs.add(p),
|
|
29
|
+
removeFile: (p) => void files.delete(p),
|
|
30
|
+
removeDir: (p) => void removedDirs.push(p),
|
|
31
|
+
chmod: (p, m) => void modes.set(p, m),
|
|
32
|
+
now: () => new Date("2026-07-26T00:00:00.000Z"),
|
|
33
|
+
};
|
|
34
|
+
return { files, dirs, removedDirs, modes, deps };
|
|
35
|
+
}
|
|
36
|
+
const SETTINGS = "/home/alice/.claude/settings.json";
|
|
37
|
+
const GRANT = "/home/alice/.claude/agenthub-capture.json";
|
|
38
|
+
function opts(over = {}) {
|
|
39
|
+
return {
|
|
40
|
+
settingsPath: SETTINGS,
|
|
41
|
+
grantPath: GRANT,
|
|
42
|
+
hookScript: "/opt/mcp/dist/session-capture.sh",
|
|
43
|
+
apiRoot: "https://agenthub.example.com",
|
|
44
|
+
userId: "user-alice",
|
|
45
|
+
agentId: "agent-alice",
|
|
46
|
+
connectToken: "rt-alice",
|
|
47
|
+
...over,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
describe("captureApiRoot", () => {
|
|
51
|
+
it("strips the client's /api suffix so the hook does not build /api/api", () => {
|
|
52
|
+
expect(captureApiRoot("https://agenthub.example.com/api")).toBe("https://agenthub.example.com");
|
|
53
|
+
expect(captureApiRoot("https://agenthub.example.com/api/")).toBe("https://agenthub.example.com");
|
|
54
|
+
});
|
|
55
|
+
it("leaves a root that has no /api alone", () => {
|
|
56
|
+
expect(captureApiRoot("https://agenthub.example.com")).toBe("https://agenthub.example.com");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("applyCaptureConsent — consent absent", () => {
|
|
60
|
+
it("writes nothing at all for a user who has not consented", () => {
|
|
61
|
+
const fs = fakeFs();
|
|
62
|
+
const status = applyCaptureConsent(false, opts(), fs.deps);
|
|
63
|
+
expect(fs.files.has(GRANT)).toBe(false);
|
|
64
|
+
expect(fs.files.has(SETTINGS)).toBe(false);
|
|
65
|
+
expect(status).toMatch(/not consented/);
|
|
66
|
+
});
|
|
67
|
+
it("removes a grant and deregisters the hook when consent is withdrawn", () => {
|
|
68
|
+
const existing = {
|
|
69
|
+
hooks: {
|
|
70
|
+
PostToolUse: [
|
|
71
|
+
{ matcher: "", hooks: [{ type: "command", command: `bash "/opt/mcp/${CAPTURE_HOOK_MARKER}"` }] },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const fs = fakeFs({
|
|
76
|
+
[SETTINGS]: JSON.stringify(existing),
|
|
77
|
+
[GRANT]: JSON.stringify({ connect_token: "rt-alice" }),
|
|
78
|
+
});
|
|
79
|
+
applyCaptureConsent(false, opts(), fs.deps);
|
|
80
|
+
expect(fs.files.has(GRANT)).toBe(false);
|
|
81
|
+
const after = JSON.parse(fs.files.get(SETTINGS));
|
|
82
|
+
expect(after.hooks.PostToolUse).toEqual([]);
|
|
83
|
+
// The per-session bootstrap cache goes too, so a stale session id cannot
|
|
84
|
+
// be reused if consent is granted again later.
|
|
85
|
+
expect(fs.removedDirs).toContain(captureStateDir(GRANT));
|
|
86
|
+
});
|
|
87
|
+
it("leaves the user's other hooks untouched while removing ours", () => {
|
|
88
|
+
const existing = {
|
|
89
|
+
model: "opus",
|
|
90
|
+
hooks: {
|
|
91
|
+
SessionStart: [{ hooks: [{ type: "command", command: "node inboxHookCli.js" }] }],
|
|
92
|
+
PostToolUse: [
|
|
93
|
+
{ matcher: "", hooks: [{ type: "command", command: "node activityHookCli.js" }] },
|
|
94
|
+
{ matcher: "", hooks: [{ type: "command", command: `bash "/opt/${CAPTURE_HOOK_MARKER}"` }] },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const fs = fakeFs({ [SETTINGS]: JSON.stringify(existing) });
|
|
99
|
+
applyCaptureConsent(false, opts(), fs.deps);
|
|
100
|
+
const after = JSON.parse(fs.files.get(SETTINGS));
|
|
101
|
+
expect(after.model).toBe("opus");
|
|
102
|
+
expect(after.hooks.SessionStart).toHaveLength(1);
|
|
103
|
+
expect(after.hooks.PostToolUse).toHaveLength(1);
|
|
104
|
+
expect(after.hooks.PostToolUse[0].hooks[0].command).toContain("activityHookCli");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe("applyCaptureConsent — consent given", () => {
|
|
108
|
+
it("writes the grant and registers the hook", () => {
|
|
109
|
+
const fs = fakeFs();
|
|
110
|
+
const status = applyCaptureConsent(true, opts(), fs.deps);
|
|
111
|
+
const grant = JSON.parse(fs.files.get(GRANT));
|
|
112
|
+
expect(grant.api_url).toBe("https://agenthub.example.com");
|
|
113
|
+
expect(grant.connect_token).toBe("rt-alice");
|
|
114
|
+
expect(grant.user_id).toBe("user-alice");
|
|
115
|
+
expect(grant.notice_interval_sec).toBeGreaterThan(0);
|
|
116
|
+
const settings = JSON.parse(fs.files.get(SETTINGS));
|
|
117
|
+
expect(settings.hooks.PostToolUse[0].hooks[0].command).toContain(CAPTURE_HOOK_MARKER);
|
|
118
|
+
expect(status).toMatch(/consented/);
|
|
119
|
+
});
|
|
120
|
+
it("writes the grant owner-only — it carries a credential", () => {
|
|
121
|
+
const fs = fakeFs();
|
|
122
|
+
applyCaptureConsent(true, opts(), fs.deps);
|
|
123
|
+
expect(fs.modes.get(GRANT)).toBe(0o600);
|
|
124
|
+
});
|
|
125
|
+
// The required test: the merge must not be a rewrite.
|
|
126
|
+
it("preserves an existing user hook configuration", () => {
|
|
127
|
+
const existing = {
|
|
128
|
+
permissions: { allow: ["Bash(npm run test)"] },
|
|
129
|
+
hooks: {
|
|
130
|
+
SessionStart: [{ hooks: [{ type: "command", command: "node inboxHookCli.js" }] }],
|
|
131
|
+
PostToolUse: [{ matcher: "Edit", hooks: [{ type: "command", command: "node activityHookCli.js" }] }],
|
|
132
|
+
Stop: [{ hooks: [{ type: "command", command: "say done" }] }],
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
const fs = fakeFs({ [SETTINGS]: JSON.stringify(existing) });
|
|
136
|
+
applyCaptureConsent(true, opts(), fs.deps);
|
|
137
|
+
const after = JSON.parse(fs.files.get(SETTINGS));
|
|
138
|
+
expect(after.permissions).toEqual(existing.permissions);
|
|
139
|
+
expect(after.hooks.SessionStart).toEqual(existing.hooks.SessionStart);
|
|
140
|
+
expect(after.hooks.Stop).toEqual(existing.hooks.Stop);
|
|
141
|
+
// The pre-existing PostToolUse entry survives verbatim, alongside ours.
|
|
142
|
+
expect(after.hooks.PostToolUse).toHaveLength(2);
|
|
143
|
+
expect(after.hooks.PostToolUse[0]).toEqual(existing.hooks.PostToolUse[0]);
|
|
144
|
+
expect(after.hooks.PostToolUse[1].hooks[0].command).toContain(CAPTURE_HOOK_MARKER);
|
|
145
|
+
});
|
|
146
|
+
it("is idempotent — a second run does not duplicate the hook entry", () => {
|
|
147
|
+
const fs = fakeFs();
|
|
148
|
+
applyCaptureConsent(true, opts(), fs.deps);
|
|
149
|
+
applyCaptureConsent(true, opts(), fs.deps);
|
|
150
|
+
const after = JSON.parse(fs.files.get(SETTINGS));
|
|
151
|
+
expect(after.hooks.PostToolUse).toHaveLength(1);
|
|
152
|
+
});
|
|
153
|
+
it("refuses to install without a credential rather than writing an inert grant", () => {
|
|
154
|
+
const fs = fakeFs();
|
|
155
|
+
const status = applyCaptureConsent(true, opts({ connectToken: "" }), fs.deps);
|
|
156
|
+
expect(fs.files.has(GRANT)).toBe(false);
|
|
157
|
+
expect(fs.files.has(SETTINGS)).toBe(false);
|
|
158
|
+
expect(status).toMatch(/no connect token/);
|
|
159
|
+
});
|
|
160
|
+
it("refuses to install when the hook script cannot be found", () => {
|
|
161
|
+
const fs = fakeFs();
|
|
162
|
+
const status = applyCaptureConsent(true, opts({ hookScript: null }), fs.deps);
|
|
163
|
+
expect(fs.files.has(GRANT)).toBe(false);
|
|
164
|
+
expect(status).toMatch(/not found/);
|
|
165
|
+
});
|
|
166
|
+
it("never rewrites a settings file it cannot parse", () => {
|
|
167
|
+
const fs = fakeFs({ [SETTINGS]: "{ this is not json" });
|
|
168
|
+
const status = applyCaptureConsent(true, opts(), fs.deps);
|
|
169
|
+
expect(fs.files.get(SETTINGS)).toBe("{ this is not json");
|
|
170
|
+
expect(fs.files.has(GRANT)).toBe(false);
|
|
171
|
+
expect(status).toMatch(/not valid JSON/);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe("removeCaptureHook", () => {
|
|
175
|
+
it("keeps an entry that also carries a hook we did not install", () => {
|
|
176
|
+
const base = {
|
|
177
|
+
hooks: {
|
|
178
|
+
PostToolUse: [
|
|
179
|
+
{
|
|
180
|
+
matcher: "",
|
|
181
|
+
hooks: [
|
|
182
|
+
{ type: "command", command: "node activityHookCli.js" },
|
|
183
|
+
{ type: "command", command: `bash "${CAPTURE_HOOK_MARKER}"` },
|
|
184
|
+
],
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
const after = removeCaptureHook(base);
|
|
190
|
+
expect(after.hooks.PostToolUse).toHaveLength(1);
|
|
191
|
+
expect(after.hooks.PostToolUse[0].hooks).toHaveLength(1);
|
|
192
|
+
});
|
|
193
|
+
it("does not mutate the input", () => {
|
|
194
|
+
const base = {
|
|
195
|
+
hooks: { PostToolUse: [{ hooks: [{ type: "command", command: CAPTURE_HOOK_MARKER }] }] },
|
|
196
|
+
};
|
|
197
|
+
const snapshot = JSON.stringify(base);
|
|
198
|
+
removeCaptureHook(base);
|
|
199
|
+
expect(JSON.stringify(base)).toBe(snapshot);
|
|
200
|
+
});
|
|
201
|
+
it("is a no-op on settings that never had the hook", () => {
|
|
202
|
+
const base = { hooks: { SessionStart: [{ hooks: [{ type: "command", command: "x" }] }] } };
|
|
203
|
+
expect(removeCaptureHook(base)).toEqual(base);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe("fetchCaptureConsent", () => {
|
|
207
|
+
const okResponse = (consented, streaming = true) => async () => ({
|
|
208
|
+
ok: true,
|
|
209
|
+
status: 200,
|
|
210
|
+
json: async () => ({
|
|
211
|
+
consent: { consented, user_id: "user-alice" },
|
|
212
|
+
streaming_enabled: streaming,
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
it("reports consent when the server grants it", async () => {
|
|
216
|
+
const got = await fetchCaptureConsent("https://x/api", "rt", okResponse(true));
|
|
217
|
+
expect(got).toEqual({ consented: true, userId: "user-alice", streamingEnabled: true });
|
|
218
|
+
});
|
|
219
|
+
it("reports the absence of consent as such, not as an error", async () => {
|
|
220
|
+
const got = await fetchCaptureConsent("https://x/api", "rt", okResponse(false));
|
|
221
|
+
expect(got?.consented).toBe(false);
|
|
222
|
+
});
|
|
223
|
+
it("returns null (⇒ do not capture) when the server refuses", async () => {
|
|
224
|
+
const f = async () => ({ ok: false, status: 401, json: async () => ({}) });
|
|
225
|
+
expect(await fetchCaptureConsent("https://x/api", "rt", f)).toBeNull();
|
|
226
|
+
});
|
|
227
|
+
it("returns null (⇒ do not capture) when the server is unreachable", async () => {
|
|
228
|
+
const f = async () => {
|
|
229
|
+
throw new Error("ECONNREFUSED");
|
|
230
|
+
};
|
|
231
|
+
expect(await fetchCaptureConsent("https://x/api", "rt", f)).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
it("does not even ask without a credential", async () => {
|
|
234
|
+
let called = false;
|
|
235
|
+
const f = async () => {
|
|
236
|
+
called = true;
|
|
237
|
+
return { ok: true, status: 200, json: async () => ({}) };
|
|
238
|
+
};
|
|
239
|
+
expect(await fetchCaptureConsent("https://x/api", "", f)).toBeNull();
|
|
240
|
+
expect(called).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe("installCaptureHook — end to end", () => {
|
|
244
|
+
const base = {
|
|
245
|
+
clientBaseUrl: "https://agenthub.example.com/api",
|
|
246
|
+
connectToken: "rt-alice",
|
|
247
|
+
agentId: "agent-alice",
|
|
248
|
+
homeDir: "/home/alice",
|
|
249
|
+
moduleDir: "/opt/mcp/dist",
|
|
250
|
+
};
|
|
251
|
+
// installCaptureHook builds these with path.join, so the tests must too —
|
|
252
|
+
// otherwise they only pass on POSIX.
|
|
253
|
+
const grantPath = path.join(base.homeDir, ".claude", "agenthub-capture.json");
|
|
254
|
+
const settingsPath = path.join(base.homeDir, ".claude", "settings.json");
|
|
255
|
+
function withScript(seed = {}) {
|
|
256
|
+
const fs = fakeFs(seed);
|
|
257
|
+
// resolveCaptureHookScript probes the filesystem through the same `exists`.
|
|
258
|
+
fs.files.set(path.join(base.moduleDir, "session-capture.sh"), "#!/bin/bash");
|
|
259
|
+
return fs;
|
|
260
|
+
}
|
|
261
|
+
it("§3.4 — a server that says 'not consented' leaves the machine clean", async () => {
|
|
262
|
+
const fs = withScript();
|
|
263
|
+
const f = async () => ({
|
|
264
|
+
ok: true,
|
|
265
|
+
status: 200,
|
|
266
|
+
json: async () => ({ consent: { consented: false, user_id: "user-alice" }, streaming_enabled: true }),
|
|
267
|
+
});
|
|
268
|
+
await installCaptureHook({ ...base, fetchImpl: f, deps: fs.deps });
|
|
269
|
+
expect(fs.files.has(grantPath)).toBe(false);
|
|
270
|
+
expect(fs.files.has(settingsPath)).toBe(false);
|
|
271
|
+
});
|
|
272
|
+
it("fails closed — an unreachable server installs nothing", async () => {
|
|
273
|
+
const fs = withScript();
|
|
274
|
+
const f = async () => {
|
|
275
|
+
throw new Error("network down");
|
|
276
|
+
};
|
|
277
|
+
const status = await installCaptureHook({ ...base, fetchImpl: f, deps: fs.deps });
|
|
278
|
+
expect(fs.files.has(grantPath)).toBe(false);
|
|
279
|
+
expect(status).toMatch(/could not be confirmed/);
|
|
280
|
+
});
|
|
281
|
+
it("installs when the server confirms consent", async () => {
|
|
282
|
+
const fs = withScript();
|
|
283
|
+
const f = async () => ({
|
|
284
|
+
ok: true,
|
|
285
|
+
status: 200,
|
|
286
|
+
json: async () => ({ consent: { consented: true, user_id: "user-alice" }, streaming_enabled: true }),
|
|
287
|
+
});
|
|
288
|
+
await installCaptureHook({ ...base, fetchImpl: f, deps: fs.deps });
|
|
289
|
+
const grant = JSON.parse(fs.files.get(grantPath));
|
|
290
|
+
expect(grant.api_url).toBe("https://agenthub.example.com");
|
|
291
|
+
expect(grant.user_id).toBe("user-alice");
|
|
292
|
+
const settings = JSON.parse(fs.files.get(settingsPath));
|
|
293
|
+
expect(settings.hooks.PostToolUse[0].hooks[0].command).toContain(CAPTURE_HOOK_MARKER);
|
|
294
|
+
});
|
|
295
|
+
it("says so when consent is given but the server-side switch is off", async () => {
|
|
296
|
+
const fs = withScript();
|
|
297
|
+
const f = async () => ({
|
|
298
|
+
ok: true,
|
|
299
|
+
status: 200,
|
|
300
|
+
json: async () => ({ consent: { consented: true, user_id: "user-alice" }, streaming_enabled: false }),
|
|
301
|
+
});
|
|
302
|
+
const status = await installCaptureHook({ ...base, fetchImpl: f, deps: fs.deps });
|
|
303
|
+
expect(status).toMatch(/streaming is disabled/);
|
|
304
|
+
});
|
|
305
|
+
it("propagates a revocation: a previously installed hook and grant are removed", async () => {
|
|
306
|
+
const fs = withScript({
|
|
307
|
+
[settingsPath]: JSON.stringify({
|
|
308
|
+
hooks: {
|
|
309
|
+
PostToolUse: [{ matcher: "", hooks: [{ type: "command", command: `bash "${CAPTURE_HOOK_MARKER}"` }] }],
|
|
310
|
+
},
|
|
311
|
+
}),
|
|
312
|
+
[grantPath]: JSON.stringify({ connect_token: "rt-alice" }),
|
|
313
|
+
});
|
|
314
|
+
const f = async () => ({
|
|
315
|
+
ok: true,
|
|
316
|
+
status: 200,
|
|
317
|
+
json: async () => ({ consent: { consented: false, user_id: "user-alice" }, streaming_enabled: true }),
|
|
318
|
+
});
|
|
319
|
+
await installCaptureHook({ ...base, fetchImpl: f, deps: fs.deps });
|
|
320
|
+
expect(fs.files.has(grantPath)).toBe(false);
|
|
321
|
+
const settings = JSON.parse(fs.files.get(settingsPath));
|
|
322
|
+
expect(settings.hooks.PostToolUse).toEqual([]);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
//# sourceMappingURL=captureHookInstall.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureHookInstall.test.js","sourceRoot":"","sources":["../src/captureHookInstall.test.ts"],"names":[],"mappings":"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"}
|