@context-engine-bridge/context-engine-mcp-bridge 0.0.83 → 0.0.85

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/AGENTS.md ADDED
@@ -0,0 +1,69 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # ctx-mcp-bridge
5
+
6
+ ## Purpose
7
+
8
+ The MCP bridge (`ctxce` CLI) is a Model Context Protocol server that aggregates the Context Engine indexer and memory servers into a single unified MCP server. It supports both stdio and HTTP transport modes, making it compatible with MCP clients like Claude Code, Windsurf, Augment, and others. The bridge is primarily launched by the VS Code extension but can run standalone.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `bin/ctxce.js` | CLI entry point and executable (chmod +x 755) |
15
+ | `src/cli.js` | Command routing and argument parsing for `mcp-serve`, `mcp-http-serve`, `auth`, `connect` |
16
+ | `src/mcpServer.js` | MCP server implementation with stdio/HTTP transport, tool deduping, and auth handling |
17
+ | `src/authCli.js` | Auth command handlers: `login`, `logout`, `status` with token and password flows |
18
+ | `src/authConfig.js` | Session storage and management in `~/.ctxce/auth.json` |
19
+ | `src/oauthHandler.js` | OAuth protocol support for remote deployments |
20
+ | `src/uploader.js` | Standalone code uploader integration |
21
+ | `src/connectCli.js` | Connection validation and setup helpers |
22
+ | `src/resultPathMapping.js` | Path remapping for tool results (container/host paths) |
23
+ | `package.json` | Node.js package manifest (requires Node >= 18) |
24
+
25
+ ## Subdirectories
26
+
27
+ | Directory | Purpose |
28
+ |-----------|---------|
29
+ | `bin/` | Executable CLI entry point |
30
+ | `docs/` | Debugging guides and documentation |
31
+ | `src/` | Core MCP server and auth logic (see `src/AGENTS.md`) |
32
+
33
+ ## For AI Agents
34
+
35
+ ### Working In This Directory
36
+
37
+ This is a Node.js MCP bridge package. Changes to MCP routing, tool forwarding, or auth handling require updates to `src/cli.js` and `src/mcpServer.js`. The bridge proxies requests between MCP clients and remote indexer/memory HTTP servers, so test both stdio and HTTP modes.
38
+
39
+ ### Testing Requirements
40
+
41
+ - Run with `npm start` or `node bin/ctxce.js --help`
42
+ - Test MCP stdio mode: `ctxce mcp-serve --workspace /tmp/test`
43
+ - Test MCP HTTP mode: `ctxce mcp-http-serve --workspace /tmp/test --port 30810`
44
+ - Test auth commands: `ctxce auth login --backend-url http://localhost:8004 --token TEST_TOKEN`
45
+ - Verify auth state: `ctxce auth status --backend-url http://localhost:8004 --json`
46
+ - E2E tests: `npm run test:e2e`
47
+
48
+ ### Common Patterns
49
+
50
+ - Environment variables: `CTXCE_INDEXER_URL`, `CTXCE_MEMORY_URL`, `CTXCE_HTTP_PORT`, `CTXCE_AUTH_*`
51
+ - Auth sessions stored in `~/.ctxce/auth.json` keyed by backend URL
52
+ - MCP tools are deduplicated and forwarded from indexer and memory servers
53
+ - Path remapping (host paths <-> container paths) handled transparently
54
+ - All MCP requests are logged to stderr or `CTXCE_DEBUG_LOG` if set
55
+
56
+ ## Dependencies
57
+
58
+ ### Internal
59
+ - Context Engine indexer (HTTP endpoint at `CTXCE_INDEXER_URL` or `http://localhost:8003/mcp`)
60
+ - Context Engine memory server (HTTP endpoint at `CTXCE_MEMORY_URL` or `http://localhost:8002/mcp`)
61
+ - Auth backend (optional, at `CTXCE_AUTH_BACKEND_URL` or `http://localhost:8004`)
62
+
63
+ ### External
64
+ - `@modelcontextprotocol/sdk` (^1.24.3) – MCP protocol implementation
65
+ - `zod` (^3.25.0) – Runtime type validation
66
+ - `tar` (^7.5.9) – Archive support for uploads
67
+ - `ignore` (^7.0.5) – .gitignore-style file filtering
68
+
69
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/bin/AGENTS.md ADDED
@@ -0,0 +1,34 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # bin
5
+
6
+ ## Purpose
7
+
8
+ Executable CLI entry point for the `ctxce` command (also aliased as `ctxce-bridge`). This directory contains the shebang-wrapped Node.js script that is installed globally or via npm when the package is installed.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `ctxce.js` | CLI executable entry point (Node.js script, chmod +x 755) |
15
+
16
+ ## For AI Agents
17
+
18
+ ### Working In This Directory
19
+
20
+ Do not modify `ctxce.js` directly unless changing the CLI bootstrap. The actual CLI logic is in `src/cli.js`. The executable must have a shebang (`#!/usr/bin/env node`) and be marked executable on Unix systems.
21
+
22
+ ### Testing Requirements
23
+
24
+ - Verify executable permission: `ls -l bin/ctxce.js` should show `-rwxr-xr-x`
25
+ - Test global install: `npm install -g` and run `ctxce --help`
26
+ - Test npx mode: `npx @context-engine-bridge/context-engine-mcp-bridge ctxce --help`
27
+ - Postinstall script auto-fixes permissions on non-Windows systems
28
+
29
+ ## Dependencies
30
+
31
+ ### Internal
32
+ - `src/cli.js` – Main CLI router and handler
33
+
34
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.83",
3
+ "version": "0.0.85",
4
4
  "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
