@context-engine-bridge/context-engine-mcp-bridge 0.0.78 → 0.0.79
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/package.json +1 -1
- package/src/authCli.js +9 -1
- package/src/authConfig.js +71 -1
- package/src/mcpServer.js +185 -50
- package/src/oauthHandler.js +17 -1
- package/src/syncDaemon.js +32 -6
package/package.json
CHANGED
package/src/authCli.js
CHANGED
|
@@ -154,11 +154,19 @@ async function doLogin(args) {
|
|
|
154
154
|
const sessionId = data.session_id || data.sessionId || null;
|
|
155
155
|
const userId = data.user_id || data.userId || null;
|
|
156
156
|
const expiresAt = data.expires_at || data.expiresAt || null;
|
|
157
|
+
const orgId = data.org_id || data.orgId || null;
|
|
158
|
+
const orgSlug = data.org_slug || data.orgSlug || null;
|
|
157
159
|
if (!sessionId) {
|
|
158
160
|
console.error("[ctxce] Auth login response missing session id.");
|
|
159
161
|
process.exit(1);
|
|
160
162
|
}
|
|
161
|
-
saveAuthEntry(url, {
|
|
163
|
+
saveAuthEntry(url, {
|
|
164
|
+
sessionId,
|
|
165
|
+
userId,
|
|
166
|
+
expiresAt,
|
|
167
|
+
org_id: orgId,
|
|
168
|
+
org_slug: orgSlug,
|
|
169
|
+
});
|
|
162
170
|
console.error("[ctxce] Auth login successful for", url);
|
|
163
171
|
}
|
|
164
172
|
|
package/src/authConfig.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
const CONFIG_DIR_NAME = ".ctxce";
|
|
6
6
|
const CONFIG_BASENAME = "auth.json";
|
|
7
|
+
const RESOLVED_COLLECTIONS_KEY = "resolved_collections_v1";
|
|
7
8
|
|
|
8
9
|
function getConfigPath() {
|
|
9
10
|
const home = os.homedir() || process.cwd();
|
|
@@ -36,6 +37,38 @@ function writeConfig(data) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function normalizeScopeValue(value) {
|
|
41
|
+
return typeof value === "string" ? value.trim() : "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function buildResolvedCollectionScopeKey({ orgId, orgSlug, logicalRepoId } = {}) {
|
|
45
|
+
const repo = normalizeScopeValue(logicalRepoId);
|
|
46
|
+
if (!repo) {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
const normalizedOrgId = normalizeScopeValue(orgId);
|
|
50
|
+
const normalizedOrgSlug = normalizeScopeValue(orgSlug);
|
|
51
|
+
const org = normalizedOrgId || (normalizedOrgSlug ? `slug:${normalizedOrgSlug}` : "org:none");
|
|
52
|
+
return `${org}::${repo}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getResolvedCollections(entry) {
|
|
56
|
+
const map = entry && typeof entry === "object" ? entry[RESOLVED_COLLECTIONS_KEY] : null;
|
|
57
|
+
return map && typeof map === "object" ? map : {};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function preserveInternalEntryState(existingEntry, nextEntry) {
|
|
61
|
+
const preserved = {};
|
|
62
|
+
const existingCollections = getResolvedCollections(existingEntry);
|
|
63
|
+
if (Object.keys(existingCollections).length > 0) {
|
|
64
|
+
preserved[RESOLVED_COLLECTIONS_KEY] = existingCollections;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
...preserved,
|
|
68
|
+
...nextEntry,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
39
72
|
export function loadAuthEntry(backendUrl) {
|
|
40
73
|
if (!backendUrl) {
|
|
41
74
|
return null;
|
|
@@ -55,7 +88,8 @@ export function saveAuthEntry(backendUrl, entry) {
|
|
|
55
88
|
}
|
|
56
89
|
const all = readConfig();
|
|
57
90
|
const key = String(backendUrl);
|
|
58
|
-
all[key]
|
|
91
|
+
const existingEntry = all[key];
|
|
92
|
+
all[key] = preserveInternalEntryState(existingEntry, entry);
|
|
59
93
|
writeConfig(all);
|
|
60
94
|
}
|
|
61
95
|
|
|
@@ -82,3 +116,39 @@ export function loadAnyAuthEntry() {
|
|
|
82
116
|
}
|
|
83
117
|
return null;
|
|
84
118
|
}
|
|
119
|
+
|
|
120
|
+
export function loadResolvedCollection(backendUrl, scope) {
|
|
121
|
+
if (!backendUrl) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const scopeKey = buildResolvedCollectionScopeKey(scope);
|
|
125
|
+
if (!scopeKey) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const entry = loadAuthEntry(backendUrl);
|
|
129
|
+
const resolved = getResolvedCollections(entry)[scopeKey];
|
|
130
|
+
return typeof resolved === "string" && resolved.trim() ? resolved.trim() : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function saveResolvedCollection(backendUrl, scope, collection) {
|
|
134
|
+
if (!backendUrl) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const scopeKey = buildResolvedCollectionScopeKey(scope);
|
|
138
|
+
const nextCollection = typeof collection === "string" ? collection.trim() : "";
|
|
139
|
+
if (!scopeKey || !nextCollection) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const all = readConfig();
|
|
143
|
+
const key = String(backendUrl);
|
|
144
|
+
const existingEntry = all[key] && typeof all[key] === "object" ? all[key] : {};
|
|
145
|
+
const resolvedCollections = {
|
|
146
|
+
...getResolvedCollections(existingEntry),
|
|
147
|
+
[scopeKey]: nextCollection,
|
|
148
|
+
};
|
|
149
|
+
all[key] = {
|
|
150
|
+
...existingEntry,
|
|
151
|
+
[RESOLVED_COLLECTIONS_KEY]: resolvedCollections,
|
|
152
|
+
};
|
|
153
|
+
writeConfig(all);
|
|
154
|
+
}
|
package/src/mcpServer.js
CHANGED
|
@@ -11,9 +11,17 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
11
11
|
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
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
loadAnyAuthEntry,
|
|
16
|
+
loadAuthEntry,
|
|
17
|
+
loadResolvedCollection,
|
|
18
|
+
readConfig,
|
|
19
|
+
saveAuthEntry,
|
|
20
|
+
saveResolvedCollection,
|
|
21
|
+
} from "./authConfig.js";
|
|
15
22
|
import { maybeRemapToolArgs, maybeRemapToolResult } from "./resultPathMapping.js";
|
|
16
23
|
import { startSyncDaemon } from "./syncDaemon.js";
|
|
24
|
+
import { computeLogicalRepoIdentity } from "./uploader.js";
|
|
17
25
|
import * as oauthHandler from "./oauthHandler.js";
|
|
18
26
|
|
|
19
27
|
const LSP_CONN_CACHE_TTL = 5000;
|
|
@@ -593,6 +601,7 @@ async function fetchBridgeCollectionState({
|
|
|
593
601
|
collection,
|
|
594
602
|
sessionId,
|
|
595
603
|
repoName,
|
|
604
|
+
logicalRepoId,
|
|
596
605
|
bridgeStateToken,
|
|
597
606
|
backendHint,
|
|
598
607
|
uploadServiceUrl,
|
|
@@ -612,6 +621,9 @@ async function fetchBridgeCollectionState({
|
|
|
612
621
|
if (repoName && repoName.trim()) {
|
|
613
622
|
url.searchParams.set("repo_name", repoName.trim());
|
|
614
623
|
}
|
|
624
|
+
if (logicalRepoId && logicalRepoId.trim()) {
|
|
625
|
+
url.searchParams.set("logical_repo_id", logicalRepoId.trim());
|
|
626
|
+
}
|
|
615
627
|
|
|
616
628
|
const headers = {
|
|
617
629
|
Accept: "application/json",
|
|
@@ -646,6 +658,131 @@ async function fetchBridgeCollectionState({
|
|
|
646
658
|
}
|
|
647
659
|
}
|
|
648
660
|
|
|
661
|
+
export async function buildDefaultsPayload({
|
|
662
|
+
workspace,
|
|
663
|
+
sessionId,
|
|
664
|
+
explicitCollection,
|
|
665
|
+
defaultCollection,
|
|
666
|
+
defaultMode,
|
|
667
|
+
defaultUnder,
|
|
668
|
+
config,
|
|
669
|
+
backendHint,
|
|
670
|
+
uploadServiceUrl,
|
|
671
|
+
bridgeStateToken = BRIDGE_STATE_TOKEN,
|
|
672
|
+
authEntry = undefined,
|
|
673
|
+
fetchState = fetchBridgeCollectionState,
|
|
674
|
+
getLogicalRepoIdentity = computeLogicalRepoIdentity,
|
|
675
|
+
loadResolvedCollectionHint = loadResolvedCollection,
|
|
676
|
+
saveResolvedCollectionHint = saveResolvedCollection,
|
|
677
|
+
log = debugLog,
|
|
678
|
+
} = {}) {
|
|
679
|
+
const defaultsPayload = { session: sessionId };
|
|
680
|
+
const pinnedCollection =
|
|
681
|
+
typeof explicitCollection === "string" ? explicitCollection.trim() : "";
|
|
682
|
+
const configuredCollection =
|
|
683
|
+
!pinnedCollection && typeof defaultCollection === "string"
|
|
684
|
+
? defaultCollection.trim()
|
|
685
|
+
: "";
|
|
686
|
+
|
|
687
|
+
if (pinnedCollection) {
|
|
688
|
+
defaultsPayload.collection = pinnedCollection;
|
|
689
|
+
} else if (configuredCollection) {
|
|
690
|
+
defaultsPayload.collection = configuredCollection;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const resolvedAuthEntry = authEntry === undefined
|
|
694
|
+
? (backendHint ? loadAuthEntry(backendHint) : null)
|
|
695
|
+
: authEntry;
|
|
696
|
+
|
|
697
|
+
if (resolvedAuthEntry && (resolvedAuthEntry.org_id || resolvedAuthEntry.org_slug)) {
|
|
698
|
+
if (resolvedAuthEntry.org_id) {
|
|
699
|
+
defaultsPayload.org_id = resolvedAuthEntry.org_id;
|
|
700
|
+
}
|
|
701
|
+
if (resolvedAuthEntry.org_slug) {
|
|
702
|
+
defaultsPayload.org_slug = resolvedAuthEntry.org_slug;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
const repoName = detectRepoName(workspace, config);
|
|
707
|
+
let logicalRepoId = "";
|
|
708
|
+
try {
|
|
709
|
+
logicalRepoId = getLogicalRepoIdentity(workspace)?.id || "";
|
|
710
|
+
} catch {
|
|
711
|
+
logicalRepoId = "";
|
|
712
|
+
}
|
|
713
|
+
|
|
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
|
+
}
|
|
734
|
+
|
|
735
|
+
if (!pinnedCollection) {
|
|
736
|
+
try {
|
|
737
|
+
const state = await fetchState({
|
|
738
|
+
workspace,
|
|
739
|
+
collection: configuredCollection,
|
|
740
|
+
sessionId,
|
|
741
|
+
repoName,
|
|
742
|
+
logicalRepoId,
|
|
743
|
+
bridgeStateToken,
|
|
744
|
+
backendHint,
|
|
745
|
+
uploadServiceUrl,
|
|
746
|
+
});
|
|
747
|
+
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}`);
|
|
758
|
+
}
|
|
759
|
+
if (backendHint && logicalRepoId) {
|
|
760
|
+
saveResolvedCollectionHint(backendHint, {
|
|
761
|
+
orgId: resolvedAuthEntry?.org_id,
|
|
762
|
+
orgSlug: resolvedAuthEntry?.org_slug,
|
|
763
|
+
logicalRepoId,
|
|
764
|
+
}, servingCollection);
|
|
765
|
+
}
|
|
766
|
+
} else if (!defaultsPayload.collection && activeCollection) {
|
|
767
|
+
defaultsPayload.collection = activeCollection;
|
|
768
|
+
log(`[ctxce] Using active collection from /bridge/state fallback: ${activeCollection}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
} catch (err) {
|
|
772
|
+
log("[ctxce] bridge/state lookup failed: " + String(err));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (defaultMode) {
|
|
777
|
+
defaultsPayload.mode = defaultMode;
|
|
778
|
+
}
|
|
779
|
+
if (defaultUnder) {
|
|
780
|
+
defaultsPayload.under = defaultUnder;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return defaultsPayload;
|
|
784
|
+
}
|
|
785
|
+
|
|
649
786
|
function _validateWorkspacePath(raw) {
|
|
650
787
|
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
651
788
|
const resolved = path.resolve(raw);
|
|
@@ -659,6 +796,42 @@ function _validateWorkspacePath(raw) {
|
|
|
659
796
|
return resolved;
|
|
660
797
|
}
|
|
661
798
|
|
|
799
|
+
function _readExactWorkspaceCachedCollection(workspacePath) {
|
|
800
|
+
const resolvedWorkspace = _validateWorkspacePath(workspacePath);
|
|
801
|
+
if (!resolvedWorkspace) return null;
|
|
802
|
+
|
|
803
|
+
const wsDir = _computeWorkspaceDir(resolvedWorkspace);
|
|
804
|
+
const configPath = path.join(wsDir, "ctx_config.json");
|
|
805
|
+
const metaPath = path.join(wsDir, "meta.json");
|
|
806
|
+
|
|
807
|
+
if (!fs.existsSync(configPath)) {
|
|
808
|
+
return null;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (fs.existsSync(metaPath)) {
|
|
812
|
+
try {
|
|
813
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, "utf8"));
|
|
814
|
+
const metaWorkspace = _validateWorkspacePath(meta && meta.workspace_path);
|
|
815
|
+
if (!metaWorkspace || metaWorkspace !== resolvedWorkspace) {
|
|
816
|
+
return null;
|
|
817
|
+
}
|
|
818
|
+
} catch (_) {
|
|
819
|
+
return null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
try {
|
|
824
|
+
const parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
825
|
+
const collection =
|
|
826
|
+
parsed && typeof parsed.default_collection === "string"
|
|
827
|
+
? parsed.default_collection.trim()
|
|
828
|
+
: "";
|
|
829
|
+
return collection || null;
|
|
830
|
+
} catch (_) {
|
|
831
|
+
return null;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
662
835
|
const MAX_WS_SCAN = 50;
|
|
663
836
|
|
|
664
837
|
function _resolveWorkspace(providedWorkspace) {
|
|
@@ -877,55 +1050,17 @@ async function createBridgeServer(options) {
|
|
|
877
1050
|
|
|
878
1051
|
// Best-effort: inform the indexer of default collection and session.
|
|
879
1052
|
// If this fails we still proceed, falling back to per-call injection.
|
|
880
|
-
const defaultsPayload = {
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
} catch {
|
|
893
|
-
// ignore auth entry lookup failures
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
const repoName = detectRepoName(workspace, config);
|
|
897
|
-
|
|
898
|
-
try {
|
|
899
|
-
const state = await fetchBridgeCollectionState({
|
|
900
|
-
workspace,
|
|
901
|
-
collection: defaultCollection,
|
|
902
|
-
sessionId,
|
|
903
|
-
repoName,
|
|
904
|
-
bridgeStateToken: BRIDGE_STATE_TOKEN,
|
|
905
|
-
backendHint,
|
|
906
|
-
uploadServiceUrl,
|
|
907
|
-
});
|
|
908
|
-
if (state) {
|
|
909
|
-
const serving = state.serving_collection || state.active_collection;
|
|
910
|
-
if (serving) {
|
|
911
|
-
defaultsPayload.collection = serving;
|
|
912
|
-
if (!defaultCollection || defaultCollection !== serving) {
|
|
913
|
-
debugLog(
|
|
914
|
-
`[ctxce] Using serving collection from /bridge/state: ${serving}`,
|
|
915
|
-
);
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
} catch (err) {
|
|
920
|
-
debugLog("[ctxce] bridge/state lookup failed: " + String(err));
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
if (defaultMode) {
|
|
924
|
-
defaultsPayload.mode = defaultMode;
|
|
925
|
-
}
|
|
926
|
-
if (defaultUnder) {
|
|
927
|
-
defaultsPayload.under = defaultUnder;
|
|
928
|
-
}
|
|
1053
|
+
const defaultsPayload = await buildDefaultsPayload({
|
|
1054
|
+
workspace,
|
|
1055
|
+
sessionId,
|
|
1056
|
+
explicitCollection,
|
|
1057
|
+
defaultCollection,
|
|
1058
|
+
defaultMode,
|
|
1059
|
+
defaultUnder,
|
|
1060
|
+
config,
|
|
1061
|
+
backendHint,
|
|
1062
|
+
uploadServiceUrl,
|
|
1063
|
+
});
|
|
929
1064
|
|
|
930
1065
|
async function initializeRemoteClients(forceRecreate = false) {
|
|
931
1066
|
if (!forceRecreate && indexerClient) {
|
package/src/oauthHandler.js
CHANGED
|
@@ -247,6 +247,8 @@ export function getLoginPage(redirectUri, clientId, state, codeChallenge, codeCh
|
|
|
247
247
|
|
|
248
248
|
const data = await resp.json();
|
|
249
249
|
const sessionId = data.session_id || data.sessionId;
|
|
250
|
+
const orgId = data.org_id || data.orgId || null;
|
|
251
|
+
const orgSlug = data.org_slug || data.orgSlug || null;
|
|
250
252
|
if (!sessionId) {
|
|
251
253
|
throw new Error('No session in response');
|
|
252
254
|
}
|
|
@@ -258,6 +260,8 @@ export function getLoginPage(redirectUri, clientId, state, codeChallenge, codeCh
|
|
|
258
260
|
body: JSON.stringify({
|
|
259
261
|
session_id: sessionId,
|
|
260
262
|
backend_url: backendUrl,
|
|
263
|
+
org_id: orgId,
|
|
264
|
+
org_slug: orgSlug,
|
|
261
265
|
redirect_uri: params.redirect_uri,
|
|
262
266
|
state: params.state,
|
|
263
267
|
code_challenge: params.code_challenge,
|
|
@@ -443,7 +447,17 @@ export function handleOAuthStoreSession(req, res) {
|
|
|
443
447
|
res.setHeader("Content-Type", "application/json");
|
|
444
448
|
try {
|
|
445
449
|
const data = JSON.parse(body);
|
|
446
|
-
const {
|
|
450
|
+
const {
|
|
451
|
+
session_id,
|
|
452
|
+
backend_url,
|
|
453
|
+
org_id,
|
|
454
|
+
org_slug,
|
|
455
|
+
redirect_uri,
|
|
456
|
+
state,
|
|
457
|
+
code_challenge,
|
|
458
|
+
code_challenge_method,
|
|
459
|
+
client_id,
|
|
460
|
+
} = data;
|
|
447
461
|
|
|
448
462
|
if (!session_id || !backend_url) {
|
|
449
463
|
res.statusCode = 400;
|
|
@@ -503,6 +517,8 @@ export function handleOAuthStoreSession(req, res) {
|
|
|
503
517
|
sessionId: session_id,
|
|
504
518
|
userId: "oauth-user",
|
|
505
519
|
expiresAt: null,
|
|
520
|
+
org_id: org_id || null,
|
|
521
|
+
org_slug: org_slug || null,
|
|
506
522
|
});
|
|
507
523
|
|
|
508
524
|
// Generate auth code
|
package/src/syncDaemon.js
CHANGED
|
@@ -26,7 +26,7 @@ const noop = () => {};
|
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
// Process-level singleton registry
|
|
29
|
-
// keyed by path.resolve(workspace) -> { intervalId, cleanup }
|
|
29
|
+
// keyed by path.resolve(workspace) -> { intervalId, cleanup, updateRuntimeState }
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
const _activeDaemons = new Map();
|
|
@@ -153,17 +153,24 @@ export function startSyncDaemon(options) {
|
|
|
153
153
|
workspace,
|
|
154
154
|
sessionId: initialSessionId,
|
|
155
155
|
authEntry: initialAuthEntry,
|
|
156
|
-
uploadEndpoint,
|
|
156
|
+
uploadEndpoint: initialUploadEndpoint,
|
|
157
157
|
intervalMs = DEFAULT_WATCH_INTERVAL_MS,
|
|
158
158
|
log = noop,
|
|
159
159
|
} = options;
|
|
160
160
|
|
|
161
161
|
const resolvedWorkspace = path.resolve(workspace);
|
|
162
162
|
|
|
163
|
-
// Singleton guard: return existing daemon if already running
|
|
163
|
+
// Singleton guard: return existing daemon if already running, but refresh
|
|
164
|
+
// any mutable runtime state supplied by the new caller.
|
|
164
165
|
if (_activeDaemons.has(resolvedWorkspace)) {
|
|
166
|
+
const existingHandle = _activeDaemons.get(resolvedWorkspace);
|
|
167
|
+
existingHandle?.updateRuntimeState?.({
|
|
168
|
+
sessionId: initialSessionId,
|
|
169
|
+
authEntry: initialAuthEntry,
|
|
170
|
+
uploadEndpoint: initialUploadEndpoint,
|
|
171
|
+
});
|
|
165
172
|
log(`[syncDaemon] Daemon already running for ${resolvedWorkspace}, reusing.`);
|
|
166
|
-
return
|
|
173
|
+
return existingHandle;
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
log(`[syncDaemon] Starting file watcher for ${resolvedWorkspace} (interval: ${intervalMs / 1000}s)`);
|
|
@@ -174,9 +181,28 @@ export function startSyncDaemon(options) {
|
|
|
174
181
|
let pendingHistoryOnly = true;
|
|
175
182
|
let sessionId = initialSessionId;
|
|
176
183
|
let authEntry = initialAuthEntry;
|
|
184
|
+
let uploadEndpoint = initialUploadEndpoint;
|
|
177
185
|
let lastKnownHead = "";
|
|
178
186
|
|
|
179
|
-
|
|
187
|
+
let authBackendUrl = deriveAuthBackendUrl(uploadEndpoint);
|
|
188
|
+
|
|
189
|
+
function updateRuntimeState(nextState = {}) {
|
|
190
|
+
if (typeof nextState.sessionId === "string" && nextState.sessionId) {
|
|
191
|
+
sessionId = nextState.sessionId;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (nextState.authEntry && typeof nextState.authEntry === "object") {
|
|
195
|
+
authEntry = nextState.authEntry;
|
|
196
|
+
if (typeof nextState.authEntry.sessionId === "string" && nextState.authEntry.sessionId) {
|
|
197
|
+
sessionId = nextState.authEntry.sessionId;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (typeof nextState.uploadEndpoint === "string" && nextState.uploadEndpoint) {
|
|
202
|
+
uploadEndpoint = nextState.uploadEndpoint;
|
|
203
|
+
authBackendUrl = deriveAuthBackendUrl(uploadEndpoint);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
180
206
|
|
|
181
207
|
// -------------------------------------------------------------------------
|
|
182
208
|
// Session refresh
|
|
@@ -407,7 +433,7 @@ export function startSyncDaemon(options) {
|
|
|
407
433
|
};
|
|
408
434
|
|
|
409
435
|
// Register in singleton map BEFORE returning so concurrent callers see it.
|
|
410
|
-
const handle = { intervalId, cleanup };
|
|
436
|
+
const handle = { intervalId, cleanup, updateRuntimeState };
|
|
411
437
|
_activeDaemons.set(resolvedWorkspace, handle);
|
|
412
438
|
|
|
413
439
|
return handle;
|