agenthub-multiagent-mcp 1.52.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.
Files changed (61) hide show
  1. package/dist/captureHookInstall.d.ts +127 -0
  2. package/dist/captureHookInstall.d.ts.map +1 -0
  3. package/dist/captureHookInstall.js +256 -0
  4. package/dist/captureHookInstall.js.map +1 -0
  5. package/dist/captureHookInstall.test.d.ts +10 -0
  6. package/dist/captureHookInstall.test.d.ts.map +1 -0
  7. package/dist/captureHookInstall.test.js +325 -0
  8. package/dist/captureHookInstall.test.js.map +1 -0
  9. package/dist/channel.js +96 -0
  10. package/dist/channel.js.map +1 -1
  11. package/dist/client.d.ts +50 -0
  12. package/dist/client.d.ts.map +1 -1
  13. package/dist/client.js +90 -0
  14. package/dist/client.js.map +1 -1
  15. package/dist/commands/setup-shell.js +11 -11
  16. package/dist/daemon.js +142 -3
  17. package/dist/daemon.js.map +1 -1
  18. package/dist/index.js +25 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/session-capture.sh +410 -0
  21. package/dist/sessionCapture.d.ts +163 -0
  22. package/dist/sessionCapture.d.ts.map +1 -0
  23. package/dist/sessionCapture.js +279 -0
  24. package/dist/sessionCapture.js.map +1 -0
  25. package/dist/sessionCapture.test.d.ts +9 -0
  26. package/dist/sessionCapture.test.d.ts.map +1 -0
  27. package/dist/sessionCapture.test.js +366 -0
  28. package/dist/sessionCapture.test.js.map +1 -0
  29. package/dist/sessionCaptureScript.test.d.ts +16 -0
  30. package/dist/sessionCaptureScript.test.d.ts.map +1 -0
  31. package/dist/sessionCaptureScript.test.js +92 -0
  32. package/dist/sessionCaptureScript.test.js.map +1 -0
  33. package/dist/steering.test.d.ts +2 -0
  34. package/dist/steering.test.d.ts.map +1 -0
  35. package/dist/steering.test.js +223 -0
  36. package/dist/steering.test.js.map +1 -0
  37. package/dist/streamingRuntimeLib.d.ts +260 -1
  38. package/dist/streamingRuntimeLib.d.ts.map +1 -1
  39. package/dist/streamingRuntimeLib.js +474 -1
  40. package/dist/streamingRuntimeLib.js.map +1 -1
  41. package/dist/streamingRuntimeLib.test.js +87 -1
  42. package/dist/streamingRuntimeLib.test.js.map +1 -1
  43. package/dist/tools/index.d.ts.map +1 -1
  44. package/dist/tools/index.js +8 -0
  45. package/dist/tools/index.js.map +1 -1
  46. package/dist/tools/sessionControl.d.ts +53 -0
  47. package/dist/tools/sessionControl.d.ts.map +1 -0
  48. package/dist/tools/sessionControl.js +265 -0
  49. package/dist/tools/sessionControl.js.map +1 -0
  50. package/dist/tools/sessionControl.test.d.ts +14 -0
  51. package/dist/tools/sessionControl.test.d.ts.map +1 -0
  52. package/dist/tools/sessionControl.test.js +298 -0
  53. package/dist/tools/sessionControl.test.js.map +1 -0
  54. package/dist/tools/tools.test.js +4 -2
  55. package/dist/tools/tools.test.js.map +1 -1
  56. package/native/agenthub-memvid/agenthub-memvid.linux-x64-gnu.node +0 -0
  57. package/package.json +2 -2
  58. package/skills/commands/start-session.md +77 -77
  59. package/skills/skills/deploy-staging/SKILL.md +164 -164
  60. package/skills/skills/deploy-vps-openclaw/SKILL.md +97 -97
  61. package/skills/skills/karpathy-guidelines/SKILL.md +67 -67