package/src/AGENTS.md ADDED
@@ -0,0 +1,59 @@
1
+ <!-- Parent: ../AGENTS.md -->
2
+ <!-- Generated: 2026-02-19 | Updated: 2026-02-19 -->
3
+
4
+ # src
5
+
6
+ ## Purpose
7
+
8
+ Core MCP server implementation, auth handling, and CLI routing for the `ctxce` bridge. This module aggregates the Context Engine indexer and memory servers, manages authentication sessions, and handles both stdio and HTTP transport modes.
9
+
10
+ ## Key Files
11
+
12
+ | File | Description |
13
+ |------|-------------|
14
+ | `cli.js` | Main command router for `mcp-serve`, `mcp-http-serve`, `auth`, `connect` subcommands; parses CLI flags and environment variables |
15
+ | `mcpServer.js` | MCP server implementation; proxies requests to indexer/memory servers, dedupes tools, handles auth, supports stdio and HTTP transports |
16
+ | `authCli.js` | Auth command handlers for `login`, `logout`, `status` with token and password flows; manages session lifecycle |
17
+ | `authConfig.js` | Persistent auth state in `~/.ctxce/auth.json`; session loading, saving, TTL handling, OAuth support |
18
+ | `oauthHandler.js` | OAuth protocol implementation for token refresh and remote auth flows |
19
+ | `uploader.js` | Integration with the standalone code uploader (tar archive support, progress tracking) |
20
+ | `connectCli.js` | Connection validation, workspace discovery, and setup helpers |
21
+ | `resultPathMapping.js` | Path remapping for tool results (host paths <-> container paths); handles path translation for Docker environments |
22
+
23
+ ## For AI Agents
24
+
25
+ ### Working In This Directory
26
+
27
+ This is the core business logic. Changes to MCP command handling, tool routing, or auth flows affect both the CLI and the VS Code extension. The server proxies all tool requests to remote indexer/memory servers and dedupes tool lists to prevent duplicates. Auth is optional but handles session TTL, token refresh, and fallback to dev tokens.
28
+
29
+ ### Testing Requirements
30
+
31
+ - Test MCP stdio transport: `node src/cli.js mcp-serve --workspace /tmp/test`
32
+ - Test MCP HTTP transport: `node src/cli.js mcp-http-serve --workspace /tmp/test --port 30810`
33
+ - Test auth login: `node src/cli.js auth login --backend-url http://localhost:8004 --token TOKEN`
34
+ - Test auth status: `node src/cli.js auth status --backend-url http://localhost:8004 --json`
35
+ - Verify tool deduping: Check that tools from indexer and memory are merged without duplicates
36
+ - Test path remapping: Verify that container paths are correctly mapped for Docker environments
37
+ - Run E2E tests: `npm run test:e2e`, `npm run test:e2e:auth`, `npm run test:e2e:happy`, `npm run test:e2e:edge`
38
+
39
+ ### Common Patterns
40
+
41
+ - Environment variables guide server initialization: `CTXCE_INDEXER_URL`, `CTXCE_MEMORY_URL`, `CTXCE_HTTP_PORT`, `CTXCE_AUTH_BACKEND_URL`, `CTXCE_AUTH_ENABLED`, `CTXCE_DEBUG_LOG`
42
+ - Sessions are stored per backend URL in `~/.ctxce/auth.json` with TTL tracking
43
+ - All MCP tools from indexer and memory are merged and deduplicated before listing
44
+ - Path remapping is applied to tool arguments and results for Docker host/container path translation
45
+ - Debug logging goes to stderr and optionally to a file (set `CTXCE_DEBUG_LOG` env var)
46
+ - HTTP transport uses Node.js `createServer` with MCP's `StreamableHTTPServerTransport`
47
+
48
+ ## Dependencies
49
+
50
+ ### Internal
51
+ - `bin/ctxce.js` – CLI entry point that imports and runs these modules
52
+
53
+ ### External
54
+ - `@modelcontextprotocol/sdk` – MCP protocol (Server, StdioServerTransport, StreamableHTTPServerTransport, Client, StreamableHTTPClientTransport)
55
+ - `zod` – Runtime type validation (used in config parsing)
56
+ - `tar` – Archive handling for uploader
57
+ - `ignore` – File filtering for .gitignore patterns
58
+
59
+ <!-- MANUAL: Any manually added notes below this line are preserved on regeneration -->
package/src/authConfig.js CHANGED
@@ -152,3 +152,26 @@ export function saveResolvedCollection(backendUrl, scope, collection) {
152
152
  };
