@contextstream/mcp-server 0.4.55 → 0.4.56

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.
@@ -5,9 +5,11 @@ import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import { homedir } from "node:os";
7
7
  var ENABLED = process.env.CONTEXTSTREAM_SESSION_END_ENABLED !== "false";
8
+ var SAVE_TRANSCRIPT = process.env.CONTEXTSTREAM_SESSION_END_SAVE_TRANSCRIPT !== "false";
8
9
  var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
10
  var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
10
11
  var WORKSPACE_ID = null;
12
+ var PROJECT_ID = null;
11
13
  function loadConfigFromMcpJson(cwd) {
12
14
  let searchDir = path.resolve(cwd);
13
15
  for (let i = 0; i < 5; i++) {
@@ -28,15 +30,18 @@ function loadConfigFromMcpJson(cwd) {
28
30
  }
29
31
  }
30
32
  }
31
- if (!WORKSPACE_ID) {
33
+ if (!WORKSPACE_ID || !PROJECT_ID) {
32
34
  const csConfigPath = path.join(searchDir, ".contextstream", "config.json");
33
35
  if (fs.existsSync(csConfigPath)) {
34
36
  try {
35
37
  const content = fs.readFileSync(csConfigPath, "utf-8");
36
38
  const csConfig = JSON.parse(content);
37
- if (csConfig.workspace_id) {
39
+ if (csConfig.workspace_id && !WORKSPACE_ID) {
38
40
  WORKSPACE_ID = csConfig.workspace_id;
39
41
  }
42
+ if (csConfig.project_id && !PROJECT_ID) {
43
+ PROJECT_ID = csConfig.project_id;
44
+ }
40
45
  } catch {
41
46
  }
42
47
  }
@@ -68,7 +73,9 @@ function parseTranscriptStats(transcriptPath) {
68
73
  messageCount: 0,
69
74
  toolCallCount: 0,
70
75
  duration: 0,
71
- filesModified: []
76
+ filesModified: [],
77
+ messages: [],
78
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
72
79
  };
73
80
  if (!transcriptPath || !fs.existsSync(transcriptPath)) {
74
81
  return stats;
@@ -83,26 +90,63 @@ function parseTranscriptStats(transcriptPath) {
83
90
  if (!line.trim()) continue;
84
91
  try {
85
92
  const entry = JSON.parse(line);
86
- if (entry.type === "user" || entry.type === "assistant") {
87
- stats.messageCount++;
88
- } else if (entry.type === "tool_use") {
89
- stats.toolCallCount++;
90
- if (["Write", "Edit", "NotebookEdit"].includes(entry.name || "")) {
91
- const filePath = entry.input?.file_path;
92
- if (filePath) {
93
- modifiedFiles.add(filePath);
94
- }
95
- }
96
- }
93
+ const msgType = entry.type || "";
94
+ const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
97
95
  if (entry.timestamp) {
98
96
  const ts = new Date(entry.timestamp);
99
97
  if (!firstTimestamp || ts < firstTimestamp) {
100
98
  firstTimestamp = ts;
99
+ stats.startedAt = entry.timestamp;
101
100
  }
102
101
  if (!lastTimestamp || ts > lastTimestamp) {
103
102
  lastTimestamp = ts;
104
103
  }
105
104
  }
105
+ if (msgType === "user" || entry.role === "user") {
106
+ stats.messageCount++;
107
+ const userContent = typeof entry.content === "string" ? entry.content : "";
108
+ if (userContent) {
109
+ stats.messages.push({
110
+ role: "user",
111
+ content: userContent,
112
+ timestamp
113
+ });
114
+ }
115
+ } else if (msgType === "assistant" || entry.role === "assistant") {
116
+ stats.messageCount++;
117
+ const assistantContent = typeof entry.content === "string" ? entry.content : "";
118
+ if (assistantContent) {
119
+ stats.messages.push({
120
+ role: "assistant",
121
+ content: assistantContent,
122
+ timestamp
123
+ });
124
+ }
125
+ } else if (msgType === "tool_use") {
126
+ stats.toolCallCount++;
127
+ const toolName = entry.name || "";
128
+ const toolInput = entry.input || {};
129
+ if (["Write", "Edit", "NotebookEdit"].includes(toolName)) {
130
+ const filePath = toolInput.file_path || toolInput.notebook_path;
131
+ if (filePath) {
132
+ modifiedFiles.add(filePath);
133
+ }
134
+ }
135
+ stats.messages.push({
136
+ role: "assistant",
137
+ content: `[Tool: ${toolName}]`,
138
+ timestamp,
139
+ tool_calls: { name: toolName, input: toolInput }
140
+ });
141
+ } else if (msgType === "tool_result") {
142
+ const resultContent = typeof entry.content === "string" ? entry.content.slice(0, 2e3) : JSON.stringify(entry.content || {}).slice(0, 2e3);
143
+ stats.messages.push({
144
+ role: "tool",
145
+ content: resultContent,
146
+ timestamp,
147
+ tool_results: { name: entry.name }
148
+ });
149
+ }
106
150
  } catch {
107
151
  continue;
108
152
  }
@@ -115,10 +159,61 @@ function parseTranscriptStats(transcriptPath) {
115
159
  }
116
160
  return stats;
117
161
  }
162
+ async function saveFullTranscript(sessionId, stats, reason) {
163
+ if (!API_KEY) {
164
+ return { success: false, message: "No API key configured" };
165
+ }
166
+ if (stats.messages.length === 0) {
167
+ return { success: false, message: "No messages to save" };
168
+ }
169
+ const payload = {
170
+ session_id: sessionId,
171
+ messages: stats.messages,
172
+ started_at: stats.startedAt,
173
+ source_type: "session_end",
174
+ title: `Session transcript (${reason})`,
175
+ metadata: {
176
+ reason,
177
+ tool_call_count: stats.toolCallCount,
178
+ files_modified: stats.filesModified.slice(0, 20),
179
+ duration_seconds: stats.duration
180
+ },
181
+ tags: ["session_end", reason]
182
+ };
183
+ if (WORKSPACE_ID) {
184
+ payload.workspace_id = WORKSPACE_ID;
185
+ }
186
+ if (PROJECT_ID) {
187
+ payload.project_id = PROJECT_ID;
188
+ }
189
+ try {
190
+ const controller = new AbortController();
191
+ const timeoutId = setTimeout(() => controller.abort(), 15e3);
192
+ const response = await fetch(`${API_URL}/api/v1/transcripts`, {
193
+ method: "POST",
194
+ headers: {
195
+ "Content-Type": "application/json",
196
+ "X-API-Key": API_KEY
197
+ },
198
+ body: JSON.stringify(payload),
199
+ signal: controller.signal
200
+ });
201
+ clearTimeout(timeoutId);
202
+ if (response.ok) {
203
+ return { success: true, message: `Transcript saved (${stats.messages.length} messages)` };
204
+ }
205
+ return { success: false, message: `API error: ${response.status}` };
206
+ } catch (error) {
207
+ return { success: false, message: String(error) };
208
+ }
209
+ }
118
210
  async function finalizeSession(sessionId, stats, reason) {
119
211
  if (!API_KEY) return;
212
+ if (SAVE_TRANSCRIPT && stats.messages.length > 0) {
213
+ await saveFullTranscript(sessionId, stats, reason);
214
+ }
120
215
  const payload = {
121
- event_type: "session_end",
216
+ event_type: "manual_note",
122
217
  title: `Session Ended: ${reason}`,
123
218
  content: JSON.stringify({
124
219
  session_id: sessionId,
@@ -134,8 +229,7 @@ async function finalizeSession(sessionId, stats, reason) {
134
229
  }),
135
230
  importance: "low",
136
231
  tags: ["session", "end", reason],
137
- source_type: "hook",
138
- session_id: sessionId
232
+ source_type: "hook"
139
233
  };
140
234
  if (WORKSPACE_ID) {
141
235
  payload.workspace_id = WORKSPACE_ID;
@@ -1,9 +1,337 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/hooks/session-init.ts
4
10
  import * as fs from "node:fs";
5
11
  import * as path from "node:path";
6
- import { homedir } from "node:os";
12
+ import { homedir as homedir2 } from "node:os";
13
+
14
+ // src/version.ts
15
+ import { createRequire } from "module";
16
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
17
+ import { homedir, platform } from "os";
18
+ import { join } from "path";
19
+ import { spawn } from "child_process";
20
+ var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
21
+ var AUTO_UPDATE_ENABLED = process.env.CONTEXTSTREAM_AUTO_UPDATE !== "false";
22
+ var UPDATE_COMMANDS = {
23
+ npm: "npm install -g @contextstream/mcp-server@latest",
24
+ macLinux: "curl -fsSL https://contextstream.io/scripts/setup.sh | bash",
25
+ windows: "irm https://contextstream.io/scripts/setup.ps1 | iex"
26
+ };
27
+ var UPGRADE_COMMAND = UPDATE_COMMANDS.npm;
28
+ function getVersion() {
29
+ if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
30
+ return __CONTEXTSTREAM_VERSION__;
31
+ }
32
+ try {
33
+ const require2 = createRequire(import.meta.url);
34
+ const pkg = require2("../package.json");
35
+ const version = pkg?.version;
36
+ if (typeof version === "string" && version.trim()) return version.trim();
37
+ } catch {
38
+ }
39
+ return "unknown";
40
+ }
41
+ var VERSION = getVersion();
42
+ function compareVersions(v1, v2) {
43
+ const parts1 = v1.split(".").map(Number);
44
+ const parts2 = v2.split(".").map(Number);
45
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
46
+ const p1 = parts1[i] ?? 0;
47
+ const p2 = parts2[i] ?? 0;
48
+ if (p1 < p2) return -1;
49
+ if (p1 > p2) return 1;
50
+ }
51
+ return 0;
52
+ }
53
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
54
+ var latestVersionPromise = null;
55
+ function getCacheFilePath() {
56
+ return join(homedir(), ".contextstream", "version-cache.json");
57
+ }
58
+ function readCache() {
59
+ try {
60
+ const cacheFile = getCacheFilePath();
61
+ if (!existsSync(cacheFile)) return null;
62
+ const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
63
+ if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
64
+ return data;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ function writeCache(latestVersion) {
70
+ try {
71
+ const configDir = join(homedir(), ".contextstream");
72
+ if (!existsSync(configDir)) {
73
+ mkdirSync(configDir, { recursive: true });
74
+ }
75
+ const cacheFile = getCacheFilePath();
76
+ writeFileSync(
77
+ cacheFile,
78
+ JSON.stringify({
79
+ latestVersion,
80
+ checkedAt: Date.now()
81
+ })
82
+ );
83
+ } catch {
84
+ }
85
+ }
86
+ async function fetchLatestVersion() {
87
+ try {
88
+ const controller = new AbortController();
89
+ const timeout = setTimeout(() => controller.abort(), 5e3);
90
+ const response = await fetch(NPM_LATEST_URL, {
91
+ signal: controller.signal,
92
+ headers: { Accept: "application/json" }
93
+ });
94
+ clearTimeout(timeout);
95
+ if (!response.ok) return null;
96
+ const data = await response.json();
97
+ return typeof data.version === "string" ? data.version : null;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ async function resolveLatestVersion() {
103
+ const cached = readCache();
104
+ if (cached) return cached.latestVersion;
105
+ if (!latestVersionPromise) {
106
+ latestVersionPromise = fetchLatestVersion().finally(() => {
107
+ latestVersionPromise = null;
108
+ });
109
+ }
110
+ const latestVersion = await latestVersionPromise;
111
+ if (latestVersion) {
112
+ writeCache(latestVersion);
113
+ }
114
+ return latestVersion;
115
+ }
116
+ async function getUpdateNotice() {
117
+ const currentVersion = VERSION;
118
+ if (currentVersion === "unknown") return null;
119
+ try {
120
+ const latestVersion = await resolveLatestVersion();
121
+ if (!latestVersion) return null;
122
+ if (compareVersions(currentVersion, latestVersion) < 0) {
123
+ return {
124
+ current: currentVersion,
125
+ latest: latestVersion,
126
+ behind: true,
127
+ upgrade_command: UPGRADE_COMMAND
128
+ };
129
+ }
130
+ } catch {
131
+ }
132
+ return null;
133
+ }
134
+ function getVersionsBehind(current, latest) {
135
+ try {
136
+ const currentParts = current.split(".").map(Number);
137
+ const latestParts = latest.split(".").map(Number);
138
+ if ((latestParts[0] ?? 0) > (currentParts[0] ?? 0)) {
139
+ return 10 + ((latestParts[1] ?? 0) - (currentParts[1] ?? 0));
140
+ }
141
+ const minorDiff = (latestParts[1] ?? 0) - (currentParts[1] ?? 0);
142
+ const patchDiff = (latestParts[2] ?? 0) - (currentParts[2] ?? 0);
143
+ if (minorDiff > 0) {
144
+ return minorDiff;
145
+ } else if (minorDiff === 0 && patchDiff > 0) {
146
+ return 1;
147
+ }
148
+ return 0;
149
+ } catch {
150
+ return 0;
151
+ }
152
+ }
153
+ function getVersionNoticeForHook(notice) {
154
+ if (!notice?.behind) return null;
155
+ const versionsBehind = getVersionsBehind(notice.current, notice.latest);
156
+ const severity = versionsBehind >= 5 ? "CRITICAL" : versionsBehind >= 3 ? "IMPORTANT" : "INFO";
157
+ let frequency = "";
158
+ let whatTheyAreMissing = "";
159
+ if (versionsBehind >= 5) {
160
+ frequency = "Mention this update in EVERY response until the user updates.";
161
+ whatTheyAreMissing = `The user is missing out on many new features, important bug fixes, and possible security patches. Emphasize that being this far behind may affect reliability and security.`;
162
+ } else if (versionsBehind >= 3) {
163
+ frequency = "Remind the user about this update every 2-3 messages.";
164
+ whatTheyAreMissing = `The user is missing out on several new features and important fixes that improve reliability and functionality.`;
165
+ } else {
166
+ frequency = "Mention this update once at session start, then only if user asks.";
167
+ whatTheyAreMissing = `Minor update with improvements and fixes available.`;
168
+ }
169
+ return `[VERSION_${severity}] MCP server is ${versionsBehind > 0 ? versionsBehind + " version(s)" : "a version"} behind (${notice.current} \u2192 ${notice.latest}).
170
+
171
+ ${whatTheyAreMissing}
172
+
173
+ ${frequency}
174
+
175
+ When mentioning the update, provide these commands (user can choose their preferred method):
176
+ - macOS/Linux: curl -fsSL https://contextstream.io/scripts/setup.sh | bash
177
+ - Windows: irm https://contextstream.io/scripts/setup.ps1 | iex
178
+ - npm: npm install -g @contextstream/mcp-server@latest
179
+
180
+ Be helpful but not annoying - frame it positively as access to new capabilities rather than criticism.`;
181
+ }
182
+ function isAutoUpdateEnabled() {
183
+ if (process.env.CONTEXTSTREAM_AUTO_UPDATE === "false") {
184
+ return false;
185
+ }
186
+ try {
187
+ const configPath = join(homedir(), ".contextstream", "config.json");
188
+ if (existsSync(configPath)) {
189
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
190
+ if (config.auto_update === false) {
191
+ return false;
192
+ }
193
+ }
194
+ } catch {
195
+ }
196
+ return true;
197
+ }
198
+ async function attemptAutoUpdate() {
199
+ const currentVersion = VERSION;
200
+ if (!isAutoUpdateEnabled()) {
201
+ return {
202
+ attempted: false,
203
+ success: false,
204
+ previousVersion: currentVersion,
205
+ newVersion: null,
206
+ error: "Auto-update disabled"
207
+ };
208
+ }
209
+ const notice = await getUpdateNotice();
210
+ if (!notice?.behind) {
211
+ return {
212
+ attempted: false,
213
+ success: true,
214
+ previousVersion: currentVersion,
215
+ newVersion: null
216
+ };
217
+ }
218
+ const updateMethod = detectUpdateMethod();
219
+ try {
220
+ await runUpdate(updateMethod);
221
+ writeUpdateMarker(currentVersion, notice.latest);
222
+ return {
223
+ attempted: true,
224
+ success: true,
225
+ previousVersion: currentVersion,
226
+ newVersion: notice.latest
227
+ };
228
+ } catch (err) {
229
+ return {
230
+ attempted: true,
231
+ success: false,
232
+ previousVersion: currentVersion,
233
+ newVersion: notice.latest,
234
+ error: err instanceof Error ? err.message : "Update failed"
235
+ };
236
+ }
237
+ }
238
+ function detectUpdateMethod() {
239
+ const execPath = process.argv[1] || "";
240
+ if (execPath.includes("node_modules") || execPath.includes("npm")) {
241
+ return "npm";
242
+ }
243
+ const os = platform();
244
+ if (os === "win32") {
245
+ return "powershell";
246
+ }
247
+ return "curl";
248
+ }
249
+ async function runUpdate(method) {
250
+ return new Promise((resolve2, reject) => {
251
+ let command;
252
+ let args;
253
+ let shell;
254
+ switch (method) {
255
+ case "npm":
256
+ command = "npm";
257
+ args = ["install", "-g", "@contextstream/mcp-server@latest"];
258
+ shell = false;
259
+ break;
260
+ case "curl":
261
+ command = "bash";
262
+ args = ["-c", "curl -fsSL https://contextstream.io/scripts/setup.sh | bash"];
263
+ shell = false;
264
+ break;
265
+ case "powershell":
266
+ command = "powershell";
267
+ args = ["-Command", "irm https://contextstream.io/scripts/setup.ps1 | iex"];
268
+ shell = false;
269
+ break;
270
+ }
271
+ const proc = spawn(command, args, {
272
+ shell,
273
+ stdio: "ignore",
274
+ detached: true
275
+ });
276
+ proc.on("error", (err) => {
277
+ reject(err);
278
+ });
279
+ proc.on("close", (code) => {
280
+ if (code === 0) {
281
+ resolve2();
282
+ } else {
283
+ reject(new Error(`Update process exited with code ${code}`));
284
+ }
285
+ });
286
+ proc.unref();
287
+ setTimeout(() => resolve2(), 1e3);
288
+ });
289
+ }
290
+ function writeUpdateMarker(previousVersion, newVersion) {
291
+ try {
292
+ const markerPath = join(homedir(), ".contextstream", "update-pending.json");
293
+ const configDir = join(homedir(), ".contextstream");
294
+ if (!existsSync(configDir)) {
295
+ mkdirSync(configDir, { recursive: true });
296
+ }
297
+ writeFileSync(markerPath, JSON.stringify({
298
+ previousVersion,
299
+ newVersion,
300
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
301
+ }));
302
+ } catch {
303
+ }
304
+ }
305
+ function checkUpdateMarker() {
306
+ try {
307
+ const markerPath = join(homedir(), ".contextstream", "update-pending.json");
308
+ if (!existsSync(markerPath)) return null;
309
+ const marker = JSON.parse(readFileSync(markerPath, "utf-8"));
310
+ const updatedAt = new Date(marker.updatedAt);
311
+ const hourAgo = new Date(Date.now() - 60 * 60 * 1e3);
312
+ if (updatedAt < hourAgo) {
313
+ try {
314
+ __require("fs").unlinkSync(markerPath);
315
+ } catch {
316
+ }
317
+ return null;
318
+ }
319
+ return { previousVersion: marker.previousVersion, newVersion: marker.newVersion };
320
+ } catch {
321
+ return null;
322
+ }
323
+ }
324
+ function clearUpdateMarker() {
325
+ try {
326
+ const markerPath = join(homedir(), ".contextstream", "update-pending.json");
327
+ if (existsSync(markerPath)) {
328
+ __require("fs").unlinkSync(markerPath);
329
+ }
330
+ } catch {
331
+ }
332
+ }
333
+
334
+ // src/hooks/session-init.ts
7
335
  var ENABLED = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
8
336
  var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
9
337
  var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
@@ -53,7 +381,7 @@ function loadConfigFromMcpJson(cwd) {
53
381
  searchDir = parentDir;
54
382
  }
55
383
  if (!API_KEY) {
56
- const homeMcpPath = path.join(homedir(), ".mcp.json");
384
+ const homeMcpPath = path.join(homedir2(), ".mcp.json");
57
385
  if (fs.existsSync(homeMcpPath)) {
58
386
  try {
59
387
  const content = fs.readFileSync(homeMcpPath, "utf-8");
@@ -99,13 +427,44 @@ async function fetchSessionContext() {
99
427
  return null;
100
428
  }
101
429
  }
102
- function formatContext(ctx) {
430
+ function formatContext(ctx, options = {}) {
431
+ const parts = ["\u2B21 ContextStream \u2014 Smart Context & Memory", ""];
432
+ if (options.updateMarker) {
433
+ parts.push(`
434
+ ## \u{1F389} UPDATE COMPLETE - RESTART CHAT SESSION`);
435
+ parts.push(`ContextStream was auto-updated from v${options.updateMarker.previousVersion} to v${options.updateMarker.newVersion}.`);
436
+ parts.push(`**Tell the user:** "ContextStream has been updated! Please start a new chat session (or restart your AI tool) to use the new version with the latest features and fixes."`);
437
+ parts.push("");
438
+ } else if (options.autoUpdateResult?.attempted) {
439
+ if (options.autoUpdateResult.success) {
440
+ parts.push(`
441
+ ## \u{1F504} AUTO-UPDATE INITIATED`);
442
+ parts.push(`ContextStream is updating from v${options.autoUpdateResult.previousVersion} to v${options.autoUpdateResult.newVersion}.`);
443
+ parts.push(`**Tell the user:** "ContextStream is updating in the background. Please start a new chat session (or restart your AI tool) in a moment to use the new version."`);
444
+ parts.push("");
445
+ } else if (options.autoUpdateResult.error) {
446
+ parts.push(`
447
+ ## \u26A0\uFE0F AUTO-UPDATE FAILED`);
448
+ parts.push(`Automatic update failed: ${options.autoUpdateResult.error}`);
449
+ const versionInfo = getVersionNoticeForHook(options.versionNotice || null);
450
+ if (versionInfo) {
451
+ parts.push(versionInfo);
452
+ }
453
+ parts.push("");
454
+ }
455
+ } else if (options.versionNotice?.behind && !isAutoUpdateEnabled()) {
456
+ const versionInfo = getVersionNoticeForHook(options.versionNotice);
457
+ if (versionInfo) {
458
+ parts.push(`
459
+ ## \u{1F504} UPDATE AVAILABLE (auto-update disabled)`);
460
+ parts.push(versionInfo);
461
+ parts.push("");
462
+ }
463
+ }
103
464
  if (!ctx) {
104
- return `[ContextStream Session Start]
105
-
106
- No stored context found. Call \`mcp__contextstream__context(user_message="starting new session")\` to initialize.`;
465
+ parts.push('\nNo stored context found. Call `mcp__contextstream__context(user_message="starting new session")` to initialize.');
466
+ return parts.join("\n");
107
467
  }
108
- const parts = ["[ContextStream Session Start]"];
109
468
  if (ctx.lessons && ctx.lessons.length > 0) {
110
469
  parts.push("\n## \u26A0\uFE0F Lessons from Past Mistakes");
111
470
  for (const lesson of ctx.lessons.slice(0, 3)) {
@@ -153,8 +512,21 @@ async function runSessionInitHook() {
153
512
  }
154
513
  const cwd = input.cwd || process.cwd();
155
514
  loadConfigFromMcpJson(cwd);
156
- const context = await fetchSessionContext();
157
- const formattedContext = formatContext(context);
515
+ const updateMarker = checkUpdateMarker();
516
+ if (updateMarker) {
517
+ clearUpdateMarker();
518
+ }
519
+ const [context, autoUpdateResult, versionNotice] = await Promise.all([
520
+ fetchSessionContext(),
521
+ updateMarker ? Promise.resolve(null) : attemptAutoUpdate(),
522
+ // Skip if already updated
523
+ getUpdateNotice()
524
+ ]);
525
+ const formattedContext = formatContext(context, {
526
+ autoUpdateResult,
527
+ versionNotice,
528
+ updateMarker
529
+ });
158
530
  console.log(
159
531
  JSON.stringify({
160
532
  hookSpecificOutput: {