@@ -0,0 +1,127 @@
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
+ /** Server's answer to "may this user's interactive sessions be captured?" */
28
+ export interface CaptureConsentStatus {
29
+ consented: boolean;
30
+ userId: string;
31
+ streamingEnabled: boolean;
32
+ }
33
+ /** Default gap between in-session recording notices, in seconds. */
34
+ export declare const DEFAULT_NOTICE_INTERVAL_SEC = 900;
35
+ /** Timeout on the consent lookup. Startup must not hang on it. */
36
+ export declare const CONSENT_TIMEOUT_MS = 4000;
37
+ /**
38
+ * Remove the capture hook from a settings object, preserving everything else.
39
+ *
40
+ * The inverse of `mergeCaptureHook`, and the reason revocation is effective on
41
+ * the *next* MCP start rather than only server-side. A PostToolUse entry is
42
+ * dropped only if every command in it is ours; an entry a user has added other
43
+ * commands to keeps those, so this can never eat a hook we did not install.
44
+ */
45
+ export declare function removeCaptureHook(base: Record<string, unknown>): Record<string, unknown>;
46
+ /** The non-secret half of a grant, plus the credential the hook posts with. */
47
+ export interface CaptureGrant {
48
+ api_url: string;
49
+ user_id: string;
50
+ agent_id: string;
51
+ connect_token: string;
52
+ consented_at: string;
53
+ notice_interval_sec: number;
54
+ }
55
+ /**
56
+ * Strip the client's `/api` suffix. `AgentHubClient` appends it for its own
57
+ * calls, but the hook builds `<root>/api/...` itself, and a doubled `/api/api`
58
+ * 404s with an HTML body — a failure that shows up as "capture silently never
59
+ * worked" rather than as an error.
60
+ */
61
+ export declare function captureApiRoot(clientBaseUrl: string): string;
62
+ export interface CaptureInstallDeps {
63
+ exists?: (p: string) => boolean;
64
+ readFile?: (p: string) => string;
65
+ writeFile?: (p: string, data: string) => void;
66
+ mkdir?: (p: string) => void;
67
+ removeFile?: (p: string) => void;
68
+ removeDir?: (p: string) => void;
69
+ chmod?: (p: string, mode: number) => void;
70
+ now?: () => Date;
71
+ }
72
+ export interface CaptureInstallOptions {
73
+ /** `~/.claude/settings.json` in production. */
74
+ settingsPath: string;
75
+ /** `~/.claude/agenthub-capture.json` in production. */
76
+ grantPath: string;
77
+ /** Absolute path to session-capture.sh, or null when it cannot be found. */
78
+ hookScript: string | null;
79
+ apiRoot: string;
80
+ userId: string;
81
+ agentId: string;
82
+ connectToken: string;
83
+ noticeIntervalSec?: number;
84
+ }
85
+ /**
86
+ * Bring the local machine into line with a consent decision, and report what
87
+ * changed.
88
+ *
89
+ * Consented → write the grant, register the hook.
90
+ * Not → delete the grant and its per-session state, deregister the hook.
91
+ *
92
+ * Both directions are idempotent and safe to run on every MCP start.
93
+ */
94
+ export declare function applyCaptureConsent(consented: boolean, opts: CaptureInstallOptions, deps?: CaptureInstallDeps): string;
95
+ /** Where the hook keeps its per-Claude-session bootstrap cache. */
96
+ export declare function captureStateDir(grantPath: string): string;
97
+ /** Minimal structural view of the fetch surface this module uses. */
98
+ export interface ConsentResponse {
99
+ ok: boolean;
100
+ status: number;
101
+ json(): Promise<unknown>;
102
+ }
103
+ export type ConsentFetch = (url: string, init: RequestInit) => Promise<ConsentResponse>;
104
+ /**
105
+ * Ask the server whether the calling identity's owner has consented.
106
+ *
107
+ * Returns `null` on any failure — unreachable server, non-2xx, unparseable
108
+ * body. `null` is not "unknown, carry on"; every caller treats it as "do not
109
+ * capture", so a server we cannot reach can never be the reason someone gets
110
+ * recorded.
111
+ */
112
+ export declare function fetchCaptureConsent(clientBaseUrl: string, connectToken: string, fetchImpl?: ConsentFetch, timeoutMs?: number): Promise<CaptureConsentStatus | null>;
113
+ export interface InstallCaptureHookOptions {
114
+ clientBaseUrl: string;
115
+ connectToken: string;
116
+ agentId: string;
117
+ homeDir: string;
118
+ moduleDir: string;
119
+ fetchImpl?: ConsentFetch;
120
+ deps?: CaptureInstallDeps;
121
+ }
122
+ /**
123
+ * Startup entry point: ask, then apply. Never throws — a capture concern must
124
+ * not be able to stop the MCP from starting.
125
+ */
126
+ export declare function installCaptureHook(opts: InstallCaptureHookOptions): Promise<string>;
127
+ //# sourceMappingURL=captureHookInstall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"captureHookInstall.d.ts","sourceRoot":"","sources":["../src/captureHookInstall.ts"],"names":[],"mappings":"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"}
@@ -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":"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"}
@@ -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"}