@remnic/core 9.69.96 → 9.69.97
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/access-cli.js +9 -9
- package/dist/access-http.d.ts.map +1 -1
- package/dist/access-http.js +7 -7
- package/dist/adapters/claude-code.js +2 -2
- package/dist/adapters/codex.js +2 -2
- package/dist/adapters/hermes.js +2 -2
- package/dist/adapters/index.js +6 -6
- package/dist/adapters/registry.js +6 -6
- package/dist/adapters/replit.js +2 -2
- package/dist/adapters/types.d.ts +2 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js +1 -1
- package/dist/{chunk-F23U5YE7.js → chunk-2KB7TTAP.js} +12 -10
- package/dist/chunk-2KB7TTAP.js.map +1 -0
- package/dist/{chunk-J64TK33U.js → chunk-3JKYQBLC.js} +5 -4
- package/dist/{chunk-J64TK33U.js.map → chunk-3JKYQBLC.js.map} +1 -1
- package/dist/{chunk-RSUYKGGZ.js → chunk-7RKSJVOQ.js} +5 -4
- package/dist/{chunk-RSUYKGGZ.js.map → chunk-7RKSJVOQ.js.map} +1 -1
- package/dist/{chunk-KNI4SGQE.js → chunk-CTEUPJMV.js} +3 -3
- package/dist/{chunk-42NQ7AVG.js → chunk-HCAGBFE7.js} +5 -4
- package/dist/{chunk-42NQ7AVG.js.map → chunk-HCAGBFE7.js.map} +1 -1
- package/dist/{chunk-R5B3OWI4.js → chunk-HJ3CBZIG.js} +2 -2
- package/dist/{chunk-7RXCMVFQ.js → chunk-NJAIR57U.js} +1 -1
- package/dist/{chunk-7RXCMVFQ.js.map → chunk-NJAIR57U.js.map} +1 -1
- package/dist/{chunk-HQCUUVG2.js → chunk-RMV2JYOT.js} +9 -2
- package/dist/chunk-RMV2JYOT.js.map +1 -0
- package/dist/{chunk-TMSXWOBZ.js → chunk-SI36RWC3.js} +5 -4
- package/dist/{chunk-TMSXWOBZ.js.map → chunk-SI36RWC3.js.map} +1 -1
- package/dist/cli.js +8 -8
- package/dist/index.js +9 -9
- package/dist/orchestrator.js +9 -9
- package/package.json +2 -2
- package/src/access-http-adapter-namespace.test.ts +217 -0
- package/src/access-http.ts +8 -0
- package/src/adapters/claude-code.ts +4 -4
- package/src/adapters/codex.ts +3 -3
- package/src/adapters/grok-opencode.test.ts +2 -0
- package/src/adapters/grok.ts +3 -3
- package/src/adapters/hermes.ts +3 -3
- package/src/adapters/opencode.ts +3 -3
- package/src/adapters/replit.ts +3 -3
- package/src/adapters/types.ts +2 -0
- package/dist/chunk-F23U5YE7.js.map +0 -1
- package/dist/chunk-HQCUUVG2.js.map +0 -1
- /package/dist/{chunk-KNI4SGQE.js.map → chunk-CTEUPJMV.js.map} +0 -0
- /package/dist/{chunk-R5B3OWI4.js.map → chunk-HJ3CBZIG.js.map} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
headerValue
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NJAIR57U.js";
|
|
4
4
|
|
|
5
5
|
// src/adapters/replit.ts
|
|
6
6
|
var ReplitAdapter = class {
|
|
@@ -14,9 +14,10 @@ var ReplitAdapter = class {
|
|
|
14
14
|
}
|
|
15
15
|
resolveIdentity(context) {
|
|
16
16
|
const mcpSessionId = headerValue(context.headers, "mcp-session-id");
|
|
17
|
-
const
|
|
17
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
18
18
|
return {
|
|
19
|
-
namespace,
|
|
19
|
+
namespace: namespaceHeader ?? "replit",
|
|
20
|
+
namespaceExplicit: namespaceHeader !== void 0,
|
|
20
21
|
principal: "replit-agent",
|
|
21
22
|
sessionKey: mcpSessionId ?? context.sessionKey,
|
|
22
23
|
adapterId: this.id
|
|
@@ -27,4 +28,4 @@ var ReplitAdapter = class {
|
|
|
27
28
|
export {
|
|
28
29
|
ReplitAdapter
|
|
29
30
|
};
|
|
30
|
-
//# sourceMappingURL=chunk-
|
|
31
|
+
//# sourceMappingURL=chunk-SI36RWC3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/replit.ts"],"sourcesContent":["import { headerValue, type AdapterContext, type EngramAdapter, type ResolvedIdentity } from \"./types.js\";\n\n/**\n * Replit Agent adapter.\n *\n * Detection: Replit Agent supports MCP natively (HTTP-only, configured\n * via the Integrations pane). It does NOT send identifying headers\n * automatically — detection relies on user-configured custom headers\n * in Replit's MCP Integrations UI.\n *\n * Replit provides env vars to running code (REPL_ID, REPL_OWNER,\n * REPL_SLUG) but these are NOT sent as HTTP headers to MCP servers.\n *\n * To identify Replit connections, users should configure a custom header\n * in the Replit Integrations pane when adding the Engram MCP server:\n * Header Name: X-Engram-Client-Id\n * Header Value: replit\n *\n * Optionally also set X-Engram-Namespace for project scoping. Principal\n * overrides are intentionally handled only by the HTTP server's\n * trustPrincipalHeader gate. Adapters must not independently trust\n * X-Engram-Principal.\n */\nexport class ReplitAdapter implements EngramAdapter {\n readonly id = \"replit\";\n\n matches(context: AdapterContext): boolean {\n // Primary: user-configured client identifier header\n const clientId = headerValue(context.headers, \"x-engram-client-id\");\n if (clientId?.toLowerCase() === \"replit\") return true;\n\n // MCP clientInfo (Replit's MCP client name is not publicly documented,\n // but check for it in case it becomes available)\n const clientName = context.clientInfo?.name?.toLowerCase() ?? \"\";\n if (clientName.includes(\"replit\")) return true;\n\n return false;\n }\n\n resolveIdentity(context: AdapterContext): ResolvedIdentity {\n const mcpSessionId = headerValue(context.headers, \"mcp-session-id\");\n\n const
|
|
1
|
+
{"version":3,"sources":["../src/adapters/replit.ts"],"sourcesContent":["import { headerValue, type AdapterContext, type EngramAdapter, type ResolvedIdentity } from \"./types.js\";\n\n/**\n * Replit Agent adapter.\n *\n * Detection: Replit Agent supports MCP natively (HTTP-only, configured\n * via the Integrations pane). It does NOT send identifying headers\n * automatically — detection relies on user-configured custom headers\n * in Replit's MCP Integrations UI.\n *\n * Replit provides env vars to running code (REPL_ID, REPL_OWNER,\n * REPL_SLUG) but these are NOT sent as HTTP headers to MCP servers.\n *\n * To identify Replit connections, users should configure a custom header\n * in the Replit Integrations pane when adding the Engram MCP server:\n * Header Name: X-Engram-Client-Id\n * Header Value: replit\n *\n * Optionally also set X-Engram-Namespace for project scoping. Principal\n * overrides are intentionally handled only by the HTTP server's\n * trustPrincipalHeader gate. Adapters must not independently trust\n * X-Engram-Principal.\n */\nexport class ReplitAdapter implements EngramAdapter {\n readonly id = \"replit\";\n\n matches(context: AdapterContext): boolean {\n // Primary: user-configured client identifier header\n const clientId = headerValue(context.headers, \"x-engram-client-id\");\n if (clientId?.toLowerCase() === \"replit\") return true;\n\n // MCP clientInfo (Replit's MCP client name is not publicly documented,\n // but check for it in case it becomes available)\n const clientName = context.clientInfo?.name?.toLowerCase() ?? \"\";\n if (clientName.includes(\"replit\")) return true;\n\n return false;\n }\n\n resolveIdentity(context: AdapterContext): ResolvedIdentity {\n const mcpSessionId = headerValue(context.headers, \"mcp-session-id\");\n\n const namespaceHeader = headerValue(context.headers, \"x-engram-namespace\");\n\n return {\n namespace: namespaceHeader ?? \"replit\",\n namespaceExplicit: namespaceHeader !== undefined,\n principal: \"replit-agent\",\n sessionKey: mcpSessionId ?? context.sessionKey,\n adapterId: this.id,\n };\n }\n}\n"],"mappings":";;;;;AAuBO,IAAM,gBAAN,MAA6C;AAAA,EACzC,KAAK;AAAA,EAEd,QAAQ,SAAkC;AAExC,UAAM,WAAW,YAAY,QAAQ,SAAS,oBAAoB;AAClE,QAAI,UAAU,YAAY,MAAM,SAAU,QAAO;AAIjD,UAAM,aAAa,QAAQ,YAAY,MAAM,YAAY,KAAK;AAC9D,QAAI,WAAW,SAAS,QAAQ,EAAG,QAAO;AAE1C,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,SAA2C;AACzD,UAAM,eAAe,YAAY,QAAQ,SAAS,gBAAgB;AAElE,UAAM,kBAAkB,YAAY,QAAQ,SAAS,oBAAoB;AAEzE,WAAO;AAAA,MACL,WAAW,mBAAmB;AAAA,MAC9B,mBAAmB,oBAAoB;AAAA,MACvC,WAAW;AAAA,MACX,YAAY,gBAAgB,QAAQ;AAAA,MACpC,WAAW,KAAK;AAAA,IAClB;AAAA,EACF;AACF;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
runWorkProductStatusCliCommand,
|
|
92
92
|
runWorkProjectCliCommand,
|
|
93
93
|
runWorkTaskCliCommand
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-HJ3CBZIG.js";
|
|
95
95
|
import "./chunk-H4CKS5B2.js";
|
|
96
96
|
import "./chunk-MMACM5G2.js";
|
|
97
97
|
import "./chunk-54BGIBCR.js";
|
|
@@ -189,16 +189,16 @@ import "./chunk-UHESJ6TQ.js";
|
|
|
189
189
|
import "./chunk-27NO2KVB.js";
|
|
190
190
|
import "./chunk-KFZY2OFH.js";
|
|
191
191
|
import "./chunk-B32WYRXX.js";
|
|
192
|
-
import "./chunk-
|
|
192
|
+
import "./chunk-RMV2JYOT.js";
|
|
193
193
|
import "./chunk-V7IZKJI3.js";
|
|
194
194
|
import "./chunk-XW2T4QAW.js";
|
|
195
|
-
import "./chunk-
|
|
196
|
-
import "./chunk-
|
|
197
|
-
import "./chunk-
|
|
195
|
+
import "./chunk-2KB7TTAP.js";
|
|
196
|
+
import "./chunk-HCAGBFE7.js";
|
|
197
|
+
import "./chunk-SI36RWC3.js";
|
|
198
198
|
import "./chunk-TB6CBZ5V.js";
|
|
199
|
-
import "./chunk-
|
|
200
|
-
import "./chunk-
|
|
201
|
-
import "./chunk-
|
|
199
|
+
import "./chunk-3JKYQBLC.js";
|
|
200
|
+
import "./chunk-7RKSJVOQ.js";
|
|
201
|
+
import "./chunk-NJAIR57U.js";
|
|
202
202
|
import "./chunk-ZSEFGDCI.js";
|
|
203
203
|
import "./chunk-P6P35FY4.js";
|
|
204
204
|
import "./chunk-3HSRWJTC.js";
|
package/dist/index.js
CHANGED
|
@@ -181,7 +181,7 @@ import {
|
|
|
181
181
|
writeManifest,
|
|
182
182
|
writeMeetingEpisodeMemory,
|
|
183
183
|
writeTimelineState
|
|
184
|
-
} from "./chunk-
|
|
184
|
+
} from "./chunk-CTEUPJMV.js";
|
|
185
185
|
import {
|
|
186
186
|
WEARABLE_SOURCE_PREFIX,
|
|
187
187
|
buildExtractionTurns,
|
|
@@ -558,7 +558,7 @@ import {
|
|
|
558
558
|
setVaultPublisherTestHooks,
|
|
559
559
|
summarizeVaultPublish,
|
|
560
560
|
validateVaultProperties
|
|
561
|
-
} from "./chunk-
|
|
561
|
+
} from "./chunk-HJ3CBZIG.js";
|
|
562
562
|
import "./chunk-H4CKS5B2.js";
|
|
563
563
|
import "./chunk-MMACM5G2.js";
|
|
564
564
|
import "./chunk-54BGIBCR.js";
|
|
@@ -895,19 +895,19 @@ import "./chunk-QW27DOHG.js";
|
|
|
895
895
|
import {
|
|
896
896
|
EngramAccessHttpServer,
|
|
897
897
|
MeetingsInputError
|
|
898
|
-
} from "./chunk-
|
|
898
|
+
} from "./chunk-RMV2JYOT.js";
|
|
899
899
|
import {
|
|
900
900
|
WearablesInputError,
|
|
901
901
|
describeErrorForOperator
|
|
902
902
|
} from "./chunk-V7IZKJI3.js";
|
|
903
903
|
import "./chunk-XW2T4QAW.js";
|
|
904
|
-
import "./chunk-
|
|
905
|
-
import "./chunk-
|
|
906
|
-
import "./chunk-
|
|
904
|
+
import "./chunk-2KB7TTAP.js";
|
|
905
|
+
import "./chunk-HCAGBFE7.js";
|
|
906
|
+
import "./chunk-SI36RWC3.js";
|
|
907
907
|
import "./chunk-TB6CBZ5V.js";
|
|
908
|
-
import "./chunk-
|
|
909
|
-
import "./chunk-
|
|
910
|
-
import "./chunk-
|
|
908
|
+
import "./chunk-3JKYQBLC.js";
|
|
909
|
+
import "./chunk-7RKSJVOQ.js";
|
|
910
|
+
import "./chunk-NJAIR57U.js";
|
|
911
911
|
import "./chunk-ZSEFGDCI.js";
|
|
912
912
|
import "./chunk-P6P35FY4.js";
|
|
913
913
|
import "./chunk-3HSRWJTC.js";
|
package/dist/orchestrator.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
graphPathRelativeToStorage,
|
|
5
5
|
mergeGraphExpandedResults,
|
|
6
6
|
qmdStartupCollectionChecksWithTimeout
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-CTEUPJMV.js";
|
|
8
8
|
import "./chunk-JDOBIFBK.js";
|
|
9
9
|
import "./chunk-KYOZ5E6V.js";
|
|
10
10
|
import {
|
|
@@ -116,7 +116,7 @@ import "./chunk-PYIR4SSB.js";
|
|
|
116
116
|
import "./chunk-4G2YCKLT.js";
|
|
117
117
|
import "./chunk-GDVORIFM.js";
|
|
118
118
|
import "./chunk-3I4FUAVJ.js";
|
|
119
|
-
import "./chunk-
|
|
119
|
+
import "./chunk-HJ3CBZIG.js";
|
|
120
120
|
import "./chunk-H4CKS5B2.js";
|
|
121
121
|
import "./chunk-MMACM5G2.js";
|
|
122
122
|
import "./chunk-54BGIBCR.js";
|
|
@@ -231,16 +231,16 @@ import "./chunk-27NO2KVB.js";
|
|
|
231
231
|
import "./chunk-KFZY2OFH.js";
|
|
232
232
|
import "./chunk-B32WYRXX.js";
|
|
233
233
|
import "./chunk-QW27DOHG.js";
|
|
234
|
-
import "./chunk-
|
|
234
|
+
import "./chunk-RMV2JYOT.js";
|
|
235
235
|
import "./chunk-V7IZKJI3.js";
|
|
236
236
|
import "./chunk-XW2T4QAW.js";
|
|
237
|
-
import "./chunk-
|
|
238
|
-
import "./chunk-
|
|
239
|
-
import "./chunk-
|
|
237
|
+
import "./chunk-2KB7TTAP.js";
|
|
238
|
+
import "./chunk-HCAGBFE7.js";
|
|
239
|
+
import "./chunk-SI36RWC3.js";
|
|
240
240
|
import "./chunk-TB6CBZ5V.js";
|
|
241
|
-
import "./chunk-
|
|
242
|
-
import "./chunk-
|
|
243
|
-
import "./chunk-
|
|
241
|
+
import "./chunk-3JKYQBLC.js";
|
|
242
|
+
import "./chunk-7RKSJVOQ.js";
|
|
243
|
+
import "./chunk-NJAIR57U.js";
|
|
244
244
|
import "./chunk-ZSEFGDCI.js";
|
|
245
245
|
import "./chunk-P6P35FY4.js";
|
|
246
246
|
import "./chunk-3HSRWJTC.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.69.
|
|
3
|
+
"version": "9.69.97",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3327,7 +3327,7 @@
|
|
|
3327
3327
|
"core"
|
|
3328
3328
|
],
|
|
3329
3329
|
"peerDependencies": {
|
|
3330
|
-
"@remnic/coding-graph": "^9.69.
|
|
3330
|
+
"@remnic/coding-graph": "^9.69.97"
|
|
3331
3331
|
},
|
|
3332
3332
|
"peerDependenciesMeta": {
|
|
3333
3333
|
"@remnic/coding-graph": {
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { EngramAccessHttpServer } from "./access-http.js";
|
|
8
|
+
import { EngramAccessService, type EngramAccessRecallRequest, type EngramAccessRecallResponse } from "./access-service.js";
|
|
9
|
+
import { parseConfig } from "./config.js";
|
|
10
|
+
import { Orchestrator } from "./orchestrator.js";
|
|
11
|
+
import type { PluginConfig } from "./types.js";
|
|
12
|
+
|
|
13
|
+
const FLAT_ADAPTER_IDS = ["claude-code", "codex", "replit", "grok", "opencode", "hermes"];
|
|
14
|
+
|
|
15
|
+
const NAMESPACED_ADAPTERS = [
|
|
16
|
+
{ clientId: "claude-code", principal: "claude-code" },
|
|
17
|
+
{ clientId: "codex", principal: "codex" },
|
|
18
|
+
{ clientId: "replit", principal: "replit-agent" },
|
|
19
|
+
{ clientId: "grok", principal: "grok" },
|
|
20
|
+
{ clientId: "opencode", principal: "opencode" },
|
|
21
|
+
{ clientId: "hermes", principal: "hermes-agent" },
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
interface TestDaemon {
|
|
25
|
+
service: EngramAccessService;
|
|
26
|
+
port: number;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function makeConfig(memoryDir: string, overrides: Partial<PluginConfig> = {}) {
|
|
31
|
+
return parseConfig({
|
|
32
|
+
openaiApiKey: "sk-test",
|
|
33
|
+
memoryDir,
|
|
34
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
35
|
+
qmdEnabled: false,
|
|
36
|
+
embeddingFallbackEnabled: false,
|
|
37
|
+
recallPlannerEnabled: false,
|
|
38
|
+
sharedContextEnabled: false,
|
|
39
|
+
initGateTimeoutMs: 1000,
|
|
40
|
+
...overrides,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function startServer(overrides: Partial<PluginConfig> = {}): Promise<TestDaemon> {
|
|
45
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-3166-"));
|
|
46
|
+
const orchestrator = new Orchestrator(makeConfig(memoryDir, overrides));
|
|
47
|
+
const service = new EngramAccessService(orchestrator);
|
|
48
|
+
const server = new EngramAccessHttpServer({
|
|
49
|
+
service,
|
|
50
|
+
host: "127.0.0.1",
|
|
51
|
+
port: 0,
|
|
52
|
+
authToken: "secret-token",
|
|
53
|
+
adminConsoleEnabled: false,
|
|
54
|
+
});
|
|
55
|
+
const { port } = await server.start();
|
|
56
|
+
return {
|
|
57
|
+
service,
|
|
58
|
+
port,
|
|
59
|
+
async close() {
|
|
60
|
+
await server.stop();
|
|
61
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface RecallOutcome {
|
|
67
|
+
status: number;
|
|
68
|
+
isError: boolean;
|
|
69
|
+
text: string;
|
|
70
|
+
payload?: EngramAccessRecallResponse;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface McpToolReply {
|
|
74
|
+
result?: { content?: Array<{ text?: string }>; isError?: boolean };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function mcpRecall(
|
|
78
|
+
port: number,
|
|
79
|
+
args: EngramAccessRecallRequest,
|
|
80
|
+
headers: Record<string, string> = {},
|
|
81
|
+
): Promise<RecallOutcome> {
|
|
82
|
+
const res = await fetch(`http://127.0.0.1:${port}/mcp`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
authorization: "Bearer secret-token",
|
|
86
|
+
"content-type": "application/json",
|
|
87
|
+
...headers,
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
jsonrpc: "2.0",
|
|
91
|
+
id: 1,
|
|
92
|
+
method: "tools/call",
|
|
93
|
+
params: { name: "engram.recall", arguments: args },
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
const body: McpToolReply = JSON.parse(await res.text());
|
|
97
|
+
const text = body.result?.content?.[0]?.text ?? "";
|
|
98
|
+
const outcome: RecallOutcome = {
|
|
99
|
+
status: res.status,
|
|
100
|
+
isError: body.result?.isError === true,
|
|
101
|
+
text,
|
|
102
|
+
};
|
|
103
|
+
if (!outcome.isError) outcome.payload = JSON.parse(text);
|
|
104
|
+
return outcome;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
test("flat daemon: all six built-in adapters resolve the configured default namespace over MCP recall", async () => {
|
|
108
|
+
const daemon = await startServer({
|
|
109
|
+
namespacesEnabled: false,
|
|
110
|
+
defaultNamespace: "flat-tenant",
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
await daemon.service.memoryStore({ content: "the launch codeword is PERIHELION" });
|
|
114
|
+
for (const clientId of FLAT_ADAPTER_IDS) {
|
|
115
|
+
const outcome = await mcpRecall(
|
|
116
|
+
daemon.port,
|
|
117
|
+
{ query: "launch codeword" },
|
|
118
|
+
{ "x-engram-client-id": clientId },
|
|
119
|
+
);
|
|
120
|
+
assert.equal(outcome.status, 200, `${clientId}: recall must reach the op`);
|
|
121
|
+
assert.equal(outcome.isError, false, `${clientId}: ${outcome.text}`);
|
|
122
|
+
assert.equal(outcome.payload?.namespace, "flat-tenant", `${clientId}: default namespace`);
|
|
123
|
+
assert.ok(
|
|
124
|
+
outcome.payload?.context.includes("PERIHELION"),
|
|
125
|
+
`${clientId}: default-namespace memory must be recalled`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
} finally {
|
|
129
|
+
await daemon.close();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("flat daemon: explicit X-Engram-Namespace is enforced, not silently dropped", async () => {
|
|
134
|
+
const daemon = await startServer({
|
|
135
|
+
namespacesEnabled: false,
|
|
136
|
+
defaultNamespace: "flat-tenant",
|
|
137
|
+
});
|
|
138
|
+
try {
|
|
139
|
+
await daemon.service.memoryStore({ content: "the launch codeword is PERIHELION" });
|
|
140
|
+
|
|
141
|
+
const validDefault = await mcpRecall(daemon.port, { query: "launch codeword" }, {
|
|
142
|
+
"x-engram-client-id": "claude-code",
|
|
143
|
+
"x-engram-namespace": "flat-tenant",
|
|
144
|
+
});
|
|
145
|
+
assert.equal(validDefault.isError, false, validDefault.text);
|
|
146
|
+
assert.equal(validDefault.payload?.namespace, "flat-tenant");
|
|
147
|
+
assert.ok(validDefault.payload?.context.includes("PERIHELION"));
|
|
148
|
+
|
|
149
|
+
for (const outcome of [
|
|
150
|
+
await mcpRecall(daemon.port, { query: "launch codeword" }, {
|
|
151
|
+
"x-engram-client-id": "claude-code",
|
|
152
|
+
"x-engram-namespace": "off-books",
|
|
153
|
+
}),
|
|
154
|
+
await mcpRecall(daemon.port, { query: "launch codeword", namespace: "off-books" }, {
|
|
155
|
+
"x-engram-client-id": "claude-code",
|
|
156
|
+
}),
|
|
157
|
+
]) {
|
|
158
|
+
assert.equal(outcome.isError, true, "non-default namespace must be rejected");
|
|
159
|
+
assert.match(outcome.text, /unsupported namespace: off-books/);
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
await daemon.close();
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("namespaced daemon: adapters keep their self-namespaces over MCP recall", async () => {
|
|
167
|
+
const daemon = await startServer({
|
|
168
|
+
namespacesEnabled: true,
|
|
169
|
+
defaultNamespace: "main",
|
|
170
|
+
namespacePolicies: NAMESPACED_ADAPTERS.map(({ clientId, principal }) => ({
|
|
171
|
+
name: clientId,
|
|
172
|
+
readPrincipals: [principal],
|
|
173
|
+
writePrincipals: [principal],
|
|
174
|
+
})),
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
await daemon.service.memoryStore({
|
|
178
|
+
content: "main vault code is QUASAR-main",
|
|
179
|
+
namespace: "main",
|
|
180
|
+
authenticatedPrincipal: "operator",
|
|
181
|
+
});
|
|
182
|
+
for (const { clientId, principal } of NAMESPACED_ADAPTERS) {
|
|
183
|
+
await daemon.service.memoryStore({
|
|
184
|
+
content: `the ${clientId} vault code is NOVA-${clientId}`,
|
|
185
|
+
namespace: clientId,
|
|
186
|
+
authenticatedPrincipal: principal,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const { clientId } of NAMESPACED_ADAPTERS) {
|
|
191
|
+
const outcome = await mcpRecall(daemon.port, { query: "vault code" }, {
|
|
192
|
+
"x-engram-client-id": clientId,
|
|
193
|
+
});
|
|
194
|
+
assert.equal(outcome.status, 200, `${clientId}: recall must reach the op`);
|
|
195
|
+
assert.equal(outcome.isError, false, `${clientId}: ${outcome.text}`);
|
|
196
|
+
assert.equal(outcome.payload?.namespace, clientId, `${clientId}: self-namespace kept`);
|
|
197
|
+
assert.ok(
|
|
198
|
+
outcome.payload?.context.includes(`NOVA-${clientId}`),
|
|
199
|
+
`${clientId}: own namespace memory must be recalled`,
|
|
200
|
+
);
|
|
201
|
+
assert.ok(
|
|
202
|
+
!outcome.payload?.context.includes("QUASAR-main"),
|
|
203
|
+
`${clientId}: default namespace must stay isolated`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const crossTenant = await mcpRecall(daemon.port, { query: "vault code" }, {
|
|
208
|
+
"x-engram-client-id": "claude-code",
|
|
209
|
+
});
|
|
210
|
+
assert.ok(
|
|
211
|
+
!crossTenant.payload?.context.includes("NOVA-codex"),
|
|
212
|
+
"one adapter's namespace must not leak into another's recall",
|
|
213
|
+
);
|
|
214
|
+
} finally {
|
|
215
|
+
await daemon.close();
|
|
216
|
+
}
|
|
217
|
+
});
|
package/src/access-http.ts
CHANGED
|
@@ -7,6 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { fileURLToPath, URL } from "node:url";
|
|
8
8
|
import { gunzipSync } from "node:zlib";
|
|
9
9
|
import { log } from "./logger.js";
|
|
10
|
+
import { resolveNamespaceCapabilities } from "./capabilities.js";
|
|
10
11
|
import { WriteRateLimiter, type WriteRateLimitReservation } from "./write-rate-limiter.js";
|
|
11
12
|
import { abortError, isAbortError } from "./abort-error.js";
|
|
12
13
|
import { EngramAccessForbiddenError } from "./access-errors.js";
|
|
@@ -716,6 +717,13 @@ export class EngramAccessHttpServer extends ReviewDeckAccessHttpBase {
|
|
|
716
717
|
principal = adapterIdentity.principal;
|
|
717
718
|
}
|
|
718
719
|
namespace = adapterIdentity.namespace;
|
|
720
|
+
const config = this.service.configRef;
|
|
721
|
+
if (
|
|
722
|
+
adapterIdentity.namespaceExplicit === false &&
|
|
723
|
+
!(config && resolveNamespaceCapabilities(config).namespaces)
|
|
724
|
+
) {
|
|
725
|
+
namespace = undefined;
|
|
726
|
+
}
|
|
719
727
|
sessionKey = adapterIdentity.sessionKey;
|
|
720
728
|
}
|
|
721
729
|
|
|
@@ -41,12 +41,12 @@ export class ClaudeCodeAdapter implements EngramAdapter {
|
|
|
41
41
|
// MCP session ID (standard MCP header, server-assigned)
|
|
42
42
|
const mcpSessionId = headerValue(context.headers, "mcp-session-id");
|
|
43
43
|
|
|
44
|
-
// Namespace: explicit header > default
|
|
45
|
-
const
|
|
46
|
-
|| "claude-code";
|
|
44
|
+
// Namespace: explicit header > adapter default
|
|
45
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
47
46
|
|
|
48
47
|
return {
|
|
49
|
-
namespace,
|
|
48
|
+
namespace: namespaceHeader ?? "claude-code",
|
|
49
|
+
namespaceExplicit: namespaceHeader !== undefined,
|
|
50
50
|
principal: "claude-code",
|
|
51
51
|
sessionKey: mcpSessionId ?? context.sessionKey,
|
|
52
52
|
adapterId: this.id,
|
package/src/adapters/codex.ts
CHANGED
|
@@ -43,11 +43,11 @@ export class CodexAdapter implements EngramAdapter {
|
|
|
43
43
|
const mcpSessionId = headerValue(context.headers, "mcp-session-id");
|
|
44
44
|
|
|
45
45
|
// Namespace: explicit header > default
|
|
46
|
-
const
|
|
47
|
-
|| "codex";
|
|
46
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
48
47
|
|
|
49
48
|
return {
|
|
50
|
-
namespace,
|
|
49
|
+
namespace: namespaceHeader ?? "codex",
|
|
50
|
+
namespaceExplicit: namespaceHeader !== undefined,
|
|
51
51
|
principal: "codex",
|
|
52
52
|
sessionKey: mcpSessionId ?? context.sessionKey,
|
|
53
53
|
adapterId: this.id,
|
|
@@ -49,6 +49,7 @@ test("GrokAdapter resolves identity with adapter-owned principal and header name
|
|
|
49
49
|
})),
|
|
50
50
|
{
|
|
51
51
|
namespace: "my-project",
|
|
52
|
+
namespaceExplicit: true,
|
|
52
53
|
principal: "grok",
|
|
53
54
|
sessionKey: "sess-grok-1",
|
|
54
55
|
adapterId: "grok",
|
|
@@ -91,6 +92,7 @@ test("OpenCodeAdapter resolves identity with adapter-owned principal", () => {
|
|
|
91
92
|
}));
|
|
92
93
|
assert.deepEqual(resolved, {
|
|
93
94
|
namespace: "opencode",
|
|
95
|
+
namespaceExplicit: false,
|
|
94
96
|
principal: "opencode",
|
|
95
97
|
sessionKey: "sess-oc-1",
|
|
96
98
|
adapterId: "opencode",
|
package/src/adapters/grok.ts
CHANGED
|
@@ -40,11 +40,11 @@ export class GrokAdapter implements EngramAdapter {
|
|
|
40
40
|
const mcpSessionId = headerValue(context.headers, "mcp-session-id");
|
|
41
41
|
|
|
42
42
|
// Namespace: explicit header > default
|
|
43
|
-
const
|
|
44
|
-
|| "grok";
|
|
43
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
45
44
|
|
|
46
45
|
return {
|
|
47
|
-
namespace,
|
|
46
|
+
namespace: namespaceHeader ?? "grok",
|
|
47
|
+
namespaceExplicit: namespaceHeader !== undefined,
|
|
48
48
|
principal: "grok",
|
|
49
49
|
sessionKey: mcpSessionId ?? context.sessionKey,
|
|
50
50
|
adapterId: this.id,
|
package/src/adapters/hermes.ts
CHANGED
|
@@ -47,11 +47,11 @@ export class HermesAdapter implements EngramAdapter {
|
|
|
47
47
|
resolveIdentity(context: AdapterContext): ResolvedIdentity {
|
|
48
48
|
const sessionId = headerValue(context.headers, "x-hermes-session-id");
|
|
49
49
|
|
|
50
|
-
const
|
|
51
|
-
|| "hermes";
|
|
50
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
52
51
|
|
|
53
52
|
return {
|
|
54
|
-
namespace,
|
|
53
|
+
namespace: namespaceHeader ?? "hermes",
|
|
54
|
+
namespaceExplicit: namespaceHeader !== undefined,
|
|
55
55
|
principal: "hermes-agent",
|
|
56
56
|
sessionKey: sessionId ?? context.sessionKey,
|
|
57
57
|
adapterId: this.id,
|
package/src/adapters/opencode.ts
CHANGED
|
@@ -39,11 +39,11 @@ export class OpenCodeAdapter implements EngramAdapter {
|
|
|
39
39
|
const mcpSessionId = headerValue(context.headers, "mcp-session-id");
|
|
40
40
|
|
|
41
41
|
// Namespace: explicit header > default
|
|
42
|
-
const
|
|
43
|
-
|| "opencode";
|
|
42
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
44
43
|
|
|
45
44
|
return {
|
|
46
|
-
namespace,
|
|
45
|
+
namespace: namespaceHeader ?? "opencode",
|
|
46
|
+
namespaceExplicit: namespaceHeader !== undefined,
|
|
47
47
|
principal: "opencode",
|
|
48
48
|
sessionKey: mcpSessionId ?? context.sessionKey,
|
|
49
49
|
adapterId: this.id,
|
package/src/adapters/replit.ts
CHANGED
|
@@ -40,11 +40,11 @@ export class ReplitAdapter implements EngramAdapter {
|
|
|
40
40
|
resolveIdentity(context: AdapterContext): ResolvedIdentity {
|
|
41
41
|
const mcpSessionId = headerValue(context.headers, "mcp-session-id");
|
|
42
42
|
|
|
43
|
-
const
|
|
44
|
-
|| "replit";
|
|
43
|
+
const namespaceHeader = headerValue(context.headers, "x-engram-namespace");
|
|
45
44
|
|
|
46
45
|
return {
|
|
47
|
-
namespace,
|
|
46
|
+
namespace: namespaceHeader ?? "replit",
|
|
47
|
+
namespaceExplicit: namespaceHeader !== undefined,
|
|
48
48
|
principal: "replit-agent",
|
|
49
49
|
sessionKey: mcpSessionId ?? context.sessionKey,
|
|
50
50
|
adapterId: this.id,
|
package/src/adapters/types.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface AdapterContext {
|
|
|
22
22
|
export interface ResolvedIdentity {
|
|
23
23
|
/** Engram namespace (scopes memory access) */
|
|
24
24
|
namespace: string;
|
|
25
|
+
/** False for an inferred namespace; omitted values retain explicit-namespace validation. */
|
|
26
|
+
namespaceExplicit?: boolean;
|
|
25
27
|
/** Engram principal (authorization subject) */
|
|
26
28
|
principal: string;
|
|
27
29
|
/** Session key for continuity tracking */
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/adapters/grok.ts","../src/adapters/opencode.ts","../src/adapters/registry.ts"],"sourcesContent":["import { headerValue, type AdapterContext, type EngramAdapter, type ResolvedIdentity } from \"./types.js\";\n\n/**\n * Grok adapter.\n *\n * Detection: Grok clients identify themselves with a clientInfo name\n * containing \"grok\" in the MCP initialize handshake, and send\n * User-Agent: grok/<version> on HTTP requests. The exact clientInfo\n * name is not yet stable across Grok surfaces (desktop app, CLI), so\n * detection uses a contains-match for forward compatibility.\n *\n * User-configured identification is also supported via headers:\n * \"headers\": { \"X-Engram-Client-Id\": \"grok\" }\n *\n * Principal overrides are intentionally handled only by the HTTP server's\n * trustPrincipalHeader gate. Adapters must not independently trust\n * X-Engram-Principal.\n */\nexport class GrokAdapter implements EngramAdapter {\n readonly id = \"grok\";\n\n matches(context: AdapterContext): boolean {\n // Primary: MCP clientInfo from initialize handshake\n const clientName = context.clientInfo?.name?.toLowerCase() ?? \"\";\n if (clientName.includes(\"grok\")) return true;\n\n // Fallback: User-Agent header (\"grok/<version>\")\n const ua = headerValue(context.headers, \"user-agent\");\n if (ua && ua.toLowerCase().startsWith(\"grok/\")) return true;\n\n // Fallback: user-configured client identifier header\n const clientId = headerValue(context.headers, \"x-engram-client-id\");\n if (clientId?.toLowerCase() === \"grok\") return true;\n\n return false;\n }\n\n resolveIdentity(context: AdapterContext): ResolvedIdentity {\n // MCP session ID (standard MCP header, server-assigned)\n const mcpSessionId = headerValue(context.headers, \"mcp-session-id\");\n\n // Namespace: explicit header > default\n const namespace = headerValue(context.headers, \"x-engram-namespace\")\n || \"grok\";\n\n return {\n namespace,\n principal: \"grok\",\n sessionKey: mcpSessionId ?? context.sessionKey,\n adapterId: this.id,\n };\n }\n}\n","import { headerValue, type AdapterContext, type EngramAdapter, type ResolvedIdentity } from \"./types.js\";\n\n/**\n * OpenCode adapter.\n *\n * Detection: OpenCode identifies itself as \"opencode\" in the MCP\n * clientInfo of its initialize handshake, and sends\n * User-Agent: opencode/<version> on HTTP requests.\n *\n * User-configured identification is also supported via headers in the\n * OpenCode MCP server config:\n * \"headers\": { \"X-Engram-Client-Id\": \"opencode\" }\n *\n * Principal overrides are intentionally handled only by the HTTP server's\n * trustPrincipalHeader gate. Adapters must not independently trust\n * X-Engram-Principal.\n */\nexport class OpenCodeAdapter implements EngramAdapter {\n readonly id = \"opencode\";\n\n matches(context: AdapterContext): boolean {\n // Primary: MCP clientInfo from initialize handshake\n const clientName = context.clientInfo?.name?.toLowerCase() ?? \"\";\n if (clientName.includes(\"opencode\")) return true;\n\n // Fallback: User-Agent header (\"opencode/<version>\")\n const ua = headerValue(context.headers, \"user-agent\");\n if (ua && ua.toLowerCase().startsWith(\"opencode/\")) return true;\n\n // Fallback: user-configured client identifier header\n const clientId = headerValue(context.headers, \"x-engram-client-id\");\n if (clientId?.toLowerCase() === \"opencode\") return true;\n\n return false;\n }\n\n resolveIdentity(context: AdapterContext): ResolvedIdentity {\n // MCP session ID (standard MCP header, server-assigned)\n const mcpSessionId = headerValue(context.headers, \"mcp-session-id\");\n\n // Namespace: explicit header > default\n const namespace = headerValue(context.headers, \"x-engram-namespace\")\n || \"opencode\";\n\n return {\n namespace,\n principal: \"opencode\",\n sessionKey: mcpSessionId ?? context.sessionKey,\n adapterId: this.id,\n };\n }\n}\n","import type { AdapterContext, EngramAdapter, ResolvedIdentity } from \"./types.js\";\nimport { ClaudeCodeAdapter } from \"./claude-code.js\";\nimport { CodexAdapter } from \"./codex.js\";\nimport { GrokAdapter } from \"./grok.js\";\nimport { OpenCodeAdapter } from \"./opencode.js\";\nimport { ReplitAdapter } from \"./replit.js\";\nimport { HermesAdapter } from \"./hermes.js\";\n\n/**\n * Adapter registry. Attempts to identify which external system is\n * connecting by checking each registered adapter in order. Falls back\n * to explicit namespace/principal from the request args.\n */\nexport class AdapterRegistry {\n private readonly adapters: EngramAdapter[];\n\n constructor(adapters?: EngramAdapter[]) {\n this.adapters = adapters ?? [\n new HermesAdapter(),\n new ReplitAdapter(),\n new CodexAdapter(),\n new ClaudeCodeAdapter(),\n new GrokAdapter(),\n new OpenCodeAdapter(),\n ];\n }\n\n /**\n * Try each adapter in order. Return the first match, or null if\n * no adapter recognizes the request context.\n */\n resolve(context: AdapterContext): ResolvedIdentity | null {\n for (const adapter of this.adapters) {\n if (adapter.matches(context)) {\n return adapter.resolveIdentity(context);\n }\n }\n const namespace = context.namespace?.trim();\n const principal = context.principal?.trim();\n if (namespace && principal) {\n return {\n namespace,\n principal,\n sessionKey: context.sessionKey,\n adapterId: \"explicit\",\n };\n }\n return null;\n }\n\n /** List registered adapter IDs */\n list(): string[] {\n return this.adapters.map((a) => a.id);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBO,IAAM,cAAN,MAA2C;AAAA,EACvC,KAAK;AAAA,EAEd,QAAQ,SAAkC;AAExC,UAAM,aAAa,QAAQ,YAAY,MAAM,YAAY,KAAK;AAC9D,QAAI,WAAW,SAAS,MAAM,EAAG,QAAO;AAGxC,UAAM,KAAK,YAAY,QAAQ,SAAS,YAAY;AACpD,QAAI,MAAM,GAAG,YAAY,EAAE,WAAW,OAAO,EAAG,QAAO;AAGvD,UAAM,WAAW,YAAY,QAAQ,SAAS,oBAAoB;AAClE,QAAI,UAAU,YAAY,MAAM,OAAQ,QAAO;AAE/C,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,SAA2C;AAEzD,UAAM,eAAe,YAAY,QAAQ,SAAS,gBAAgB;AAGlE,UAAM,YAAY,YAAY,QAAQ,SAAS,oBAAoB,KAC9D;AAEL,WAAO;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX,YAAY,gBAAgB,QAAQ;AAAA,MACpC,WAAW,KAAK;AAAA,IAClB;AAAA,EACF;AACF;;;ACnCO,IAAM,kBAAN,MAA+C;AAAA,EAC3C,KAAK;AAAA,EAEd,QAAQ,SAAkC;AAExC,UAAM,aAAa,QAAQ,YAAY,MAAM,YAAY,KAAK;AAC9D,QAAI,WAAW,SAAS,UAAU,EAAG,QAAO;AAG5C,UAAM,KAAK,YAAY,QAAQ,SAAS,YAAY;AACpD,QAAI,MAAM,GAAG,YAAY,EAAE,WAAW,WAAW,EAAG,QAAO;AAG3D,UAAM,WAAW,YAAY,QAAQ,SAAS,oBAAoB;AAClE,QAAI,UAAU,YAAY,MAAM,WAAY,QAAO;AAEnD,WAAO;AAAA,EACT;AAAA,EAEA,gBAAgB,SAA2C;AAEzD,UAAM,eAAe,YAAY,QAAQ,SAAS,gBAAgB;AAGlE,UAAM,YAAY,YAAY,QAAQ,SAAS,oBAAoB,KAC9D;AAEL,WAAO;AAAA,MACL;AAAA,MACA,WAAW;AAAA,MACX,YAAY,gBAAgB,QAAQ;AAAA,MACpC,WAAW,KAAK;AAAA,IAClB;AAAA,EACF;AACF;;;ACtCO,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EAEjB,YAAY,UAA4B;AACtC,SAAK,WAAW,YAAY;AAAA,MAC1B,IAAI,cAAc;AAAA,MAClB,IAAI,cAAc;AAAA,MAClB,IAAI,aAAa;AAAA,MACjB,IAAI,kBAAkB;AAAA,MACtB,IAAI,YAAY;AAAA,MAChB,IAAI,gBAAgB;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,QAAQ,SAAkD;AACxD,eAAW,WAAW,KAAK,UAAU;AACnC,UAAI,QAAQ,QAAQ,OAAO,GAAG;AAC5B,eAAO,QAAQ,gBAAgB,OAAO;AAAA,MACxC;AAAA,IACF;AACA,UAAM,YAAY,QAAQ,WAAW,KAAK;AAC1C,UAAM,YAAY,QAAQ,WAAW,KAAK;AAC1C,QAAI,aAAa,WAAW;AAC1B,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,YAAY,QAAQ;AAAA,QACpB,WAAW;AAAA,MACb;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,OAAiB;AACf,WAAO,KAAK,SAAS,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,EACtC;AACF;","names":[]}
|