@hasna/mementos 0.8.0 → 0.9.0
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/cli/index.js +508 -1
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/session-jobs.d.ts +49 -0
- package/dist/db/session-jobs.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/lib/open-sessions-connector.d.ts +60 -0
- package/dist/lib/open-sessions-connector.d.ts.map +1 -0
- package/dist/lib/session-auto-resolve.d.ts +28 -0
- package/dist/lib/session-auto-resolve.d.ts.map +1 -0
- package/dist/lib/session-processor.d.ts +38 -0
- package/dist/lib/session-processor.d.ts.map +1 -0
- package/dist/lib/session-queue.d.ts +28 -0
- package/dist/lib/session-queue.d.ts.map +1 -0
- package/dist/mcp/index.js +413 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +518 -1
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-resolve agent_id and project_id from session metadata.
|
|
3
|
+
* When a session is ingested without explicit agent/project context,
|
|
4
|
+
* try to detect them from available metadata fields.
|
|
5
|
+
*/
|
|
6
|
+
import type { Database } from "bun:sqlite";
|
|
7
|
+
export interface AutoResolveResult {
|
|
8
|
+
agentId: string | null;
|
|
9
|
+
projectId: string | null;
|
|
10
|
+
confidence: "high" | "low" | "none";
|
|
11
|
+
method: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Attempt to resolve agent_id and project_id from metadata fields.
|
|
15
|
+
*
|
|
16
|
+
* Strategy:
|
|
17
|
+
* 1. If metadata.agentName matches an existing agent → agentId = agent.id, confidence=high
|
|
18
|
+
* 2. If metadata.workingDir matches a registered project path → projectId = project.id, confidence=high
|
|
19
|
+
* 3. If metadata.gitRemote contains a repo name that matches a project name → confidence=low
|
|
20
|
+
* 4. Otherwise confidence=none
|
|
21
|
+
*/
|
|
22
|
+
export declare function autoResolveAgentProject(metadata: {
|
|
23
|
+
workingDir?: string;
|
|
24
|
+
agentName?: string;
|
|
25
|
+
gitRemote?: string;
|
|
26
|
+
sessionSource?: string;
|
|
27
|
+
}, db?: Database): AutoResolveResult;
|
|
28
|
+
//# sourceMappingURL=session-auto-resolve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-auto-resolve.d.ts","sourceRoot":"","sources":["../../src/lib/session-auto-resolve.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAQ3C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE;IACR,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,EACD,EAAE,CAAC,EAAE,QAAQ,GACZ,iBAAiB,CAsFnB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session transcript processor.
|
|
3
|
+
* Takes a full session transcript, chunks it, calls LLM to extract memories,
|
|
4
|
+
* saves extracted memories tagged with session_id + source.
|
|
5
|
+
* All failures are silently caught — never throws.
|
|
6
|
+
*/
|
|
7
|
+
import type { Database } from "bun:sqlite";
|
|
8
|
+
export interface ChunkResult {
|
|
9
|
+
chunkIndex: number;
|
|
10
|
+
memoriesExtracted: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ProcessingResult {
|
|
13
|
+
jobId: string;
|
|
14
|
+
chunksProcessed: number;
|
|
15
|
+
memoriesExtracted: number;
|
|
16
|
+
errors: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Split a transcript into chunks with overlap.
|
|
20
|
+
*/
|
|
21
|
+
export declare function chunkTranscript(transcript: string, chunkSize?: number, overlap?: number): string[];
|
|
22
|
+
/**
|
|
23
|
+
* Extract memories from a single chunk using the available LLM provider.
|
|
24
|
+
* Returns the count of memories saved.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractMemoriesFromChunk(chunk: string, context: {
|
|
27
|
+
sessionId: string;
|
|
28
|
+
agentId?: string;
|
|
29
|
+
projectId?: string;
|
|
30
|
+
source?: string;
|
|
31
|
+
}, db?: Database): Promise<number>;
|
|
32
|
+
/**
|
|
33
|
+
* Process a session memory job end-to-end.
|
|
34
|
+
* Fetches the job, marks it processing, chunks transcript, extracts memories.
|
|
35
|
+
* Updates job status to completed or failed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function processSessionJob(jobId: string, db?: Database): Promise<ProcessingResult>;
|
|
38
|
+
//# sourceMappingURL=session-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-processor.d.ts","sourceRoot":"","sources":["../../src/lib/session-processor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAa3C,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAaD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,SAAS,SAAO,EAChB,OAAO,SAAM,GACZ,MAAM,EAAE,CAeV;AAMD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IACP,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,EACD,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,MAAM,CAAC,CAyHjB;AAMD;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,EAAE,CAAC,EAAE,QAAQ,GACZ,OAAO,CAAC,gBAAgB,CAAC,CA+F3B"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background queue for session memory job processing.
|
|
3
|
+
* Polls for pending jobs every 5 seconds, processes one at a time (concurrency=1).
|
|
4
|
+
* Fire-and-forget: enqueueSessionJob() returns immediately, processing happens async.
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionQueueStats {
|
|
7
|
+
pending: number;
|
|
8
|
+
processing: number;
|
|
9
|
+
completed: number;
|
|
10
|
+
failed: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Enqueue a job ID for processing. Fire-and-forget.
|
|
14
|
+
* The background worker will pick it up within the next polling interval.
|
|
15
|
+
*/
|
|
16
|
+
export declare function enqueueSessionJob(jobId: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get in-memory queue stats.
|
|
19
|
+
* Note: pending/completed/failed counts come from DB when a full scan is needed;
|
|
20
|
+
* this returns a lightweight in-memory snapshot.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getSessionQueueStats(): SessionQueueStats;
|
|
23
|
+
/**
|
|
24
|
+
* Start the background polling worker.
|
|
25
|
+
* Idempotent — safe to call multiple times (only starts once).
|
|
26
|
+
*/
|
|
27
|
+
export declare function startSessionQueueWorker(): void;
|
|
28
|
+
//# sourceMappingURL=session-queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-queue.d.ts","sourceRoot":"","sources":["../../src/lib/session-queue.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAeD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMrD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,iBAAiB,CA6BxD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAQ9C"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -491,7 +491,30 @@ var init_database = __esm(() => {
|
|
|
491
491
|
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_type ON webhook_hooks(type);
|
|
492
492
|
CREATE INDEX IF NOT EXISTS idx_webhook_hooks_enabled ON webhook_hooks(enabled);
|
|
493
493
|
INSERT OR IGNORE INTO _migrations (id) VALUES (10);
|
|
494
|
-
|
|
494
|
+
`,
|
|
495
|
+
`
|
|
496
|
+
CREATE TABLE IF NOT EXISTS session_memory_jobs (
|
|
497
|
+
id TEXT PRIMARY KEY,
|
|
498
|
+
session_id TEXT NOT NULL,
|
|
499
|
+
agent_id TEXT,
|
|
500
|
+
project_id TEXT,
|
|
501
|
+
source TEXT NOT NULL DEFAULT 'manual' CHECK(source IN ('claude-code','codex','manual','open-sessions')),
|
|
502
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending','processing','completed','failed')),
|
|
503
|
+
transcript TEXT NOT NULL,
|
|
504
|
+
chunk_count INTEGER NOT NULL DEFAULT 0,
|
|
505
|
+
memories_extracted INTEGER NOT NULL DEFAULT 0,
|
|
506
|
+
error TEXT,
|
|
507
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
508
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
509
|
+
started_at TEXT,
|
|
510
|
+
completed_at TEXT
|
|
511
|
+
);
|
|
512
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_status ON session_memory_jobs(status);
|
|
513
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_agent ON session_memory_jobs(agent_id);
|
|
514
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_project ON session_memory_jobs(project_id);
|
|
515
|
+
CREATE INDEX IF NOT EXISTS idx_session_memory_jobs_session ON session_memory_jobs(session_id);
|
|
516
|
+
INSERT OR IGNORE INTO _migrations (id) VALUES (13);
|
|
517
|
+
`
|
|
495
518
|
];
|
|
496
519
|
});
|
|
497
520
|
|
|
@@ -8494,6 +8517,351 @@ function getSynthesisStatus(runId, projectId, db) {
|
|
|
8494
8517
|
|
|
8495
8518
|
// src/mcp/index.ts
|
|
8496
8519
|
init_synthesis();
|
|
8520
|
+
|
|
8521
|
+
// src/db/session-jobs.ts
|
|
8522
|
+
init_database();
|
|
8523
|
+
function parseJobRow(row) {
|
|
8524
|
+
return {
|
|
8525
|
+
id: row["id"],
|
|
8526
|
+
session_id: row["session_id"],
|
|
8527
|
+
agent_id: row["agent_id"] || null,
|
|
8528
|
+
project_id: row["project_id"] || null,
|
|
8529
|
+
source: row["source"],
|
|
8530
|
+
status: row["status"],
|
|
8531
|
+
transcript: row["transcript"],
|
|
8532
|
+
chunk_count: row["chunk_count"],
|
|
8533
|
+
memories_extracted: row["memories_extracted"],
|
|
8534
|
+
error: row["error"] || null,
|
|
8535
|
+
metadata: JSON.parse(row["metadata"] || "{}"),
|
|
8536
|
+
created_at: row["created_at"],
|
|
8537
|
+
started_at: row["started_at"] || null,
|
|
8538
|
+
completed_at: row["completed_at"] || null
|
|
8539
|
+
};
|
|
8540
|
+
}
|
|
8541
|
+
function createSessionJob(input, db) {
|
|
8542
|
+
const d = db || getDatabase();
|
|
8543
|
+
const id = uuid();
|
|
8544
|
+
const timestamp = now();
|
|
8545
|
+
const source = input.source ?? "manual";
|
|
8546
|
+
const metadata = JSON.stringify(input.metadata ?? {});
|
|
8547
|
+
d.run(`INSERT INTO session_memory_jobs
|
|
8548
|
+
(id, session_id, agent_id, project_id, source, status, transcript, chunk_count, memories_extracted, metadata, created_at)
|
|
8549
|
+
VALUES (?, ?, ?, ?, ?, 'pending', ?, 0, 0, ?, ?)`, [
|
|
8550
|
+
id,
|
|
8551
|
+
input.session_id,
|
|
8552
|
+
input.agent_id ?? null,
|
|
8553
|
+
input.project_id ?? null,
|
|
8554
|
+
source,
|
|
8555
|
+
input.transcript,
|
|
8556
|
+
metadata,
|
|
8557
|
+
timestamp
|
|
8558
|
+
]);
|
|
8559
|
+
return getSessionJob(id, d);
|
|
8560
|
+
}
|
|
8561
|
+
function getSessionJob(id, db) {
|
|
8562
|
+
const d = db || getDatabase();
|
|
8563
|
+
const row = d.query("SELECT * FROM session_memory_jobs WHERE id = ?").get(id);
|
|
8564
|
+
if (!row)
|
|
8565
|
+
return null;
|
|
8566
|
+
return parseJobRow(row);
|
|
8567
|
+
}
|
|
8568
|
+
function listSessionJobs(filter, db) {
|
|
8569
|
+
const d = db || getDatabase();
|
|
8570
|
+
const conditions = [];
|
|
8571
|
+
const params = [];
|
|
8572
|
+
if (filter?.agent_id) {
|
|
8573
|
+
conditions.push("agent_id = ?");
|
|
8574
|
+
params.push(filter.agent_id);
|
|
8575
|
+
}
|
|
8576
|
+
if (filter?.project_id) {
|
|
8577
|
+
conditions.push("project_id = ?");
|
|
8578
|
+
params.push(filter.project_id);
|
|
8579
|
+
}
|
|
8580
|
+
if (filter?.status) {
|
|
8581
|
+
conditions.push("status = ?");
|
|
8582
|
+
params.push(filter.status);
|
|
8583
|
+
}
|
|
8584
|
+
if (filter?.session_id) {
|
|
8585
|
+
conditions.push("session_id = ?");
|
|
8586
|
+
params.push(filter.session_id);
|
|
8587
|
+
}
|
|
8588
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
8589
|
+
const limit = filter?.limit ?? 20;
|
|
8590
|
+
const offset = filter?.offset ?? 0;
|
|
8591
|
+
const rows = d.query(`SELECT * FROM session_memory_jobs ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
|
|
8592
|
+
return rows.map(parseJobRow);
|
|
8593
|
+
}
|
|
8594
|
+
function updateSessionJob(id, updates, db) {
|
|
8595
|
+
const d = db || getDatabase();
|
|
8596
|
+
const setClauses = [];
|
|
8597
|
+
const params = [];
|
|
8598
|
+
if (updates.status !== undefined) {
|
|
8599
|
+
setClauses.push("status = ?");
|
|
8600
|
+
params.push(updates.status);
|
|
8601
|
+
}
|
|
8602
|
+
if (updates.chunk_count !== undefined) {
|
|
8603
|
+
setClauses.push("chunk_count = ?");
|
|
8604
|
+
params.push(updates.chunk_count);
|
|
8605
|
+
}
|
|
8606
|
+
if (updates.memories_extracted !== undefined) {
|
|
8607
|
+
setClauses.push("memories_extracted = ?");
|
|
8608
|
+
params.push(updates.memories_extracted);
|
|
8609
|
+
}
|
|
8610
|
+
if ("error" in updates) {
|
|
8611
|
+
setClauses.push("error = ?");
|
|
8612
|
+
params.push(updates.error ?? null);
|
|
8613
|
+
}
|
|
8614
|
+
if ("started_at" in updates) {
|
|
8615
|
+
setClauses.push("started_at = ?");
|
|
8616
|
+
params.push(updates.started_at ?? null);
|
|
8617
|
+
}
|
|
8618
|
+
if ("completed_at" in updates) {
|
|
8619
|
+
setClauses.push("completed_at = ?");
|
|
8620
|
+
params.push(updates.completed_at ?? null);
|
|
8621
|
+
}
|
|
8622
|
+
if (setClauses.length === 0)
|
|
8623
|
+
return getSessionJob(id, d);
|
|
8624
|
+
params.push(id);
|
|
8625
|
+
d.run(`UPDATE session_memory_jobs SET ${setClauses.join(", ")} WHERE id = ?`, params);
|
|
8626
|
+
return getSessionJob(id, d);
|
|
8627
|
+
}
|
|
8628
|
+
function getNextPendingJob(db) {
|
|
8629
|
+
const d = db || getDatabase();
|
|
8630
|
+
const row = d.query("SELECT * FROM session_memory_jobs WHERE status = 'pending' ORDER BY created_at ASC LIMIT 1").get();
|
|
8631
|
+
if (!row)
|
|
8632
|
+
return null;
|
|
8633
|
+
return parseJobRow(row);
|
|
8634
|
+
}
|
|
8635
|
+
|
|
8636
|
+
// src/lib/session-queue.ts
|
|
8637
|
+
init_database();
|
|
8638
|
+
|
|
8639
|
+
// src/lib/session-processor.ts
|
|
8640
|
+
init_memories();
|
|
8641
|
+
init_registry();
|
|
8642
|
+
var SESSION_EXTRACTION_USER_TEMPLATE = (chunk, sessionId) => `Extract memories from this session chunk (session: ${sessionId}):
|
|
8643
|
+
|
|
8644
|
+
${chunk}
|
|
8645
|
+
|
|
8646
|
+
Return JSON array: [{"key": "...", "value": "...", "category": "knowledge|fact|preference|history", "importance": 1-10, "tags": [...]}]`;
|
|
8647
|
+
function chunkTranscript(transcript, chunkSize = 2000, overlap = 200) {
|
|
8648
|
+
if (!transcript || transcript.length === 0)
|
|
8649
|
+
return [];
|
|
8650
|
+
if (transcript.length <= chunkSize)
|
|
8651
|
+
return [transcript];
|
|
8652
|
+
const chunks = [];
|
|
8653
|
+
let start = 0;
|
|
8654
|
+
while (start < transcript.length) {
|
|
8655
|
+
const end = Math.min(start + chunkSize, transcript.length);
|
|
8656
|
+
chunks.push(transcript.slice(start, end));
|
|
8657
|
+
if (end === transcript.length)
|
|
8658
|
+
break;
|
|
8659
|
+
start += chunkSize - overlap;
|
|
8660
|
+
}
|
|
8661
|
+
return chunks;
|
|
8662
|
+
}
|
|
8663
|
+
async function extractMemoriesFromChunk(chunk, context, db) {
|
|
8664
|
+
const provider = providerRegistry.getAvailable();
|
|
8665
|
+
if (!provider)
|
|
8666
|
+
return 0;
|
|
8667
|
+
try {
|
|
8668
|
+
const extracted = await provider.extractMemories(SESSION_EXTRACTION_USER_TEMPLATE(chunk, context.sessionId), {
|
|
8669
|
+
sessionId: context.sessionId,
|
|
8670
|
+
agentId: context.agentId,
|
|
8671
|
+
projectId: context.projectId
|
|
8672
|
+
});
|
|
8673
|
+
let savedCount = 0;
|
|
8674
|
+
const sourceTag = context.source ? `source:${context.source}` : "source:manual";
|
|
8675
|
+
for (const memory of extracted) {
|
|
8676
|
+
if (!memory.content || !memory.content.trim())
|
|
8677
|
+
continue;
|
|
8678
|
+
try {
|
|
8679
|
+
createMemory({
|
|
8680
|
+
key: memory.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
|
|
8681
|
+
value: memory.content,
|
|
8682
|
+
category: memory.category,
|
|
8683
|
+
scope: memory.suggestedScope ?? "shared",
|
|
8684
|
+
importance: memory.importance,
|
|
8685
|
+
tags: [
|
|
8686
|
+
...memory.tags,
|
|
8687
|
+
"session-extracted",
|
|
8688
|
+
sourceTag,
|
|
8689
|
+
`session:${context.sessionId}`
|
|
8690
|
+
],
|
|
8691
|
+
source: "auto",
|
|
8692
|
+
agent_id: context.agentId,
|
|
8693
|
+
project_id: context.projectId,
|
|
8694
|
+
session_id: context.sessionId,
|
|
8695
|
+
metadata: {
|
|
8696
|
+
auto_extracted: true,
|
|
8697
|
+
session_source: context.source ?? "manual",
|
|
8698
|
+
extracted_at: new Date().toISOString(),
|
|
8699
|
+
reasoning: memory.reasoning
|
|
8700
|
+
}
|
|
8701
|
+
}, "merge", db);
|
|
8702
|
+
savedCount++;
|
|
8703
|
+
} catch {}
|
|
8704
|
+
}
|
|
8705
|
+
return savedCount;
|
|
8706
|
+
} catch {
|
|
8707
|
+
try {
|
|
8708
|
+
const fallbacks = providerRegistry.getFallbacks();
|
|
8709
|
+
for (const fallback of fallbacks) {
|
|
8710
|
+
try {
|
|
8711
|
+
const extracted = await fallback.extractMemories(SESSION_EXTRACTION_USER_TEMPLATE(chunk, context.sessionId), {
|
|
8712
|
+
sessionId: context.sessionId,
|
|
8713
|
+
agentId: context.agentId,
|
|
8714
|
+
projectId: context.projectId
|
|
8715
|
+
});
|
|
8716
|
+
let savedCount = 0;
|
|
8717
|
+
const sourceTag = context.source ? `source:${context.source}` : "source:manual";
|
|
8718
|
+
for (const memory of extracted) {
|
|
8719
|
+
if (!memory.content || !memory.content.trim())
|
|
8720
|
+
continue;
|
|
8721
|
+
try {
|
|
8722
|
+
createMemory({
|
|
8723
|
+
key: memory.content.slice(0, 120).replace(/\s+/g, "-").toLowerCase(),
|
|
8724
|
+
value: memory.content,
|
|
8725
|
+
category: memory.category,
|
|
8726
|
+
scope: memory.suggestedScope ?? "shared",
|
|
8727
|
+
importance: memory.importance,
|
|
8728
|
+
tags: [
|
|
8729
|
+
...memory.tags,
|
|
8730
|
+
"session-extracted",
|
|
8731
|
+
sourceTag,
|
|
8732
|
+
`session:${context.sessionId}`
|
|
8733
|
+
],
|
|
8734
|
+
source: "auto",
|
|
8735
|
+
agent_id: context.agentId,
|
|
8736
|
+
project_id: context.projectId,
|
|
8737
|
+
session_id: context.sessionId,
|
|
8738
|
+
metadata: {
|
|
8739
|
+
auto_extracted: true,
|
|
8740
|
+
session_source: context.source ?? "manual",
|
|
8741
|
+
extracted_at: new Date().toISOString(),
|
|
8742
|
+
reasoning: memory.reasoning
|
|
8743
|
+
}
|
|
8744
|
+
}, "merge", db);
|
|
8745
|
+
savedCount++;
|
|
8746
|
+
} catch {}
|
|
8747
|
+
}
|
|
8748
|
+
if (savedCount > 0)
|
|
8749
|
+
return savedCount;
|
|
8750
|
+
} catch {
|
|
8751
|
+
continue;
|
|
8752
|
+
}
|
|
8753
|
+
}
|
|
8754
|
+
} catch {}
|
|
8755
|
+
return 0;
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8758
|
+
async function processSessionJob(jobId, db) {
|
|
8759
|
+
const result = {
|
|
8760
|
+
jobId,
|
|
8761
|
+
chunksProcessed: 0,
|
|
8762
|
+
memoriesExtracted: 0,
|
|
8763
|
+
errors: []
|
|
8764
|
+
};
|
|
8765
|
+
let job;
|
|
8766
|
+
try {
|
|
8767
|
+
job = getSessionJob(jobId, db);
|
|
8768
|
+
if (!job) {
|
|
8769
|
+
result.errors.push(`Job not found: ${jobId}`);
|
|
8770
|
+
return result;
|
|
8771
|
+
}
|
|
8772
|
+
} catch (e) {
|
|
8773
|
+
result.errors.push(`Failed to fetch job: ${String(e)}`);
|
|
8774
|
+
return result;
|
|
8775
|
+
}
|
|
8776
|
+
try {
|
|
8777
|
+
updateSessionJob(jobId, { status: "processing", started_at: new Date().toISOString() }, db);
|
|
8778
|
+
} catch (e) {
|
|
8779
|
+
result.errors.push(`Failed to mark job as processing: ${String(e)}`);
|
|
8780
|
+
return result;
|
|
8781
|
+
}
|
|
8782
|
+
const chunks = chunkTranscript(job.transcript);
|
|
8783
|
+
try {
|
|
8784
|
+
updateSessionJob(jobId, { chunk_count: chunks.length }, db);
|
|
8785
|
+
} catch {}
|
|
8786
|
+
let totalMemories = 0;
|
|
8787
|
+
for (let i = 0;i < chunks.length; i++) {
|
|
8788
|
+
const chunk = chunks[i];
|
|
8789
|
+
try {
|
|
8790
|
+
const count = await extractMemoriesFromChunk(chunk, {
|
|
8791
|
+
sessionId: job.session_id,
|
|
8792
|
+
agentId: job.agent_id ?? undefined,
|
|
8793
|
+
projectId: job.project_id ?? undefined,
|
|
8794
|
+
source: job.source
|
|
8795
|
+
}, db);
|
|
8796
|
+
totalMemories += count;
|
|
8797
|
+
result.chunksProcessed++;
|
|
8798
|
+
} catch (e) {
|
|
8799
|
+
result.errors.push(`Chunk ${i} failed: ${String(e)}`);
|
|
8800
|
+
}
|
|
8801
|
+
}
|
|
8802
|
+
result.memoriesExtracted = totalMemories;
|
|
8803
|
+
try {
|
|
8804
|
+
if (result.errors.length > 0 && result.chunksProcessed === 0) {
|
|
8805
|
+
updateSessionJob(jobId, {
|
|
8806
|
+
status: "failed",
|
|
8807
|
+
error: result.errors.join("; "),
|
|
8808
|
+
completed_at: new Date().toISOString(),
|
|
8809
|
+
memories_extracted: totalMemories,
|
|
8810
|
+
chunk_count: chunks.length
|
|
8811
|
+
}, db);
|
|
8812
|
+
} else {
|
|
8813
|
+
updateSessionJob(jobId, {
|
|
8814
|
+
status: "completed",
|
|
8815
|
+
completed_at: new Date().toISOString(),
|
|
8816
|
+
memories_extracted: totalMemories,
|
|
8817
|
+
chunk_count: chunks.length
|
|
8818
|
+
}, db);
|
|
8819
|
+
}
|
|
8820
|
+
} catch (e) {
|
|
8821
|
+
result.errors.push(`Failed to update job status: ${String(e)}`);
|
|
8822
|
+
}
|
|
8823
|
+
return result;
|
|
8824
|
+
}
|
|
8825
|
+
|
|
8826
|
+
// src/lib/session-queue.ts
|
|
8827
|
+
var _pendingQueue = new Set;
|
|
8828
|
+
var _isProcessing = false;
|
|
8829
|
+
function enqueueSessionJob(jobId) {
|
|
8830
|
+
_pendingQueue.add(jobId);
|
|
8831
|
+
if (!_isProcessing) {
|
|
8832
|
+
_processNext();
|
|
8833
|
+
}
|
|
8834
|
+
}
|
|
8835
|
+
async function _processNext() {
|
|
8836
|
+
if (_isProcessing)
|
|
8837
|
+
return;
|
|
8838
|
+
let jobId;
|
|
8839
|
+
if (_pendingQueue.size > 0) {
|
|
8840
|
+
jobId = [..._pendingQueue][0];
|
|
8841
|
+
_pendingQueue.delete(jobId);
|
|
8842
|
+
} else {
|
|
8843
|
+
try {
|
|
8844
|
+
const job = getNextPendingJob();
|
|
8845
|
+
if (job)
|
|
8846
|
+
jobId = job.id;
|
|
8847
|
+
} catch {
|
|
8848
|
+
return;
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
if (!jobId)
|
|
8852
|
+
return;
|
|
8853
|
+
_isProcessing = true;
|
|
8854
|
+
try {
|
|
8855
|
+
await processSessionJob(jobId);
|
|
8856
|
+
} catch {} finally {
|
|
8857
|
+
_isProcessing = false;
|
|
8858
|
+
if (_pendingQueue.size > 0) {
|
|
8859
|
+
_processNext();
|
|
8860
|
+
}
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
|
|
8864
|
+
// src/mcp/index.ts
|
|
8497
8865
|
init_auto_memory();
|
|
8498
8866
|
init_registry();
|
|
8499
8867
|
import { createRequire } from "module";
|
|
@@ -10712,6 +11080,50 @@ server.tool("memory_auto_test", "Test memory extraction on text WITHOUT saving a
|
|
|
10712
11080
|
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
10713
11081
|
}
|
|
10714
11082
|
});
|
|
11083
|
+
server.tool("memory_ingest_session", "Submit a session transcript for async memory extraction. Returns job_id to track progress.", {
|
|
11084
|
+
transcript: exports_external.string(),
|
|
11085
|
+
session_id: exports_external.string(),
|
|
11086
|
+
agent_id: exports_external.string().optional(),
|
|
11087
|
+
project_id: exports_external.string().optional(),
|
|
11088
|
+
source: exports_external.enum(["claude-code", "codex", "manual", "open-sessions"]).optional()
|
|
11089
|
+
}, async (args) => {
|
|
11090
|
+
try {
|
|
11091
|
+
const job = createSessionJob({
|
|
11092
|
+
session_id: args.session_id,
|
|
11093
|
+
transcript: args.transcript,
|
|
11094
|
+
source: args.source ?? "manual",
|
|
11095
|
+
agent_id: args.agent_id,
|
|
11096
|
+
project_id: args.project_id
|
|
11097
|
+
});
|
|
11098
|
+
enqueueSessionJob(job.id);
|
|
11099
|
+
return { content: [{ type: "text", text: JSON.stringify({ job_id: job.id, status: "queued" }, null, 2) }] };
|
|
11100
|
+
} catch (e) {
|
|
11101
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11102
|
+
}
|
|
11103
|
+
});
|
|
11104
|
+
server.tool("memory_session_status", "Get the status of a session memory extraction job.", { job_id: exports_external.string() }, async (args) => {
|
|
11105
|
+
try {
|
|
11106
|
+
const job = getSessionJob(args.job_id);
|
|
11107
|
+
if (!job)
|
|
11108
|
+
return { content: [{ type: "text", text: `Job not found: ${args.job_id}` }], isError: true };
|
|
11109
|
+
return { content: [{ type: "text", text: JSON.stringify(job, null, 2) }] };
|
|
11110
|
+
} catch (e) {
|
|
11111
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11112
|
+
}
|
|
11113
|
+
});
|
|
11114
|
+
server.tool("memory_session_list", "List session memory extraction jobs.", {
|
|
11115
|
+
agent_id: exports_external.string().optional(),
|
|
11116
|
+
project_id: exports_external.string().optional(),
|
|
11117
|
+
status: exports_external.enum(["pending", "processing", "completed", "failed"]).optional(),
|
|
11118
|
+
limit: exports_external.coerce.number().optional()
|
|
11119
|
+
}, async (args) => {
|
|
11120
|
+
try {
|
|
11121
|
+
const jobs = listSessionJobs({ agent_id: args.agent_id, project_id: args.project_id, status: args.status, limit: args.limit ?? 20 });
|
|
11122
|
+
return { content: [{ type: "text", text: JSON.stringify(jobs, null, 2) }] };
|
|
11123
|
+
} catch (e) {
|
|
11124
|
+
return { content: [{ type: "text", text: formatError(e) }], isError: true };
|
|
11125
|
+
}
|
|
11126
|
+
});
|
|
10715
11127
|
async function main() {
|
|
10716
11128
|
loadWebhooksFromDb();
|
|
10717
11129
|
const transport = new StdioServerTransport;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AA6gDH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAkI9C"}
|