@remnic/core 9.3.614 → 9.3.616
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 +3 -3
- package/dist/access-http.d.ts +1 -1
- package/dist/access-http.js +5 -5
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +4 -4
- package/dist/access-schema.d.ts +48 -36
- package/dist/access-schema.js +1 -1
- package/dist/{access-service-DGG_2xPK.d.ts → access-service-CBNEKjzN.d.ts} +70 -5
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +2 -2
- package/dist/{chunk-B6FDZPCF.js → chunk-5OHHEORR.js} +50 -15
- package/dist/chunk-5OHHEORR.js.map +1 -0
- package/dist/{chunk-T5XWMMU2.js → chunk-EXUAP5LH.js} +2 -2
- package/dist/{chunk-EUML3N6B.js → chunk-IMA6GU4Y.js} +3 -3
- package/dist/chunk-IMA6GU4Y.js.map +1 -0
- package/dist/{chunk-7YQFWOF7.js → chunk-KGLPJROV.js} +4 -4
- package/dist/{chunk-VPGUMLBA.js → chunk-NM5NQYJE.js} +16 -16
- package/dist/chunk-NM5NQYJE.js.map +1 -0
- package/dist/{chunk-QEMCQFDW.js → chunk-WD2W4234.js} +8 -2
- package/dist/chunk-WD2W4234.js.map +1 -0
- package/dist/{chunk-ADNZVFXG.js → chunk-ZK32E74R.js} +142 -31
- package/dist/chunk-ZK32E74R.js.map +1 -0
- package/dist/{cli-DWeu7eTY.d.ts → cli-Cw729yLf.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +6 -6
- package/dist/explicit-capture.d.ts +10 -0
- package/dist/explicit-capture.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/orchestrator.js +2 -2
- package/dist/schemas.d.ts +64 -64
- package/dist/shared-context/manager.d.ts +2 -2
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +1 -1
- package/src/access-http.ts +21 -10
- package/src/access-mcp.test.ts +109 -0
- package/src/access-mcp.ts +46 -2
- package/src/access-schema.ts +11 -0
- package/src/access-service-coding-write.test.ts +478 -0
- package/src/access-service.ts +237 -32
- package/src/explicit-capture.ts +19 -2
- package/dist/chunk-ADNZVFXG.js.map +0 -1
- package/dist/chunk-B6FDZPCF.js.map +0 -1
- package/dist/chunk-EUML3N6B.js.map +0 -1
- package/dist/chunk-QEMCQFDW.js.map +0 -1
- package/dist/chunk-VPGUMLBA.js.map +0 -1
- /package/dist/{chunk-T5XWMMU2.js.map → chunk-EXUAP5LH.js.map} +0 -0
- /package/dist/{chunk-7YQFWOF7.js.map → chunk-KGLPJROV.js.map} +0 -0
package/src/access-http.ts
CHANGED
|
@@ -1094,12 +1094,20 @@ export class EngramAccessHttpServer {
|
|
|
1094
1094
|
entityRef: body.entityRef,
|
|
1095
1095
|
ttl: body.ttl,
|
|
1096
1096
|
sourceReason: body.sourceReason,
|
|
1097
|
+
cwd: body.cwd,
|
|
1098
|
+
projectTag: body.projectTag,
|
|
1097
1099
|
};
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1100
|
+
// Rate-limit enforcement is SOLELY authoritative inside memoryStore via
|
|
1101
|
+
// enforceWriteQuota: it runs atomically with the real idempotency-miss
|
|
1102
|
+
// determination (and the real resolved namespace), and is never invoked
|
|
1103
|
+
// for a replay. We deliberately do NOT pre-check here: the write namespace
|
|
1104
|
+
// is resolved from mutable session/git context, so a stale peek could
|
|
1105
|
+
// report "miss" for a request that is actually an idempotent replay in the
|
|
1106
|
+
// now-scoped namespace and hard-reject a safe replay with 429 (#1434 Codex
|
|
1107
|
+
// review). Letting the in-lock hook be the only hard gate avoids that.
|
|
1108
|
+
const response = await this.service.memoryStore(request, {
|
|
1109
|
+
enforceWriteQuota: () => this.ensureWriteRateLimitAvailable(),
|
|
1110
|
+
});
|
|
1103
1111
|
if (this.shouldCountWriteRateLimit(response as { dryRun?: boolean; idempotencyReplay?: boolean })) {
|
|
1104
1112
|
this.recordWriteRateLimitHit();
|
|
1105
1113
|
}
|
|
@@ -1123,12 +1131,15 @@ export class EngramAccessHttpServer {
|
|
|
1123
1131
|
entityRef: body.entityRef,
|
|
1124
1132
|
ttl: body.ttl,
|
|
1125
1133
|
sourceReason: body.sourceReason,
|
|
1134
|
+
cwd: body.cwd,
|
|
1135
|
+
projectTag: body.projectTag,
|
|
1126
1136
|
};
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1137
|
+
// Quota enforcement is solely authoritative inside suggestionSubmit
|
|
1138
|
+
// (enforceWriteQuota), atomic with the real miss and never on a replay; no
|
|
1139
|
+
// HTTP pre-check, so a stale peek can't 429 a safe replay (#1434 Codex review).
|
|
1140
|
+
const response = await this.service.suggestionSubmit(request, {
|
|
1141
|
+
enforceWriteQuota: () => this.ensureWriteRateLimitAvailable(),
|
|
1142
|
+
});
|
|
1132
1143
|
if (this.shouldCountWriteRateLimit(response as { dryRun?: boolean; idempotencyReplay?: boolean })) {
|
|
1133
1144
|
this.recordWriteRateLimitHit();
|
|
1134
1145
|
}
|
package/src/access-mcp.test.ts
CHANGED
|
@@ -262,6 +262,115 @@ test("MCP memory write tools reject malformed arguments before dispatch", async
|
|
|
262
262
|
}
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
test("MCP write tools accept and forward client-injected cwd/projectTag (#1434)", async () => {
|
|
266
|
+
for (const toolName of ["engram.memory_store", "engram.suggestion_submit"]) {
|
|
267
|
+
let received: Record<string, unknown> | undefined;
|
|
268
|
+
const service = {
|
|
269
|
+
...makeMockService(),
|
|
270
|
+
memoryStore: async (args: Record<string, unknown>) => {
|
|
271
|
+
received = args;
|
|
272
|
+
return {
|
|
273
|
+
schemaVersion: 1,
|
|
274
|
+
operation: "memory_store",
|
|
275
|
+
namespace: "default",
|
|
276
|
+
dryRun: true,
|
|
277
|
+
accepted: true,
|
|
278
|
+
queued: false,
|
|
279
|
+
status: "validated",
|
|
280
|
+
};
|
|
281
|
+
},
|
|
282
|
+
suggestionSubmit: async (args: Record<string, unknown>) => {
|
|
283
|
+
received = args;
|
|
284
|
+
return {
|
|
285
|
+
schemaVersion: 1,
|
|
286
|
+
operation: "suggestion_submit",
|
|
287
|
+
namespace: "default",
|
|
288
|
+
dryRun: true,
|
|
289
|
+
accepted: true,
|
|
290
|
+
queued: false,
|
|
291
|
+
status: "validated",
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
} as unknown as EngramAccessService;
|
|
295
|
+
const server = new EngramMcpServer(service);
|
|
296
|
+
|
|
297
|
+
const response = await server.handleRequest(
|
|
298
|
+
makeToolRequest(toolName, {
|
|
299
|
+
content: "valid durable content",
|
|
300
|
+
category: "fact",
|
|
301
|
+
dryRun: true,
|
|
302
|
+
cwd: "/home/dev/project-x",
|
|
303
|
+
projectTag: "Blend/Supply",
|
|
304
|
+
}),
|
|
305
|
+
);
|
|
306
|
+
const result = (response as Record<string, unknown> & {
|
|
307
|
+
result?: { isError?: boolean };
|
|
308
|
+
}).result;
|
|
309
|
+
|
|
310
|
+
assert.equal(result?.isError, false, `${toolName} should accept cwd/projectTag`);
|
|
311
|
+
assert.equal(received?.cwd, "/home/dev/project-x", `${toolName} must forward cwd`);
|
|
312
|
+
assert.equal(received?.projectTag, "Blend/Supply", `${toolName} must forward projectTag`);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("MCP write tools still reject genuinely-unknown keys after the cwd fix (#1434)", async () => {
|
|
317
|
+
let dispatched = false;
|
|
318
|
+
const service = {
|
|
319
|
+
...makeMockService(),
|
|
320
|
+
memoryStore: async () => {
|
|
321
|
+
dispatched = true;
|
|
322
|
+
return {
|
|
323
|
+
schemaVersion: 1,
|
|
324
|
+
operation: "memory_store",
|
|
325
|
+
namespace: "default",
|
|
326
|
+
dryRun: true,
|
|
327
|
+
accepted: true,
|
|
328
|
+
queued: false,
|
|
329
|
+
status: "validated",
|
|
330
|
+
};
|
|
331
|
+
},
|
|
332
|
+
} as unknown as EngramAccessService;
|
|
333
|
+
const server = new EngramMcpServer(service);
|
|
334
|
+
const response = await server.handleRequest(
|
|
335
|
+
makeToolRequest("engram.memory_store", {
|
|
336
|
+
content: "valid durable content",
|
|
337
|
+
category: "fact",
|
|
338
|
+
dryRun: true,
|
|
339
|
+
cwd: "/ok",
|
|
340
|
+
bogusField: "must still be rejected",
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
const result = (response as Record<string, unknown> & {
|
|
344
|
+
result?: { isError?: boolean; content?: Array<{ text?: string }> };
|
|
345
|
+
}).result;
|
|
346
|
+
assert.equal(result?.isError, true, "unknown keys must still be rejected");
|
|
347
|
+
assert.equal(dispatched, false, "malformed write must not dispatch");
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("MCP capsule tools tolerate client-injected cwd/projectTag (#1434)", async () => {
|
|
351
|
+
for (const toolName of [
|
|
352
|
+
"engram.capsule_list",
|
|
353
|
+
"engram.capsule_export",
|
|
354
|
+
"engram.capsule_import",
|
|
355
|
+
]) {
|
|
356
|
+
const service = {
|
|
357
|
+
...makeMockService(),
|
|
358
|
+
capsuleList: async () => ({ capsules: [] }),
|
|
359
|
+
capsuleExport: async () => ({ exported: true }),
|
|
360
|
+
capsuleImport: async () => ({ imported: true }),
|
|
361
|
+
} as unknown as EngramAccessService;
|
|
362
|
+
const server = new EngramMcpServer(service);
|
|
363
|
+
const args: Record<string, unknown> = { cwd: "/x", projectTag: "t" };
|
|
364
|
+
if (toolName === "engram.capsule_export") args.name = "cap-1";
|
|
365
|
+
if (toolName === "engram.capsule_import") args.archivePath = "/tmp/a.capsule.json.gz";
|
|
366
|
+
const response = await server.handleRequest(makeToolRequest(toolName, args));
|
|
367
|
+
const result = (response as Record<string, unknown> & {
|
|
368
|
+
result?: { isError?: boolean };
|
|
369
|
+
}).result;
|
|
370
|
+
assert.equal(result?.isError, false, `${toolName} should tolerate cwd/projectTag`);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
265
374
|
test("MCP session override is injected only into tools that accept sessionKey", async () => {
|
|
266
375
|
let capsuleListArgs: Record<string, unknown> | undefined;
|
|
267
376
|
let observeArgs: Record<string, unknown> | undefined;
|
package/src/access-mcp.ts
CHANGED
|
@@ -115,6 +115,8 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
|
|
|
115
115
|
"entityRef",
|
|
116
116
|
"ttl",
|
|
117
117
|
"sourceReason",
|
|
118
|
+
"cwd",
|
|
119
|
+
"projectTag",
|
|
118
120
|
],
|
|
119
121
|
suggestionSubmit: [
|
|
120
122
|
"schemaVersion",
|
|
@@ -129,6 +131,8 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
|
|
|
129
131
|
"entityRef",
|
|
130
132
|
"ttl",
|
|
131
133
|
"sourceReason",
|
|
134
|
+
"cwd",
|
|
135
|
+
"projectTag",
|
|
132
136
|
],
|
|
133
137
|
capsuleExport: [
|
|
134
138
|
"name",
|
|
@@ -138,9 +142,40 @@ const STRICT_MCP_SCHEMA_KEYS: Partial<Record<SchemaName, readonly string[]>> = {
|
|
|
138
142
|
"peerIds",
|
|
139
143
|
"includeTranscripts",
|
|
140
144
|
"encrypt",
|
|
145
|
+
"cwd",
|
|
146
|
+
"projectTag",
|
|
141
147
|
],
|
|
142
|
-
capsuleImport: ["archivePath", "namespace", "mode", "passphrase"],
|
|
143
|
-
capsuleList: ["namespace"],
|
|
148
|
+
capsuleImport: ["archivePath", "namespace", "mode", "passphrase", "cwd", "projectTag"],
|
|
149
|
+
capsuleList: ["namespace", "cwd", "projectTag"],
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Shared JSON-schema fragments for the client-injected git/project context
|
|
153
|
+
// fields (#1434). Declared once to avoid drift across tool definitions.
|
|
154
|
+
// `_SCOPED` is for write tools that resolve a project namespace from these
|
|
155
|
+
// fields; `_IGNORED` is for tools that merely tolerate them for MCP client
|
|
156
|
+
// compatibility (clients like Pi MCPorter auto-inject `cwd` on every call).
|
|
157
|
+
const MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED: Record<string, unknown> = {
|
|
158
|
+
cwd: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description:
|
|
161
|
+
"Optional working directory. When no explicit namespace is given, resolves the project namespace this write is stored in (mirrors recall/observe).",
|
|
162
|
+
},
|
|
163
|
+
projectTag: {
|
|
164
|
+
type: "string",
|
|
165
|
+
description:
|
|
166
|
+
"Optional project tag for non-git project scoping. When no explicit namespace is given, routes this write to the tagged project namespace.",
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
const MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED: Record<string, unknown> = {
|
|
170
|
+
cwd: {
|
|
171
|
+
type: "string",
|
|
172
|
+
description:
|
|
173
|
+
"Accepted for MCP client compatibility (git-context auto-injection); ignored by this tool.",
|
|
174
|
+
},
|
|
175
|
+
projectTag: {
|
|
176
|
+
type: "string",
|
|
177
|
+
description: "Accepted for MCP client compatibility; ignored by this tool.",
|
|
178
|
+
},
|
|
144
179
|
};
|
|
145
180
|
|
|
146
181
|
function parseMcpRequest<N extends SchemaName>(
|
|
@@ -614,6 +649,7 @@ export class EngramMcpServer {
|
|
|
614
649
|
},
|
|
615
650
|
includeTranscripts: { type: "boolean" },
|
|
616
651
|
encrypt: { type: "boolean" },
|
|
652
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
|
|
617
653
|
},
|
|
618
654
|
required: ["name"],
|
|
619
655
|
additionalProperties: false,
|
|
@@ -639,6 +675,7 @@ export class EngramMcpServer {
|
|
|
639
675
|
type: "string",
|
|
640
676
|
description: "Passphrase for encrypted capsule archives.",
|
|
641
677
|
},
|
|
678
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
|
|
642
679
|
},
|
|
643
680
|
required: ["archivePath"],
|
|
644
681
|
additionalProperties: false,
|
|
@@ -651,6 +688,7 @@ export class EngramMcpServer {
|
|
|
651
688
|
type: "object",
|
|
652
689
|
properties: {
|
|
653
690
|
namespace: { type: "string" },
|
|
691
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_IGNORED,
|
|
654
692
|
},
|
|
655
693
|
additionalProperties: false,
|
|
656
694
|
},
|
|
@@ -755,6 +793,7 @@ export class EngramMcpServer {
|
|
|
755
793
|
entityRef: { type: "string" },
|
|
756
794
|
ttl: { type: "string" },
|
|
757
795
|
sourceReason: { type: "string" },
|
|
796
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED,
|
|
758
797
|
},
|
|
759
798
|
required: ["content"],
|
|
760
799
|
additionalProperties: false,
|
|
@@ -778,6 +817,7 @@ export class EngramMcpServer {
|
|
|
778
817
|
entityRef: { type: "string" },
|
|
779
818
|
ttl: { type: "string" },
|
|
780
819
|
sourceReason: { type: "string" },
|
|
820
|
+
...MCP_GIT_CONTEXT_SCHEMA_PROPS_SCOPED,
|
|
781
821
|
},
|
|
782
822
|
required: ["content"],
|
|
783
823
|
additionalProperties: false,
|
|
@@ -2502,6 +2542,8 @@ export class EngramMcpServer {
|
|
|
2502
2542
|
entityRef: body.entityRef,
|
|
2503
2543
|
ttl: body.ttl,
|
|
2504
2544
|
sourceReason: body.sourceReason,
|
|
2545
|
+
cwd: body.cwd,
|
|
2546
|
+
projectTag: body.projectTag,
|
|
2505
2547
|
});
|
|
2506
2548
|
}
|
|
2507
2549
|
case "engram.suggestion_submit": {
|
|
@@ -2520,6 +2562,8 @@ export class EngramMcpServer {
|
|
|
2520
2562
|
entityRef: body.entityRef,
|
|
2521
2563
|
ttl: body.ttl,
|
|
2522
2564
|
sourceReason: body.sourceReason,
|
|
2565
|
+
cwd: body.cwd,
|
|
2566
|
+
projectTag: body.projectTag,
|
|
2523
2567
|
});
|
|
2524
2568
|
}
|
|
2525
2569
|
case "engram.entity_get":
|
package/src/access-schema.ts
CHANGED
|
@@ -243,6 +243,17 @@ export const memoryStoreRequestSchema = z.object({
|
|
|
243
243
|
entityRef: entityRefSchema,
|
|
244
244
|
ttl: ttlSchema,
|
|
245
245
|
sourceReason: sourceReasonSchema,
|
|
246
|
+
// Git/project context for project-scoped writes (#1434). When no explicit
|
|
247
|
+
// `namespace` is given, these route the write to the same project namespace
|
|
248
|
+
// recall/observe resolve from `cwd`/`projectTag` (issue #569, rule 42). Also
|
|
249
|
+
// lets MCP clients that auto-inject `cwd` (e.g. Pi MCPorter) call write tools.
|
|
250
|
+
cwd: z.string().trim().min(1, "cwd must be non-empty when provided").max(2048).optional(),
|
|
251
|
+
projectTag: z
|
|
252
|
+
.string()
|
|
253
|
+
.trim()
|
|
254
|
+
.min(1, "projectTag must be non-empty when provided")
|
|
255
|
+
.max(256)
|
|
256
|
+
.optional(),
|
|
246
257
|
});
|
|
247
258
|
|
|
248
259
|
export const suggestionSubmitRequestSchema = memoryStoreRequestSchema;
|