@contextstream/mcp-server 0.4.81 → 1.0.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/NOTICE +25 -0
- package/README.md +77 -328
- package/npm/bin/contextstream-hook.mjs +9 -0
- package/npm/bin/contextstream-mcp.mjs +9 -0
- package/npm/bin/mcp-server.mjs +9 -0
- package/npm/launcher.mjs +390 -0
- package/package.json +28 -72
- package/server.json +43 -0
- package/dist/hooks/auto-rules.js +0 -3384
- package/dist/hooks/media-aware.js +0 -103
- package/dist/hooks/notification.js +0 -162
- package/dist/hooks/on-bash.js +0 -190
- package/dist/hooks/on-read.js +0 -321
- package/dist/hooks/on-save-intent.js +0 -132
- package/dist/hooks/on-task.js +0 -139
- package/dist/hooks/on-web.js +0 -155
- package/dist/hooks/permission-request.js +0 -174
- package/dist/hooks/post-compact.js +0 -172
- package/dist/hooks/post-tool-use-failure.js +0 -213
- package/dist/hooks/post-write.js +0 -348
- package/dist/hooks/pre-compact.js +0 -317
- package/dist/hooks/pre-tool-use.js +0 -553
- package/dist/hooks/runner.js +0 -5102
- package/dist/hooks/session-end.js +0 -285
- package/dist/hooks/session-init.js +0 -1846
- package/dist/hooks/stop.js +0 -171
- package/dist/hooks/subagent-start.js +0 -195
- package/dist/hooks/subagent-stop.js +0 -301
- package/dist/hooks/task-completed.js +0 -260
- package/dist/hooks/teammate-idle.js +0 -200
- package/dist/hooks/user-prompt-submit.js +0 -859
- package/dist/index.js +0 -41375
- package/dist/test-server.js +0 -4363
- package/scripts/postinstall.js +0 -56
- package/scripts/registration-smoke.mts +0 -45
package/dist/hooks/on-web.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/hooks/on-web.ts
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
var ENABLED = process.env.CONTEXTSTREAM_WEB_HOOK_ENABLED !== "false";
|
|
8
|
-
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
9
|
-
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
10
|
-
var WORKSPACE_ID = null;
|
|
11
|
-
function loadConfigFromMcpJson(cwd) {
|
|
12
|
-
let searchDir = path.resolve(cwd);
|
|
13
|
-
for (let i = 0; i < 5; i++) {
|
|
14
|
-
if (!API_KEY) {
|
|
15
|
-
const mcpPath = path.join(searchDir, ".mcp.json");
|
|
16
|
-
if (fs.existsSync(mcpPath)) {
|
|
17
|
-
try {
|
|
18
|
-
const content = fs.readFileSync(mcpPath, "utf-8");
|
|
19
|
-
const config = JSON.parse(content);
|
|
20
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
21
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
22
|
-
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
23
|
-
}
|
|
24
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
25
|
-
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
26
|
-
}
|
|
27
|
-
} catch {
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (!WORKSPACE_ID) {
|
|
32
|
-
const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
|
|
33
|
-
if (fs.existsSync(csConfigPath)) {
|
|
34
|
-
try {
|
|
35
|
-
const content = fs.readFileSync(csConfigPath, "utf-8");
|
|
36
|
-
const csConfig = JSON.parse(content);
|
|
37
|
-
if (csConfig.workspace_id) {
|
|
38
|
-
WORKSPACE_ID = csConfig.workspace_id;
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const parentDir = path.dirname(searchDir);
|
|
45
|
-
if (parentDir === searchDir) break;
|
|
46
|
-
searchDir = parentDir;
|
|
47
|
-
}
|
|
48
|
-
if (!API_KEY) {
|
|
49
|
-
const homeMcpPath = path.join(homedir(), ".mcp.json");
|
|
50
|
-
if (fs.existsSync(homeMcpPath)) {
|
|
51
|
-
try {
|
|
52
|
-
const content = fs.readFileSync(homeMcpPath, "utf-8");
|
|
53
|
-
const config = JSON.parse(content);
|
|
54
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
55
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
56
|
-
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
57
|
-
}
|
|
58
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
59
|
-
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function captureWebResearch(toolName, target, summary, sessionId) {
|
|
67
|
-
if (!API_KEY) return;
|
|
68
|
-
const payload = {
|
|
69
|
-
event_type: "web_research",
|
|
70
|
-
title: `${toolName}: ${target.slice(0, 60)}`,
|
|
71
|
-
content: JSON.stringify({
|
|
72
|
-
tool: toolName,
|
|
73
|
-
target,
|
|
74
|
-
summary: summary.slice(0, 1e3),
|
|
75
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
76
|
-
}),
|
|
77
|
-
importance: "medium",
|
|
78
|
-
tags: ["research", "web", toolName.toLowerCase()],
|
|
79
|
-
source_type: "hook",
|
|
80
|
-
session_id: sessionId
|
|
81
|
-
};
|
|
82
|
-
if (WORKSPACE_ID) {
|
|
83
|
-
payload.workspace_id = WORKSPACE_ID;
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const controller = new AbortController();
|
|
87
|
-
const timeoutId = setTimeout(() => controller.abort(), 3e3);
|
|
88
|
-
await fetch(`${API_URL}/api/v1/memory/events`, {
|
|
89
|
-
method: "POST",
|
|
90
|
-
headers: {
|
|
91
|
-
"Content-Type": "application/json",
|
|
92
|
-
"X-API-Key": API_KEY
|
|
93
|
-
},
|
|
94
|
-
body: JSON.stringify(payload),
|
|
95
|
-
signal: controller.signal
|
|
96
|
-
});
|
|
97
|
-
clearTimeout(timeoutId);
|
|
98
|
-
} catch {
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function runOnWebHook() {
|
|
102
|
-
if (!ENABLED) {
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
105
|
-
let inputData = "";
|
|
106
|
-
for await (const chunk of process.stdin) {
|
|
107
|
-
inputData += chunk;
|
|
108
|
-
}
|
|
109
|
-
if (!inputData.trim()) {
|
|
110
|
-
process.exit(0);
|
|
111
|
-
}
|
|
112
|
-
let input;
|
|
113
|
-
try {
|
|
114
|
-
input = JSON.parse(inputData);
|
|
115
|
-
} catch {
|
|
116
|
-
process.exit(0);
|
|
117
|
-
}
|
|
118
|
-
const toolName = input.tool_name || "";
|
|
119
|
-
if (!["WebFetch", "WebSearch"].includes(toolName)) {
|
|
120
|
-
process.exit(0);
|
|
121
|
-
}
|
|
122
|
-
const cwd = input.cwd || process.cwd();
|
|
123
|
-
loadConfigFromMcpJson(cwd);
|
|
124
|
-
const sessionId = input.session_id || "unknown";
|
|
125
|
-
let target = "";
|
|
126
|
-
let summary = "";
|
|
127
|
-
switch (toolName) {
|
|
128
|
-
case "WebFetch":
|
|
129
|
-
target = input.tool_input?.url || "";
|
|
130
|
-
const prompt = input.tool_input?.prompt || "fetched content";
|
|
131
|
-
const content = input.tool_result?.output || input.tool_result?.content || "";
|
|
132
|
-
summary = `Fetched ${target} (${prompt}): ${content.slice(0, 300)}`;
|
|
133
|
-
break;
|
|
134
|
-
case "WebSearch":
|
|
135
|
-
target = input.tool_input?.query || "";
|
|
136
|
-
const results = input.tool_result?.results || [];
|
|
137
|
-
const topResults = results.slice(0, 3).map((r) => `- ${r.title}: ${r.url}`).join("\n");
|
|
138
|
-
summary = `Search: "${target}"
|
|
139
|
-
Top results:
|
|
140
|
-
${topResults}`;
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
if (target) {
|
|
144
|
-
captureWebResearch(toolName, target, summary, sessionId).catch(() => {
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
process.exit(0);
|
|
148
|
-
}
|
|
149
|
-
var isDirectRun = process.argv[1]?.includes("on-web") || process.argv[2] === "on-web";
|
|
150
|
-
if (isDirectRun) {
|
|
151
|
-
runOnWebHook().catch(() => process.exit(0));
|
|
152
|
-
}
|
|
153
|
-
export {
|
|
154
|
-
runOnWebHook
|
|
155
|
-
};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/hooks/common.ts
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
var DEFAULT_API_URL = "https://api.contextstream.io";
|
|
8
|
-
var HOOK_SPECIFIC_OUTPUT_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "UserPromptSubmit", "PostToolUse"]);
|
|
9
|
-
function readHookInput() {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(fs.readFileSync(0, "utf8"));
|
|
12
|
-
} catch {
|
|
13
|
-
return {};
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function writeHookOutput(output) {
|
|
17
|
-
const eventName = output?.hookEventName || (typeof process.env.HOOK_EVENT_NAME === "string" ? process.env.HOOK_EVENT_NAME.trim() : "");
|
|
18
|
-
const canUseHookSpecificOutput = HOOK_SPECIFIC_OUTPUT_EVENTS.has(eventName);
|
|
19
|
-
const payload = output && (output.additionalContext || output.blocked || output.reason) ? {
|
|
20
|
-
hookSpecificOutput: output.additionalContext && canUseHookSpecificOutput ? {
|
|
21
|
-
hookEventName: eventName,
|
|
22
|
-
additionalContext: output.additionalContext
|
|
23
|
-
} : void 0,
|
|
24
|
-
additionalContext: output.additionalContext,
|
|
25
|
-
blocked: output.blocked,
|
|
26
|
-
reason: output.reason
|
|
27
|
-
} : {};
|
|
28
|
-
console.log(JSON.stringify(payload));
|
|
29
|
-
}
|
|
30
|
-
function extractCwd(input) {
|
|
31
|
-
const cwd = typeof input.cwd === "string" && input.cwd.trim() ? input.cwd.trim() : process.cwd();
|
|
32
|
-
return cwd;
|
|
33
|
-
}
|
|
34
|
-
function loadHookConfig(cwd) {
|
|
35
|
-
let apiUrl = process.env.CONTEXTSTREAM_API_URL || DEFAULT_API_URL;
|
|
36
|
-
let apiKey = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
37
|
-
let jwt = process.env.CONTEXTSTREAM_JWT || "";
|
|
38
|
-
let workspaceId = process.env.CONTEXTSTREAM_WORKSPACE_ID || null;
|
|
39
|
-
let projectId = process.env.CONTEXTSTREAM_PROJECT_ID || null;
|
|
40
|
-
let searchDir = path.resolve(cwd);
|
|
41
|
-
for (let i = 0; i < 6; i++) {
|
|
42
|
-
if (!apiKey && !jwt) {
|
|
43
|
-
const mcpPath = path.join(searchDir, ".mcp.json");
|
|
44
|
-
if (fs.existsSync(mcpPath)) {
|
|
45
|
-
try {
|
|
46
|
-
const config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
|
|
47
|
-
const env = config.mcpServers?.contextstream?.env;
|
|
48
|
-
if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
|
|
49
|
-
if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
|
|
50
|
-
if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
|
|
51
|
-
if (env?.CONTEXTSTREAM_WORKSPACE_ID && !workspaceId) workspaceId = env.CONTEXTSTREAM_WORKSPACE_ID;
|
|
52
|
-
if (env?.CONTEXTSTREAM_PROJECT_ID && !projectId) projectId = env.CONTEXTSTREAM_PROJECT_ID;
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!workspaceId || !projectId) {
|
|
58
|
-
const localConfigPath = path.join(searchDir, ".contextstream", "config.json");
|
|
59
|
-
if (fs.existsSync(localConfigPath)) {
|
|
60
|
-
try {
|
|
61
|
-
const localConfig = JSON.parse(fs.readFileSync(localConfigPath, "utf8"));
|
|
62
|
-
if (localConfig.workspace_id && !workspaceId) workspaceId = localConfig.workspace_id;
|
|
63
|
-
if (localConfig.project_id && !projectId) projectId = localConfig.project_id;
|
|
64
|
-
} catch {
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
const parentDir = path.dirname(searchDir);
|
|
69
|
-
if (parentDir === searchDir) break;
|
|
70
|
-
searchDir = parentDir;
|
|
71
|
-
}
|
|
72
|
-
if (!apiKey && !jwt) {
|
|
73
|
-
const homeMcpPath = path.join(homedir(), ".mcp.json");
|
|
74
|
-
if (fs.existsSync(homeMcpPath)) {
|
|
75
|
-
try {
|
|
76
|
-
const config = JSON.parse(fs.readFileSync(homeMcpPath, "utf8"));
|
|
77
|
-
const env = config.mcpServers?.contextstream?.env;
|
|
78
|
-
if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
|
|
79
|
-
if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
|
|
80
|
-
if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
|
|
81
|
-
} catch {
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return { apiUrl, apiKey, jwt, workspaceId, projectId };
|
|
86
|
-
}
|
|
87
|
-
function isConfigured(config) {
|
|
88
|
-
return Boolean(config.apiKey || config.jwt);
|
|
89
|
-
}
|
|
90
|
-
function authHeaders(config) {
|
|
91
|
-
if (config.apiKey) {
|
|
92
|
-
return { "X-API-Key": config.apiKey };
|
|
93
|
-
}
|
|
94
|
-
if (config.jwt) {
|
|
95
|
-
return { Authorization: `Bearer ${config.jwt}` };
|
|
96
|
-
}
|
|
97
|
-
return {};
|
|
98
|
-
}
|
|
99
|
-
async function apiRequest(config, apiPath, init = {}) {
|
|
100
|
-
const response = await fetch(`${config.apiUrl}${apiPath}`, {
|
|
101
|
-
method: init.method || "GET",
|
|
102
|
-
headers: {
|
|
103
|
-
"Content-Type": "application/json",
|
|
104
|
-
...authHeaders(config)
|
|
105
|
-
},
|
|
106
|
-
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
107
|
-
});
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
110
|
-
}
|
|
111
|
-
const json = await response.json();
|
|
112
|
-
if (json && typeof json === "object" && "data" in json) {
|
|
113
|
-
return json.data;
|
|
114
|
-
}
|
|
115
|
-
return json;
|
|
116
|
-
}
|
|
117
|
-
async function postMemoryEvent(config, title, content, tags, eventType = "operation") {
|
|
118
|
-
if (!isConfigured(config) || !config.workspaceId) return;
|
|
119
|
-
await apiRequest(config, "/memory/events", {
|
|
120
|
-
method: "POST",
|
|
121
|
-
body: {
|
|
122
|
-
workspace_id: config.workspaceId,
|
|
123
|
-
project_id: config.projectId || void 0,
|
|
124
|
-
event_type: eventType,
|
|
125
|
-
title,
|
|
126
|
-
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
127
|
-
metadata: {
|
|
128
|
-
tags,
|
|
129
|
-
source: "mcp_hook",
|
|
130
|
-
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/hooks/permission-request.ts
|
|
137
|
-
function isHighRiskCommand(command) {
|
|
138
|
-
const lower = command.toLowerCase();
|
|
139
|
-
return ["rm -rf", "git reset --hard", "mkfs", "dd if=", "shutdown", "reboot"].some(
|
|
140
|
-
(pattern) => lower.includes(pattern)
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
async function runPermissionRequestHook() {
|
|
144
|
-
const input = readHookInput();
|
|
145
|
-
const cwd = extractCwd(input);
|
|
146
|
-
const config = loadHookConfig(cwd);
|
|
147
|
-
const command = typeof input.command === "string" && input.command || typeof input.cmd === "string" && input.cmd || "";
|
|
148
|
-
if (isConfigured(config)) {
|
|
149
|
-
await postMemoryEvent(
|
|
150
|
-
config,
|
|
151
|
-
"Permission request",
|
|
152
|
-
{
|
|
153
|
-
request: input,
|
|
154
|
-
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
155
|
-
},
|
|
156
|
-
["hook", "permission_request"]
|
|
157
|
-
).catch(() => {
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
if (isHighRiskCommand(command)) {
|
|
161
|
-
writeHookOutput({
|
|
162
|
-
additionalContext: "High-risk command detected. Confirm scope and prefer least-privilege execution."
|
|
163
|
-
});
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
writeHookOutput();
|
|
167
|
-
}
|
|
168
|
-
var isDirectRun = process.argv[1]?.includes("permission-request") || process.argv[2] === "permission-request";
|
|
169
|
-
if (isDirectRun) {
|
|
170
|
-
runPermissionRequestHook().catch(() => process.exit(0));
|
|
171
|
-
}
|
|
172
|
-
export {
|
|
173
|
-
runPermissionRequestHook
|
|
174
|
-
};
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/hooks/post-compact.ts
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
var ENABLED = process.env.CONTEXTSTREAM_POSTCOMPACT_ENABLED !== "false";
|
|
8
|
-
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
9
|
-
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
10
|
-
var WORKSPACE_ID = null;
|
|
11
|
-
function loadConfigFromMcpJson(cwd) {
|
|
12
|
-
let searchDir = path.resolve(cwd);
|
|
13
|
-
for (let i = 0; i < 5; i++) {
|
|
14
|
-
if (!API_KEY) {
|
|
15
|
-
const mcpPath = path.join(searchDir, ".mcp.json");
|
|
16
|
-
if (fs.existsSync(mcpPath)) {
|
|
17
|
-
try {
|
|
18
|
-
const content = fs.readFileSync(mcpPath, "utf-8");
|
|
19
|
-
const config = JSON.parse(content);
|
|
20
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
21
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
22
|
-
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
23
|
-
}
|
|
24
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
25
|
-
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
26
|
-
}
|
|
27
|
-
} catch {
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (!WORKSPACE_ID) {
|
|
32
|
-
const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
|
|
33
|
-
if (fs.existsSync(csConfigPath)) {
|
|
34
|
-
try {
|
|
35
|
-
const content = fs.readFileSync(csConfigPath, "utf-8");
|
|
36
|
-
const csConfig = JSON.parse(content);
|
|
37
|
-
if (csConfig.workspace_id) {
|
|
38
|
-
WORKSPACE_ID = csConfig.workspace_id;
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const parentDir = path.dirname(searchDir);
|
|
45
|
-
if (parentDir === searchDir) break;
|
|
46
|
-
searchDir = parentDir;
|
|
47
|
-
}
|
|
48
|
-
if (!API_KEY) {
|
|
49
|
-
const homeMcpPath = path.join(homedir(), ".mcp.json");
|
|
50
|
-
if (fs.existsSync(homeMcpPath)) {
|
|
51
|
-
try {
|
|
52
|
-
const content = fs.readFileSync(homeMcpPath, "utf-8");
|
|
53
|
-
const config = JSON.parse(content);
|
|
54
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
55
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
56
|
-
API_KEY = csEnv.CONTEXTSTREAM_API_KEY;
|
|
57
|
-
}
|
|
58
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
59
|
-
API_URL = csEnv.CONTEXTSTREAM_API_URL;
|
|
60
|
-
}
|
|
61
|
-
} catch {
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async function fetchLastTranscript(sessionId) {
|
|
67
|
-
if (!API_KEY) {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
try {
|
|
71
|
-
const controller = new AbortController();
|
|
72
|
-
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
73
|
-
const url = new URL(`${API_URL}/api/v1/transcripts`);
|
|
74
|
-
url.searchParams.set("session_id", sessionId);
|
|
75
|
-
url.searchParams.set("limit", "1");
|
|
76
|
-
url.searchParams.set("sort", "created_at:desc");
|
|
77
|
-
if (WORKSPACE_ID) {
|
|
78
|
-
url.searchParams.set("workspace_id", WORKSPACE_ID);
|
|
79
|
-
}
|
|
80
|
-
const response = await fetch(url.toString(), {
|
|
81
|
-
method: "GET",
|
|
82
|
-
headers: {
|
|
83
|
-
"X-API-Key": API_KEY
|
|
84
|
-
},
|
|
85
|
-
signal: controller.signal
|
|
86
|
-
});
|
|
87
|
-
clearTimeout(timeoutId);
|
|
88
|
-
if (response.ok) {
|
|
89
|
-
const data = await response.json();
|
|
90
|
-
if (data.transcripts && data.transcripts.length > 0) {
|
|
91
|
-
return data.transcripts[0];
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function formatTranscriptSummary(transcript) {
|
|
100
|
-
const messages = transcript.messages || [];
|
|
101
|
-
const activeFiles = transcript.metadata?.active_files || [];
|
|
102
|
-
const toolCallCount = transcript.metadata?.tool_call_count || 0;
|
|
103
|
-
const userMessages = messages.filter((m) => m.role === "user").slice(-3).map((m) => `- "${m.content.slice(0, 100)}${m.content.length > 100 ? "..." : ""}"`).join("\n");
|
|
104
|
-
const lastAssistant = messages.filter((m) => m.role === "assistant" && !m.content.startsWith("[Tool:")).slice(-1)[0];
|
|
105
|
-
const lastWork = lastAssistant ? lastAssistant.content.slice(0, 300) + (lastAssistant.content.length > 300 ? "..." : "") : "None recorded";
|
|
106
|
-
return `## Pre-Compaction State Restored
|
|
107
|
-
|
|
108
|
-
### Active Files (${activeFiles.length})
|
|
109
|
-
${activeFiles.slice(0, 10).map((f) => `- ${f}`).join("\n") || "None tracked"}
|
|
110
|
-
|
|
111
|
-
### Recent User Requests
|
|
112
|
-
${userMessages || "None recorded"}
|
|
113
|
-
|
|
114
|
-
### Last Work in Progress
|
|
115
|
-
${lastWork}
|
|
116
|
-
|
|
117
|
-
### Session Stats
|
|
118
|
-
- Tool calls: ${toolCallCount}
|
|
119
|
-
- Messages: ${messages.length}
|
|
120
|
-
- Saved at: ${transcript.created_at}`;
|
|
121
|
-
}
|
|
122
|
-
async function runPostCompactHook() {
|
|
123
|
-
if (!ENABLED) {
|
|
124
|
-
process.exit(0);
|
|
125
|
-
}
|
|
126
|
-
let inputData = "";
|
|
127
|
-
for await (const chunk of process.stdin) {
|
|
128
|
-
inputData += chunk;
|
|
129
|
-
}
|
|
130
|
-
if (!inputData.trim()) {
|
|
131
|
-
process.exit(0);
|
|
132
|
-
}
|
|
133
|
-
let input;
|
|
134
|
-
try {
|
|
135
|
-
input = JSON.parse(inputData);
|
|
136
|
-
} catch {
|
|
137
|
-
process.exit(0);
|
|
138
|
-
}
|
|
139
|
-
const cwd = input.cwd || process.cwd();
|
|
140
|
-
loadConfigFromMcpJson(cwd);
|
|
141
|
-
const sessionId = input.session_id || "";
|
|
142
|
-
let restoredContext = "";
|
|
143
|
-
if (sessionId && API_KEY) {
|
|
144
|
-
const transcript = await fetchLastTranscript(sessionId);
|
|
145
|
-
if (transcript) {
|
|
146
|
-
restoredContext = formatTranscriptSummary(transcript);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const context = `[POST-COMPACTION - Context Restored]
|
|
150
|
-
|
|
151
|
-
${restoredContext || "No saved state found. Starting fresh."}
|
|
152
|
-
|
|
153
|
-
**IMPORTANT:** Call \`mcp__contextstream__context(user_message="resuming after compaction")\` to get full context and any pending tasks.
|
|
154
|
-
|
|
155
|
-
The conversation was compacted to save memory. The above summary was automatically restored from ContextStream.`;
|
|
156
|
-
console.log(
|
|
157
|
-
JSON.stringify({
|
|
158
|
-
hookSpecificOutput: {
|
|
159
|
-
hookEventName: "PostCompact",
|
|
160
|
-
additionalContext: context
|
|
161
|
-
}
|
|
162
|
-
})
|
|
163
|
-
);
|
|
164
|
-
process.exit(0);
|
|
165
|
-
}
|
|
166
|
-
var isDirectRun = process.argv[1]?.includes("post-compact") || process.argv[2] === "post-compact";
|
|
167
|
-
if (isDirectRun) {
|
|
168
|
-
runPostCompactHook().catch(() => process.exit(0));
|
|
169
|
-
}
|
|
170
|
-
export {
|
|
171
|
-
runPostCompactHook
|
|
172
|
-
};
|