@joshuaswarren/openclaw-engram 9.0.68 → 9.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +756 -737
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3509,7 +3509,6 @@ Respond with valid JSON matching this schema:
|
|
|
3509
3509
|
},
|
|
3510
3510
|
{ role: "user", content: conversation }
|
|
3511
3511
|
],
|
|
3512
|
-
temperature: 0.3,
|
|
3513
3512
|
...buildChatCompletionTokenLimit(this.config.model, 4096, {
|
|
3514
3513
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
3515
3514
|
})
|
|
@@ -3785,7 +3784,6 @@ Respond with valid JSON only, matching this schema:
|
|
|
3785
3784
|
{ role: "user", content: "Consolidate the new memories against existing ones." }
|
|
3786
3785
|
],
|
|
3787
3786
|
...this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {},
|
|
3788
|
-
temperature: 0.3,
|
|
3789
3787
|
...buildChatCompletionTokenLimit(this.config.model, 4096, {
|
|
3790
3788
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
3791
3789
|
})
|
|
@@ -4013,7 +4011,6 @@ Respond with valid JSON matching this schema:
|
|
|
4013
4011
|
{ role: "user", content: fullProfileContent }
|
|
4014
4012
|
],
|
|
4015
4013
|
...this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {},
|
|
4016
|
-
temperature: 0.3,
|
|
4017
4014
|
...buildChatCompletionTokenLimit(this.config.model, 4096, {
|
|
4018
4015
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
4019
4016
|
})
|
|
@@ -4209,7 +4206,6 @@ Respond with valid JSON matching this schema:
|
|
|
4209
4206
|
{ role: "user", content: fullIdentityContent }
|
|
4210
4207
|
],
|
|
4211
4208
|
...this.config.reasoningEffort !== "none" ? { reasoning_effort: this.config.reasoningEffort } : {},
|
|
4212
|
-
temperature: 0.3,
|
|
4213
4209
|
...buildChatCompletionTokenLimit(this.config.model, 4096, {
|
|
4214
4210
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
4215
4211
|
})
|
|
@@ -4407,7 +4403,6 @@ Respond with valid JSON matching this schema:
|
|
|
4407
4403
|
{ role: "system", content: systemPrompt },
|
|
4408
4404
|
{ role: "user", content: input }
|
|
4409
4405
|
],
|
|
4410
|
-
temperature: 0.3,
|
|
4411
4406
|
...buildChatCompletionTokenLimit(this.config.model, 2048, {
|
|
4412
4407
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
4413
4408
|
})
|
|
@@ -4511,7 +4506,6 @@ Respond with valid JSON matching this schema:
|
|
|
4511
4506
|
{ role: "system", content: systemPrompt },
|
|
4512
4507
|
{ role: "user", content: input }
|
|
4513
4508
|
],
|
|
4514
|
-
temperature: 0.3,
|
|
4515
4509
|
...buildChatCompletionTokenLimit(this.config.model, 2048, {
|
|
4516
4510
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
4517
4511
|
})
|
|
@@ -4611,7 +4605,6 @@ ${memoryList}` }
|
|
|
4611
4605
|
|
|
4612
4606
|
${memoryList}` }
|
|
4613
4607
|
],
|
|
4614
|
-
temperature: 0.3,
|
|
4615
4608
|
...buildChatCompletionTokenLimit(this.config.model, 4096, {
|
|
4616
4609
|
assumeOpenAI: this.directClientUsesOpenAiTokenSemantics()
|
|
4617
4610
|
})
|
|
@@ -34222,786 +34215,812 @@ var EngramAccessService = class {
|
|
|
34222
34215
|
import { createServer as createServer3 } from "http";
|
|
34223
34216
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
34224
34217
|
import { existsSync as existsSync5 } from "fs";
|
|
34225
|
-
import { readFile as
|
|
34218
|
+
import { readFile as readFile39 } from "fs/promises";
|
|
34226
34219
|
import path64 from "path";
|
|
34227
34220
|
import { fileURLToPath as fileURLToPath2, URL as URL3 } from "url";
|
|
34228
|
-
|
|
34229
|
-
|
|
34230
|
-
|
|
34231
|
-
|
|
34232
|
-
|
|
34233
|
-
|
|
34234
|
-
|
|
34235
|
-
|
|
34236
|
-
|
|
34237
|
-
|
|
34238
|
-
|
|
34239
|
-
|
|
34240
|
-
|
|
34241
|
-
|
|
34242
|
-
}
|
|
34243
|
-
};
|
|
34244
|
-
function hostToUrlAuthority2(host) {
|
|
34245
|
-
if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]")) {
|
|
34246
|
-
return `[${host}]`;
|
|
34221
|
+
|
|
34222
|
+
// src/access-mcp.ts
|
|
34223
|
+
import { readFile as readFile38 } from "fs/promises";
|
|
34224
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
34225
|
+
async function getMcpServerVersion() {
|
|
34226
|
+
const envVersion = process.env.OPENCLAW_ENGRAM_VERSION?.trim() || process.env.npm_package_version?.trim();
|
|
34227
|
+
if (envVersion) return envVersion;
|
|
34228
|
+
try {
|
|
34229
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
34230
|
+
const raw = await readFile38(pkgPath, "utf-8");
|
|
34231
|
+
const parsed = JSON.parse(raw);
|
|
34232
|
+
return parsed.version?.trim() || "unknown";
|
|
34233
|
+
} catch {
|
|
34234
|
+
return "unknown";
|
|
34247
34235
|
}
|
|
34248
|
-
return host;
|
|
34249
34236
|
}
|
|
34250
|
-
var
|
|
34251
|
-
service
|
|
34252
|
-
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34257
|
-
|
|
34258
|
-
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
|
|
34263
|
-
|
|
34264
|
-
|
|
34265
|
-
|
|
34266
|
-
|
|
34267
|
-
|
|
34268
|
-
|
|
34269
|
-
|
|
34270
|
-
this.adminConsolePublicDir = options.adminConsolePublicDir ?? defaultAdminConsolePublicDir;
|
|
34271
|
-
}
|
|
34272
|
-
async start() {
|
|
34273
|
-
if (!this.authToken) {
|
|
34274
|
-
throw new Error("engram access HTTP requires authToken");
|
|
34275
|
-
}
|
|
34276
|
-
if (this.server) return this.status();
|
|
34277
|
-
const server = createServer3((req, res) => {
|
|
34278
|
-
void this.handle(req, res).catch((err) => {
|
|
34279
|
-
log.debug(`engram access HTTP request failed: ${err}`);
|
|
34280
|
-
if (err instanceof HttpError) {
|
|
34281
|
-
this.respondJson(res, err.status, { error: err.message });
|
|
34282
|
-
return;
|
|
34237
|
+
var EngramMcpServer = class {
|
|
34238
|
+
constructor(service, options = {}) {
|
|
34239
|
+
this.service = service;
|
|
34240
|
+
this.authenticatedPrincipal = options.principal?.trim() || process.env.OPENCLAW_ENGRAM_ACCESS_PRINCIPAL?.trim() || void 0;
|
|
34241
|
+
this.tools = [
|
|
34242
|
+
{
|
|
34243
|
+
name: "engram.recall",
|
|
34244
|
+
description: "Recall Engram context for a query.",
|
|
34245
|
+
inputSchema: {
|
|
34246
|
+
type: "object",
|
|
34247
|
+
properties: {
|
|
34248
|
+
query: { type: "string" },
|
|
34249
|
+
sessionKey: { type: "string" },
|
|
34250
|
+
namespace: { type: "string" },
|
|
34251
|
+
topK: { type: "number" },
|
|
34252
|
+
mode: { type: "string", enum: ["auto", "no_recall", "minimal", "full", "graph_mode"] },
|
|
34253
|
+
includeDebug: { type: "boolean" }
|
|
34254
|
+
},
|
|
34255
|
+
required: ["query"],
|
|
34256
|
+
additionalProperties: false
|
|
34283
34257
|
}
|
|
34284
|
-
|
|
34285
|
-
|
|
34286
|
-
|
|
34258
|
+
},
|
|
34259
|
+
{
|
|
34260
|
+
name: "engram.recall_explain",
|
|
34261
|
+
description: "Return the last recall snapshot for a session or the most recent one.",
|
|
34262
|
+
inputSchema: {
|
|
34263
|
+
type: "object",
|
|
34264
|
+
properties: {
|
|
34265
|
+
sessionKey: { type: "string" },
|
|
34266
|
+
namespace: { type: "string" }
|
|
34267
|
+
},
|
|
34268
|
+
additionalProperties: false
|
|
34287
34269
|
}
|
|
34288
|
-
|
|
34289
|
-
|
|
34290
|
-
|
|
34270
|
+
},
|
|
34271
|
+
{
|
|
34272
|
+
name: "engram.memory_get",
|
|
34273
|
+
description: "Fetch one Engram memory by id.",
|
|
34274
|
+
inputSchema: {
|
|
34275
|
+
type: "object",
|
|
34276
|
+
properties: {
|
|
34277
|
+
memoryId: { type: "string" },
|
|
34278
|
+
namespace: { type: "string" }
|
|
34279
|
+
},
|
|
34280
|
+
required: ["memoryId"],
|
|
34281
|
+
additionalProperties: false
|
|
34291
34282
|
}
|
|
34292
|
-
|
|
34293
|
-
|
|
34294
|
-
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
|
|
34299
|
-
|
|
34300
|
-
|
|
34301
|
-
|
|
34302
|
-
|
|
34303
|
-
|
|
34304
|
-
|
|
34305
|
-
|
|
34306
|
-
|
|
34307
|
-
|
|
34308
|
-
|
|
34309
|
-
|
|
34310
|
-
|
|
34311
|
-
|
|
34312
|
-
|
|
34313
|
-
|
|
34314
|
-
|
|
34315
|
-
|
|
34316
|
-
|
|
34317
|
-
|
|
34318
|
-
|
|
34319
|
-
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34324
|
-
|
|
34325
|
-
|
|
34326
|
-
|
|
34327
|
-
|
|
34328
|
-
|
|
34329
|
-
|
|
34330
|
-
|
|
34331
|
-
|
|
34332
|
-
|
|
34333
|
-
|
|
34283
|
+
},
|
|
34284
|
+
{
|
|
34285
|
+
name: "engram.memory_timeline",
|
|
34286
|
+
description: "Fetch one Engram memory timeline by id.",
|
|
34287
|
+
inputSchema: {
|
|
34288
|
+
type: "object",
|
|
34289
|
+
properties: {
|
|
34290
|
+
memoryId: { type: "string" },
|
|
34291
|
+
namespace: { type: "string" },
|
|
34292
|
+
limit: { type: "number" }
|
|
34293
|
+
},
|
|
34294
|
+
required: ["memoryId"],
|
|
34295
|
+
additionalProperties: false
|
|
34296
|
+
}
|
|
34297
|
+
},
|
|
34298
|
+
{
|
|
34299
|
+
name: "engram.memory_store",
|
|
34300
|
+
description: "Store an explicit Engram memory through the access layer.",
|
|
34301
|
+
inputSchema: {
|
|
34302
|
+
type: "object",
|
|
34303
|
+
properties: {
|
|
34304
|
+
schemaVersion: { type: "number" },
|
|
34305
|
+
idempotencyKey: { type: "string" },
|
|
34306
|
+
dryRun: { type: "boolean" },
|
|
34307
|
+
sessionKey: { type: "string" },
|
|
34308
|
+
content: { type: "string" },
|
|
34309
|
+
category: { type: "string" },
|
|
34310
|
+
confidence: { type: "number" },
|
|
34311
|
+
namespace: { type: "string" },
|
|
34312
|
+
tags: { type: "array", items: { type: "string" } },
|
|
34313
|
+
entityRef: { type: "string" },
|
|
34314
|
+
ttl: { type: "string" },
|
|
34315
|
+
sourceReason: { type: "string" }
|
|
34316
|
+
},
|
|
34317
|
+
required: ["content"],
|
|
34318
|
+
additionalProperties: false
|
|
34319
|
+
}
|
|
34320
|
+
},
|
|
34321
|
+
{
|
|
34322
|
+
name: "engram.suggestion_submit",
|
|
34323
|
+
description: "Queue a suggested Engram memory for review.",
|
|
34324
|
+
inputSchema: {
|
|
34325
|
+
type: "object",
|
|
34326
|
+
properties: {
|
|
34327
|
+
schemaVersion: { type: "number" },
|
|
34328
|
+
idempotencyKey: { type: "string" },
|
|
34329
|
+
dryRun: { type: "boolean" },
|
|
34330
|
+
sessionKey: { type: "string" },
|
|
34331
|
+
content: { type: "string" },
|
|
34332
|
+
category: { type: "string" },
|
|
34333
|
+
confidence: { type: "number" },
|
|
34334
|
+
namespace: { type: "string" },
|
|
34335
|
+
tags: { type: "array", items: { type: "string" } },
|
|
34336
|
+
entityRef: { type: "string" },
|
|
34337
|
+
ttl: { type: "string" },
|
|
34338
|
+
sourceReason: { type: "string" }
|
|
34339
|
+
},
|
|
34340
|
+
required: ["content"],
|
|
34341
|
+
additionalProperties: false
|
|
34342
|
+
}
|
|
34343
|
+
},
|
|
34344
|
+
{
|
|
34345
|
+
name: "engram.entity_get",
|
|
34346
|
+
description: "Fetch one Engram entity by name.",
|
|
34347
|
+
inputSchema: {
|
|
34348
|
+
type: "object",
|
|
34349
|
+
properties: {
|
|
34350
|
+
name: { type: "string" },
|
|
34351
|
+
namespace: { type: "string" }
|
|
34352
|
+
},
|
|
34353
|
+
required: ["name"],
|
|
34354
|
+
additionalProperties: false
|
|
34355
|
+
}
|
|
34356
|
+
},
|
|
34357
|
+
{
|
|
34358
|
+
name: "engram.review_queue_list",
|
|
34359
|
+
description: "Fetch the latest Engram review queue artifact bundle.",
|
|
34360
|
+
inputSchema: {
|
|
34361
|
+
type: "object",
|
|
34362
|
+
properties: {
|
|
34363
|
+
runId: { type: "string" },
|
|
34364
|
+
namespace: { type: "string" }
|
|
34365
|
+
},
|
|
34366
|
+
additionalProperties: false
|
|
34367
|
+
}
|
|
34368
|
+
}
|
|
34369
|
+
];
|
|
34334
34370
|
}
|
|
34335
|
-
|
|
34336
|
-
|
|
34337
|
-
|
|
34338
|
-
|
|
34339
|
-
|
|
34340
|
-
|
|
34341
|
-
|
|
34342
|
-
|
|
34343
|
-
|
|
34344
|
-
|
|
34345
|
-
});
|
|
34346
|
-
res.end(JSON.stringify({ error: "unauthorized" }));
|
|
34347
|
-
return;
|
|
34348
|
-
}
|
|
34349
|
-
if (req.method === "GET" && pathname === "/engram/v1/health") {
|
|
34350
|
-
this.respondJson(res, 200, await this.service.health());
|
|
34351
|
-
return;
|
|
34352
|
-
}
|
|
34353
|
-
if (req.method === "POST" && pathname === "/engram/v1/recall") {
|
|
34354
|
-
const body = await this.readJsonBody(req);
|
|
34355
|
-
const response = await this.service.recall({
|
|
34356
|
-
query: typeof body.query === "string" ? body.query : "",
|
|
34357
|
-
sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
|
|
34358
|
-
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34359
|
-
topK: typeof body.topK === "number" ? body.topK : void 0,
|
|
34360
|
-
mode: typeof body.mode === "string" ? body.mode : void 0,
|
|
34361
|
-
includeDebug: body.includeDebug === true
|
|
34362
|
-
});
|
|
34363
|
-
this.respondJson(res, 200, response);
|
|
34364
|
-
return;
|
|
34365
|
-
}
|
|
34366
|
-
if (req.method === "POST" && pathname === "/engram/v1/recall/explain") {
|
|
34367
|
-
const body = await this.readJsonBody(req);
|
|
34368
|
-
const response = await this.service.recallExplain({
|
|
34369
|
-
sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
|
|
34370
|
-
namespace: typeof body.namespace === "string" ? body.namespace : void 0
|
|
34371
|
-
});
|
|
34372
|
-
this.respondJson(res, 200, response);
|
|
34373
|
-
return;
|
|
34371
|
+
buffer = Buffer.alloc(0);
|
|
34372
|
+
flushTask = null;
|
|
34373
|
+
tools;
|
|
34374
|
+
authenticatedPrincipal;
|
|
34375
|
+
async handleRequest(request) {
|
|
34376
|
+
const id = request.id ?? null;
|
|
34377
|
+
const method = request.method ?? "";
|
|
34378
|
+
if (method === "notifications/initialized") return null;
|
|
34379
|
+
if (method === "ping") {
|
|
34380
|
+
return { jsonrpc: "2.0", id, result: {} };
|
|
34374
34381
|
}
|
|
34375
|
-
if (
|
|
34376
|
-
const
|
|
34377
|
-
|
|
34378
|
-
|
|
34379
|
-
|
|
34380
|
-
|
|
34381
|
-
|
|
34382
|
-
|
|
34383
|
-
|
|
34384
|
-
|
|
34385
|
-
|
|
34386
|
-
|
|
34387
|
-
|
|
34388
|
-
|
|
34389
|
-
|
|
34390
|
-
sourceReason: typeof body.sourceReason === "string" ? body.sourceReason : void 0
|
|
34382
|
+
if (method === "initialize") {
|
|
34383
|
+
const version = await getMcpServerVersion();
|
|
34384
|
+
return {
|
|
34385
|
+
jsonrpc: "2.0",
|
|
34386
|
+
id,
|
|
34387
|
+
result: {
|
|
34388
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
34389
|
+
capabilities: {
|
|
34390
|
+
tools: {}
|
|
34391
|
+
},
|
|
34392
|
+
serverInfo: {
|
|
34393
|
+
name: "openclaw-engram",
|
|
34394
|
+
version
|
|
34395
|
+
}
|
|
34396
|
+
}
|
|
34391
34397
|
};
|
|
34392
|
-
const idempotencyStatus = await this.service.peekMemoryStoreIdempotency(request);
|
|
34393
|
-
if (idempotencyStatus === "miss" && request.dryRun !== true) {
|
|
34394
|
-
this.ensureWriteRateLimitAvailable();
|
|
34395
|
-
}
|
|
34396
|
-
const response = await this.service.memoryStore(request);
|
|
34397
|
-
if (this.shouldCountWriteRateLimit(response)) {
|
|
34398
|
-
this.recordWriteRateLimitHit();
|
|
34399
|
-
}
|
|
34400
|
-
this.respondJson(res, this.writeResponseStatus(response), response);
|
|
34401
|
-
return;
|
|
34402
34398
|
}
|
|
34403
|
-
if (
|
|
34404
|
-
|
|
34405
|
-
|
|
34406
|
-
|
|
34407
|
-
|
|
34408
|
-
|
|
34409
|
-
|
|
34410
|
-
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34411
|
-
content: typeof body.content === "string" ? body.content : "",
|
|
34412
|
-
category: typeof body.category === "string" ? body.category : void 0,
|
|
34413
|
-
confidence: typeof body.confidence === "number" ? body.confidence : void 0,
|
|
34414
|
-
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34415
|
-
tags: Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34416
|
-
entityRef: typeof body.entityRef === "string" ? body.entityRef : void 0,
|
|
34417
|
-
ttl: typeof body.ttl === "string" ? body.ttl : void 0,
|
|
34418
|
-
sourceReason: typeof body.sourceReason === "string" ? body.sourceReason : void 0
|
|
34399
|
+
if (method === "tools/list") {
|
|
34400
|
+
return {
|
|
34401
|
+
jsonrpc: "2.0",
|
|
34402
|
+
id,
|
|
34403
|
+
result: {
|
|
34404
|
+
tools: this.tools
|
|
34405
|
+
}
|
|
34419
34406
|
};
|
|
34420
|
-
const idempotencyStatus = await this.service.peekSuggestionSubmitIdempotency(request);
|
|
34421
|
-
if (idempotencyStatus === "miss" && request.dryRun !== true) {
|
|
34422
|
-
this.ensureWriteRateLimitAvailable();
|
|
34423
|
-
}
|
|
34424
|
-
const response = await this.service.suggestionSubmit(request);
|
|
34425
|
-
if (this.shouldCountWriteRateLimit(response)) {
|
|
34426
|
-
this.recordWriteRateLimitHit();
|
|
34427
|
-
}
|
|
34428
|
-
this.respondJson(res, this.writeResponseStatus(response), response);
|
|
34429
|
-
return;
|
|
34430
|
-
}
|
|
34431
|
-
if (req.method === "GET" && pathname === "/engram/v1/memories") {
|
|
34432
|
-
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
|
|
34433
|
-
const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
|
|
34434
|
-
const sortParam = parsed.searchParams.get("sort") ?? void 0;
|
|
34435
|
-
const sort = sortParam === "updated_desc" || sortParam === "updated_asc" || sortParam === "created_desc" || sortParam === "created_asc" ? sortParam : void 0;
|
|
34436
|
-
const response = await this.service.memoryBrowse({
|
|
34437
|
-
query: parsed.searchParams.get("q") ?? void 0,
|
|
34438
|
-
status: parsed.searchParams.get("status") ?? void 0,
|
|
34439
|
-
category: parsed.searchParams.get("category") ?? void 0,
|
|
34440
|
-
namespace: parsed.searchParams.get("namespace") ?? void 0,
|
|
34441
|
-
sort,
|
|
34442
|
-
limit: Number.isFinite(limitRaw) ? limitRaw : 50,
|
|
34443
|
-
offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
|
|
34444
|
-
});
|
|
34445
|
-
this.respondJson(res, 200, response);
|
|
34446
|
-
return;
|
|
34447
|
-
}
|
|
34448
|
-
const memoryMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)$/);
|
|
34449
|
-
if (req.method === "GET" && memoryMatch) {
|
|
34450
|
-
const memoryId = decodeURIComponent(memoryMatch[1] ?? "");
|
|
34451
|
-
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
34452
|
-
const response = await this.service.memoryGet(memoryId, namespace);
|
|
34453
|
-
this.respondJson(res, response.found ? 200 : 404, response);
|
|
34454
|
-
return;
|
|
34455
|
-
}
|
|
34456
|
-
const timelineMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)\/timeline$/);
|
|
34457
|
-
if (req.method === "GET" && timelineMatch) {
|
|
34458
|
-
const memoryId = decodeURIComponent(timelineMatch[1] ?? "");
|
|
34459
|
-
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
34460
|
-
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "200", 10);
|
|
34461
|
-
const limit = Number.isFinite(limitRaw) ? limitRaw : 200;
|
|
34462
|
-
const response = await this.service.memoryTimeline(memoryId, namespace, limit);
|
|
34463
|
-
this.respondJson(res, response.found ? 200 : 404, response);
|
|
34464
|
-
return;
|
|
34465
|
-
}
|
|
34466
|
-
if (req.method === "GET" && pathname === "/engram/v1/entities") {
|
|
34467
|
-
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
|
|
34468
|
-
const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
|
|
34469
|
-
const response = await this.service.entityList({
|
|
34470
|
-
namespace: parsed.searchParams.get("namespace") ?? void 0,
|
|
34471
|
-
query: parsed.searchParams.get("q") ?? void 0,
|
|
34472
|
-
limit: Number.isFinite(limitRaw) ? limitRaw : 50,
|
|
34473
|
-
offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
|
|
34474
|
-
});
|
|
34475
|
-
this.respondJson(res, 200, response);
|
|
34476
|
-
return;
|
|
34477
34407
|
}
|
|
34478
|
-
|
|
34479
|
-
|
|
34480
|
-
const
|
|
34481
|
-
const
|
|
34482
|
-
|
|
34483
|
-
|
|
34484
|
-
|
|
34485
|
-
|
|
34486
|
-
|
|
34487
|
-
|
|
34488
|
-
|
|
34489
|
-
|
|
34490
|
-
|
|
34491
|
-
|
|
34492
|
-
|
|
34493
|
-
|
|
34494
|
-
|
|
34495
|
-
|
|
34496
|
-
|
|
34497
|
-
|
|
34498
|
-
|
|
34499
|
-
|
|
34500
|
-
|
|
34501
|
-
|
|
34502
|
-
|
|
34503
|
-
const body = await this.readJsonBody(req);
|
|
34504
|
-
const status = typeof body.status === "string" ? body.status : "";
|
|
34505
|
-
if (status !== "active" && status !== "pending_review" && status !== "quarantined" && status !== "rejected" && status !== "superseded" && status !== "archived") {
|
|
34506
|
-
throw new HttpError(400, "invalid_review_status");
|
|
34507
|
-
}
|
|
34508
|
-
this.ensureWriteRateLimitAvailable();
|
|
34509
|
-
const response = await this.service.reviewDisposition({
|
|
34510
|
-
memoryId: typeof body.memoryId === "string" ? body.memoryId : "",
|
|
34511
|
-
status,
|
|
34512
|
-
reasonCode: typeof body.reasonCode === "string" ? body.reasonCode : "",
|
|
34513
|
-
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34514
|
-
authenticatedPrincipal: this.authenticatedPrincipal
|
|
34515
|
-
});
|
|
34516
|
-
if (this.shouldCountWriteRateLimit(response)) {
|
|
34517
|
-
this.recordWriteRateLimitHit();
|
|
34408
|
+
if (method === "tools/call") {
|
|
34409
|
+
const params = request.params ?? {};
|
|
34410
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
34411
|
+
const argumentsObject = params.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments) ? params.arguments : {};
|
|
34412
|
+
try {
|
|
34413
|
+
const result = await this.callTool(name, argumentsObject);
|
|
34414
|
+
return {
|
|
34415
|
+
jsonrpc: "2.0",
|
|
34416
|
+
id,
|
|
34417
|
+
result: {
|
|
34418
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
34419
|
+
structuredContent: result,
|
|
34420
|
+
isError: false
|
|
34421
|
+
}
|
|
34422
|
+
};
|
|
34423
|
+
} catch (err) {
|
|
34424
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34425
|
+
return {
|
|
34426
|
+
jsonrpc: "2.0",
|
|
34427
|
+
id,
|
|
34428
|
+
result: {
|
|
34429
|
+
content: [{ type: "text", text: message }],
|
|
34430
|
+
isError: true
|
|
34431
|
+
}
|
|
34432
|
+
};
|
|
34518
34433
|
}
|
|
34519
|
-
this.respondJson(res, 200, response);
|
|
34520
|
-
return;
|
|
34521
34434
|
}
|
|
34522
|
-
|
|
34435
|
+
return {
|
|
34436
|
+
jsonrpc: "2.0",
|
|
34437
|
+
id,
|
|
34438
|
+
error: {
|
|
34439
|
+
code: -32601,
|
|
34440
|
+
message: `Method not found: ${method}`
|
|
34441
|
+
}
|
|
34442
|
+
};
|
|
34523
34443
|
}
|
|
34524
|
-
|
|
34525
|
-
|
|
34526
|
-
|
|
34527
|
-
|
|
34528
|
-
|
|
34529
|
-
|
|
34444
|
+
async runStdio(input, output) {
|
|
34445
|
+
input.on("data", (chunk) => {
|
|
34446
|
+
this.buffer = Buffer.concat([this.buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
|
|
34447
|
+
this.scheduleFlush(output);
|
|
34448
|
+
});
|
|
34449
|
+
await new Promise((resolve, reject) => {
|
|
34450
|
+
input.on("end", resolve);
|
|
34451
|
+
input.on("error", reject);
|
|
34452
|
+
});
|
|
34453
|
+
while (this.flushTask) {
|
|
34454
|
+
await this.flushTask;
|
|
34455
|
+
}
|
|
34530
34456
|
}
|
|
34531
|
-
|
|
34532
|
-
if (
|
|
34533
|
-
|
|
34534
|
-
|
|
34535
|
-
|
|
34536
|
-
|
|
34537
|
-
|
|
34538
|
-
|
|
34539
|
-
|
|
34540
|
-
|
|
34541
|
-
|
|
34542
|
-
|
|
34543
|
-
|
|
34544
|
-
|
|
34545
|
-
|
|
34546
|
-
|
|
34547
|
-
|
|
34548
|
-
|
|
34549
|
-
|
|
34550
|
-
|
|
34551
|
-
this.respondJson(res, 404, { error: "not_found" });
|
|
34552
|
-
}
|
|
34457
|
+
scheduleFlush(output) {
|
|
34458
|
+
if (this.flushTask) return;
|
|
34459
|
+
const task = this.flushBuffer(output).catch((err) => {
|
|
34460
|
+
this.writeMessage(output, {
|
|
34461
|
+
jsonrpc: "2.0",
|
|
34462
|
+
id: null,
|
|
34463
|
+
error: {
|
|
34464
|
+
code: -32700,
|
|
34465
|
+
message: err instanceof Error ? err.message : String(err)
|
|
34466
|
+
}
|
|
34467
|
+
});
|
|
34468
|
+
}).finally(() => {
|
|
34469
|
+
if (this.flushTask === task) {
|
|
34470
|
+
this.flushTask = null;
|
|
34471
|
+
}
|
|
34472
|
+
if (this.buffer.length > 0) {
|
|
34473
|
+
this.scheduleFlush(output);
|
|
34474
|
+
}
|
|
34475
|
+
});
|
|
34476
|
+
this.flushTask = task;
|
|
34553
34477
|
}
|
|
34554
|
-
async
|
|
34555
|
-
|
|
34556
|
-
|
|
34557
|
-
|
|
34558
|
-
const
|
|
34559
|
-
|
|
34560
|
-
|
|
34561
|
-
|
|
34478
|
+
async flushBuffer(output) {
|
|
34479
|
+
while (true) {
|
|
34480
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
34481
|
+
if (headerEnd < 0) return;
|
|
34482
|
+
const headerText = this.buffer.slice(0, headerEnd).toString("utf-8");
|
|
34483
|
+
const headers = headerText.split("\r\n");
|
|
34484
|
+
const contentLengthHeader = headers.find((line) => line.toLowerCase().startsWith("content-length:"));
|
|
34485
|
+
if (!contentLengthHeader) {
|
|
34486
|
+
this.buffer = Buffer.alloc(0);
|
|
34487
|
+
return;
|
|
34488
|
+
}
|
|
34489
|
+
const contentLength = parseInt(contentLengthHeader.split(":")[1]?.trim() ?? "0", 10);
|
|
34490
|
+
if (!Number.isFinite(contentLength) || contentLength < 0) {
|
|
34491
|
+
this.buffer = Buffer.alloc(0);
|
|
34492
|
+
return;
|
|
34493
|
+
}
|
|
34494
|
+
const messageStart = headerEnd + 4;
|
|
34495
|
+
const messageEnd = messageStart + contentLength;
|
|
34496
|
+
if (this.buffer.length < messageEnd) return;
|
|
34497
|
+
const body = this.buffer.slice(messageStart, messageEnd).toString("utf-8");
|
|
34498
|
+
this.buffer = this.buffer.slice(messageEnd);
|
|
34499
|
+
let parsed;
|
|
34500
|
+
try {
|
|
34501
|
+
parsed = JSON.parse(body);
|
|
34502
|
+
} catch {
|
|
34503
|
+
this.writeMessage(output, {
|
|
34504
|
+
jsonrpc: "2.0",
|
|
34505
|
+
id: null,
|
|
34506
|
+
error: {
|
|
34507
|
+
code: -32700,
|
|
34508
|
+
message: "Parse error"
|
|
34509
|
+
}
|
|
34510
|
+
});
|
|
34511
|
+
continue;
|
|
34512
|
+
}
|
|
34513
|
+
const response = await this.handleRequest(parsed);
|
|
34514
|
+
if (response) {
|
|
34515
|
+
this.writeMessage(output, response);
|
|
34562
34516
|
}
|
|
34563
|
-
chunks.push(buffer);
|
|
34564
|
-
}
|
|
34565
|
-
if (chunks.length === 0) return {};
|
|
34566
|
-
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
34567
|
-
if (raw.length === 0) return {};
|
|
34568
|
-
let parsed;
|
|
34569
|
-
try {
|
|
34570
|
-
parsed = JSON.parse(raw);
|
|
34571
|
-
} catch {
|
|
34572
|
-
throw new HttpError(400, "invalid_json");
|
|
34573
|
-
}
|
|
34574
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
34575
|
-
throw new HttpError(400, "invalid_json_object");
|
|
34576
34517
|
}
|
|
34577
|
-
return parsed;
|
|
34578
|
-
}
|
|
34579
|
-
isAuthorized(req) {
|
|
34580
|
-
if (!this.authToken) return false;
|
|
34581
|
-
const raw = req.headers.authorization;
|
|
34582
|
-
if (!raw) return false;
|
|
34583
|
-
const separator = raw.indexOf(" ");
|
|
34584
|
-
if (separator <= 0) return false;
|
|
34585
|
-
const scheme = raw.slice(0, separator).toLowerCase();
|
|
34586
|
-
if (scheme !== "bearer") return false;
|
|
34587
|
-
const token = raw.slice(separator + 1).trim();
|
|
34588
|
-
return this.timingSafeStringEqual(token, this.authToken);
|
|
34589
|
-
}
|
|
34590
|
-
timingSafeStringEqual(a, b) {
|
|
34591
|
-
const left = this.encodeSecret(a);
|
|
34592
|
-
const right = this.encodeSecret(b);
|
|
34593
|
-
if (!left || !right) return false;
|
|
34594
|
-
return timingSafeEqual2(left, right);
|
|
34595
|
-
}
|
|
34596
|
-
encodeSecret(value) {
|
|
34597
|
-
const encoded = Buffer.from(value, "utf-8");
|
|
34598
|
-
if (encoded.length > 1024) return null;
|
|
34599
|
-
const out = Buffer.alloc(2 + 1024);
|
|
34600
|
-
out.writeUInt16BE(encoded.length, 0);
|
|
34601
|
-
encoded.copy(out, 2);
|
|
34602
|
-
return out;
|
|
34603
34518
|
}
|
|
34604
|
-
|
|
34605
|
-
|
|
34606
|
-
|
|
34607
|
-
|
|
34519
|
+
writeMessage(output, payload) {
|
|
34520
|
+
const body = JSON.stringify(payload);
|
|
34521
|
+
const message = `Content-Length: ${Buffer.byteLength(body, "utf-8")}\r
|
|
34522
|
+
\r
|
|
34523
|
+
${body}`;
|
|
34524
|
+
output.write(message);
|
|
34608
34525
|
}
|
|
34609
|
-
|
|
34610
|
-
|
|
34611
|
-
|
|
34612
|
-
|
|
34613
|
-
|
|
34614
|
-
|
|
34615
|
-
|
|
34526
|
+
async callTool(name, args) {
|
|
34527
|
+
switch (name) {
|
|
34528
|
+
case "engram.recall":
|
|
34529
|
+
return this.service.recall({
|
|
34530
|
+
query: typeof args.query === "string" ? args.query : "",
|
|
34531
|
+
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : void 0,
|
|
34532
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
34533
|
+
topK: typeof args.topK === "number" && Number.isFinite(args.topK) ? args.topK : void 0,
|
|
34534
|
+
mode: typeof args.mode === "string" ? args.mode : void 0,
|
|
34535
|
+
includeDebug: args.includeDebug === true
|
|
34536
|
+
});
|
|
34537
|
+
case "engram.recall_explain":
|
|
34538
|
+
return this.service.recallExplain({
|
|
34539
|
+
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : void 0,
|
|
34540
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0
|
|
34541
|
+
});
|
|
34542
|
+
case "engram.memory_get":
|
|
34543
|
+
return this.service.memoryGet(
|
|
34544
|
+
typeof args.memoryId === "string" ? args.memoryId : "",
|
|
34545
|
+
typeof args.namespace === "string" ? args.namespace : void 0
|
|
34546
|
+
);
|
|
34547
|
+
case "engram.memory_timeline": {
|
|
34548
|
+
const limit = typeof args.limit === "number" && Number.isFinite(args.limit) ? args.limit : 200;
|
|
34549
|
+
return this.service.memoryTimeline(
|
|
34550
|
+
typeof args.memoryId === "string" ? args.memoryId : "",
|
|
34551
|
+
typeof args.namespace === "string" ? args.namespace : void 0,
|
|
34552
|
+
limit
|
|
34553
|
+
);
|
|
34554
|
+
}
|
|
34555
|
+
case "engram.memory_store":
|
|
34556
|
+
return this.service.memoryStore({
|
|
34557
|
+
schemaVersion: typeof args.schemaVersion === "number" ? args.schemaVersion : void 0,
|
|
34558
|
+
idempotencyKey: typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0,
|
|
34559
|
+
dryRun: args.dryRun === true,
|
|
34560
|
+
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : void 0,
|
|
34561
|
+
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34562
|
+
content: typeof args.content === "string" ? args.content : "",
|
|
34563
|
+
category: typeof args.category === "string" ? args.category : void 0,
|
|
34564
|
+
confidence: typeof args.confidence === "number" ? args.confidence : void 0,
|
|
34565
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
34566
|
+
tags: Array.isArray(args.tags) ? args.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34567
|
+
entityRef: typeof args.entityRef === "string" ? args.entityRef : void 0,
|
|
34568
|
+
ttl: typeof args.ttl === "string" ? args.ttl : void 0,
|
|
34569
|
+
sourceReason: typeof args.sourceReason === "string" ? args.sourceReason : void 0
|
|
34570
|
+
});
|
|
34571
|
+
case "engram.suggestion_submit":
|
|
34572
|
+
return this.service.suggestionSubmit({
|
|
34573
|
+
schemaVersion: typeof args.schemaVersion === "number" ? args.schemaVersion : void 0,
|
|
34574
|
+
idempotencyKey: typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0,
|
|
34575
|
+
dryRun: args.dryRun === true,
|
|
34576
|
+
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : void 0,
|
|
34577
|
+
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34578
|
+
content: typeof args.content === "string" ? args.content : "",
|
|
34579
|
+
category: typeof args.category === "string" ? args.category : void 0,
|
|
34580
|
+
confidence: typeof args.confidence === "number" ? args.confidence : void 0,
|
|
34581
|
+
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
34582
|
+
tags: Array.isArray(args.tags) ? args.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34583
|
+
entityRef: typeof args.entityRef === "string" ? args.entityRef : void 0,
|
|
34584
|
+
ttl: typeof args.ttl === "string" ? args.ttl : void 0,
|
|
34585
|
+
sourceReason: typeof args.sourceReason === "string" ? args.sourceReason : void 0
|
|
34586
|
+
});
|
|
34587
|
+
case "engram.entity_get":
|
|
34588
|
+
return this.service.entityGet(
|
|
34589
|
+
typeof args.name === "string" ? args.name : "",
|
|
34590
|
+
typeof args.namespace === "string" ? args.namespace : void 0
|
|
34591
|
+
);
|
|
34592
|
+
case "engram.review_queue_list":
|
|
34593
|
+
return this.service.reviewQueue(
|
|
34594
|
+
typeof args.runId === "string" ? args.runId : void 0,
|
|
34595
|
+
typeof args.namespace === "string" ? args.namespace : void 0
|
|
34596
|
+
);
|
|
34597
|
+
default:
|
|
34598
|
+
throw new Error(`unknown tool: ${name}`);
|
|
34616
34599
|
}
|
|
34617
34600
|
}
|
|
34618
|
-
recordWriteRateLimitHit() {
|
|
34619
|
-
this.writeRequestTimestamps.push(Date.now());
|
|
34620
|
-
}
|
|
34621
|
-
shouldCountWriteRateLimit(response) {
|
|
34622
|
-
return response.dryRun !== true && response.idempotencyReplay !== true;
|
|
34623
|
-
}
|
|
34624
34601
|
};
|
|
34625
34602
|
|
|
34626
|
-
// src/access-
|
|
34627
|
-
|
|
34628
|
-
|
|
34629
|
-
|
|
34630
|
-
|
|
34631
|
-
|
|
34632
|
-
|
|
34633
|
-
|
|
34634
|
-
|
|
34635
|
-
|
|
34636
|
-
|
|
34637
|
-
|
|
34638
|
-
|
|
34603
|
+
// src/access-http.ts
|
|
34604
|
+
function resolveDefaultAdminConsolePublicDir() {
|
|
34605
|
+
const candidates = [
|
|
34606
|
+
fileURLToPath2(new URL3("../admin-console/public", import.meta.url)),
|
|
34607
|
+
fileURLToPath2(new URL3("./admin-console/public", import.meta.url))
|
|
34608
|
+
];
|
|
34609
|
+
return candidates.find((candidate) => existsSync5(candidate)) ?? candidates[0];
|
|
34610
|
+
}
|
|
34611
|
+
var defaultAdminConsolePublicDir = resolveDefaultAdminConsolePublicDir();
|
|
34612
|
+
var WRITE_RATE_LIMIT_WINDOW_MS = 6e4;
|
|
34613
|
+
var WRITE_RATE_LIMIT_MAX_REQUESTS = 30;
|
|
34614
|
+
var HttpError = class extends Error {
|
|
34615
|
+
constructor(status, message) {
|
|
34616
|
+
super(message);
|
|
34617
|
+
this.status = status;
|
|
34618
|
+
}
|
|
34619
|
+
};
|
|
34620
|
+
function hostToUrlAuthority2(host) {
|
|
34621
|
+
if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]")) {
|
|
34622
|
+
return `[${host}]`;
|
|
34639
34623
|
}
|
|
34624
|
+
return host;
|
|
34640
34625
|
}
|
|
34641
|
-
var
|
|
34642
|
-
|
|
34643
|
-
|
|
34644
|
-
|
|
34645
|
-
|
|
34646
|
-
|
|
34647
|
-
|
|
34648
|
-
|
|
34649
|
-
|
|
34650
|
-
|
|
34651
|
-
|
|
34652
|
-
|
|
34653
|
-
|
|
34654
|
-
|
|
34655
|
-
|
|
34656
|
-
|
|
34657
|
-
|
|
34658
|
-
|
|
34659
|
-
|
|
34660
|
-
|
|
34661
|
-
|
|
34662
|
-
|
|
34663
|
-
|
|
34664
|
-
|
|
34665
|
-
|
|
34666
|
-
|
|
34667
|
-
|
|
34668
|
-
|
|
34669
|
-
|
|
34670
|
-
|
|
34671
|
-
|
|
34672
|
-
|
|
34673
|
-
|
|
34674
|
-
|
|
34675
|
-
|
|
34676
|
-
name: "engram.memory_get",
|
|
34677
|
-
description: "Fetch one Engram memory by id.",
|
|
34678
|
-
inputSchema: {
|
|
34679
|
-
type: "object",
|
|
34680
|
-
properties: {
|
|
34681
|
-
memoryId: { type: "string" },
|
|
34682
|
-
namespace: { type: "string" }
|
|
34683
|
-
},
|
|
34684
|
-
required: ["memoryId"],
|
|
34685
|
-
additionalProperties: false
|
|
34686
|
-
}
|
|
34687
|
-
},
|
|
34688
|
-
{
|
|
34689
|
-
name: "engram.memory_timeline",
|
|
34690
|
-
description: "Fetch one Engram memory timeline by id.",
|
|
34691
|
-
inputSchema: {
|
|
34692
|
-
type: "object",
|
|
34693
|
-
properties: {
|
|
34694
|
-
memoryId: { type: "string" },
|
|
34695
|
-
namespace: { type: "string" },
|
|
34696
|
-
limit: { type: "number" }
|
|
34697
|
-
},
|
|
34698
|
-
required: ["memoryId"],
|
|
34699
|
-
additionalProperties: false
|
|
34700
|
-
}
|
|
34701
|
-
},
|
|
34702
|
-
{
|
|
34703
|
-
name: "engram.memory_store",
|
|
34704
|
-
description: "Store an explicit Engram memory through the access layer.",
|
|
34705
|
-
inputSchema: {
|
|
34706
|
-
type: "object",
|
|
34707
|
-
properties: {
|
|
34708
|
-
schemaVersion: { type: "number" },
|
|
34709
|
-
idempotencyKey: { type: "string" },
|
|
34710
|
-
dryRun: { type: "boolean" },
|
|
34711
|
-
sessionKey: { type: "string" },
|
|
34712
|
-
content: { type: "string" },
|
|
34713
|
-
category: { type: "string" },
|
|
34714
|
-
confidence: { type: "number" },
|
|
34715
|
-
namespace: { type: "string" },
|
|
34716
|
-
tags: { type: "array", items: { type: "string" } },
|
|
34717
|
-
entityRef: { type: "string" },
|
|
34718
|
-
ttl: { type: "string" },
|
|
34719
|
-
sourceReason: { type: "string" }
|
|
34720
|
-
},
|
|
34721
|
-
required: ["content"],
|
|
34722
|
-
additionalProperties: false
|
|
34723
|
-
}
|
|
34724
|
-
},
|
|
34725
|
-
{
|
|
34726
|
-
name: "engram.suggestion_submit",
|
|
34727
|
-
description: "Queue a suggested Engram memory for review.",
|
|
34728
|
-
inputSchema: {
|
|
34729
|
-
type: "object",
|
|
34730
|
-
properties: {
|
|
34731
|
-
schemaVersion: { type: "number" },
|
|
34732
|
-
idempotencyKey: { type: "string" },
|
|
34733
|
-
dryRun: { type: "boolean" },
|
|
34734
|
-
sessionKey: { type: "string" },
|
|
34735
|
-
content: { type: "string" },
|
|
34736
|
-
category: { type: "string" },
|
|
34737
|
-
confidence: { type: "number" },
|
|
34738
|
-
namespace: { type: "string" },
|
|
34739
|
-
tags: { type: "array", items: { type: "string" } },
|
|
34740
|
-
entityRef: { type: "string" },
|
|
34741
|
-
ttl: { type: "string" },
|
|
34742
|
-
sourceReason: { type: "string" }
|
|
34743
|
-
},
|
|
34744
|
-
required: ["content"],
|
|
34745
|
-
additionalProperties: false
|
|
34626
|
+
var EngramAccessHttpServer = class {
|
|
34627
|
+
service;
|
|
34628
|
+
host;
|
|
34629
|
+
requestedPort;
|
|
34630
|
+
authToken;
|
|
34631
|
+
authenticatedPrincipal;
|
|
34632
|
+
maxBodyBytes;
|
|
34633
|
+
adminConsoleEnabled;
|
|
34634
|
+
adminConsolePublicDir;
|
|
34635
|
+
writeRequestTimestamps = [];
|
|
34636
|
+
mcpServer;
|
|
34637
|
+
server = null;
|
|
34638
|
+
boundPort = 0;
|
|
34639
|
+
constructor(options) {
|
|
34640
|
+
this.service = options.service;
|
|
34641
|
+
this.host = options.host?.trim() || "127.0.0.1";
|
|
34642
|
+
this.requestedPort = Number.isFinite(options.port) ? Math.max(0, Math.floor(options.port ?? 0)) : 0;
|
|
34643
|
+
this.authToken = options.authToken?.trim() || void 0;
|
|
34644
|
+
this.authenticatedPrincipal = options.principal?.trim() || void 0;
|
|
34645
|
+
this.maxBodyBytes = Number.isFinite(options.maxBodyBytes) ? Math.max(1, Math.floor(options.maxBodyBytes ?? 131072)) : 131072;
|
|
34646
|
+
this.adminConsoleEnabled = options.adminConsoleEnabled !== false;
|
|
34647
|
+
this.adminConsolePublicDir = options.adminConsolePublicDir ?? defaultAdminConsolePublicDir;
|
|
34648
|
+
this.mcpServer = new EngramMcpServer(this.service, { principal: options.principal });
|
|
34649
|
+
}
|
|
34650
|
+
async start() {
|
|
34651
|
+
if (!this.authToken) {
|
|
34652
|
+
throw new Error("engram access HTTP requires authToken");
|
|
34653
|
+
}
|
|
34654
|
+
if (this.server) return this.status();
|
|
34655
|
+
const server = createServer3((req, res) => {
|
|
34656
|
+
void this.handle(req, res).catch((err) => {
|
|
34657
|
+
log.debug(`engram access HTTP request failed: ${err}`);
|
|
34658
|
+
if (err instanceof HttpError) {
|
|
34659
|
+
this.respondJson(res, err.status, { error: err.message });
|
|
34660
|
+
return;
|
|
34746
34661
|
}
|
|
34747
|
-
|
|
34748
|
-
|
|
34749
|
-
|
|
34750
|
-
description: "Fetch one Engram entity by name.",
|
|
34751
|
-
inputSchema: {
|
|
34752
|
-
type: "object",
|
|
34753
|
-
properties: {
|
|
34754
|
-
name: { type: "string" },
|
|
34755
|
-
namespace: { type: "string" }
|
|
34756
|
-
},
|
|
34757
|
-
required: ["name"],
|
|
34758
|
-
additionalProperties: false
|
|
34662
|
+
if (err instanceof EngramAccessInputError) {
|
|
34663
|
+
this.respondJson(res, 400, { error: err.message });
|
|
34664
|
+
return;
|
|
34759
34665
|
}
|
|
34760
|
-
|
|
34761
|
-
|
|
34762
|
-
|
|
34763
|
-
description: "Fetch the latest Engram review queue artifact bundle.",
|
|
34764
|
-
inputSchema: {
|
|
34765
|
-
type: "object",
|
|
34766
|
-
properties: {
|
|
34767
|
-
runId: { type: "string" },
|
|
34768
|
-
namespace: { type: "string" }
|
|
34769
|
-
},
|
|
34770
|
-
additionalProperties: false
|
|
34666
|
+
if (res.headersSent) {
|
|
34667
|
+
res.destroy(err);
|
|
34668
|
+
return;
|
|
34771
34669
|
}
|
|
34772
|
-
|
|
34773
|
-
|
|
34670
|
+
this.respondJson(res, 500, { error: "internal_error" });
|
|
34671
|
+
});
|
|
34672
|
+
});
|
|
34673
|
+
try {
|
|
34674
|
+
await new Promise((resolve, reject) => {
|
|
34675
|
+
const onError = (err) => {
|
|
34676
|
+
server.off("listening", onListening);
|
|
34677
|
+
reject(err);
|
|
34678
|
+
};
|
|
34679
|
+
const onListening = () => {
|
|
34680
|
+
server.off("error", onError);
|
|
34681
|
+
resolve();
|
|
34682
|
+
};
|
|
34683
|
+
server.once("error", onError);
|
|
34684
|
+
server.once("listening", onListening);
|
|
34685
|
+
server.listen(this.requestedPort, this.host);
|
|
34686
|
+
});
|
|
34687
|
+
} catch (err) {
|
|
34688
|
+
server.close();
|
|
34689
|
+
throw err;
|
|
34690
|
+
}
|
|
34691
|
+
this.server = server;
|
|
34692
|
+
const address = server.address();
|
|
34693
|
+
this.boundPort = typeof address === "object" && address ? address.port : this.requestedPort;
|
|
34694
|
+
return this.status();
|
|
34774
34695
|
}
|
|
34775
|
-
|
|
34776
|
-
|
|
34777
|
-
|
|
34778
|
-
|
|
34779
|
-
|
|
34780
|
-
|
|
34781
|
-
|
|
34782
|
-
|
|
34783
|
-
|
|
34784
|
-
|
|
34696
|
+
async stop() {
|
|
34697
|
+
if (!this.server) return;
|
|
34698
|
+
const server = this.server;
|
|
34699
|
+
this.server = null;
|
|
34700
|
+
this.boundPort = 0;
|
|
34701
|
+
await new Promise((resolve, reject) => {
|
|
34702
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
34703
|
+
});
|
|
34704
|
+
}
|
|
34705
|
+
status() {
|
|
34706
|
+
return {
|
|
34707
|
+
running: this.server !== null,
|
|
34708
|
+
host: this.host,
|
|
34709
|
+
port: this.boundPort,
|
|
34710
|
+
maxBodyBytes: this.maxBodyBytes
|
|
34711
|
+
};
|
|
34712
|
+
}
|
|
34713
|
+
async handle(req, res) {
|
|
34714
|
+
const parsed = new URL3(req.url ?? "/", `http://${hostToUrlAuthority2(this.host)}`);
|
|
34715
|
+
const pathname = parsed.pathname;
|
|
34716
|
+
if (this.adminConsoleEnabled && await this.handleAdminConsole(req, res, pathname)) {
|
|
34717
|
+
return;
|
|
34785
34718
|
}
|
|
34786
|
-
if (
|
|
34787
|
-
|
|
34788
|
-
|
|
34789
|
-
|
|
34790
|
-
|
|
34791
|
-
|
|
34792
|
-
|
|
34793
|
-
|
|
34794
|
-
|
|
34795
|
-
|
|
34796
|
-
|
|
34797
|
-
|
|
34798
|
-
|
|
34799
|
-
|
|
34800
|
-
|
|
34719
|
+
if (!this.isAuthorized(req)) {
|
|
34720
|
+
res.writeHead(401, {
|
|
34721
|
+
"content-type": "application/json; charset=utf-8",
|
|
34722
|
+
"www-authenticate": "Bearer"
|
|
34723
|
+
});
|
|
34724
|
+
res.end(JSON.stringify({ error: "unauthorized" }));
|
|
34725
|
+
return;
|
|
34726
|
+
}
|
|
34727
|
+
if (req.method === "POST" && pathname === "/mcp") {
|
|
34728
|
+
await this.handleMcpRequest(req, res);
|
|
34729
|
+
return;
|
|
34730
|
+
}
|
|
34731
|
+
if (req.method === "GET" && pathname === "/engram/v1/health") {
|
|
34732
|
+
this.respondJson(res, 200, await this.service.health());
|
|
34733
|
+
return;
|
|
34734
|
+
}
|
|
34735
|
+
if (req.method === "POST" && pathname === "/engram/v1/recall") {
|
|
34736
|
+
const body = await this.readJsonBody(req);
|
|
34737
|
+
const response = await this.service.recall({
|
|
34738
|
+
query: typeof body.query === "string" ? body.query : "",
|
|
34739
|
+
sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
|
|
34740
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34741
|
+
topK: typeof body.topK === "number" ? body.topK : void 0,
|
|
34742
|
+
mode: typeof body.mode === "string" ? body.mode : void 0,
|
|
34743
|
+
includeDebug: body.includeDebug === true
|
|
34744
|
+
});
|
|
34745
|
+
this.respondJson(res, 200, response);
|
|
34746
|
+
return;
|
|
34747
|
+
}
|
|
34748
|
+
if (req.method === "POST" && pathname === "/engram/v1/recall/explain") {
|
|
34749
|
+
const body = await this.readJsonBody(req);
|
|
34750
|
+
const response = await this.service.recallExplain({
|
|
34751
|
+
sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
|
|
34752
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0
|
|
34753
|
+
});
|
|
34754
|
+
this.respondJson(res, 200, response);
|
|
34755
|
+
return;
|
|
34756
|
+
}
|
|
34757
|
+
if (req.method === "POST" && pathname === "/engram/v1/memories") {
|
|
34758
|
+
const body = await this.readJsonBody(req);
|
|
34759
|
+
const request = {
|
|
34760
|
+
schemaVersion: typeof body.schemaVersion === "number" ? body.schemaVersion : void 0,
|
|
34761
|
+
idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : void 0,
|
|
34762
|
+
dryRun: body.dryRun === true,
|
|
34763
|
+
sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
|
|
34764
|
+
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34765
|
+
content: typeof body.content === "string" ? body.content : "",
|
|
34766
|
+
category: typeof body.category === "string" ? body.category : void 0,
|
|
34767
|
+
confidence: typeof body.confidence === "number" ? body.confidence : void 0,
|
|
34768
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34769
|
+
tags: Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34770
|
+
entityRef: typeof body.entityRef === "string" ? body.entityRef : void 0,
|
|
34771
|
+
ttl: typeof body.ttl === "string" ? body.ttl : void 0,
|
|
34772
|
+
sourceReason: typeof body.sourceReason === "string" ? body.sourceReason : void 0
|
|
34801
34773
|
};
|
|
34774
|
+
const idempotencyStatus = await this.service.peekMemoryStoreIdempotency(request);
|
|
34775
|
+
if (idempotencyStatus === "miss" && request.dryRun !== true) {
|
|
34776
|
+
this.ensureWriteRateLimitAvailable();
|
|
34777
|
+
}
|
|
34778
|
+
const response = await this.service.memoryStore(request);
|
|
34779
|
+
if (this.shouldCountWriteRateLimit(response)) {
|
|
34780
|
+
this.recordWriteRateLimitHit();
|
|
34781
|
+
}
|
|
34782
|
+
this.respondJson(res, this.writeResponseStatus(response), response);
|
|
34783
|
+
return;
|
|
34802
34784
|
}
|
|
34803
|
-
if (method === "
|
|
34804
|
-
|
|
34805
|
-
|
|
34806
|
-
|
|
34807
|
-
|
|
34808
|
-
|
|
34809
|
-
|
|
34785
|
+
if (req.method === "POST" && pathname === "/engram/v1/suggestions") {
|
|
34786
|
+
const body = await this.readJsonBody(req);
|
|
34787
|
+
const request = {
|
|
34788
|
+
schemaVersion: typeof body.schemaVersion === "number" ? body.schemaVersion : void 0,
|
|
34789
|
+
idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : void 0,
|
|
34790
|
+
dryRun: body.dryRun === true,
|
|
34791
|
+
sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : void 0,
|
|
34792
|
+
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34793
|
+
content: typeof body.content === "string" ? body.content : "",
|
|
34794
|
+
category: typeof body.category === "string" ? body.category : void 0,
|
|
34795
|
+
confidence: typeof body.confidence === "number" ? body.confidence : void 0,
|
|
34796
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34797
|
+
tags: Array.isArray(body.tags) ? body.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34798
|
+
entityRef: typeof body.entityRef === "string" ? body.entityRef : void 0,
|
|
34799
|
+
ttl: typeof body.ttl === "string" ? body.ttl : void 0,
|
|
34800
|
+
sourceReason: typeof body.sourceReason === "string" ? body.sourceReason : void 0
|
|
34810
34801
|
};
|
|
34802
|
+
const idempotencyStatus = await this.service.peekSuggestionSubmitIdempotency(request);
|
|
34803
|
+
if (idempotencyStatus === "miss" && request.dryRun !== true) {
|
|
34804
|
+
this.ensureWriteRateLimitAvailable();
|
|
34805
|
+
}
|
|
34806
|
+
const response = await this.service.suggestionSubmit(request);
|
|
34807
|
+
if (this.shouldCountWriteRateLimit(response)) {
|
|
34808
|
+
this.recordWriteRateLimitHit();
|
|
34809
|
+
}
|
|
34810
|
+
this.respondJson(res, this.writeResponseStatus(response), response);
|
|
34811
|
+
return;
|
|
34811
34812
|
}
|
|
34812
|
-
if (method === "
|
|
34813
|
-
const
|
|
34814
|
-
const
|
|
34815
|
-
const
|
|
34816
|
-
|
|
34817
|
-
|
|
34818
|
-
|
|
34819
|
-
|
|
34820
|
-
|
|
34821
|
-
|
|
34822
|
-
|
|
34823
|
-
|
|
34824
|
-
|
|
34825
|
-
|
|
34826
|
-
|
|
34827
|
-
|
|
34828
|
-
|
|
34829
|
-
|
|
34830
|
-
|
|
34831
|
-
|
|
34832
|
-
|
|
34833
|
-
|
|
34834
|
-
|
|
34835
|
-
|
|
34836
|
-
|
|
34813
|
+
if (req.method === "GET" && pathname === "/engram/v1/memories") {
|
|
34814
|
+
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
|
|
34815
|
+
const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
|
|
34816
|
+
const sortParam = parsed.searchParams.get("sort") ?? void 0;
|
|
34817
|
+
const sort = sortParam === "updated_desc" || sortParam === "updated_asc" || sortParam === "created_desc" || sortParam === "created_asc" ? sortParam : void 0;
|
|
34818
|
+
const response = await this.service.memoryBrowse({
|
|
34819
|
+
query: parsed.searchParams.get("q") ?? void 0,
|
|
34820
|
+
status: parsed.searchParams.get("status") ?? void 0,
|
|
34821
|
+
category: parsed.searchParams.get("category") ?? void 0,
|
|
34822
|
+
namespace: parsed.searchParams.get("namespace") ?? void 0,
|
|
34823
|
+
sort,
|
|
34824
|
+
limit: Number.isFinite(limitRaw) ? limitRaw : 50,
|
|
34825
|
+
offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
|
|
34826
|
+
});
|
|
34827
|
+
this.respondJson(res, 200, response);
|
|
34828
|
+
return;
|
|
34829
|
+
}
|
|
34830
|
+
const memoryMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)$/);
|
|
34831
|
+
if (req.method === "GET" && memoryMatch) {
|
|
34832
|
+
const memoryId = decodeURIComponent(memoryMatch[1] ?? "");
|
|
34833
|
+
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
34834
|
+
const response = await this.service.memoryGet(memoryId, namespace);
|
|
34835
|
+
this.respondJson(res, response.found ? 200 : 404, response);
|
|
34836
|
+
return;
|
|
34837
|
+
}
|
|
34838
|
+
const timelineMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)\/timeline$/);
|
|
34839
|
+
if (req.method === "GET" && timelineMatch) {
|
|
34840
|
+
const memoryId = decodeURIComponent(timelineMatch[1] ?? "");
|
|
34841
|
+
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
34842
|
+
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "200", 10);
|
|
34843
|
+
const limit = Number.isFinite(limitRaw) ? limitRaw : 200;
|
|
34844
|
+
const response = await this.service.memoryTimeline(memoryId, namespace, limit);
|
|
34845
|
+
this.respondJson(res, response.found ? 200 : 404, response);
|
|
34846
|
+
return;
|
|
34847
|
+
}
|
|
34848
|
+
if (req.method === "GET" && pathname === "/engram/v1/entities") {
|
|
34849
|
+
const limitRaw = parseInt(parsed.searchParams.get("limit") ?? "50", 10);
|
|
34850
|
+
const offsetRaw = parseInt(parsed.searchParams.get("offset") ?? "0", 10);
|
|
34851
|
+
const response = await this.service.entityList({
|
|
34852
|
+
namespace: parsed.searchParams.get("namespace") ?? void 0,
|
|
34853
|
+
query: parsed.searchParams.get("q") ?? void 0,
|
|
34854
|
+
limit: Number.isFinite(limitRaw) ? limitRaw : 50,
|
|
34855
|
+
offset: Number.isFinite(offsetRaw) ? offsetRaw : 0
|
|
34856
|
+
});
|
|
34857
|
+
this.respondJson(res, 200, response);
|
|
34858
|
+
return;
|
|
34859
|
+
}
|
|
34860
|
+
const entityMatch = pathname.match(/^\/engram\/v1\/entities\/([^/]+)$/);
|
|
34861
|
+
if (req.method === "GET" && entityMatch) {
|
|
34862
|
+
const entityName = decodeURIComponent(entityMatch[1] ?? "");
|
|
34863
|
+
const namespace = parsed.searchParams.get("namespace") ?? void 0;
|
|
34864
|
+
const response = await this.service.entityGet(entityName, namespace);
|
|
34865
|
+
this.respondJson(res, response.found ? 200 : 404, response);
|
|
34866
|
+
return;
|
|
34867
|
+
}
|
|
34868
|
+
if (req.method === "GET" && pathname === "/engram/v1/review-queue") {
|
|
34869
|
+
const response = await this.service.reviewQueue(
|
|
34870
|
+
parsed.searchParams.get("runId") ?? void 0,
|
|
34871
|
+
parsed.searchParams.get("namespace") ?? void 0
|
|
34872
|
+
);
|
|
34873
|
+
this.respondJson(res, 200, response);
|
|
34874
|
+
return;
|
|
34875
|
+
}
|
|
34876
|
+
if (req.method === "GET" && pathname === "/engram/v1/maintenance") {
|
|
34877
|
+
this.respondJson(res, 200, await this.service.maintenance(parsed.searchParams.get("namespace") ?? void 0));
|
|
34878
|
+
return;
|
|
34879
|
+
}
|
|
34880
|
+
if (req.method === "GET" && pathname === "/engram/v1/quality") {
|
|
34881
|
+
this.respondJson(res, 200, await this.service.quality(parsed.searchParams.get("namespace") ?? void 0));
|
|
34882
|
+
return;
|
|
34883
|
+
}
|
|
34884
|
+
if (req.method === "POST" && pathname === "/engram/v1/review-disposition") {
|
|
34885
|
+
const body = await this.readJsonBody(req);
|
|
34886
|
+
const status = typeof body.status === "string" ? body.status : "";
|
|
34887
|
+
if (status !== "active" && status !== "pending_review" && status !== "quarantined" && status !== "rejected" && status !== "superseded" && status !== "archived") {
|
|
34888
|
+
throw new HttpError(400, "invalid_review_status");
|
|
34889
|
+
}
|
|
34890
|
+
this.ensureWriteRateLimitAvailable();
|
|
34891
|
+
const response = await this.service.reviewDisposition({
|
|
34892
|
+
memoryId: typeof body.memoryId === "string" ? body.memoryId : "",
|
|
34893
|
+
status,
|
|
34894
|
+
reasonCode: typeof body.reasonCode === "string" ? body.reasonCode : "",
|
|
34895
|
+
namespace: typeof body.namespace === "string" ? body.namespace : void 0,
|
|
34896
|
+
authenticatedPrincipal: this.authenticatedPrincipal
|
|
34897
|
+
});
|
|
34898
|
+
if (this.shouldCountWriteRateLimit(response)) {
|
|
34899
|
+
this.recordWriteRateLimitHit();
|
|
34837
34900
|
}
|
|
34901
|
+
this.respondJson(res, 200, response);
|
|
34902
|
+
return;
|
|
34838
34903
|
}
|
|
34839
|
-
|
|
34840
|
-
|
|
34841
|
-
|
|
34842
|
-
|
|
34843
|
-
|
|
34844
|
-
|
|
34904
|
+
this.respondJson(res, 404, { error: "not_found" });
|
|
34905
|
+
}
|
|
34906
|
+
async handleMcpRequest(req, res) {
|
|
34907
|
+
const body = await this.readJsonBody(req);
|
|
34908
|
+
const request = body;
|
|
34909
|
+
if (request.method === "tools/call") {
|
|
34910
|
+
const toolName = typeof request.params?.name === "string" ? request.params.name : "";
|
|
34911
|
+
if (toolName === "engram.memory_store" || toolName === "engram.suggestion_submit") {
|
|
34912
|
+
this.ensureWriteRateLimitAvailable();
|
|
34913
|
+
this.recordWriteRateLimitHit();
|
|
34845
34914
|
}
|
|
34846
|
-
}
|
|
34915
|
+
}
|
|
34916
|
+
const response = await this.mcpServer.handleRequest(request);
|
|
34917
|
+
if (response === null) {
|
|
34918
|
+
res.statusCode = 202;
|
|
34919
|
+
res.end();
|
|
34920
|
+
return;
|
|
34921
|
+
}
|
|
34922
|
+
this.respondJson(res, 200, response);
|
|
34847
34923
|
}
|
|
34848
|
-
|
|
34849
|
-
|
|
34850
|
-
|
|
34851
|
-
|
|
34852
|
-
|
|
34853
|
-
|
|
34854
|
-
|
|
34855
|
-
|
|
34856
|
-
|
|
34857
|
-
|
|
34858
|
-
await this.
|
|
34924
|
+
respondJson(res, status, payload) {
|
|
34925
|
+
const body = JSON.stringify(payload, null, 2);
|
|
34926
|
+
res.statusCode = status;
|
|
34927
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
34928
|
+
res.setHeader("content-length", String(Buffer.byteLength(body)));
|
|
34929
|
+
res.end(body);
|
|
34930
|
+
}
|
|
34931
|
+
async handleAdminConsole(req, res, pathname) {
|
|
34932
|
+
if (req.method !== "GET") return false;
|
|
34933
|
+
if (pathname === "/engram/ui" || pathname === "/engram/ui/") {
|
|
34934
|
+
await this.respondStatic(res, path64.join(this.adminConsolePublicDir, "index.html"), "text/html; charset=utf-8");
|
|
34935
|
+
return true;
|
|
34936
|
+
}
|
|
34937
|
+
if (pathname === "/engram/ui/app.js") {
|
|
34938
|
+
await this.respondStatic(res, path64.join(this.adminConsolePublicDir, "app.js"), "application/javascript; charset=utf-8");
|
|
34939
|
+
return true;
|
|
34859
34940
|
}
|
|
34941
|
+
return false;
|
|
34860
34942
|
}
|
|
34861
|
-
|
|
34862
|
-
|
|
34863
|
-
|
|
34864
|
-
|
|
34865
|
-
|
|
34866
|
-
|
|
34867
|
-
|
|
34868
|
-
|
|
34869
|
-
|
|
34870
|
-
|
|
34871
|
-
});
|
|
34872
|
-
}).finally(() => {
|
|
34873
|
-
if (this.flushTask === task) {
|
|
34874
|
-
this.flushTask = null;
|
|
34875
|
-
}
|
|
34876
|
-
if (this.buffer.length > 0) {
|
|
34877
|
-
this.scheduleFlush(output);
|
|
34878
|
-
}
|
|
34879
|
-
});
|
|
34880
|
-
this.flushTask = task;
|
|
34943
|
+
async respondStatic(res, filePath, contentType) {
|
|
34944
|
+
try {
|
|
34945
|
+
const body = await readFile39(filePath, "utf-8");
|
|
34946
|
+
res.statusCode = 200;
|
|
34947
|
+
res.setHeader("content-type", contentType);
|
|
34948
|
+
res.setHeader("content-length", String(Buffer.byteLength(body)));
|
|
34949
|
+
res.end(body);
|
|
34950
|
+
} catch {
|
|
34951
|
+
this.respondJson(res, 404, { error: "not_found" });
|
|
34952
|
+
}
|
|
34881
34953
|
}
|
|
34882
|
-
async
|
|
34883
|
-
|
|
34884
|
-
|
|
34885
|
-
|
|
34886
|
-
const
|
|
34887
|
-
|
|
34888
|
-
|
|
34889
|
-
|
|
34890
|
-
this.buffer = Buffer.alloc(0);
|
|
34891
|
-
return;
|
|
34892
|
-
}
|
|
34893
|
-
const contentLength = parseInt(contentLengthHeader.split(":")[1]?.trim() ?? "0", 10);
|
|
34894
|
-
if (!Number.isFinite(contentLength) || contentLength < 0) {
|
|
34895
|
-
this.buffer = Buffer.alloc(0);
|
|
34896
|
-
return;
|
|
34897
|
-
}
|
|
34898
|
-
const messageStart = headerEnd + 4;
|
|
34899
|
-
const messageEnd = messageStart + contentLength;
|
|
34900
|
-
if (this.buffer.length < messageEnd) return;
|
|
34901
|
-
const body = this.buffer.slice(messageStart, messageEnd).toString("utf-8");
|
|
34902
|
-
this.buffer = this.buffer.slice(messageEnd);
|
|
34903
|
-
let parsed;
|
|
34904
|
-
try {
|
|
34905
|
-
parsed = JSON.parse(body);
|
|
34906
|
-
} catch {
|
|
34907
|
-
this.writeMessage(output, {
|
|
34908
|
-
jsonrpc: "2.0",
|
|
34909
|
-
id: null,
|
|
34910
|
-
error: {
|
|
34911
|
-
code: -32700,
|
|
34912
|
-
message: "Parse error"
|
|
34913
|
-
}
|
|
34914
|
-
});
|
|
34915
|
-
continue;
|
|
34916
|
-
}
|
|
34917
|
-
const response = await this.handleRequest(parsed);
|
|
34918
|
-
if (response) {
|
|
34919
|
-
this.writeMessage(output, response);
|
|
34954
|
+
async readJsonBody(req) {
|
|
34955
|
+
const chunks = [];
|
|
34956
|
+
let total = 0;
|
|
34957
|
+
for await (const chunk of req) {
|
|
34958
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
34959
|
+
total += buffer.length;
|
|
34960
|
+
if (total > this.maxBodyBytes) {
|
|
34961
|
+
throw new HttpError(413, "request_body_too_large");
|
|
34920
34962
|
}
|
|
34963
|
+
chunks.push(buffer);
|
|
34964
|
+
}
|
|
34965
|
+
if (chunks.length === 0) return {};
|
|
34966
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
34967
|
+
if (raw.length === 0) return {};
|
|
34968
|
+
let parsed;
|
|
34969
|
+
try {
|
|
34970
|
+
parsed = JSON.parse(raw);
|
|
34971
|
+
} catch {
|
|
34972
|
+
throw new HttpError(400, "invalid_json");
|
|
34973
|
+
}
|
|
34974
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
34975
|
+
throw new HttpError(400, "invalid_json_object");
|
|
34921
34976
|
}
|
|
34977
|
+
return parsed;
|
|
34922
34978
|
}
|
|
34923
|
-
|
|
34924
|
-
|
|
34925
|
-
const
|
|
34926
|
-
|
|
34927
|
-
|
|
34928
|
-
|
|
34979
|
+
isAuthorized(req) {
|
|
34980
|
+
if (!this.authToken) return false;
|
|
34981
|
+
const raw = req.headers.authorization;
|
|
34982
|
+
if (!raw) return false;
|
|
34983
|
+
const separator = raw.indexOf(" ");
|
|
34984
|
+
if (separator <= 0) return false;
|
|
34985
|
+
const scheme = raw.slice(0, separator).toLowerCase();
|
|
34986
|
+
if (scheme !== "bearer") return false;
|
|
34987
|
+
const token = raw.slice(separator + 1).trim();
|
|
34988
|
+
return this.timingSafeStringEqual(token, this.authToken);
|
|
34929
34989
|
}
|
|
34930
|
-
|
|
34931
|
-
|
|
34932
|
-
|
|
34933
|
-
|
|
34934
|
-
|
|
34935
|
-
|
|
34936
|
-
|
|
34937
|
-
|
|
34938
|
-
|
|
34939
|
-
|
|
34940
|
-
|
|
34941
|
-
|
|
34942
|
-
|
|
34943
|
-
|
|
34944
|
-
|
|
34945
|
-
|
|
34946
|
-
|
|
34947
|
-
|
|
34948
|
-
|
|
34949
|
-
|
|
34950
|
-
|
|
34951
|
-
|
|
34952
|
-
|
|
34953
|
-
|
|
34954
|
-
|
|
34955
|
-
|
|
34956
|
-
limit
|
|
34957
|
-
);
|
|
34958
|
-
}
|
|
34959
|
-
case "engram.memory_store":
|
|
34960
|
-
return this.service.memoryStore({
|
|
34961
|
-
schemaVersion: typeof args.schemaVersion === "number" ? args.schemaVersion : void 0,
|
|
34962
|
-
idempotencyKey: typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0,
|
|
34963
|
-
dryRun: args.dryRun === true,
|
|
34964
|
-
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : void 0,
|
|
34965
|
-
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34966
|
-
content: typeof args.content === "string" ? args.content : "",
|
|
34967
|
-
category: typeof args.category === "string" ? args.category : void 0,
|
|
34968
|
-
confidence: typeof args.confidence === "number" ? args.confidence : void 0,
|
|
34969
|
-
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
34970
|
-
tags: Array.isArray(args.tags) ? args.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34971
|
-
entityRef: typeof args.entityRef === "string" ? args.entityRef : void 0,
|
|
34972
|
-
ttl: typeof args.ttl === "string" ? args.ttl : void 0,
|
|
34973
|
-
sourceReason: typeof args.sourceReason === "string" ? args.sourceReason : void 0
|
|
34974
|
-
});
|
|
34975
|
-
case "engram.suggestion_submit":
|
|
34976
|
-
return this.service.suggestionSubmit({
|
|
34977
|
-
schemaVersion: typeof args.schemaVersion === "number" ? args.schemaVersion : void 0,
|
|
34978
|
-
idempotencyKey: typeof args.idempotencyKey === "string" ? args.idempotencyKey : void 0,
|
|
34979
|
-
dryRun: args.dryRun === true,
|
|
34980
|
-
sessionKey: typeof args.sessionKey === "string" ? args.sessionKey : void 0,
|
|
34981
|
-
authenticatedPrincipal: this.authenticatedPrincipal,
|
|
34982
|
-
content: typeof args.content === "string" ? args.content : "",
|
|
34983
|
-
category: typeof args.category === "string" ? args.category : void 0,
|
|
34984
|
-
confidence: typeof args.confidence === "number" ? args.confidence : void 0,
|
|
34985
|
-
namespace: typeof args.namespace === "string" ? args.namespace : void 0,
|
|
34986
|
-
tags: Array.isArray(args.tags) ? args.tags.filter((tag) => typeof tag === "string") : void 0,
|
|
34987
|
-
entityRef: typeof args.entityRef === "string" ? args.entityRef : void 0,
|
|
34988
|
-
ttl: typeof args.ttl === "string" ? args.ttl : void 0,
|
|
34989
|
-
sourceReason: typeof args.sourceReason === "string" ? args.sourceReason : void 0
|
|
34990
|
-
});
|
|
34991
|
-
case "engram.entity_get":
|
|
34992
|
-
return this.service.entityGet(
|
|
34993
|
-
typeof args.name === "string" ? args.name : "",
|
|
34994
|
-
typeof args.namespace === "string" ? args.namespace : void 0
|
|
34995
|
-
);
|
|
34996
|
-
case "engram.review_queue_list":
|
|
34997
|
-
return this.service.reviewQueue(
|
|
34998
|
-
typeof args.runId === "string" ? args.runId : void 0,
|
|
34999
|
-
typeof args.namespace === "string" ? args.namespace : void 0
|
|
35000
|
-
);
|
|
35001
|
-
default:
|
|
35002
|
-
throw new Error(`unknown tool: ${name}`);
|
|
34990
|
+
timingSafeStringEqual(a, b) {
|
|
34991
|
+
const left = this.encodeSecret(a);
|
|
34992
|
+
const right = this.encodeSecret(b);
|
|
34993
|
+
if (!left || !right) return false;
|
|
34994
|
+
return timingSafeEqual2(left, right);
|
|
34995
|
+
}
|
|
34996
|
+
encodeSecret(value) {
|
|
34997
|
+
const encoded = Buffer.from(value, "utf-8");
|
|
34998
|
+
if (encoded.length > 1024) return null;
|
|
34999
|
+
const out = Buffer.alloc(2 + 1024);
|
|
35000
|
+
out.writeUInt16BE(encoded.length, 0);
|
|
35001
|
+
encoded.copy(out, 2);
|
|
35002
|
+
return out;
|
|
35003
|
+
}
|
|
35004
|
+
writeResponseStatus(response) {
|
|
35005
|
+
if (response.dryRun === true) return 200;
|
|
35006
|
+
if (response.status === "stored" || response.status === "queued_for_review") return 201;
|
|
35007
|
+
return 200;
|
|
35008
|
+
}
|
|
35009
|
+
ensureWriteRateLimitAvailable() {
|
|
35010
|
+
const now = Date.now();
|
|
35011
|
+
while (this.writeRequestTimestamps.length > 0 && now - (this.writeRequestTimestamps[0] ?? 0) > WRITE_RATE_LIMIT_WINDOW_MS) {
|
|
35012
|
+
this.writeRequestTimestamps.shift();
|
|
35013
|
+
}
|
|
35014
|
+
if (this.writeRequestTimestamps.length >= WRITE_RATE_LIMIT_MAX_REQUESTS) {
|
|
35015
|
+
throw new HttpError(429, "write_rate_limited");
|
|
35003
35016
|
}
|
|
35004
35017
|
}
|
|
35018
|
+
recordWriteRateLimitHit() {
|
|
35019
|
+
this.writeRequestTimestamps.push(Date.now());
|
|
35020
|
+
}
|
|
35021
|
+
shouldCountWriteRateLimit(response) {
|
|
35022
|
+
return response.dryRun !== true && response.idempotencyReplay !== true;
|
|
35023
|
+
}
|
|
35005
35024
|
};
|
|
35006
35025
|
|
|
35007
35026
|
// src/compat/checks.ts
|