153
153
  writeConfig(all);
154
154
  }
155
+
156
+ export function clearResolvedCollection(backendUrl, scope) {
157
+ if (!backendUrl) {
158
+ return;
159
+ }
160
+ const scopeKey = buildResolvedCollectionScopeKey(scope);
161
+ if (!scopeKey) {
162
+ return;
163
+ }
164
+ const all = readConfig();
165
+ const key = String(backendUrl);
166
+ const existingEntry = all[key] && typeof all[key] === "object" ? all[key] : {};
167
+ const resolvedCollections = { ...getResolvedCollections(existingEntry) };
168
+ if (!Object.prototype.hasOwnProperty.call(resolvedCollections, scopeKey)) {
169
+ return;
170
+ }
171
+ delete resolvedCollections[scopeKey];
172
+ all[key] = {
173
+ ...existingEntry,
174
+ [RESOLVED_COLLECTIONS_KEY]: resolvedCollections,
175
+ };
176
+ writeConfig(all);
177
+ }
package/src/mcpServer.js CHANGED
@@ -12,6 +12,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
12
12
  import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
13
13
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
14
14
  import {
15
+ clearResolvedCollection,
15
16
  loadAnyAuthEntry,
16
17
  loadAuthEntry,
17
18
  loadResolvedCollection,
@@ -264,7 +265,7 @@ async function sendSessionDefaults(client, payload, label) {
264
265
  try {
265
266
  await client.callTool({
266
267
  name: "set_session_defaults",
267
- arguments: payload,
268
+ arguments: { ...payload },
268
269
  });
269
270
  } catch (err) {
270
271
  // eslint-disable-next-line no-console
@@ -522,6 +523,70 @@ function isConnectionDeadError(error) {
522
523
  }
523
524
  }
524
525
 
526
+ /**
527
+ * Detect a strong signal that the active collection has been deleted on the server.
528
+ * Only triggers on unambiguous signals to avoid false positives on transient errors.
529
+ *
530
+ * Signals recognized:
531
+ * - HTTP 404 error whose message contains a collection-related keyword
532
+ * - Error code "COLLECTION_DELETED"
533
+ * - Error message containing "collection_deleted", "collection not found",
534
+ * "collection doesn't exist", or "collection does not exist"
535
+ */
536
+ function isCollectionDeletedSignal(error) {
537
+ try {
538
+ if (!error) return false;
539
+ // Explicit error code
540
+ if (
541
+ typeof error.code === "string" &&
542
+ error.code.toUpperCase() === "COLLECTION_DELETED"
543
+ ) {
544
+ return true;
545
+ }
546
+ const msg = _errorMessage(error);
547
+ if (!msg) return false;
548
+ const lower = msg.toLowerCase();
549
+ // 404 with a collection-related keyword is a strong signal
550
+ if (
551
+ (lower.includes("404") || lower.includes("not found")) &&
552
+ lower.includes("collection")
553
+ ) {
554
+ return true;
555
+ }
556
+ // Explicit deletion phrases
557
+ if (
558
+ lower.includes("collection_deleted") ||
559
+ lower.includes("collection not found") ||
560
+ lower.includes("collection doesn't exist") ||
561
+ lower.includes("collection does not exist")
562
+ ) {
563
+ return true;
564
+ }
565
+ return false;
566
+ } catch {
567
+ return false;
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Inspect a successful tool call result for an explicit collection_deleted flag.
573
+ * Returns true only when the response payload contains `collection_deleted: true`.
574
+ */
575
+ function resultIndicatesCollectionDeleted(result) {
576
+ try {
577
+ if (!result || !Array.isArray(result.content)) return false;
578
+ const textBlock = result.content.find((c) => c && c.type === "text");
579
+ if (!textBlock || typeof textBlock.text !== "string") return false;
580
+ const parsed = JSON.parse(textBlock.text);
581
+ if (parsed && parsed.collection_deleted === true) return true;
582
+ // Also check nested result envelope
583
+ if (parsed && parsed.result && parsed.result.collection_deleted === true) return true;
584
+ return false;
585
+ } catch {
586
+ return false;
587
+ }
588
+ }
589
+
525
590
  // MCP stdio server implemented using the official MCP TypeScript SDK.
526
591
  // Acts as a low-level proxy for tools, forwarding tools/list and tools/call
527
592
  // to the remote qdrant-indexer MCP server while adding a local `ping` tool.
@@ -647,6 +712,12 @@ async function fetchBridgeCollectionState({
647
712
  );
648
713
  return null;
649
714
  }
715
+ if (resp.status === 404) {
716
+ // Server is reachable but has no workspace state — do NOT fall back to local caches.
717
+ // This can happen when workspace state was cleaned up during collection deletion.
718
+ debugLog("[ctxce] /bridge/state responded 404; server has no state for this workspace");
719
+ return { _server_reachable: true };
720
+ }
650
721
  throw new Error(`bridge/state responded ${resp.status}`);
651
722
  }
652
723
  debugLog(`[ctxce] bridge/state responded ${resp.status}`);
@@ -711,26 +782,11 @@ export async function buildDefaultsPayload({
711
782
  logicalRepoId = "";
712
783
  }
713
784
 
714
- const exactWorkspaceCollection = !pinnedCollection
715
- ? _readExactWorkspaceCachedCollection(workspace)
716
- : null;
717
-
718
- if (!defaultsPayload.collection && exactWorkspaceCollection) {
719
- defaultsPayload.collection = exactWorkspaceCollection;
720
- log(`[ctxce] Using exact workspace cached collection: ${exactWorkspaceCollection}`);
721
- }
722
-
723
- if (!defaultsPayload.collection && backendHint && logicalRepoId) {
724
- const cachedCollection = loadResolvedCollectionHint(backendHint, {
725
- orgId: resolvedAuthEntry?.org_id,
726
- orgSlug: resolvedAuthEntry?.org_slug,
727
- logicalRepoId,
728
- });
729
- if (cachedCollection) {
730
- defaultsPayload.collection = cachedCollection;
731
- log(`[ctxce] Using cached resolved collection from auth store: ${cachedCollection}`);
732
- }
733
- }
785
+ // --- Server-first collection resolution ---
786
+ // Priority: pinned > server /bridge/state > local caches (fallback only).
787
+ // This prevents stale local caches from winning over server truth
788
+ // after a collection is deleted.
789
+ let serverResolved = false;
734
790
 
735
791
  if (!pinnedCollection) {
736
792
  try {
@@ -745,31 +801,87 @@ export async function buildDefaultsPayload({
745
801
  uploadServiceUrl,
746
802
  });
747
803
  if (state) {
748
- const servingCollection = typeof state.serving_collection === "string"
749
- ? state.serving_collection.trim()
750
- : "";
751
- const activeCollection = typeof state.active_collection === "string"
752
- ? state.active_collection.trim()
753
- : "";
754
- if (servingCollection) {
755
- defaultsPayload.collection = servingCollection;
756
- if (!configuredCollection || configuredCollection !== servingCollection) {
757
- log(`[ctxce] Using serving collection from /bridge/state: ${servingCollection}`);
804
+ // Server responded 404 — reachable but no workspace state.
805
+ // Mark serverResolved to prevent falling back to stale local caches.
806
+ if (state._server_reachable && !state.collection_deleted && !state.serving_collection && !state.active_collection) {
807
+ serverResolved = true;
808
+ log("[ctxce] Server reachable but no workspace state found, skipping local cache fallback");
809
+ }
810
+ // Handle explicit collection_deleted signal from the server
811
+ if (state.collection_deleted === true) {
812
+ log("[ctxce] Server reports collection deleted, clearing all local caches");
813
+ defaultsPayload.collection = "";
814
+ serverResolved = true;
815
+ try {
816
+ _clearExactWorkspaceCachedCollection(workspace);
817
+ } catch (wsErr) {
818
+ log("[ctxce] Failed to clear workspace ctx_config.json default_collection: " + String(wsErr));
819
+ }
820
+ // Clear the auth.json resolved collection cache
821
+ try {
822
+ if (backendHint && logicalRepoId) {
823
+ clearResolvedCollection(backendHint, {
824
+ orgId: resolvedAuthEntry?.org_id,
825
+ orgSlug: resolvedAuthEntry?.org_slug,
826
+ logicalRepoId,
827
+ });
828
+ }
829
+ } catch (authErr) {
830
+ log("[ctxce] Failed to clear auth.json resolved collection: " + String(authErr));
758
831
  }
759
- if (backendHint && logicalRepoId) {
760
- saveResolvedCollectionHint(backendHint, {
761
- orgId: resolvedAuthEntry?.org_id,
762
- orgSlug: resolvedAuthEntry?.org_slug,
763
- logicalRepoId,
764
- }, servingCollection);
832
+ } else {
833
+ const servingCollection = typeof state.serving_collection === "string"
834
+ ? state.serving_collection.trim()
835
+ : "";
836
+ const activeCollection = typeof state.active_collection === "string"
837
+ ? state.active_collection.trim()
838
+ : "";
839
+ if (servingCollection) {
840
+ defaultsPayload.collection = servingCollection;
841
+ serverResolved = true;
842
+ if (!configuredCollection || configuredCollection !== servingCollection) {
843
+ log(`[ctxce] Using serving collection from /bridge/state: ${servingCollection}`);
844
+ }
845
+ // Update local caches to match server truth
846
+ if (backendHint && logicalRepoId) {
847
+ saveResolvedCollectionHint(backendHint, {
848
+ orgId: resolvedAuthEntry?.org_id,
849
+ orgSlug: resolvedAuthEntry?.org_slug,
850
+ logicalRepoId,
851
+ }, servingCollection);
852
+ }
853
+ _writeExactWorkspaceCachedCollection(workspace, servingCollection);
854
+ } else if (activeCollection) {
855
+ defaultsPayload.collection = activeCollection;
856
+ serverResolved = true;
857
+ log(`[ctxce] Using active collection from /bridge/state: ${activeCollection}`);
765
858
  }
766
- } else if (!defaultsPayload.collection && activeCollection) {
767
- defaultsPayload.collection = activeCollection;
768
- log(`[ctxce] Using active collection from /bridge/state fallback: ${activeCollection}`);
769
859
  }
770
860
  }
771
861
  } catch (err) {
772
- log("[ctxce] bridge/state lookup failed: " + String(err));
862
+ log("[ctxce] bridge/state lookup failed, falling back to local caches: " + String(err));
863
+ }
864
+ }
865
+
866
+ // Fallback to local caches ONLY when server is unreachable
867
+ if (!serverResolved && !pinnedCollection) {
868
+ const exactWorkspaceCollection = _readExactWorkspaceCachedCollection(workspace);
869
+
870
+ if (!defaultsPayload.collection && exactWorkspaceCollection) {
871
+ defaultsPayload.collection = exactWorkspaceCollection;
872
+ log(`[ctxce] WARNING: Using cached collection ${exactWorkspaceCollection} (server unavailable — may be stale)`);
873
+ }
874
+
875
+ if (!defaultsPayload.collection && backendHint && logicalRepoId) {
876
+ const cachedCollection = loadResolvedCollectionHint(backendHint, {
877
+ orgId: resolvedAuthEntry?.org_id,
878
+ orgSlug: resolvedAuthEntry?.org_slug,
879
+ logicalRepoId,
880
+ });
881
+ if (cachedCollection) {
882
+ defaultsPayload.collection = cachedCollection;
883
+ log(`[ctxce] WARNING: Using cached collection ${cachedCollection} from auth store (server unavailable — may be stale)`);
884
+ }
773
885
  }
774
886
  }
775
887
 
@@ -832,6 +944,74 @@ function _readExactWorkspaceCachedCollection(workspacePath) {
832
944
  }
833
945
  }
834
946
 
947
+ function _writeExactWorkspaceCachedCollection(workspacePath, collection) {
948
+ try {
949
+ const resolvedWorkspace = _validateWorkspacePath(workspacePath);
950
+ if (!resolvedWorkspace) return;
951
+ const wsDir = _computeWorkspaceDir(resolvedWorkspace);
952
+ const configPath = path.join(wsDir, "ctx_config.json");
953
+ if (!fs.existsSync(configPath)) return;
954
+ const existing = JSON.parse(fs.readFileSync(configPath, "utf8"));
955
+ if (existing && typeof existing === "object") {
956
+ existing.default_collection = collection;
957
+ fs.writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf8");
958
+ }
959
+ } catch (_) {}
960
+ }
961
+
962
+ function _clearExactWorkspaceCachedCollection(workspacePath) {
963
+ try {
964
+ const resolvedWorkspace = _validateWorkspacePath(workspacePath);
965
+ if (!resolvedWorkspace) return;
966
+ const wsDir = _computeWorkspaceDir(resolvedWorkspace);
967
+ // Clear default_collection from ctx_config.json
968
+ const configPath = path.join(wsDir, "ctx_config.json");
969
+ if (fs.existsSync(configPath)) {
970
+ try {
971
+ const existing = JSON.parse(fs.readFileSync(configPath, "utf8"));
972
+ if (existing && existing.default_collection) {
973
+ existing.default_collection = "";
974
+ fs.writeFileSync(configPath, JSON.stringify(existing, null, 2), "utf8");
975
+ }
976
+ } catch (_) {}
977
+ }
978
+ // Clear session_collections.json (may reference the deleted collection)
979
+ const sessionCollPath = path.join(wsDir, "session_collections.json");
980
+ try {
981
+ if (fs.existsSync(sessionCollPath)) {
982
+ fs.unlinkSync(sessionCollPath);
983
+ debugLog("[ctxce] Cleared session_collections.json after collection deletion");
984
+ }
985
+ } catch (_) {}
986
+ // Clear workspace hints (PID-keyed hint files in ~/.ctxce/workspace-hints/)
987
+ try {
988
+ const hintsDir = path.join(os.homedir(), ".ctxce", "workspace-hints");
989
+ if (fs.existsSync(hintsDir)) {
990
+ // Clear hint for current process tree (ppid is the VS Code process)
991
+ const currentPpid = process.ppid;
992
+ if (currentPpid && currentPpid > 1) {
993
+ const currentHint = path.join(hintsDir, `${currentPpid}.json`);
994
+ try {
995
+ if (fs.existsSync(currentHint)) {
996
+ fs.unlinkSync(currentHint);
997
+ debugLog(`[ctxce] Cleared workspace hint for current VS Code process (PID ${currentPpid})`);
998
+ }
999
+ } catch (_) {}
1000
+ }
1001
+ // Also clean up stale hints for dead PIDs
1002
+ for (const name of fs.readdirSync(hintsDir)) {
1003
+ if (!name.endsWith(".json")) continue;
1004
+ const pid = parseInt(name.replace(".json", ""), 10);
1005
+ if (!pid || pid <= 1) continue;
1006
+ try { process.kill(pid, 0); } catch (_) {
1007
+ try { fs.unlinkSync(path.join(hintsDir, name)); } catch (_) {}
1008
+ }
1009
+ }
1010
+ }
1011
+ } catch (_) {}
1012
+ } catch (_) {}
1013
+ }
1014
+
835
1015
  const MAX_WS_SCAN = 50;
836
1016
 
837
1017
  function _resolveWorkspace(providedWorkspace) {
@@ -1062,6 +1242,62 @@ async function createBridgeServer(options) {
1062
1242
  uploadServiceUrl,
1063
1243
  });
1064
1244
 
1245
+ async function recoverDeletedCollection(reason) {
1246
+ debugLog(`[ctxce] Collection appears deleted (${reason}), clearing cache and re-resolving`);
1247
+ defaultsPayload.collection = "";
1248
+ try {
1249
+ _clearExactWorkspaceCachedCollection(workspace);
1250
+ } catch (wsErr) {
1251
+ debugLog("[ctxce] Failed to clear workspace collection cache: " + String(wsErr));
1252
+ }
1253
+ try {
1254
+ if (backendHint) {
1255
+ const authEntry = loadAuthEntry(backendHint);
1256
+ let repoId = "";
1257
+ try { repoId = computeLogicalRepoIdentity(workspace)?.id || ""; } catch { repoId = ""; }
1258
+ clearResolvedCollection(backendHint, {
1259
+ orgId: authEntry?.org_id,
1260
+ orgSlug: authEntry?.org_slug,
1261
+ logicalRepoId: repoId,
1262
+ });
1263
+ }
1264
+ } catch (clearErr) {
1265
+ debugLog("[ctxce] Failed to clear resolved collection cache: " + String(clearErr));
1266
+ }
1267
+ try {
1268
+ const freshPayload = await buildDefaultsPayload({
1269
+ workspace,
1270
+ sessionId,
1271
+ explicitCollection: "",
1272
+ defaultCollection: "",
1273
+ defaultMode,
1274
+ defaultUnder,
1275
+ config,
1276
+ backendHint,
1277
+ uploadServiceUrl,
1278
+ });
1279
+ if (Object.hasOwn(freshPayload, "collection")) {
1280
+ defaultsPayload.collection = freshPayload.collection;
1281
+ }
1282
+ if (freshPayload.collection) {
1283
+ debugLog("[ctxce] Re-resolved collection after deletion: " + freshPayload.collection);
1284
+ }
1285
+ } catch (reResolveErr) {
1286
+ debugLog("[ctxce] Failed to re-resolve collection after deletion: " + String(reResolveErr));
1287
+ }
1288
+ try {
1289
+ if (indexerClient) {
1290
+ await sendSessionDefaults(indexerClient, defaultsPayload, "indexer");
1291
+ }
1292
+ if (memoryClient) {
1293
+ await sendSessionDefaults(memoryClient, defaultsPayload, "memory");
1294
+ }
1295
+ debugLog(`[ctxce] Re-sent session defaults after collection deletion detection (${reason})`);
1296
+ } catch (sdErr) {
1297
+ debugLog("[ctxce] Failed to re-send session defaults after deletion: " + String(sdErr));
1298
+ }
1299
+ }
1300
+
1065
1301
  async function initializeRemoteClients(forceRecreate = false) {
1066
1302
  if (!forceRecreate && indexerClient) {
1067
1303
  return;
@@ -1523,10 +1759,20 @@ async function createBridgeServer(options) {
1523
1759
  }
1524
1760
  } catch (e) { debugLog("[ctxce] LSP not_available inject failed: " + String(e)); }
1525
1761
  }
1762
+ // Detect explicit collection_deleted flag in a successful response
1763
+ if (resultIndicatesCollectionDeleted(result)) {
1764
+ await recoverDeletedCollection("result path");
1765
+ }
1766
+
1526
1767
  return maybeRemapToolResult(name, result, workspace);
1527
1768
  } catch (err) {
1528
1769
  lastError = err;
1529
1770
 
1771
+ // Detect collection deletion from error signals
1772
+ if (isCollectionDeletedSignal(err)) {
1773
+ await recoverDeletedCollection("error path");
1774
+ }
1775
+
1530
1776
  if (isConnectionDeadError(err) && !connectionRetried) {
1531
1777
  debugLog(
1532
1778
  "[ctxce] tools/call: connection dead (server may have restarted); recreating clients and retrying: " +
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file