@contextstream/mcp-server 0.4.37 → 0.4.38
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/dist/index.js +781 -32
- package/dist/test-server.js +5 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
14
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
9
15
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
16
|
};
|
|
11
17
|
var __export = (target, all) => {
|
|
@@ -4676,7 +4682,8 @@ var configSchema = external_exports.object({
|
|
|
4676
4682
|
defaultProjectId: external_exports.string().uuid().optional(),
|
|
4677
4683
|
userAgent: external_exports.string().default(`contextstream-mcp/${VERSION}`),
|
|
4678
4684
|
allowHeaderAuth: external_exports.boolean().optional(),
|
|
4679
|
-
contextPackEnabled: external_exports.boolean().default(true)
|
|
4685
|
+
contextPackEnabled: external_exports.boolean().default(true),
|
|
4686
|
+
showTiming: external_exports.boolean().default(false)
|
|
4680
4687
|
});
|
|
4681
4688
|
var MISSING_CREDENTIALS_ERROR = "Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT for authentication (or CONTEXTSTREAM_ALLOW_HEADER_AUTH=true for header-based auth).";
|
|
4682
4689
|
function isMissingCredentialsError(err) {
|
|
@@ -4690,6 +4697,7 @@ function loadConfig() {
|
|
|
4690
4697
|
const contextPackEnabled = parseBooleanEnv(
|
|
4691
4698
|
process.env.CONTEXTSTREAM_CONTEXT_PACK ?? process.env.CONTEXTSTREAM_CONTEXT_PACK_ENABLED
|
|
4692
4699
|
);
|
|
4700
|
+
const showTiming = parseBooleanEnv(process.env.CONTEXTSTREAM_SHOW_TIMING);
|
|
4693
4701
|
const parsed = configSchema.safeParse({
|
|
4694
4702
|
apiUrl: process.env.CONTEXTSTREAM_API_URL,
|
|
4695
4703
|
apiKey: process.env.CONTEXTSTREAM_API_KEY,
|
|
@@ -4698,7 +4706,8 @@ function loadConfig() {
|
|
|
4698
4706
|
defaultProjectId: process.env.CONTEXTSTREAM_PROJECT_ID,
|
|
4699
4707
|
userAgent: process.env.CONTEXTSTREAM_USER_AGENT,
|
|
4700
4708
|
allowHeaderAuth,
|
|
4701
|
-
contextPackEnabled
|
|
4709
|
+
contextPackEnabled,
|
|
4710
|
+
showTiming
|
|
4702
4711
|
});
|
|
4703
4712
|
if (!parsed.success) {
|
|
4704
4713
|
const missing = parsed.error.errors.map((e) => e.path.join(".")).join(", ");
|
|
@@ -5572,6 +5581,47 @@ var INGEST_BENEFITS = [
|
|
|
5572
5581
|
"Allow the AI assistant to find relevant code without manual file navigation",
|
|
5573
5582
|
"Build a searchable knowledge base of your codebase structure"
|
|
5574
5583
|
];
|
|
5584
|
+
var PROJECT_MARKERS = [
|
|
5585
|
+
".git",
|
|
5586
|
+
"package.json",
|
|
5587
|
+
"Cargo.toml",
|
|
5588
|
+
"pyproject.toml",
|
|
5589
|
+
"go.mod",
|
|
5590
|
+
"pom.xml",
|
|
5591
|
+
"build.gradle",
|
|
5592
|
+
"Gemfile",
|
|
5593
|
+
"composer.json",
|
|
5594
|
+
".contextstream"
|
|
5595
|
+
];
|
|
5596
|
+
function isMultiProjectFolder(folderPath) {
|
|
5597
|
+
try {
|
|
5598
|
+
const fs8 = __require("fs");
|
|
5599
|
+
const pathModule = __require("path");
|
|
5600
|
+
const rootHasGit = fs8.existsSync(pathModule.join(folderPath, ".git"));
|
|
5601
|
+
const entries = fs8.readdirSync(folderPath, { withFileTypes: true });
|
|
5602
|
+
const subdirs = entries.filter(
|
|
5603
|
+
(e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules"
|
|
5604
|
+
);
|
|
5605
|
+
const projectSubdirs = [];
|
|
5606
|
+
for (const subdir of subdirs) {
|
|
5607
|
+
const subdirPath = pathModule.join(folderPath, subdir.name);
|
|
5608
|
+
for (const marker of PROJECT_MARKERS) {
|
|
5609
|
+
if (fs8.existsSync(pathModule.join(subdirPath, marker))) {
|
|
5610
|
+
projectSubdirs.push(subdir.name);
|
|
5611
|
+
break;
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
const isMultiProject = projectSubdirs.length >= 1 && (!rootHasGit || projectSubdirs.length >= 2);
|
|
5616
|
+
return {
|
|
5617
|
+
isMultiProject,
|
|
5618
|
+
projectCount: projectSubdirs.length,
|
|
5619
|
+
projectNames: projectSubdirs
|
|
5620
|
+
};
|
|
5621
|
+
} catch {
|
|
5622
|
+
return { isMultiProject: false, projectCount: 0, projectNames: [] };
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5575
5625
|
var ContextStreamClient = class {
|
|
5576
5626
|
constructor(config) {
|
|
5577
5627
|
this.config = config;
|
|
@@ -6607,7 +6657,25 @@ var ContextStreamClient = class {
|
|
|
6607
6657
|
return context;
|
|
6608
6658
|
}
|
|
6609
6659
|
}
|
|
6610
|
-
|
|
6660
|
+
let autoDetectedMultiProject = false;
|
|
6661
|
+
if (!params.skip_project_creation && !projectId && rootPath) {
|
|
6662
|
+
const detection = isMultiProjectFolder(rootPath);
|
|
6663
|
+
if (detection.isMultiProject) {
|
|
6664
|
+
autoDetectedMultiProject = true;
|
|
6665
|
+
context.workspace_only_mode = true;
|
|
6666
|
+
context.auto_detected_multi_project = true;
|
|
6667
|
+
context.detected_projects = detection.projectNames;
|
|
6668
|
+
context.project_skipped_reason = `Auto-detected ${detection.projectCount} projects in folder: ${detection.projectNames.slice(0, 5).join(", ")}${detection.projectCount > 5 ? "..." : ""}. Working at workspace level.`;
|
|
6669
|
+
console.error(
|
|
6670
|
+
`[ContextStream] Auto-detected multi-project folder with ${detection.projectCount} projects: ${detection.projectNames.slice(0, 5).join(", ")}`
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
if (params.skip_project_creation) {
|
|
6675
|
+
context.workspace_only_mode = true;
|
|
6676
|
+
context.project_skipped_reason = "skip_project_creation=true - working at workspace level for multi-project folder";
|
|
6677
|
+
} else if (autoDetectedMultiProject) {
|
|
6678
|
+
} else if (!projectId && workspaceId && rootPath && params.auto_index !== false) {
|
|
6611
6679
|
const projectName = path4.basename(rootPath) || "My Project";
|
|
6612
6680
|
try {
|
|
6613
6681
|
const projects = await this.listProjects({ workspace_id: workspaceId });
|
|
@@ -6918,11 +6986,16 @@ var ContextStreamClient = class {
|
|
|
6918
6986
|
* Persists the selection to .contextstream/config.json for future sessions.
|
|
6919
6987
|
*/
|
|
6920
6988
|
async associateWorkspace(params) {
|
|
6921
|
-
const { folder_path, workspace_id, workspace_name, create_parent_mapping } = params;
|
|
6989
|
+
const { folder_path, workspace_id, workspace_name, create_parent_mapping, version, configured_editors, context_pack, api_url } = params;
|
|
6922
6990
|
const saved = writeLocalConfig(folder_path, {
|
|
6923
6991
|
workspace_id,
|
|
6924
6992
|
workspace_name,
|
|
6925
|
-
associated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6993
|
+
associated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6994
|
+
version,
|
|
6995
|
+
configured_editors,
|
|
6996
|
+
context_pack,
|
|
6997
|
+
api_url,
|
|
6998
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6926
6999
|
});
|
|
6927
7000
|
if (create_parent_mapping) {
|
|
6928
7001
|
const parentDir = path4.dirname(folder_path);
|
|
@@ -7593,7 +7666,10 @@ var ContextStreamClient = class {
|
|
|
7593
7666
|
distill: params.distill,
|
|
7594
7667
|
client_version: VERSION,
|
|
7595
7668
|
rules_version: VERSION,
|
|
7596
|
-
notice_inline: false
|
|
7669
|
+
notice_inline: false,
|
|
7670
|
+
// Session token tracking for context pressure
|
|
7671
|
+
...params.session_tokens !== void 0 && { session_tokens: params.session_tokens },
|
|
7672
|
+
...params.context_threshold !== void 0 && { context_threshold: params.context_threshold }
|
|
7597
7673
|
}
|
|
7598
7674
|
});
|
|
7599
7675
|
const data = unwrapApiResponse(apiResult);
|
|
@@ -7613,7 +7689,8 @@ var ContextStreamClient = class {
|
|
|
7613
7689
|
project_id: withDefaults.project_id,
|
|
7614
7690
|
...versionNotice2 ? { version_notice: versionNotice2 } : {},
|
|
7615
7691
|
...Array.isArray(data?.errors) ? { errors: data.errors } : {},
|
|
7616
|
-
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {}
|
|
7692
|
+
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {},
|
|
7693
|
+
...data?.context_pressure ? { context_pressure: data.context_pressure } : {}
|
|
7617
7694
|
};
|
|
7618
7695
|
} catch (err) {
|
|
7619
7696
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
@@ -8981,6 +9058,39 @@ If context still feels missing, use \`session(action="recall", query="...")\` fo
|
|
|
8981
9058
|
|
|
8982
9059
|
---
|
|
8983
9060
|
|
|
9061
|
+
### Context Pressure & Compaction Awareness
|
|
9062
|
+
|
|
9063
|
+
ContextStream tracks context pressure to help you stay ahead of conversation compaction:
|
|
9064
|
+
|
|
9065
|
+
**Automatic tracking:** Token usage is tracked automatically. \`context_smart\` returns \`context_pressure\` when usage is high.
|
|
9066
|
+
|
|
9067
|
+
**When \`context_smart\` returns \`context_pressure\` with high/critical level:**
|
|
9068
|
+
1. Review the \`suggested_action\` field:
|
|
9069
|
+
- \`prepare_save\`: Start thinking about saving important state
|
|
9070
|
+
- \`save_now\`: Immediately call \`session(action="capture", event_type="session_snapshot")\` to preserve state
|
|
9071
|
+
|
|
9072
|
+
**PreCompact Hook (Optional):** If enabled, Claude Code will inject a reminder to save state before compaction.
|
|
9073
|
+
Enable with: \`generate_rules(install_hooks=true, include_pre_compact=true)\`
|
|
9074
|
+
|
|
9075
|
+
**Before compaction happens (when warned):**
|
|
9076
|
+
\`\`\`
|
|
9077
|
+
session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="{
|
|
9078
|
+
\\"conversation_summary\\": \\"<summarize what we've been doing>\\",
|
|
9079
|
+
\\"current_goal\\": \\"<the main task>\\",
|
|
9080
|
+
\\"active_files\\": [\\"file1.ts\\", \\"file2.ts\\"],
|
|
9081
|
+
\\"recent_decisions\\": [{title: \\"...\\", rationale: \\"...\\"}],
|
|
9082
|
+
\\"unfinished_work\\": [{task: \\"...\\", status: \\"...\\", next_steps: \\"...\\"}]
|
|
9083
|
+
}")
|
|
9084
|
+
\`\`\`
|
|
9085
|
+
|
|
9086
|
+
**After compaction (when context seems lost):**
|
|
9087
|
+
1. Call \`session_init(folder_path="...", is_post_compact=true)\` - this auto-restores the most recent snapshot
|
|
9088
|
+
2. Or call \`session_restore_context()\` directly to get the saved state
|
|
9089
|
+
3. Review the \`restored_context\` to understand prior work
|
|
9090
|
+
4. Acknowledge to the user what was restored and continue
|
|
9091
|
+
|
|
9092
|
+
---
|
|
9093
|
+
|
|
8984
9094
|
### Index Status (Auto-Managed)
|
|
8985
9095
|
|
|
8986
9096
|
**Indexing is automatic.** After \`session_init\`, the project is auto-indexed in the background.
|
|
@@ -9214,12 +9324,27 @@ ContextStream search is **indexed** and returns semantic matches + context in ON
|
|
|
9214
9324
|
- Before risky work: \`session(action="get_lessons", query="<topic>")\`
|
|
9215
9325
|
- On mistakes: \`session(action="capture_lesson", title="...", trigger="...", impact="...", prevention="...")\`
|
|
9216
9326
|
|
|
9327
|
+
### Context Pressure & Compaction
|
|
9328
|
+
|
|
9329
|
+
- If \`context_smart\` returns high/critical \`context_pressure\`: call \`session_capture_smart(...)\` to save state
|
|
9330
|
+
- After compaction (context lost): call \`session_init(..., is_post_compact=true)\` or \`session_restore_context()\`
|
|
9331
|
+
|
|
9217
9332
|
### Plans & Tasks
|
|
9218
9333
|
|
|
9219
9334
|
When user asks for a plan, use ContextStream (not EnterPlanMode):
|
|
9220
9335
|
1. \`session(action="capture_plan", title="...", steps=[...])\`
|
|
9221
9336
|
2. \`memory(action="create_task", title="...", plan_id="<id>")\`
|
|
9222
9337
|
|
|
9338
|
+
### Workspace-Only Mode (Multi-Project Folders)
|
|
9339
|
+
|
|
9340
|
+
If working in a parent folder containing multiple projects:
|
|
9341
|
+
\`\`\`
|
|
9342
|
+
session_init(folder_path="...", skip_project_creation=true)
|
|
9343
|
+
\`\`\`
|
|
9344
|
+
|
|
9345
|
+
This enables workspace-level memory and context without project-specific indexing.
|
|
9346
|
+
Use for monorepos or folders with multiple independent projects.
|
|
9347
|
+
|
|
9223
9348
|
Full docs: https://contextstream.io/docs/mcp/tools
|
|
9224
9349
|
`.trim();
|
|
9225
9350
|
var TEMPLATES = {
|
|
@@ -9672,6 +9797,81 @@ def main():
|
|
|
9672
9797
|
print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": REMINDER}}))
|
|
9673
9798
|
sys.exit(0)
|
|
9674
9799
|
|
|
9800
|
+
if __name__ == "__main__":
|
|
9801
|
+
main()
|
|
9802
|
+
`;
|
|
9803
|
+
var PRECOMPACT_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
9804
|
+
"""
|
|
9805
|
+
ContextStream PreCompact Hook for Claude Code
|
|
9806
|
+
|
|
9807
|
+
Runs BEFORE conversation context is compacted (manual via /compact or automatic).
|
|
9808
|
+
Injects a reminder for the AI to save conversation state using session_capture_smart.
|
|
9809
|
+
|
|
9810
|
+
Input (via stdin):
|
|
9811
|
+
{
|
|
9812
|
+
"session_id": "...",
|
|
9813
|
+
"transcript_path": "/path/to/transcript.jsonl",
|
|
9814
|
+
"permission_mode": "default",
|
|
9815
|
+
"hook_event_name": "PreCompact",
|
|
9816
|
+
"trigger": "manual" | "auto",
|
|
9817
|
+
"custom_instructions": "..."
|
|
9818
|
+
}
|
|
9819
|
+
|
|
9820
|
+
Output (to stdout):
|
|
9821
|
+
{
|
|
9822
|
+
"hookSpecificOutput": {
|
|
9823
|
+
"hookEventName": "PreCompact",
|
|
9824
|
+
"additionalContext": "... instructions for AI ..."
|
|
9825
|
+
}
|
|
9826
|
+
}
|
|
9827
|
+
"""
|
|
9828
|
+
|
|
9829
|
+
import json
|
|
9830
|
+
import sys
|
|
9831
|
+
import os
|
|
9832
|
+
|
|
9833
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_PRECOMPACT_ENABLED", "true").lower() == "true"
|
|
9834
|
+
|
|
9835
|
+
def main():
|
|
9836
|
+
if not ENABLED:
|
|
9837
|
+
sys.exit(0)
|
|
9838
|
+
|
|
9839
|
+
try:
|
|
9840
|
+
data = json.load(sys.stdin)
|
|
9841
|
+
except:
|
|
9842
|
+
sys.exit(0)
|
|
9843
|
+
|
|
9844
|
+
trigger = data.get("trigger", "unknown")
|
|
9845
|
+
custom_instructions = data.get("custom_instructions", "")
|
|
9846
|
+
|
|
9847
|
+
# Build context injection for the AI
|
|
9848
|
+
context = f"""[CONTEXT COMPACTION IMMINENT - {trigger.upper()}]
|
|
9849
|
+
CRITICAL: Before context is compacted, you MUST save conversation state:
|
|
9850
|
+
|
|
9851
|
+
1. IMMEDIATELY call: mcp__contextstream__session(action="capture", event_type="session_snapshot", title="Pre-compaction snapshot", content="<JSON with: conversation_summary, active_goals, recent_decisions, active_files, unfinished_work>")
|
|
9852
|
+
|
|
9853
|
+
2. Include in the snapshot:
|
|
9854
|
+
- conversation_summary: Brief summary of what was discussed
|
|
9855
|
+
- active_goals: List of goals/tasks in progress
|
|
9856
|
+
- recent_decisions: Key decisions made in this session
|
|
9857
|
+
- active_files: Files currently being worked on
|
|
9858
|
+
- unfinished_work: Any incomplete tasks
|
|
9859
|
+
|
|
9860
|
+
3. After compaction, call session_init(is_post_compact=true) to restore context.
|
|
9861
|
+
|
|
9862
|
+
{f"User instructions: {custom_instructions}" if custom_instructions else ""}
|
|
9863
|
+
[END COMPACTION WARNING]"""
|
|
9864
|
+
|
|
9865
|
+
output = {
|
|
9866
|
+
"hookSpecificOutput": {
|
|
9867
|
+
"hookEventName": "PreCompact",
|
|
9868
|
+
"additionalContext": context
|
|
9869
|
+
}
|
|
9870
|
+
}
|
|
9871
|
+
|
|
9872
|
+
print(json.dumps(output))
|
|
9873
|
+
sys.exit(0)
|
|
9874
|
+
|
|
9675
9875
|
if __name__ == "__main__":
|
|
9676
9876
|
main()
|
|
9677
9877
|
`;
|
|
@@ -9687,11 +9887,12 @@ function getClaudeSettingsPath(scope, projectPath) {
|
|
|
9687
9887
|
function getHooksDir() {
|
|
9688
9888
|
return path5.join(homedir2(), ".claude", "hooks");
|
|
9689
9889
|
}
|
|
9690
|
-
function buildHooksConfig() {
|
|
9890
|
+
function buildHooksConfig(options) {
|
|
9691
9891
|
const hooksDir = getHooksDir();
|
|
9692
9892
|
const preToolUsePath = path5.join(hooksDir, "contextstream-redirect.py");
|
|
9693
9893
|
const userPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
|
|
9694
|
-
|
|
9894
|
+
const preCompactPath = path5.join(hooksDir, "contextstream-precompact.py");
|
|
9895
|
+
const config = {
|
|
9695
9896
|
PreToolUse: [
|
|
9696
9897
|
{
|
|
9697
9898
|
matcher: "Glob|Grep|Search|Task|EnterPlanMode",
|
|
@@ -9717,15 +9918,40 @@ function buildHooksConfig() {
|
|
|
9717
9918
|
}
|
|
9718
9919
|
]
|
|
9719
9920
|
};
|
|
9921
|
+
if (options?.includePreCompact) {
|
|
9922
|
+
config.PreCompact = [
|
|
9923
|
+
{
|
|
9924
|
+
// Match both manual (/compact) and automatic compaction
|
|
9925
|
+
matcher: "*",
|
|
9926
|
+
hooks: [
|
|
9927
|
+
{
|
|
9928
|
+
type: "command",
|
|
9929
|
+
command: `python3 "${preCompactPath}"`,
|
|
9930
|
+
timeout: 10
|
|
9931
|
+
}
|
|
9932
|
+
]
|
|
9933
|
+
}
|
|
9934
|
+
];
|
|
9935
|
+
}
|
|
9936
|
+
return config;
|
|
9720
9937
|
}
|
|
9721
|
-
async function installHookScripts() {
|
|
9938
|
+
async function installHookScripts(options) {
|
|
9722
9939
|
const hooksDir = getHooksDir();
|
|
9723
9940
|
await fs4.mkdir(hooksDir, { recursive: true });
|
|
9724
9941
|
const preToolUsePath = path5.join(hooksDir, "contextstream-redirect.py");
|
|
9725
9942
|
const userPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
|
|
9943
|
+
const preCompactPath = path5.join(hooksDir, "contextstream-precompact.py");
|
|
9726
9944
|
await fs4.writeFile(preToolUsePath, PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
9727
9945
|
await fs4.writeFile(userPromptPath, USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
|
|
9728
|
-
|
|
9946
|
+
const result = {
|
|
9947
|
+
preToolUse: preToolUsePath,
|
|
9948
|
+
userPrompt: userPromptPath
|
|
9949
|
+
};
|
|
9950
|
+
if (options?.includePreCompact) {
|
|
9951
|
+
await fs4.writeFile(preCompactPath, PRECOMPACT_HOOK_SCRIPT, { mode: 493 });
|
|
9952
|
+
result.preCompact = preCompactPath;
|
|
9953
|
+
}
|
|
9954
|
+
return result;
|
|
9729
9955
|
}
|
|
9730
9956
|
async function readClaudeSettings(scope, projectPath) {
|
|
9731
9957
|
const settingsPath = getClaudeSettingsPath(scope, projectPath);
|
|
@@ -9759,16 +9985,22 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
|
|
|
9759
9985
|
async function installClaudeCodeHooks(options) {
|
|
9760
9986
|
const result = { scripts: [], settings: [] };
|
|
9761
9987
|
if (!options.dryRun) {
|
|
9762
|
-
const scripts = await installHookScripts();
|
|
9988
|
+
const scripts = await installHookScripts({ includePreCompact: options.includePreCompact });
|
|
9763
9989
|
result.scripts.push(scripts.preToolUse, scripts.userPrompt);
|
|
9990
|
+
if (scripts.preCompact) {
|
|
9991
|
+
result.scripts.push(scripts.preCompact);
|
|
9992
|
+
}
|
|
9764
9993
|
} else {
|
|
9765
9994
|
const hooksDir = getHooksDir();
|
|
9766
9995
|
result.scripts.push(
|
|
9767
9996
|
path5.join(hooksDir, "contextstream-redirect.py"),
|
|
9768
9997
|
path5.join(hooksDir, "contextstream-reminder.py")
|
|
9769
9998
|
);
|
|
9999
|
+
if (options.includePreCompact) {
|
|
10000
|
+
result.scripts.push(path5.join(hooksDir, "contextstream-precompact.py"));
|
|
10001
|
+
}
|
|
9770
10002
|
}
|
|
9771
|
-
const hooksConfig = buildHooksConfig();
|
|
10003
|
+
const hooksConfig = buildHooksConfig({ includePreCompact: options.includePreCompact });
|
|
9772
10004
|
if (options.scope === "user" || options.scope === "both") {
|
|
9773
10005
|
const settingsPath = getClaudeSettingsPath("user");
|
|
9774
10006
|
if (!options.dryRun) {
|
|
@@ -10618,13 +10850,17 @@ function resolveAuthOverride(extra) {
|
|
|
10618
10850
|
return { workspaceId, projectId };
|
|
10619
10851
|
}
|
|
10620
10852
|
var LIGHT_TOOLSET = /* @__PURE__ */ new Set([
|
|
10621
|
-
// Core session tools (
|
|
10853
|
+
// Core session tools (15)
|
|
10622
10854
|
"session_init",
|
|
10623
10855
|
"session_tools",
|
|
10624
10856
|
"context_smart",
|
|
10625
10857
|
"context_feedback",
|
|
10626
10858
|
"session_summary",
|
|
10627
10859
|
"session_capture",
|
|
10860
|
+
"session_capture_smart",
|
|
10861
|
+
// Pre-compaction state capture
|
|
10862
|
+
"session_restore_context",
|
|
10863
|
+
// Post-compaction context restore
|
|
10628
10864
|
"session_capture_lesson",
|
|
10629
10865
|
"session_get_lessons",
|
|
10630
10866
|
"session_recall",
|
|
@@ -10664,13 +10900,17 @@ var LIGHT_TOOLSET = /* @__PURE__ */ new Set([
|
|
|
10664
10900
|
"mcp_server_version"
|
|
10665
10901
|
]);
|
|
10666
10902
|
var STANDARD_TOOLSET = /* @__PURE__ */ new Set([
|
|
10667
|
-
// Core session tools (
|
|
10903
|
+
// Core session tools (16)
|
|
10668
10904
|
"session_init",
|
|
10669
10905
|
"session_tools",
|
|
10670
10906
|
"context_smart",
|
|
10671
10907
|
"context_feedback",
|
|
10672
10908
|
"session_summary",
|
|
10673
10909
|
"session_capture",
|
|
10910
|
+
"session_capture_smart",
|
|
10911
|
+
// Pre-compaction state capture
|
|
10912
|
+
"session_restore_context",
|
|
10913
|
+
// Post-compaction context restore
|
|
10674
10914
|
"session_capture_lesson",
|
|
10675
10915
|
"session_get_lessons",
|
|
10676
10916
|
"session_recall",
|
|
@@ -11121,6 +11361,7 @@ function parsePositiveInt(raw, fallback) {
|
|
|
11121
11361
|
}
|
|
11122
11362
|
var OUTPUT_FORMAT = process.env.CONTEXTSTREAM_OUTPUT_FORMAT || "compact";
|
|
11123
11363
|
var COMPACT_OUTPUT = OUTPUT_FORMAT === "compact";
|
|
11364
|
+
var SHOW_TIMING = process.env.CONTEXTSTREAM_SHOW_TIMING === "true" || process.env.CONTEXTSTREAM_SHOW_TIMING === "1";
|
|
11124
11365
|
var DEFAULT_SEARCH_LIMIT = parsePositiveInt(process.env.CONTEXTSTREAM_SEARCH_LIMIT, 3);
|
|
11125
11366
|
var DEFAULT_SEARCH_CONTENT_MAX_CHARS = parsePositiveInt(
|
|
11126
11367
|
process.env.CONTEXTSTREAM_SEARCH_MAX_CHARS,
|
|
@@ -11233,6 +11474,31 @@ function toStructured(data) {
|
|
|
11233
11474
|
}
|
|
11234
11475
|
return void 0;
|
|
11235
11476
|
}
|
|
11477
|
+
function formatTimingSummary(roundTripMs, resultCount) {
|
|
11478
|
+
if (!SHOW_TIMING) return "";
|
|
11479
|
+
const countStr = resultCount !== void 0 ? `${resultCount} results` : "done";
|
|
11480
|
+
return `\u2713 ${countStr} in ${roundTripMs}ms
|
|
11481
|
+
|
|
11482
|
+
`;
|
|
11483
|
+
}
|
|
11484
|
+
function getResultCount(data) {
|
|
11485
|
+
if (!data || typeof data !== "object") return void 0;
|
|
11486
|
+
const response = data;
|
|
11487
|
+
const dataObj = response.data;
|
|
11488
|
+
if (dataObj?.results && Array.isArray(dataObj.results)) {
|
|
11489
|
+
return dataObj.results.length;
|
|
11490
|
+
}
|
|
11491
|
+
if (typeof dataObj?.total === "number") {
|
|
11492
|
+
return dataObj.total;
|
|
11493
|
+
}
|
|
11494
|
+
if (typeof dataObj?.count === "number") {
|
|
11495
|
+
return dataObj.count;
|
|
11496
|
+
}
|
|
11497
|
+
if (dataObj?.paths && Array.isArray(dataObj.paths)) {
|
|
11498
|
+
return dataObj.paths.length;
|
|
11499
|
+
}
|
|
11500
|
+
return void 0;
|
|
11501
|
+
}
|
|
11236
11502
|
function readStatNumber(payload, key) {
|
|
11237
11503
|
if (!payload || typeof payload !== "object") return void 0;
|
|
11238
11504
|
const direct = payload[key];
|
|
@@ -13553,10 +13819,17 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13553
13819
|
auto_index: external_exports.boolean().optional().describe("Automatically create and index project from IDE workspace (default: true)"),
|
|
13554
13820
|
allow_no_workspace: external_exports.boolean().optional().describe(
|
|
13555
13821
|
"If true, allow session_init to return connected even if no workspace is resolved (workspace-level tools may not work)."
|
|
13822
|
+
),
|
|
13823
|
+
skip_project_creation: external_exports.boolean().optional().describe(
|
|
13824
|
+
"If true, skip automatic project creation/matching. Use for parent folders containing multiple projects where you want workspace-level context but no project-specific context."
|
|
13825
|
+
),
|
|
13826
|
+
is_post_compact: external_exports.boolean().optional().describe(
|
|
13827
|
+
"Set to true when resuming after conversation compaction. This prioritizes session_snapshot restoration and recent decisions."
|
|
13556
13828
|
)
|
|
13557
13829
|
})
|
|
13558
13830
|
},
|
|
13559
13831
|
async (input) => {
|
|
13832
|
+
const startTime = Date.now();
|
|
13560
13833
|
let ideRoots = [];
|
|
13561
13834
|
try {
|
|
13562
13835
|
const rootsResponse = await server.server.listRoots();
|
|
@@ -13572,8 +13845,48 @@ This does semantic search on the first message. You only need context_smart on s
|
|
|
13572
13845
|
}
|
|
13573
13846
|
const result = await client.initSession(input, ideRoots);
|
|
13574
13847
|
result.tools_hint = getCoreToolsHint();
|
|
13848
|
+
if (input.is_post_compact) {
|
|
13849
|
+
const workspaceIdForRestore = typeof result.workspace_id === "string" ? result.workspace_id : void 0;
|
|
13850
|
+
const projectIdForRestore = typeof result.project_id === "string" ? result.project_id : void 0;
|
|
13851
|
+
if (workspaceIdForRestore) {
|
|
13852
|
+
try {
|
|
13853
|
+
const snapshotSearch = await client.searchEvents({
|
|
13854
|
+
workspace_id: workspaceIdForRestore,
|
|
13855
|
+
project_id: projectIdForRestore,
|
|
13856
|
+
query: "session_snapshot",
|
|
13857
|
+
event_types: ["session_snapshot"],
|
|
13858
|
+
limit: 1
|
|
13859
|
+
});
|
|
13860
|
+
const snapshots = snapshotSearch?.data?.results || snapshotSearch?.results || snapshotSearch?.data || [];
|
|
13861
|
+
if (snapshots && snapshots.length > 0) {
|
|
13862
|
+
const latestSnapshot = snapshots[0];
|
|
13863
|
+
let snapshotData;
|
|
13864
|
+
try {
|
|
13865
|
+
snapshotData = JSON.parse(latestSnapshot.content);
|
|
13866
|
+
} catch {
|
|
13867
|
+
snapshotData = { conversation_summary: latestSnapshot.content };
|
|
13868
|
+
}
|
|
13869
|
+
result.restored_context = {
|
|
13870
|
+
snapshot_id: latestSnapshot.id,
|
|
13871
|
+
captured_at: snapshotData.captured_at || latestSnapshot.created_at,
|
|
13872
|
+
...snapshotData
|
|
13873
|
+
};
|
|
13874
|
+
result.is_post_compact = true;
|
|
13875
|
+
result.post_compact_hint = "Session restored from pre-compaction snapshot. Review the 'restored_context' to continue where you left off.";
|
|
13876
|
+
} else {
|
|
13877
|
+
result.is_post_compact = true;
|
|
13878
|
+
result.post_compact_hint = "Post-compaction session started, but no snapshots found. Use context_smart to retrieve relevant context.";
|
|
13879
|
+
}
|
|
13880
|
+
} catch (err) {
|
|
13881
|
+
console.error("[ContextStream] Failed to restore post-compact context:", err);
|
|
13882
|
+
result.is_post_compact = true;
|
|
13883
|
+
result.post_compact_hint = "Post-compaction session started. Snapshot restoration failed, use context_smart for context.";
|
|
13884
|
+
}
|
|
13885
|
+
}
|
|
13886
|
+
}
|
|
13575
13887
|
if (sessionManager) {
|
|
13576
13888
|
sessionManager.markInitialized(result);
|
|
13889
|
+
sessionManager.resetTokenCount();
|
|
13577
13890
|
}
|
|
13578
13891
|
const folderPathForRules = input.folder_path || ideRoots[0] || resolveFolderPath(void 0, sessionManager);
|
|
13579
13892
|
if (sessionManager && folderPathForRules) {
|
|
@@ -13720,6 +14033,12 @@ ${noticeLines.filter(Boolean).join("\n")}`;
|
|
|
13720
14033
|
text = `${text}
|
|
13721
14034
|
|
|
13722
14035
|
${SEARCH_RULES_REMINDER}`;
|
|
14036
|
+
}
|
|
14037
|
+
const roundTripMs = Date.now() - startTime;
|
|
14038
|
+
if (SHOW_TIMING) {
|
|
14039
|
+
text = `\u2713 session initialized in ${roundTripMs}ms
|
|
14040
|
+
|
|
14041
|
+
${text}`;
|
|
13723
14042
|
}
|
|
13724
14043
|
return {
|
|
13725
14044
|
content: [{ type: "text", text }],
|
|
@@ -13997,8 +14316,11 @@ Use this to persist decisions, insights, preferences, or important information.`
|
|
|
13997
14316
|
// Extracted lesson from correction
|
|
13998
14317
|
"warning",
|
|
13999
14318
|
// Proactive reminder
|
|
14000
|
-
"frustration"
|
|
14319
|
+
"frustration",
|
|
14001
14320
|
// User expressed frustration
|
|
14321
|
+
// Compaction awareness
|
|
14322
|
+
"session_snapshot"
|
|
14323
|
+
// Pre-compaction state capture
|
|
14002
14324
|
]).describe("Type of context being captured"),
|
|
14003
14325
|
title: external_exports.string().describe("Brief title for the captured context"),
|
|
14004
14326
|
content: external_exports.string().describe("Full content/details to capture"),
|
|
@@ -14053,6 +14375,224 @@ Use this to persist decisions, insights, preferences, or important information.`
|
|
|
14053
14375
|
};
|
|
14054
14376
|
}
|
|
14055
14377
|
);
|
|
14378
|
+
registerTool(
|
|
14379
|
+
"session_capture_smart",
|
|
14380
|
+
{
|
|
14381
|
+
title: "Smart capture for conversation compaction",
|
|
14382
|
+
description: `Intelligently capture conversation state before compaction or context loss.
|
|
14383
|
+
This creates a session_snapshot that can be restored after compaction.
|
|
14384
|
+
|
|
14385
|
+
Use when:
|
|
14386
|
+
- Context pressure is high/critical (context_smart returns threshold_warning)
|
|
14387
|
+
- Before manual /compact commands
|
|
14388
|
+
- When significant work progress needs preservation
|
|
14389
|
+
|
|
14390
|
+
Captures:
|
|
14391
|
+
- Conversation summary and current goals
|
|
14392
|
+
- Active files being worked on
|
|
14393
|
+
- Recent decisions with rationale
|
|
14394
|
+
- Unfinished work items
|
|
14395
|
+
- User preferences expressed in session
|
|
14396
|
+
|
|
14397
|
+
The snapshot is automatically prioritized during post-compaction session_init.`,
|
|
14398
|
+
inputSchema: external_exports.object({
|
|
14399
|
+
workspace_id: external_exports.string().uuid().optional(),
|
|
14400
|
+
project_id: external_exports.string().uuid().optional(),
|
|
14401
|
+
conversation_summary: external_exports.string().describe("AI's summary of the conversation so far - what was discussed and accomplished"),
|
|
14402
|
+
current_goal: external_exports.string().optional().describe("The primary goal or task being worked on"),
|
|
14403
|
+
active_files: external_exports.array(external_exports.string()).optional().describe("List of files currently being worked on"),
|
|
14404
|
+
recent_decisions: external_exports.array(
|
|
14405
|
+
external_exports.object({
|
|
14406
|
+
title: external_exports.string(),
|
|
14407
|
+
rationale: external_exports.string().optional()
|
|
14408
|
+
})
|
|
14409
|
+
).optional().describe("Key decisions made in this session with their rationale"),
|
|
14410
|
+
unfinished_work: external_exports.array(
|
|
14411
|
+
external_exports.object({
|
|
14412
|
+
task: external_exports.string(),
|
|
14413
|
+
status: external_exports.string().optional(),
|
|
14414
|
+
next_steps: external_exports.string().optional()
|
|
14415
|
+
})
|
|
14416
|
+
).optional().describe("Work items that are in progress or pending"),
|
|
14417
|
+
user_preferences: external_exports.array(external_exports.string()).optional().describe("Preferences expressed by user during this session"),
|
|
14418
|
+
priority_items: external_exports.array(external_exports.string()).optional().describe("User-flagged important items to remember"),
|
|
14419
|
+
metadata: external_exports.record(external_exports.unknown()).optional().describe("Additional context to preserve")
|
|
14420
|
+
})
|
|
14421
|
+
},
|
|
14422
|
+
async (input) => {
|
|
14423
|
+
let workspaceId = input.workspace_id;
|
|
14424
|
+
let projectId = input.project_id;
|
|
14425
|
+
if (!workspaceId && sessionManager) {
|
|
14426
|
+
const ctx = sessionManager.getContext();
|
|
14427
|
+
if (ctx) {
|
|
14428
|
+
workspaceId = ctx.workspace_id;
|
|
14429
|
+
projectId = projectId || ctx.project_id;
|
|
14430
|
+
}
|
|
14431
|
+
}
|
|
14432
|
+
if (!workspaceId) {
|
|
14433
|
+
return errorResult(
|
|
14434
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
14435
|
+
);
|
|
14436
|
+
}
|
|
14437
|
+
const snapshotContent = {
|
|
14438
|
+
conversation_summary: input.conversation_summary,
|
|
14439
|
+
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
14440
|
+
};
|
|
14441
|
+
if (input.current_goal) {
|
|
14442
|
+
snapshotContent.current_goal = input.current_goal;
|
|
14443
|
+
}
|
|
14444
|
+
if (input.active_files?.length) {
|
|
14445
|
+
snapshotContent.active_files = input.active_files;
|
|
14446
|
+
}
|
|
14447
|
+
if (input.recent_decisions?.length) {
|
|
14448
|
+
snapshotContent.recent_decisions = input.recent_decisions;
|
|
14449
|
+
}
|
|
14450
|
+
if (input.unfinished_work?.length) {
|
|
14451
|
+
snapshotContent.unfinished_work = input.unfinished_work;
|
|
14452
|
+
}
|
|
14453
|
+
if (input.user_preferences?.length) {
|
|
14454
|
+
snapshotContent.user_preferences = input.user_preferences;
|
|
14455
|
+
}
|
|
14456
|
+
if (input.priority_items?.length) {
|
|
14457
|
+
snapshotContent.priority_items = input.priority_items;
|
|
14458
|
+
}
|
|
14459
|
+
if (input.metadata) {
|
|
14460
|
+
snapshotContent.metadata = input.metadata;
|
|
14461
|
+
}
|
|
14462
|
+
const result = await client.captureContext({
|
|
14463
|
+
workspace_id: workspaceId,
|
|
14464
|
+
project_id: projectId,
|
|
14465
|
+
event_type: "session_snapshot",
|
|
14466
|
+
title: `Session Snapshot: ${input.current_goal || "Conversation State"}`,
|
|
14467
|
+
content: JSON.stringify(snapshotContent, null, 2),
|
|
14468
|
+
importance: "high",
|
|
14469
|
+
tags: ["session_snapshot", "pre_compaction"]
|
|
14470
|
+
});
|
|
14471
|
+
const response = {
|
|
14472
|
+
...result,
|
|
14473
|
+
snapshot_id: result?.data?.id || result?.id,
|
|
14474
|
+
message: "Session state captured successfully. This snapshot will be prioritized after compaction.",
|
|
14475
|
+
hint: "After compaction, call session_init with is_post_compact=true to restore this context."
|
|
14476
|
+
};
|
|
14477
|
+
return {
|
|
14478
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
14479
|
+
structuredContent: toStructured(response)
|
|
14480
|
+
};
|
|
14481
|
+
}
|
|
14482
|
+
);
|
|
14483
|
+
registerTool(
|
|
14484
|
+
"session_restore_context",
|
|
14485
|
+
{
|
|
14486
|
+
title: "Restore context after compaction",
|
|
14487
|
+
description: `Restore conversation context after compaction or context loss.
|
|
14488
|
+
Call this after conversation compaction to retrieve saved session state.
|
|
14489
|
+
|
|
14490
|
+
Returns structured context including:
|
|
14491
|
+
- conversation_summary: What was being discussed
|
|
14492
|
+
- current_goal: The primary task being worked on
|
|
14493
|
+
- active_files: Files that were being modified
|
|
14494
|
+
- recent_decisions: Key decisions made in the session
|
|
14495
|
+
- unfinished_work: Tasks that are still in progress
|
|
14496
|
+
- user_preferences: Preferences expressed during the session
|
|
14497
|
+
|
|
14498
|
+
Use this in combination with session_init(is_post_compact=true) for seamless continuation.`,
|
|
14499
|
+
inputSchema: external_exports.object({
|
|
14500
|
+
workspace_id: external_exports.string().uuid().optional(),
|
|
14501
|
+
project_id: external_exports.string().uuid().optional(),
|
|
14502
|
+
snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
|
|
14503
|
+
max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
|
|
14504
|
+
})
|
|
14505
|
+
},
|
|
14506
|
+
async (input) => {
|
|
14507
|
+
let workspaceId = input.workspace_id;
|
|
14508
|
+
let projectId = input.project_id;
|
|
14509
|
+
if (!workspaceId && sessionManager) {
|
|
14510
|
+
const ctx = sessionManager.getContext();
|
|
14511
|
+
if (ctx) {
|
|
14512
|
+
workspaceId = ctx.workspace_id;
|
|
14513
|
+
projectId = projectId || ctx.project_id;
|
|
14514
|
+
}
|
|
14515
|
+
}
|
|
14516
|
+
if (!workspaceId) {
|
|
14517
|
+
return errorResult(
|
|
14518
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
14519
|
+
);
|
|
14520
|
+
}
|
|
14521
|
+
try {
|
|
14522
|
+
if (input.snapshot_id) {
|
|
14523
|
+
const eventResult = await client.getEvent(input.snapshot_id);
|
|
14524
|
+
const event = eventResult?.data || eventResult;
|
|
14525
|
+
if (!event || !event.content) {
|
|
14526
|
+
return errorResult(
|
|
14527
|
+
`Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
|
|
14528
|
+
);
|
|
14529
|
+
}
|
|
14530
|
+
let snapshotData2;
|
|
14531
|
+
try {
|
|
14532
|
+
snapshotData2 = JSON.parse(event.content);
|
|
14533
|
+
} catch {
|
|
14534
|
+
snapshotData2 = { conversation_summary: event.content };
|
|
14535
|
+
}
|
|
14536
|
+
const response2 = {
|
|
14537
|
+
restored: true,
|
|
14538
|
+
snapshot_id: event.id,
|
|
14539
|
+
captured_at: snapshotData2.captured_at || event.created_at,
|
|
14540
|
+
...snapshotData2,
|
|
14541
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
14542
|
+
};
|
|
14543
|
+
return {
|
|
14544
|
+
content: [{ type: "text", text: formatContent(response2) }],
|
|
14545
|
+
structuredContent: toStructured(response2)
|
|
14546
|
+
};
|
|
14547
|
+
}
|
|
14548
|
+
const listResult = await client.listMemoryEvents({
|
|
14549
|
+
workspace_id: workspaceId,
|
|
14550
|
+
project_id: projectId,
|
|
14551
|
+
limit: 50
|
|
14552
|
+
// Fetch more to filter
|
|
14553
|
+
});
|
|
14554
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
14555
|
+
const events = allEvents.filter(
|
|
14556
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
14557
|
+
).slice(0, input.max_snapshots || 1);
|
|
14558
|
+
if (!events || events.length === 0) {
|
|
14559
|
+
return {
|
|
14560
|
+
content: [
|
|
14561
|
+
{
|
|
14562
|
+
type: "text",
|
|
14563
|
+
text: formatContent({
|
|
14564
|
+
restored: false,
|
|
14565
|
+
message: "No session snapshots found. This may be a new session or snapshots have not been captured.",
|
|
14566
|
+
hint: "Use session_capture_smart to save session state before compaction."
|
|
14567
|
+
})
|
|
14568
|
+
}
|
|
14569
|
+
]
|
|
14570
|
+
};
|
|
14571
|
+
}
|
|
14572
|
+
const latestEvent = events[0];
|
|
14573
|
+
let snapshotData;
|
|
14574
|
+
try {
|
|
14575
|
+
snapshotData = JSON.parse(latestEvent.content);
|
|
14576
|
+
} catch {
|
|
14577
|
+
snapshotData = { conversation_summary: latestEvent.content };
|
|
14578
|
+
}
|
|
14579
|
+
const response = {
|
|
14580
|
+
restored: true,
|
|
14581
|
+
snapshot_id: latestEvent.id,
|
|
14582
|
+
captured_at: snapshotData.captured_at || latestEvent.created_at,
|
|
14583
|
+
...snapshotData,
|
|
14584
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
14585
|
+
};
|
|
14586
|
+
return {
|
|
14587
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
14588
|
+
structuredContent: toStructured(response)
|
|
14589
|
+
};
|
|
14590
|
+
} catch (error) {
|
|
14591
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14592
|
+
return errorResult(`Failed to restore context: ${message}`);
|
|
14593
|
+
}
|
|
14594
|
+
}
|
|
14595
|
+
);
|
|
14056
14596
|
registerTool(
|
|
14057
14597
|
"session_capture_lesson",
|
|
14058
14598
|
{
|
|
@@ -14411,6 +14951,7 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
14411
14951
|
overwrite_existing: external_exports.boolean().optional().describe("Allow overwriting existing rule files (ContextStream block only)"),
|
|
14412
14952
|
apply_global: external_exports.boolean().optional().describe("Also write global rule files for supported editors"),
|
|
14413
14953
|
install_hooks: external_exports.boolean().optional().describe("Install Claude Code hooks to enforce ContextStream-first search. Defaults to true for Claude users. Set to false to skip."),
|
|
14954
|
+
include_pre_compact: external_exports.boolean().optional().describe("Include PreCompact hook for automatic state saving before context compaction. Defaults to false."),
|
|
14414
14955
|
dry_run: external_exports.boolean().optional().describe("If true, return content without writing files")
|
|
14415
14956
|
})
|
|
14416
14957
|
},
|
|
@@ -14491,8 +15032,14 @@ Supported editors: ${getAvailableEditors().join(", ")}`,
|
|
|
14491
15032
|
{ file: "~/.claude/hooks/contextstream-reminder.py", status: "dry run - would create" },
|
|
14492
15033
|
{ file: "~/.claude/settings.json", status: "dry run - would update" }
|
|
14493
15034
|
];
|
|
15035
|
+
if (input.include_pre_compact) {
|
|
15036
|
+
hooksResults.push({ file: "~/.claude/hooks/contextstream-precompact.py", status: "dry run - would create" });
|
|
15037
|
+
}
|
|
14494
15038
|
} else {
|
|
14495
|
-
const hookResult = await installClaudeCodeHooks({
|
|
15039
|
+
const hookResult = await installClaudeCodeHooks({
|
|
15040
|
+
scope: "user",
|
|
15041
|
+
includePreCompact: input.include_pre_compact
|
|
15042
|
+
});
|
|
14496
15043
|
hooksResults = [
|
|
14497
15044
|
...hookResult.scripts.map((f) => ({ file: f, status: "created" })),
|
|
14498
15045
|
...hookResult.settings.map((f) => ({ file: f, status: "updated" }))
|
|
@@ -14851,10 +15398,13 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14851
15398
|
max_tokens: external_exports.number().optional().describe("Maximum tokens for context (default: 800)"),
|
|
14852
15399
|
format: external_exports.enum(["minified", "readable", "structured"]).optional().describe("Context format (default: minified)"),
|
|
14853
15400
|
mode: external_exports.enum(["standard", "pack"]).optional().describe("Context pack mode (default: pack when enabled)"),
|
|
14854
|
-
distill: external_exports.boolean().optional().describe("Use distillation for context pack (default: true)")
|
|
15401
|
+
distill: external_exports.boolean().optional().describe("Use distillation for context pack (default: true)"),
|
|
15402
|
+
session_tokens: external_exports.number().optional().describe("Cumulative session token count for context pressure calculation"),
|
|
15403
|
+
context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)")
|
|
14855
15404
|
})
|
|
14856
15405
|
},
|
|
14857
15406
|
async (input) => {
|
|
15407
|
+
const startTime = Date.now();
|
|
14858
15408
|
if (sessionManager) {
|
|
14859
15409
|
sessionManager.markContextSmartCalled();
|
|
14860
15410
|
}
|
|
@@ -14867,6 +15417,17 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14867
15417
|
projectId = projectId || ctx.project_id;
|
|
14868
15418
|
}
|
|
14869
15419
|
}
|
|
15420
|
+
let sessionTokens = input.session_tokens;
|
|
15421
|
+
let contextThreshold = input.context_threshold;
|
|
15422
|
+
if (sessionManager) {
|
|
15423
|
+
if (sessionTokens === void 0) {
|
|
15424
|
+
sessionTokens = sessionManager.getSessionTokens();
|
|
15425
|
+
}
|
|
15426
|
+
if (contextThreshold === void 0) {
|
|
15427
|
+
contextThreshold = sessionManager.getContextThreshold();
|
|
15428
|
+
}
|
|
15429
|
+
sessionManager.addTokens(input.user_message);
|
|
15430
|
+
}
|
|
14870
15431
|
const result = await client.getSmartContext({
|
|
14871
15432
|
user_message: input.user_message,
|
|
14872
15433
|
workspace_id: workspaceId,
|
|
@@ -14874,11 +15435,18 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14874
15435
|
max_tokens: input.max_tokens,
|
|
14875
15436
|
format: input.format,
|
|
14876
15437
|
mode: input.mode,
|
|
14877
|
-
distill: input.distill
|
|
15438
|
+
distill: input.distill,
|
|
15439
|
+
session_tokens: sessionTokens,
|
|
15440
|
+
context_threshold: contextThreshold
|
|
14878
15441
|
});
|
|
15442
|
+
if (sessionManager && result.token_estimate) {
|
|
15443
|
+
sessionManager.addTokens(result.token_estimate);
|
|
15444
|
+
}
|
|
15445
|
+
const roundTripMs = Date.now() - startTime;
|
|
15446
|
+
const timingStr = SHOW_TIMING ? ` | ${roundTripMs}ms` : "";
|
|
14879
15447
|
const footer = `
|
|
14880
15448
|
---
|
|
14881
|
-
\u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}`;
|
|
15449
|
+
\u{1F3AF} ${result.sources_used} sources | ~${result.token_estimate} tokens | format: ${result.format}${timingStr}`;
|
|
14882
15450
|
const folderPathForRules = resolveFolderPath(void 0, sessionManager);
|
|
14883
15451
|
const rulesNotice = getRulesNotice(folderPathForRules, detectedClientInfo?.name);
|
|
14884
15452
|
let versionNotice = result.version_notice;
|
|
@@ -14905,6 +15473,22 @@ This saves ~80% tokens compared to including full chat history.`,
|
|
|
14905
15473
|
const searchRulesLine = SEARCH_RULES_REMINDER_ENABLED ? `
|
|
14906
15474
|
|
|
14907
15475
|
${SEARCH_RULES_REMINDER}` : "";
|
|
15476
|
+
let contextPressureWarning = "";
|
|
15477
|
+
if (result.context_pressure) {
|
|
15478
|
+
const cp = result.context_pressure;
|
|
15479
|
+
if (cp.level === "critical") {
|
|
15480
|
+
contextPressureWarning = `
|
|
15481
|
+
|
|
15482
|
+
\u{1F6A8} [CONTEXT PRESSURE: CRITICAL] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
|
|
15483
|
+
Action: ${cp.suggested_action === "save_now" ? 'SAVE STATE NOW - Call session(action="capture") to preserve conversation state before compaction.' : cp.suggested_action}
|
|
15484
|
+
The conversation may compact soon. Save important decisions, insights, and progress immediately.`;
|
|
15485
|
+
} else if (cp.level === "high") {
|
|
15486
|
+
contextPressureWarning = `
|
|
15487
|
+
|
|
15488
|
+
\u26A0\uFE0F [CONTEXT PRESSURE: HIGH] ${cp.usage_percent}% of context used (${cp.session_tokens}/${cp.threshold} tokens)
|
|
15489
|
+
Action: ${cp.suggested_action === "prepare_save" ? "Consider saving important decisions and conversation state soon." : cp.suggested_action}`;
|
|
15490
|
+
}
|
|
15491
|
+
}
|
|
14908
15492
|
const allWarnings = [
|
|
14909
15493
|
lessonsWarningLine,
|
|
14910
15494
|
rulesWarningLine ? `
|
|
@@ -14913,6 +15497,7 @@ ${rulesWarningLine}` : "",
|
|
|
14913
15497
|
versionWarningLine ? `
|
|
14914
15498
|
|
|
14915
15499
|
${versionWarningLine}` : "",
|
|
15500
|
+
contextPressureWarning,
|
|
14916
15501
|
searchRulesLine
|
|
14917
15502
|
].filter(Boolean).join("");
|
|
14918
15503
|
return {
|
|
@@ -15943,6 +16528,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15943
16528
|
},
|
|
15944
16529
|
async (input) => {
|
|
15945
16530
|
const params = normalizeSearchParams(input);
|
|
16531
|
+
const startTime = Date.now();
|
|
15946
16532
|
let result;
|
|
15947
16533
|
let toolType;
|
|
15948
16534
|
switch (input.mode) {
|
|
@@ -15973,7 +16559,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15973
16559
|
default:
|
|
15974
16560
|
toolType = "search_hybrid";
|
|
15975
16561
|
}
|
|
15976
|
-
const
|
|
16562
|
+
const roundTripMs = Date.now() - startTime;
|
|
16563
|
+
const timingSummary = formatTimingSummary(roundTripMs, getResultCount(result));
|
|
16564
|
+
const outputText = timingSummary + formatContent(result);
|
|
15977
16565
|
trackToolTokenSavings(client, toolType, outputText, {
|
|
15978
16566
|
workspace_id: params.workspace_id,
|
|
15979
16567
|
project_id: params.project_id
|
|
@@ -15988,7 +16576,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
15988
16576
|
"session",
|
|
15989
16577
|
{
|
|
15990
16578
|
title: "Session",
|
|
15991
|
-
description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
|
|
16579
|
+
description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans).`,
|
|
15992
16580
|
inputSchema: external_exports.object({
|
|
15993
16581
|
action: external_exports.enum([
|
|
15994
16582
|
"capture",
|
|
@@ -16006,7 +16594,9 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16006
16594
|
"capture_plan",
|
|
16007
16595
|
"get_plan",
|
|
16008
16596
|
"update_plan",
|
|
16009
|
-
"list_plans"
|
|
16597
|
+
"list_plans",
|
|
16598
|
+
// Context restore
|
|
16599
|
+
"restore_context"
|
|
16010
16600
|
]).describe("Action to perform"),
|
|
16011
16601
|
workspace_id: external_exports.string().uuid().optional(),
|
|
16012
16602
|
project_id: external_exports.string().uuid().optional(),
|
|
@@ -16028,7 +16618,8 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16028
16618
|
"lesson",
|
|
16029
16619
|
"warning",
|
|
16030
16620
|
"frustration",
|
|
16031
|
-
"conversation"
|
|
16621
|
+
"conversation",
|
|
16622
|
+
"session_snapshot"
|
|
16032
16623
|
]).optional().describe("Event type for capture"),
|
|
16033
16624
|
importance: external_exports.enum(["low", "medium", "high", "critical"]).optional(),
|
|
16034
16625
|
tags: external_exports.array(external_exports.string()).optional(),
|
|
@@ -16078,7 +16669,10 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16078
16669
|
status: external_exports.enum(["draft", "active", "completed", "archived", "abandoned"]).optional().describe("Plan status"),
|
|
16079
16670
|
due_at: external_exports.string().optional().describe("Due date for plan (ISO timestamp)"),
|
|
16080
16671
|
source_tool: external_exports.string().optional().describe("Tool that generated this plan"),
|
|
16081
|
-
include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan")
|
|
16672
|
+
include_tasks: external_exports.boolean().optional().describe("Include tasks when getting plan"),
|
|
16673
|
+
// Restore context params
|
|
16674
|
+
snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
|
|
16675
|
+
max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
|
|
16082
16676
|
})
|
|
16083
16677
|
},
|
|
16084
16678
|
async (input) => {
|
|
@@ -16384,6 +16978,87 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
|
|
|
16384
16978
|
structuredContent: toStructured(result)
|
|
16385
16979
|
};
|
|
16386
16980
|
}
|
|
16981
|
+
case "restore_context": {
|
|
16982
|
+
if (!workspaceId) {
|
|
16983
|
+
return errorResult(
|
|
16984
|
+
"restore_context requires workspace_id. Call session_init first."
|
|
16985
|
+
);
|
|
16986
|
+
}
|
|
16987
|
+
if (input.snapshot_id) {
|
|
16988
|
+
const eventResult = await client.getEvent(input.snapshot_id);
|
|
16989
|
+
const event = eventResult?.data || eventResult;
|
|
16990
|
+
if (!event || !event.content) {
|
|
16991
|
+
return errorResult(
|
|
16992
|
+
`Snapshot not found: ${input.snapshot_id}. The snapshot may have been deleted or does not exist.`
|
|
16993
|
+
);
|
|
16994
|
+
}
|
|
16995
|
+
let snapshotData;
|
|
16996
|
+
try {
|
|
16997
|
+
snapshotData = JSON.parse(event.content);
|
|
16998
|
+
} catch {
|
|
16999
|
+
snapshotData = { conversation_summary: event.content };
|
|
17000
|
+
}
|
|
17001
|
+
const response2 = {
|
|
17002
|
+
restored: true,
|
|
17003
|
+
snapshot_id: event.id,
|
|
17004
|
+
captured_at: snapshotData.captured_at || event.created_at,
|
|
17005
|
+
...snapshotData,
|
|
17006
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
17007
|
+
};
|
|
17008
|
+
return {
|
|
17009
|
+
content: [{ type: "text", text: formatContent(response2) }],
|
|
17010
|
+
structuredContent: toStructured(response2)
|
|
17011
|
+
};
|
|
17012
|
+
}
|
|
17013
|
+
const listResult = await client.listMemoryEvents({
|
|
17014
|
+
workspace_id: workspaceId,
|
|
17015
|
+
project_id: projectId,
|
|
17016
|
+
limit: 50
|
|
17017
|
+
// Fetch more to filter
|
|
17018
|
+
});
|
|
17019
|
+
const allEvents = listResult?.data?.items || listResult?.items || listResult?.data || [];
|
|
17020
|
+
const snapshotEvents = allEvents.filter(
|
|
17021
|
+
(e) => e.event_type === "session_snapshot" || e.metadata?.original_type === "session_snapshot" || e.metadata?.tags?.includes("session_snapshot") || e.tags?.includes("session_snapshot")
|
|
17022
|
+
).slice(0, input.max_snapshots || 1);
|
|
17023
|
+
if (!snapshotEvents || snapshotEvents.length === 0) {
|
|
17024
|
+
return {
|
|
17025
|
+
content: [
|
|
17026
|
+
{
|
|
17027
|
+
type: "text",
|
|
17028
|
+
text: formatContent({
|
|
17029
|
+
restored: false,
|
|
17030
|
+
message: "No session snapshots found. Use session_capture_smart to save state before compaction.",
|
|
17031
|
+
hint: "Start fresh or use session_init to get recent context."
|
|
17032
|
+
})
|
|
17033
|
+
}
|
|
17034
|
+
]
|
|
17035
|
+
};
|
|
17036
|
+
}
|
|
17037
|
+
const snapshots = snapshotEvents.map((event) => {
|
|
17038
|
+
let snapshotData;
|
|
17039
|
+
try {
|
|
17040
|
+
snapshotData = JSON.parse(event.content || "{}");
|
|
17041
|
+
} catch {
|
|
17042
|
+
snapshotData = { conversation_summary: event.content };
|
|
17043
|
+
}
|
|
17044
|
+
return {
|
|
17045
|
+
snapshot_id: event.id,
|
|
17046
|
+
captured_at: snapshotData.captured_at || event.created_at,
|
|
17047
|
+
...snapshotData
|
|
17048
|
+
};
|
|
17049
|
+
});
|
|
17050
|
+
const response = {
|
|
17051
|
+
restored: true,
|
|
17052
|
+
snapshots_found: snapshots.length,
|
|
17053
|
+
latest: snapshots[0],
|
|
17054
|
+
all_snapshots: snapshots.length > 1 ? snapshots : void 0,
|
|
17055
|
+
hint: "Context restored. Continue the conversation with awareness of the above state."
|
|
17056
|
+
};
|
|
17057
|
+
return {
|
|
17058
|
+
content: [{ type: "text", text: formatContent(response) }],
|
|
17059
|
+
structuredContent: toStructured(response)
|
|
17060
|
+
};
|
|
17061
|
+
}
|
|
16387
17062
|
default:
|
|
16388
17063
|
return errorResult(`Unknown action: ${input.action}`);
|
|
16389
17064
|
}
|
|
@@ -18949,6 +19624,7 @@ function registerPrompts(server) {
|
|
|
18949
19624
|
|
|
18950
19625
|
// src/session-manager.ts
|
|
18951
19626
|
var SessionManager = class {
|
|
19627
|
+
// Conservative default for 100k context window
|
|
18952
19628
|
constructor(server, client) {
|
|
18953
19629
|
this.server = server;
|
|
18954
19630
|
this.client = client;
|
|
@@ -18959,6 +19635,9 @@ var SessionManager = class {
|
|
|
18959
19635
|
this.folderPath = null;
|
|
18960
19636
|
this.contextSmartCalled = false;
|
|
18961
19637
|
this.warningShown = false;
|
|
19638
|
+
// Token tracking for context pressure calculation
|
|
19639
|
+
this.sessionTokens = 0;
|
|
19640
|
+
this.contextThreshold = 7e4;
|
|
18962
19641
|
}
|
|
18963
19642
|
/**
|
|
18964
19643
|
* Check if session has been auto-initialized
|
|
@@ -19006,6 +19685,51 @@ var SessionManager = class {
|
|
|
19006
19685
|
markContextSmartCalled() {
|
|
19007
19686
|
this.contextSmartCalled = true;
|
|
19008
19687
|
}
|
|
19688
|
+
/**
|
|
19689
|
+
* Get current session token count for context pressure calculation.
|
|
19690
|
+
*/
|
|
19691
|
+
getSessionTokens() {
|
|
19692
|
+
return this.sessionTokens;
|
|
19693
|
+
}
|
|
19694
|
+
/**
|
|
19695
|
+
* Get the context threshold (max tokens before compaction warning).
|
|
19696
|
+
*/
|
|
19697
|
+
getContextThreshold() {
|
|
19698
|
+
return this.contextThreshold;
|
|
19699
|
+
}
|
|
19700
|
+
/**
|
|
19701
|
+
* Set a custom context threshold (useful if client provides model info).
|
|
19702
|
+
*/
|
|
19703
|
+
setContextThreshold(threshold) {
|
|
19704
|
+
this.contextThreshold = threshold;
|
|
19705
|
+
}
|
|
19706
|
+
/**
|
|
19707
|
+
* Add tokens to the session count.
|
|
19708
|
+
* Call this after each tool response to track token accumulation.
|
|
19709
|
+
*
|
|
19710
|
+
* @param tokens - Exact token count or text to estimate
|
|
19711
|
+
*/
|
|
19712
|
+
addTokens(tokens) {
|
|
19713
|
+
if (typeof tokens === "number") {
|
|
19714
|
+
this.sessionTokens += tokens;
|
|
19715
|
+
} else {
|
|
19716
|
+
this.sessionTokens += Math.ceil(tokens.length / 4);
|
|
19717
|
+
}
|
|
19718
|
+
}
|
|
19719
|
+
/**
|
|
19720
|
+
* Estimate tokens from a tool response.
|
|
19721
|
+
* Uses a simple heuristic: ~4 characters per token.
|
|
19722
|
+
*/
|
|
19723
|
+
estimateTokens(content) {
|
|
19724
|
+
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
19725
|
+
return Math.ceil(text.length / 4);
|
|
19726
|
+
}
|
|
19727
|
+
/**
|
|
19728
|
+
* Reset token count (e.g., after compaction or new session).
|
|
19729
|
+
*/
|
|
19730
|
+
resetTokenCount() {
|
|
19731
|
+
this.sessionTokens = 0;
|
|
19732
|
+
}
|
|
19009
19733
|
/**
|
|
19010
19734
|
* Check if context_smart has been called and warn if not.
|
|
19011
19735
|
* Returns true if a warning was shown, false otherwise.
|
|
@@ -20025,6 +20749,9 @@ function buildContextStreamMcpServer(params) {
|
|
|
20025
20749
|
env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
|
|
20026
20750
|
}
|
|
20027
20751
|
env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
|
|
20752
|
+
if (params.showTiming) {
|
|
20753
|
+
env.CONTEXTSTREAM_SHOW_TIMING = "true";
|
|
20754
|
+
}
|
|
20028
20755
|
if (IS_WINDOWS) {
|
|
20029
20756
|
return {
|
|
20030
20757
|
command: "cmd",
|
|
@@ -20047,6 +20774,9 @@ function buildContextStreamVsCodeServer(params) {
|
|
|
20047
20774
|
env.CONTEXTSTREAM_PROGRESSIVE_MODE = "true";
|
|
20048
20775
|
}
|
|
20049
20776
|
env.CONTEXTSTREAM_CONTEXT_PACK = params.contextPackEnabled === false ? "false" : "true";
|
|
20777
|
+
if (params.showTiming) {
|
|
20778
|
+
env.CONTEXTSTREAM_SHOW_TIMING = "true";
|
|
20779
|
+
}
|
|
20050
20780
|
if (IS_WINDOWS) {
|
|
20051
20781
|
return {
|
|
20052
20782
|
type: "stdio",
|
|
@@ -20147,6 +20877,8 @@ async function upsertCodexTomlConfig(filePath, params) {
|
|
|
20147
20877
|
` : "";
|
|
20148
20878
|
const contextPackLine = `CONTEXTSTREAM_CONTEXT_PACK = "${params.contextPackEnabled === false ? "false" : "true"}"
|
|
20149
20879
|
`;
|
|
20880
|
+
const showTimingLine = params.showTiming ? `CONTEXTSTREAM_SHOW_TIMING = "true"
|
|
20881
|
+
` : "";
|
|
20150
20882
|
const commandLine = IS_WINDOWS ? `command = "cmd"
|
|
20151
20883
|
args = ["/c", "npx", "-y", "@contextstream/mcp-server"]
|
|
20152
20884
|
` : `command = "npx"
|
|
@@ -20160,7 +20892,7 @@ args = ["-y", "@contextstream/mcp-server"]
|
|
|
20160
20892
|
[mcp_servers.contextstream.env]
|
|
20161
20893
|
CONTEXTSTREAM_API_URL = "${params.apiUrl}"
|
|
20162
20894
|
CONTEXTSTREAM_API_KEY = "${params.apiKey}"
|
|
20163
|
-
` + toolsetLine + contextPackLine;
|
|
20895
|
+
` + toolsetLine + contextPackLine + showTimingLine;
|
|
20164
20896
|
if (!exists) {
|
|
20165
20897
|
await fs7.writeFile(filePath, block.trimStart(), "utf8");
|
|
20166
20898
|
return "created";
|
|
@@ -20517,6 +21249,11 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20517
21249
|
console.log(" Uses more operations/credits; can be disabled in settings or via env.");
|
|
20518
21250
|
const contextPackChoice = normalizeInput(await rl.question("Enable Context Pack? [Y/n]: "));
|
|
20519
21251
|
const contextPackEnabled = !(contextPackChoice.toLowerCase() === "n" || contextPackChoice.toLowerCase() === "no");
|
|
21252
|
+
console.log("\nResponse Timing:");
|
|
21253
|
+
console.log(" Show response time for tool calls (e.g., '\u2713 3 results in 142ms').");
|
|
21254
|
+
console.log(" Useful for debugging performance; disabled by default.");
|
|
21255
|
+
const showTimingChoice = normalizeInput(await rl.question("Show response timing? [y/N]: "));
|
|
21256
|
+
const showTiming = showTimingChoice.toLowerCase() === "y" || showTimingChoice.toLowerCase() === "yes";
|
|
20520
21257
|
const editors = [
|
|
20521
21258
|
"codex",
|
|
20522
21259
|
"claude",
|
|
@@ -20591,18 +21328,20 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20591
21328
|
)
|
|
20592
21329
|
) || mcpChoiceDefault;
|
|
20593
21330
|
const mcpScope = mcpChoice === "2" && hasCodex && !hasProjectMcpEditors ? "skip" : mcpChoice === "4" ? "skip" : mcpChoice === "1" ? "global" : mcpChoice === "2" ? "project" : "both";
|
|
20594
|
-
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled });
|
|
21331
|
+
const mcpServer = buildContextStreamMcpServer({ apiUrl, apiKey, toolset, contextPackEnabled, showTiming });
|
|
20595
21332
|
const mcpServerClaude = buildContextStreamMcpServer({
|
|
20596
21333
|
apiUrl,
|
|
20597
21334
|
apiKey,
|
|
20598
21335
|
toolset,
|
|
20599
|
-
contextPackEnabled
|
|
21336
|
+
contextPackEnabled,
|
|
21337
|
+
showTiming
|
|
20600
21338
|
});
|
|
20601
21339
|
const vsCodeServer = buildContextStreamVsCodeServer({
|
|
20602
21340
|
apiUrl,
|
|
20603
21341
|
apiKey,
|
|
20604
21342
|
toolset,
|
|
20605
|
-
contextPackEnabled
|
|
21343
|
+
contextPackEnabled,
|
|
21344
|
+
showTiming
|
|
20606
21345
|
});
|
|
20607
21346
|
const needsGlobalMcpConfig = mcpScope === "global" || mcpScope === "both" || mcpScope === "project" && hasCodex;
|
|
20608
21347
|
if (needsGlobalMcpConfig) {
|
|
@@ -20621,7 +21360,8 @@ Detected plan: ${planLabel} (graph: ${graphTierLabel})`);
|
|
|
20621
21360
|
apiUrl,
|
|
20622
21361
|
apiKey,
|
|
20623
21362
|
toolset,
|
|
20624
|
-
contextPackEnabled
|
|
21363
|
+
contextPackEnabled,
|
|
21364
|
+
showTiming
|
|
20625
21365
|
});
|
|
20626
21366
|
writeActions.push({ kind: "mcp-config", target: filePath, status });
|
|
20627
21367
|
console.log(`- ${EDITOR_LABELS[editor]}: ${status} ${filePath}`);
|
|
@@ -20936,7 +21676,12 @@ Applying to ${projects.length} project(s)...`);
|
|
|
20936
21676
|
folder_path: projectPath,
|
|
20937
21677
|
workspace_id: workspaceId,
|
|
20938
21678
|
workspace_name: workspaceName,
|
|
20939
|
-
create_parent_mapping: createParentMapping
|
|
21679
|
+
create_parent_mapping: createParentMapping,
|
|
21680
|
+
// Include version and config info for desktop app compatibility
|
|
21681
|
+
version: VERSION,
|
|
21682
|
+
configured_editors: configuredEditors,
|
|
21683
|
+
context_pack: contextPackEnabled,
|
|
21684
|
+
api_url: apiUrl
|
|
20940
21685
|
});
|
|
20941
21686
|
writeActions.push({
|
|
20942
21687
|
kind: "workspace-config",
|
|
@@ -21053,6 +21798,7 @@ Applying to ${projects.length} project(s)...`);
|
|
|
21053
21798
|
console.log(`Toolset: ${toolset} (${toolsetDesc})`);
|
|
21054
21799
|
console.log(`Token reduction: ~75% compared to previous versions.`);
|
|
21055
21800
|
console.log(`Context Pack: ${contextPackEnabled ? "enabled" : "disabled"}`);
|
|
21801
|
+
console.log(`Response Timing: ${showTiming ? "enabled" : "disabled"}`);
|
|
21056
21802
|
}
|
|
21057
21803
|
console.log("\nNext steps:");
|
|
21058
21804
|
console.log("- Restart your editor/CLI after changing MCP config or rules.");
|
|
@@ -21068,6 +21814,9 @@ Applying to ${projects.length} project(s)...`);
|
|
|
21068
21814
|
console.log(
|
|
21069
21815
|
"- Toggle Context Pack with CONTEXTSTREAM_CONTEXT_PACK=true|false (and in dashboard settings)."
|
|
21070
21816
|
);
|
|
21817
|
+
console.log(
|
|
21818
|
+
"- Toggle Response Timing with CONTEXTSTREAM_SHOW_TIMING=true|false."
|
|
21819
|
+
);
|
|
21071
21820
|
console.log("");
|
|
21072
21821
|
console.log("You're set up! Now try these prompts in your AI tool:");
|
|
21073
21822
|
console.log(' 1) "session summary"');
|
package/dist/test-server.js
CHANGED
|
@@ -4082,7 +4082,8 @@ var configSchema = external_exports.object({
|
|
|
4082
4082
|
defaultProjectId: external_exports.string().uuid().optional(),
|
|
4083
4083
|
userAgent: external_exports.string().default(`contextstream-mcp/${VERSION}`),
|
|
4084
4084
|
allowHeaderAuth: external_exports.boolean().optional(),
|
|
4085
|
-
contextPackEnabled: external_exports.boolean().default(true)
|
|
4085
|
+
contextPackEnabled: external_exports.boolean().default(true),
|
|
4086
|
+
showTiming: external_exports.boolean().default(false)
|
|
4086
4087
|
});
|
|
4087
4088
|
var MISSING_CREDENTIALS_ERROR = "Set CONTEXTSTREAM_API_KEY or CONTEXTSTREAM_JWT for authentication (or CONTEXTSTREAM_ALLOW_HEADER_AUTH=true for header-based auth).";
|
|
4088
4089
|
function loadConfig() {
|
|
@@ -4090,6 +4091,7 @@ function loadConfig() {
|
|
|
4090
4091
|
const contextPackEnabled = parseBooleanEnv(
|
|
4091
4092
|
process.env.CONTEXTSTREAM_CONTEXT_PACK ?? process.env.CONTEXTSTREAM_CONTEXT_PACK_ENABLED
|
|
4092
4093
|
);
|
|
4094
|
+
const showTiming = parseBooleanEnv(process.env.CONTEXTSTREAM_SHOW_TIMING);
|
|
4093
4095
|
const parsed = configSchema.safeParse({
|
|
4094
4096
|
apiUrl: process.env.CONTEXTSTREAM_API_URL,
|
|
4095
4097
|
apiKey: process.env.CONTEXTSTREAM_API_KEY,
|
|
@@ -4098,7 +4100,8 @@ function loadConfig() {
|
|
|
4098
4100
|
defaultProjectId: process.env.CONTEXTSTREAM_PROJECT_ID,
|
|
4099
4101
|
userAgent: process.env.CONTEXTSTREAM_USER_AGENT,
|
|
4100
4102
|
allowHeaderAuth,
|
|
4101
|
-
contextPackEnabled
|
|
4103
|
+
contextPackEnabled,
|
|
4104
|
+
showTiming
|
|
4102
4105
|
});
|
|
4103
4106
|
if (!parsed.success) {
|
|
4104
4107
|
const missing = parsed.error.errors.map((e) => e.path.join(".")).join(", ");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextstream/mcp-server",
|
|
3
3
|
"mcpName": "io.github.contextstreamio/mcp-server",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.38",
|
|
5
5
|
"description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|