@shodh/memory-mcp 0.1.61 → 0.1.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/README.md +5 -2
- package/dist/index.js +2151 -544
- package/package.json +1 -1
- package/scripts/postinstall.cjs +2 -0
package/dist/index.js
CHANGED
|
@@ -4903,6 +4903,26 @@ if (!API_KEY) {
|
|
|
4903
4903
|
var RETRY_ATTEMPTS = 3;
|
|
4904
4904
|
var RETRY_DELAY_MS = 1000;
|
|
4905
4905
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
4906
|
+
var TOKEN_BUDGET = parseInt(process.env.SHODH_TOKEN_BUDGET || "100000", 10);
|
|
4907
|
+
var ALERT_THRESHOLD = parseFloat(process.env.SHODH_ALERT_THRESHOLD || "0.9");
|
|
4908
|
+
var sessionTokens = 0;
|
|
4909
|
+
var sessionStartTime = Date.now();
|
|
4910
|
+
function estimateTokens(text) {
|
|
4911
|
+
return Math.ceil(text.length / 4);
|
|
4912
|
+
}
|
|
4913
|
+
function getTokenStatus() {
|
|
4914
|
+
const percent = sessionTokens / TOKEN_BUDGET;
|
|
4915
|
+
return {
|
|
4916
|
+
tokens: sessionTokens,
|
|
4917
|
+
budget: TOKEN_BUDGET,
|
|
4918
|
+
percent: Math.round(percent * 100) / 100,
|
|
4919
|
+
alert: percent >= ALERT_THRESHOLD ? `context_${Math.round(ALERT_THRESHOLD * 100)}_percent` : null
|
|
4920
|
+
};
|
|
4921
|
+
}
|
|
4922
|
+
function resetTokenSession() {
|
|
4923
|
+
sessionTokens = 0;
|
|
4924
|
+
sessionStartTime = Date.now();
|
|
4925
|
+
}
|
|
4906
4926
|
var STREAM_ENABLED = process.env.SHODH_STREAM !== "false";
|
|
4907
4927
|
var STREAM_MIN_CONTENT_LENGTH = 50;
|
|
4908
4928
|
var PROACTIVE_SURFACING = process.env.SHODH_PROACTIVE !== "false";
|
|
@@ -5122,11 +5142,12 @@ async function isServerAvailable() {
|
|
|
5122
5142
|
}
|
|
5123
5143
|
var server = new Server({
|
|
5124
5144
|
name: "shodh-memory",
|
|
5125
|
-
version: "0.1.
|
|
5145
|
+
version: "0.1.61"
|
|
5126
5146
|
}, {
|
|
5127
5147
|
capabilities: {
|
|
5128
5148
|
tools: {},
|
|
5129
|
-
resources: {}
|
|
5149
|
+
resources: {},
|
|
5150
|
+
prompts: {}
|
|
5130
5151
|
}
|
|
5131
5152
|
});
|
|
5132
5153
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -5196,17 +5217,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
5196
5217
|
},
|
|
5197
5218
|
{
|
|
5198
5219
|
name: "recall",
|
|
5199
|
-
description: "Search memories using
|
|
5220
|
+
description: "Search memories AND todos using semantic similarity. Returns both relevant memories and matching todos. Use this to find past experiences, decisions, context, or pending work. Modes: 'semantic' (vector similarity), 'associative' (graph traversal), 'hybrid' (combined).",
|
|
5200
5221
|
inputSchema: {
|
|
5201
5222
|
type: "object",
|
|
5202
5223
|
properties: {
|
|
5203
5224
|
query: {
|
|
5204
5225
|
type: "string",
|
|
5205
|
-
description: "Natural language search query"
|
|
5226
|
+
description: "Natural language search query - searches both memories and todos"
|
|
5206
5227
|
},
|
|
5207
5228
|
limit: {
|
|
5208
5229
|
type: "number",
|
|
5209
|
-
description: "Maximum number of results (default: 5)",
|
|
5230
|
+
description: "Maximum number of memory results (default: 5). Todos limited to 5.",
|
|
5210
5231
|
default: 5
|
|
5211
5232
|
},
|
|
5212
5233
|
mode: {
|
|
@@ -5301,79 +5322,47 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
5301
5322
|
}
|
|
5302
5323
|
},
|
|
5303
5324
|
{
|
|
5304
|
-
name: "
|
|
5305
|
-
description: "
|
|
5325
|
+
name: "backup_create",
|
|
5326
|
+
description: "Create a backup of all memories. Returns backup metadata including ID, size, and checksum. Backups are stored locally and can be restored later.",
|
|
5306
5327
|
inputSchema: {
|
|
5307
5328
|
type: "object",
|
|
5308
|
-
properties: {
|
|
5309
|
-
tags: {
|
|
5310
|
-
type: "array",
|
|
5311
|
-
items: { type: "string" },
|
|
5312
|
-
description: "Tags to search for (returns memories matching ANY tag)"
|
|
5313
|
-
},
|
|
5314
|
-
limit: {
|
|
5315
|
-
type: "number",
|
|
5316
|
-
description: "Maximum number of results (default: 20)",
|
|
5317
|
-
default: 20
|
|
5318
|
-
}
|
|
5319
|
-
},
|
|
5320
|
-
required: ["tags"]
|
|
5329
|
+
properties: {}
|
|
5321
5330
|
}
|
|
5322
5331
|
},
|
|
5323
5332
|
{
|
|
5324
|
-
name: "
|
|
5325
|
-
description: "
|
|
5333
|
+
name: "backup_list",
|
|
5334
|
+
description: "List all available backups for this user. Returns backup history with IDs, timestamps, and sizes.",
|
|
5326
5335
|
inputSchema: {
|
|
5327
5336
|
type: "object",
|
|
5328
|
-
properties: {
|
|
5329
|
-
start: {
|
|
5330
|
-
type: "string",
|
|
5331
|
-
description: "Start date (ISO 8601 format, e.g., '2024-01-01T00:00:00Z')"
|
|
5332
|
-
},
|
|
5333
|
-
end: {
|
|
5334
|
-
type: "string",
|
|
5335
|
-
description: "End date (ISO 8601 format, e.g., '2024-12-31T23:59:59Z')"
|
|
5336
|
-
},
|
|
5337
|
-
limit: {
|
|
5338
|
-
type: "number",
|
|
5339
|
-
description: "Maximum number of results (default: 20)",
|
|
5340
|
-
default: 20
|
|
5341
|
-
}
|
|
5342
|
-
},
|
|
5343
|
-
required: ["start", "end"]
|
|
5337
|
+
properties: {}
|
|
5344
5338
|
}
|
|
5345
5339
|
},
|
|
5346
5340
|
{
|
|
5347
|
-
name: "
|
|
5348
|
-
description: "
|
|
5341
|
+
name: "backup_verify",
|
|
5342
|
+
description: "Verify backup integrity using SHA-256 checksum. Use to check if a backup is corrupted before restoring.",
|
|
5349
5343
|
inputSchema: {
|
|
5350
5344
|
type: "object",
|
|
5351
5345
|
properties: {
|
|
5352
|
-
|
|
5353
|
-
type: "
|
|
5354
|
-
|
|
5355
|
-
description: "Tags to match for deletion"
|
|
5346
|
+
backup_id: {
|
|
5347
|
+
type: "number",
|
|
5348
|
+
description: "The backup ID to verify"
|
|
5356
5349
|
}
|
|
5357
5350
|
},
|
|
5358
|
-
required: ["
|
|
5351
|
+
required: ["backup_id"]
|
|
5359
5352
|
}
|
|
5360
5353
|
},
|
|
5361
5354
|
{
|
|
5362
|
-
name: "
|
|
5363
|
-
description: "
|
|
5355
|
+
name: "backup_purge",
|
|
5356
|
+
description: "Purge old backups, keeping only the most recent N. Useful for managing disk space.",
|
|
5364
5357
|
inputSchema: {
|
|
5365
5358
|
type: "object",
|
|
5366
5359
|
properties: {
|
|
5367
|
-
|
|
5368
|
-
type: "
|
|
5369
|
-
description: "
|
|
5370
|
-
|
|
5371
|
-
end: {
|
|
5372
|
-
type: "string",
|
|
5373
|
-
description: "End date (ISO 8601 format)"
|
|
5360
|
+
keep_count: {
|
|
5361
|
+
type: "number",
|
|
5362
|
+
description: "Number of backups to keep (default: 7)",
|
|
5363
|
+
default: 7
|
|
5374
5364
|
}
|
|
5375
|
-
}
|
|
5376
|
-
required: ["start", "end"]
|
|
5365
|
+
}
|
|
5377
5366
|
}
|
|
5378
5367
|
},
|
|
5379
5368
|
{
|
|
@@ -5438,457 +5427,1087 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
5438
5427
|
}
|
|
5439
5428
|
},
|
|
5440
5429
|
{
|
|
5441
|
-
name: "
|
|
5442
|
-
description: "
|
|
5430
|
+
name: "token_status",
|
|
5431
|
+
description: "Get current token usage status for this session. Returns tokens used, budget remaining, and percentage consumed. Use this to check context window health.",
|
|
5443
5432
|
inputSchema: {
|
|
5444
5433
|
type: "object",
|
|
5445
5434
|
properties: {}
|
|
5446
5435
|
}
|
|
5447
|
-
}
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
let context = "";
|
|
5455
|
-
if (args.query && typeof args.query === "string") {
|
|
5456
|
-
context = `Query: ${args.query}`;
|
|
5457
|
-
} else if (args.content && typeof args.content === "string") {
|
|
5458
|
-
context = args.content;
|
|
5459
|
-
} else if (args.context && typeof args.context === "string") {
|
|
5460
|
-
context = args.context;
|
|
5461
|
-
}
|
|
5462
|
-
if (context.length >= 20) {
|
|
5463
|
-
streamMemory(context, ["auto-context", toolName], "user");
|
|
5464
|
-
}
|
|
5465
|
-
}
|
|
5466
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
5467
|
-
const { name, arguments: args } = request.params;
|
|
5468
|
-
if (STREAM_ENABLED && (!streamSocket || streamSocket.readyState !== WebSocket.OPEN)) {
|
|
5469
|
-
connectStream().catch(() => {});
|
|
5470
|
-
}
|
|
5471
|
-
autoStreamContext(name, args);
|
|
5472
|
-
const serverUp = await isServerAvailable();
|
|
5473
|
-
if (!serverUp) {
|
|
5474
|
-
return {
|
|
5475
|
-
content: [
|
|
5476
|
-
{
|
|
5477
|
-
type: "text",
|
|
5478
|
-
text: `Memory server unavailable at ${API_URL}. Please ensure shodh-memory-server is running.
|
|
5479
|
-
|
|
5480
|
-
To start: cd shodh-memory && cargo run`
|
|
5436
|
+
},
|
|
5437
|
+
{
|
|
5438
|
+
name: "reset_token_session",
|
|
5439
|
+
description: "Reset the token counter for a new session. Call this when starting a new conversation or after context has been compressed/summarized.",
|
|
5440
|
+
inputSchema: {
|
|
5441
|
+
type: "object",
|
|
5442
|
+
properties: {}
|
|
5481
5443
|
}
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
{
|
|
5521
|
-
type: "text",
|
|
5522
|
-
text: `Remembered: "${content.slice(0, 50)}${content.length > 50 ? "..." : ""}"
|
|
5523
|
-
Memory ID: ${result.id}`
|
|
5444
|
+
},
|
|
5445
|
+
{
|
|
5446
|
+
name: "set_reminder",
|
|
5447
|
+
description: "Set a reminder for the future. Triggers on time (at specific time or after duration) or context match (when keywords appear in conversation). Reminders will surface automatically when conditions are met.",
|
|
5448
|
+
inputSchema: {
|
|
5449
|
+
type: "object",
|
|
5450
|
+
properties: {
|
|
5451
|
+
content: {
|
|
5452
|
+
type: "string",
|
|
5453
|
+
description: "What to remember/remind about"
|
|
5454
|
+
},
|
|
5455
|
+
trigger_type: {
|
|
5456
|
+
type: "string",
|
|
5457
|
+
enum: ["time", "duration", "context"],
|
|
5458
|
+
description: "When to trigger: 'time' (at specific ISO timestamp), 'duration' (after N seconds), 'context' (when keywords match)"
|
|
5459
|
+
},
|
|
5460
|
+
trigger_at: {
|
|
5461
|
+
type: "string",
|
|
5462
|
+
description: "ISO 8601 timestamp for 'time' trigger (e.g., '2025-12-23T18:00:00Z')"
|
|
5463
|
+
},
|
|
5464
|
+
after_seconds: {
|
|
5465
|
+
type: "number",
|
|
5466
|
+
description: "Seconds from now for 'duration' trigger"
|
|
5467
|
+
},
|
|
5468
|
+
keywords: {
|
|
5469
|
+
type: "array",
|
|
5470
|
+
items: { type: "string" },
|
|
5471
|
+
description: "Keywords for 'context' trigger - reminder surfaces when any keyword appears"
|
|
5472
|
+
},
|
|
5473
|
+
priority: {
|
|
5474
|
+
type: "number",
|
|
5475
|
+
description: "Priority 1-5 (5 = highest, default: 3)",
|
|
5476
|
+
default: 3
|
|
5477
|
+
},
|
|
5478
|
+
tags: {
|
|
5479
|
+
type: "array",
|
|
5480
|
+
items: { type: "string" },
|
|
5481
|
+
description: "Optional tags for categorization"
|
|
5524
5482
|
}
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
}
|
|
5528
|
-
case "recall": {
|
|
5529
|
-
const { query, limit = 5, mode = "hybrid" } = args;
|
|
5530
|
-
const result = await apiCall("/api/recall", "POST", {
|
|
5531
|
-
user_id: USER_ID,
|
|
5532
|
-
query,
|
|
5533
|
-
limit,
|
|
5534
|
-
mode
|
|
5535
|
-
});
|
|
5536
|
-
const memories = result.memories || [];
|
|
5537
|
-
const stats = result.retrieval_stats;
|
|
5538
|
-
if (memories.length === 0) {
|
|
5539
|
-
return {
|
|
5540
|
-
content: [
|
|
5541
|
-
{
|
|
5542
|
-
type: "text",
|
|
5543
|
-
text: `No memories found for: "${query}" (mode: ${mode})`
|
|
5544
|
-
}
|
|
5545
|
-
]
|
|
5546
|
-
};
|
|
5547
|
-
}
|
|
5548
|
-
const formatted = memories.map((m, i) => {
|
|
5549
|
-
const content = getContent(m);
|
|
5550
|
-
const score = ((m.score || 0) * 100).toFixed(0);
|
|
5551
|
-
return `${i + 1}. [${score}% match] ${content}
|
|
5552
|
-
Type: ${getType(m)} | ID: ${m.id.slice(0, 8)}...`;
|
|
5553
|
-
}).join(`
|
|
5554
|
-
|
|
5555
|
-
`);
|
|
5556
|
-
let statsText = "";
|
|
5557
|
-
if (stats && (mode === "associative" || mode === "hybrid")) {
|
|
5558
|
-
const graphPct = (stats.graph_weight * 100).toFixed(0);
|
|
5559
|
-
const semPct = (stats.semantic_weight * 100).toFixed(0);
|
|
5560
|
-
statsText = `
|
|
5561
|
-
|
|
5562
|
-
[Stats: ${stats.mode} mode | graph=${graphPct}% semantic=${semPct}% | density=${stats.graph_density.toFixed(2)} | ${stats.graph_candidates} graph + ${stats.semantic_candidates} semantic candidates | ${stats.entities_activated} entities | ${(stats.retrieval_time_us / 1000).toFixed(1)}ms]`;
|
|
5483
|
+
},
|
|
5484
|
+
required: ["content", "trigger_type"]
|
|
5563
5485
|
}
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5486
|
+
},
|
|
5487
|
+
{
|
|
5488
|
+
name: "list_reminders",
|
|
5489
|
+
description: "List all pending reminders. Use to check what reminders are scheduled.",
|
|
5490
|
+
inputSchema: {
|
|
5491
|
+
type: "object",
|
|
5492
|
+
properties: {
|
|
5493
|
+
status: {
|
|
5494
|
+
type: "string",
|
|
5495
|
+
enum: ["pending", "triggered", "dismissed", "all"],
|
|
5496
|
+
description: "Filter by status (default: pending)",
|
|
5497
|
+
default: "pending"
|
|
5571
5498
|
}
|
|
5572
|
-
]
|
|
5573
|
-
};
|
|
5574
|
-
}
|
|
5575
|
-
case "context_summary": {
|
|
5576
|
-
const {
|
|
5577
|
-
include_decisions = true,
|
|
5578
|
-
include_learnings = true,
|
|
5579
|
-
include_context = true,
|
|
5580
|
-
max_items = 5
|
|
5581
|
-
} = args;
|
|
5582
|
-
const result = await apiCall("/api/memories", "POST", {
|
|
5583
|
-
user_id: USER_ID
|
|
5584
|
-
});
|
|
5585
|
-
const memories = result.memories || [];
|
|
5586
|
-
if (memories.length === 0) {
|
|
5587
|
-
return {
|
|
5588
|
-
content: [
|
|
5589
|
-
{
|
|
5590
|
-
type: "text",
|
|
5591
|
-
text: "No memories stored yet. Start remembering things to build context!"
|
|
5592
|
-
}
|
|
5593
|
-
]
|
|
5594
|
-
};
|
|
5595
|
-
}
|
|
5596
|
-
const decisions = [];
|
|
5597
|
-
const learnings = [];
|
|
5598
|
-
const context = [];
|
|
5599
|
-
const patterns = [];
|
|
5600
|
-
const errors2 = [];
|
|
5601
|
-
for (const m of memories) {
|
|
5602
|
-
const type = getType(m);
|
|
5603
|
-
switch (type) {
|
|
5604
|
-
case "Decision":
|
|
5605
|
-
decisions.push(m);
|
|
5606
|
-
break;
|
|
5607
|
-
case "Learning":
|
|
5608
|
-
learnings.push(m);
|
|
5609
|
-
break;
|
|
5610
|
-
case "Context":
|
|
5611
|
-
context.push(m);
|
|
5612
|
-
break;
|
|
5613
|
-
case "Pattern":
|
|
5614
|
-
patterns.push(m);
|
|
5615
|
-
break;
|
|
5616
|
-
case "Error":
|
|
5617
|
-
errors2.push(m);
|
|
5618
|
-
break;
|
|
5619
5499
|
}
|
|
5620
5500
|
}
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
${items.join(`
|
|
5632
|
-
`)}`);
|
|
5633
|
-
}
|
|
5634
|
-
if (include_learnings && learnings.length > 0) {
|
|
5635
|
-
const items = learnings.slice(0, max_items).map((m) => ` - ${getContent(m).slice(0, 100)}`);
|
|
5636
|
-
sections.push(`LEARNINGS:
|
|
5637
|
-
${items.join(`
|
|
5638
|
-
`)}`);
|
|
5639
|
-
}
|
|
5640
|
-
if (patterns.length > 0) {
|
|
5641
|
-
const items = patterns.slice(0, max_items).map((m) => ` - ${getContent(m).slice(0, 100)}`);
|
|
5642
|
-
sections.push(`PATTERNS NOTICED:
|
|
5643
|
-
${items.join(`
|
|
5644
|
-
`)}`);
|
|
5645
|
-
}
|
|
5646
|
-
if (errors2.length > 0) {
|
|
5647
|
-
const items = errors2.slice(0, Math.min(3, max_items)).map((m) => ` - ${getContent(m).slice(0, 100)}`);
|
|
5648
|
-
sections.push(`ERRORS TO AVOID:
|
|
5649
|
-
${items.join(`
|
|
5650
|
-
`)}`);
|
|
5651
|
-
}
|
|
5652
|
-
const summary = sections.length > 0 ? sections.join(`
|
|
5653
|
-
|
|
5654
|
-
`) : `${memories.length} memories stored, but none categorized as decisions, learnings, or context. Consider using those types when remembering.`;
|
|
5655
|
-
return {
|
|
5656
|
-
content: [
|
|
5657
|
-
{
|
|
5658
|
-
type: "text",
|
|
5659
|
-
text: `CONTEXT SUMMARY (${memories.length} total memories)
|
|
5660
|
-
${"=".repeat(40)}
|
|
5661
|
-
|
|
5662
|
-
${summary}`
|
|
5501
|
+
},
|
|
5502
|
+
{
|
|
5503
|
+
name: "dismiss_reminder",
|
|
5504
|
+
description: "Dismiss/acknowledge a triggered reminder. Call this after you've handled a reminder.",
|
|
5505
|
+
inputSchema: {
|
|
5506
|
+
type: "object",
|
|
5507
|
+
properties: {
|
|
5508
|
+
reminder_id: {
|
|
5509
|
+
type: "string",
|
|
5510
|
+
description: "ID of the reminder to dismiss"
|
|
5663
5511
|
}
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5512
|
+
},
|
|
5513
|
+
required: ["reminder_id"]
|
|
5514
|
+
}
|
|
5515
|
+
},
|
|
5516
|
+
{
|
|
5517
|
+
name: "add_todo",
|
|
5518
|
+
description: "Add a task to your todo list. Supports GTD workflow with projects, contexts (@computer, @phone), priorities, due dates, and subtasks (via parent_id).",
|
|
5519
|
+
inputSchema: {
|
|
5520
|
+
type: "object",
|
|
5521
|
+
properties: {
|
|
5522
|
+
content: {
|
|
5523
|
+
type: "string",
|
|
5524
|
+
description: "What needs to be done"
|
|
5525
|
+
},
|
|
5526
|
+
status: {
|
|
5527
|
+
type: "string",
|
|
5528
|
+
enum: ["backlog", "todo", "in_progress", "blocked"],
|
|
5529
|
+
description: "Initial status (default: todo)",
|
|
5530
|
+
default: "todo"
|
|
5531
|
+
},
|
|
5532
|
+
priority: {
|
|
5533
|
+
type: "string",
|
|
5534
|
+
enum: ["urgent", "high", "medium", "low", "none"],
|
|
5535
|
+
description: "Priority level (default: medium)",
|
|
5536
|
+
default: "medium"
|
|
5537
|
+
},
|
|
5538
|
+
project: {
|
|
5539
|
+
type: "string",
|
|
5540
|
+
description: "Project name (created if doesn't exist)"
|
|
5541
|
+
},
|
|
5542
|
+
contexts: {
|
|
5543
|
+
type: "array",
|
|
5544
|
+
items: { type: "string" },
|
|
5545
|
+
description: "Contexts like @computer, @phone, @errands"
|
|
5546
|
+
},
|
|
5547
|
+
due_date: {
|
|
5548
|
+
type: "string",
|
|
5549
|
+
description: "Due date - ISO format or 'today', 'tomorrow', 'monday', etc."
|
|
5550
|
+
},
|
|
5551
|
+
tags: {
|
|
5552
|
+
type: "array",
|
|
5553
|
+
items: { type: "string" },
|
|
5554
|
+
description: "Optional tags for categorization"
|
|
5555
|
+
},
|
|
5556
|
+
blocked_on: {
|
|
5557
|
+
type: "string",
|
|
5558
|
+
description: "Who/what you're waiting on (sets status to blocked)"
|
|
5559
|
+
},
|
|
5560
|
+
notes: {
|
|
5561
|
+
type: "string",
|
|
5562
|
+
description: "Additional notes"
|
|
5563
|
+
},
|
|
5564
|
+
recurrence: {
|
|
5565
|
+
type: "string",
|
|
5566
|
+
enum: ["daily", "weekly", "monthly"],
|
|
5567
|
+
description: "Recurrence pattern for repeating tasks"
|
|
5568
|
+
}
|
|
5569
|
+
},
|
|
5570
|
+
required: ["content"]
|
|
5571
|
+
}
|
|
5572
|
+
},
|
|
5573
|
+
{
|
|
5574
|
+
name: "list_todos",
|
|
5575
|
+
description: "List or search todos. Supports semantic search via query parameter, or GTD-style filtering. Returns Linear-style formatted output grouped by status.",
|
|
5576
|
+
inputSchema: {
|
|
5577
|
+
type: "object",
|
|
5578
|
+
properties: {
|
|
5579
|
+
query: {
|
|
5580
|
+
type: "string",
|
|
5581
|
+
description: "Semantic search query - when provided, uses vector similarity to find matching todos instead of listing all"
|
|
5582
|
+
},
|
|
5583
|
+
status: {
|
|
5584
|
+
type: "array",
|
|
5585
|
+
items: { type: "string", enum: ["backlog", "todo", "in_progress", "blocked", "done", "cancelled"] },
|
|
5586
|
+
description: "Filter by status(es)"
|
|
5587
|
+
},
|
|
5588
|
+
project: {
|
|
5589
|
+
type: "string",
|
|
5590
|
+
description: "Filter by project name"
|
|
5591
|
+
},
|
|
5592
|
+
context: {
|
|
5593
|
+
type: "string",
|
|
5594
|
+
description: "Filter by context (e.g., @computer)"
|
|
5595
|
+
},
|
|
5596
|
+
priority: {
|
|
5597
|
+
type: "string",
|
|
5598
|
+
enum: ["urgent", "high", "medium", "low"],
|
|
5599
|
+
description: "Filter by priority"
|
|
5600
|
+
},
|
|
5601
|
+
due: {
|
|
5602
|
+
type: "string",
|
|
5603
|
+
enum: ["today", "overdue", "this_week", "all"],
|
|
5604
|
+
description: "Filter by due date"
|
|
5605
|
+
},
|
|
5606
|
+
limit: {
|
|
5607
|
+
type: "number",
|
|
5608
|
+
description: "Maximum results (default: 50)",
|
|
5609
|
+
default: 50
|
|
5610
|
+
},
|
|
5611
|
+
offset: {
|
|
5612
|
+
type: "number",
|
|
5613
|
+
description: "Skip first N items for pagination (default: 0)",
|
|
5614
|
+
default: 0
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5618
|
+
},
|
|
5619
|
+
{
|
|
5620
|
+
name: "update_todo",
|
|
5621
|
+
description: "Update a todo's properties. Use short ID prefix (e.g., SHO-1a2b) or full ID.",
|
|
5622
|
+
inputSchema: {
|
|
5623
|
+
type: "object",
|
|
5624
|
+
properties: {
|
|
5625
|
+
todo_id: {
|
|
5626
|
+
type: "string",
|
|
5627
|
+
description: "Todo ID or short prefix"
|
|
5628
|
+
},
|
|
5629
|
+
content: {
|
|
5630
|
+
type: "string",
|
|
5631
|
+
description: "New content"
|
|
5632
|
+
},
|
|
5633
|
+
status: {
|
|
5634
|
+
type: "string",
|
|
5635
|
+
enum: ["backlog", "todo", "in_progress", "blocked", "done", "cancelled"],
|
|
5636
|
+
description: "New status"
|
|
5637
|
+
},
|
|
5638
|
+
priority: {
|
|
5639
|
+
type: "string",
|
|
5640
|
+
enum: ["urgent", "high", "medium", "low", "none"],
|
|
5641
|
+
description: "New priority"
|
|
5642
|
+
},
|
|
5643
|
+
project: {
|
|
5644
|
+
type: "string",
|
|
5645
|
+
description: "New project name"
|
|
5646
|
+
},
|
|
5647
|
+
contexts: {
|
|
5648
|
+
type: "array",
|
|
5649
|
+
items: { type: "string" },
|
|
5650
|
+
description: "New contexts"
|
|
5651
|
+
},
|
|
5652
|
+
due_date: {
|
|
5653
|
+
type: "string",
|
|
5654
|
+
description: "New due date"
|
|
5655
|
+
},
|
|
5656
|
+
blocked_on: {
|
|
5657
|
+
type: "string",
|
|
5658
|
+
description: "Who/what you're waiting on"
|
|
5659
|
+
},
|
|
5660
|
+
notes: {
|
|
5661
|
+
type: "string",
|
|
5662
|
+
description: "Additional notes"
|
|
5663
|
+
},
|
|
5664
|
+
tags: {
|
|
5665
|
+
type: "array",
|
|
5666
|
+
items: { type: "string" },
|
|
5667
|
+
description: "New tags"
|
|
5668
|
+
},
|
|
5669
|
+
parent_id: {
|
|
5670
|
+
type: "string",
|
|
5671
|
+
description: "Parent todo ID or short prefix to make this a subtask. Pass empty string to remove parent."
|
|
5672
|
+
}
|
|
5673
|
+
},
|
|
5674
|
+
required: ["todo_id"]
|
|
5675
|
+
}
|
|
5676
|
+
},
|
|
5677
|
+
{
|
|
5678
|
+
name: "complete_todo",
|
|
5679
|
+
description: "Mark a todo as complete. For recurring tasks, automatically creates the next occurrence.",
|
|
5680
|
+
inputSchema: {
|
|
5681
|
+
type: "object",
|
|
5682
|
+
properties: {
|
|
5683
|
+
todo_id: {
|
|
5684
|
+
type: "string",
|
|
5685
|
+
description: "Todo ID or short prefix"
|
|
5686
|
+
}
|
|
5687
|
+
},
|
|
5688
|
+
required: ["todo_id"]
|
|
5689
|
+
}
|
|
5690
|
+
},
|
|
5691
|
+
{
|
|
5692
|
+
name: "delete_todo",
|
|
5693
|
+
description: "Delete a todo permanently.",
|
|
5694
|
+
inputSchema: {
|
|
5695
|
+
type: "object",
|
|
5696
|
+
properties: {
|
|
5697
|
+
todo_id: {
|
|
5698
|
+
type: "string",
|
|
5699
|
+
description: "Todo ID or short prefix"
|
|
5700
|
+
}
|
|
5701
|
+
},
|
|
5702
|
+
required: ["todo_id"]
|
|
5703
|
+
}
|
|
5704
|
+
},
|
|
5705
|
+
{
|
|
5706
|
+
name: "reorder_todo",
|
|
5707
|
+
description: "Move a todo up or down within its status group. Use to prioritize tasks manually.",
|
|
5708
|
+
inputSchema: {
|
|
5709
|
+
type: "object",
|
|
5710
|
+
properties: {
|
|
5711
|
+
todo_id: {
|
|
5712
|
+
type: "string",
|
|
5713
|
+
description: "Todo ID or short prefix"
|
|
5714
|
+
},
|
|
5715
|
+
direction: {
|
|
5716
|
+
type: "string",
|
|
5717
|
+
enum: ["up", "down"],
|
|
5718
|
+
description: "Direction to move the todo"
|
|
5719
|
+
}
|
|
5720
|
+
},
|
|
5721
|
+
required: ["todo_id", "direction"]
|
|
5722
|
+
}
|
|
5723
|
+
},
|
|
5724
|
+
{
|
|
5725
|
+
name: "add_project",
|
|
5726
|
+
description: "Create a new project to group todos. Use parent to create a sub-project under another project.",
|
|
5727
|
+
inputSchema: {
|
|
5728
|
+
type: "object",
|
|
5729
|
+
properties: {
|
|
5730
|
+
name: {
|
|
5731
|
+
type: "string",
|
|
5732
|
+
description: "Project name"
|
|
5733
|
+
},
|
|
5734
|
+
prefix: {
|
|
5735
|
+
type: "string",
|
|
5736
|
+
description: "Custom prefix for todo IDs (e.g., 'BOLT', 'MEM'). Auto-derived from name if not provided."
|
|
5737
|
+
},
|
|
5738
|
+
description: {
|
|
5739
|
+
type: "string",
|
|
5740
|
+
description: "Project description"
|
|
5741
|
+
},
|
|
5742
|
+
parent: {
|
|
5743
|
+
type: "string",
|
|
5744
|
+
description: "Parent project name or ID to create a sub-project"
|
|
5745
|
+
}
|
|
5746
|
+
},
|
|
5747
|
+
required: ["name"]
|
|
5748
|
+
}
|
|
5749
|
+
},
|
|
5750
|
+
{
|
|
5751
|
+
name: "list_projects",
|
|
5752
|
+
description: "List all projects with todo counts and status breakdown.",
|
|
5753
|
+
inputSchema: {
|
|
5754
|
+
type: "object",
|
|
5755
|
+
properties: {}
|
|
5756
|
+
}
|
|
5757
|
+
},
|
|
5758
|
+
{
|
|
5759
|
+
name: "archive_project",
|
|
5760
|
+
description: "Archive a project. Archived projects are hidden by default but can be restored.",
|
|
5761
|
+
inputSchema: {
|
|
5762
|
+
type: "object",
|
|
5763
|
+
properties: {
|
|
5764
|
+
project: {
|
|
5765
|
+
type: "string",
|
|
5766
|
+
description: "Project name or ID to archive"
|
|
5767
|
+
}
|
|
5768
|
+
},
|
|
5769
|
+
required: ["project"]
|
|
5770
|
+
}
|
|
5771
|
+
},
|
|
5772
|
+
{
|
|
5773
|
+
name: "delete_project",
|
|
5774
|
+
description: "Permanently delete a project. Use delete_todos=true to also delete all todos in the project.",
|
|
5775
|
+
inputSchema: {
|
|
5776
|
+
type: "object",
|
|
5777
|
+
properties: {
|
|
5778
|
+
project: {
|
|
5779
|
+
type: "string",
|
|
5780
|
+
description: "Project name or ID to delete"
|
|
5781
|
+
},
|
|
5782
|
+
delete_todos: {
|
|
5783
|
+
type: "boolean",
|
|
5784
|
+
description: "Also delete all todos in this project (default: false)"
|
|
5785
|
+
}
|
|
5786
|
+
},
|
|
5787
|
+
required: ["project"]
|
|
5788
|
+
}
|
|
5789
|
+
},
|
|
5790
|
+
{
|
|
5791
|
+
name: "todo_stats",
|
|
5792
|
+
description: "Get statistics about your todos - counts by status, overdue items, etc.",
|
|
5793
|
+
inputSchema: {
|
|
5794
|
+
type: "object",
|
|
5795
|
+
properties: {}
|
|
5796
|
+
}
|
|
5797
|
+
},
|
|
5798
|
+
{
|
|
5799
|
+
name: "list_subtasks",
|
|
5800
|
+
description: "List subtasks of a parent todo. Use add_todo with parent_id to create subtasks.",
|
|
5801
|
+
inputSchema: {
|
|
5802
|
+
type: "object",
|
|
5803
|
+
properties: {
|
|
5804
|
+
parent_id: {
|
|
5805
|
+
type: "string",
|
|
5806
|
+
description: "Parent todo ID or short prefix"
|
|
5807
|
+
}
|
|
5808
|
+
},
|
|
5809
|
+
required: ["parent_id"]
|
|
5810
|
+
}
|
|
5811
|
+
},
|
|
5812
|
+
{
|
|
5813
|
+
name: "add_todo_comment",
|
|
5814
|
+
description: "Add a comment to a todo. Use to track progress, notes, or resolution details.",
|
|
5815
|
+
inputSchema: {
|
|
5816
|
+
type: "object",
|
|
5817
|
+
properties: {
|
|
5818
|
+
todo_id: {
|
|
5819
|
+
type: "string",
|
|
5820
|
+
description: "Todo ID or short prefix (e.g., 'BOLT-1', 'MEM-2')"
|
|
5821
|
+
},
|
|
5822
|
+
content: {
|
|
5823
|
+
type: "string",
|
|
5824
|
+
description: "Comment content (supports markdown)"
|
|
5825
|
+
},
|
|
5826
|
+
comment_type: {
|
|
5827
|
+
type: "string",
|
|
5828
|
+
enum: ["comment", "progress", "resolution", "activity"],
|
|
5829
|
+
description: "Type of comment: comment (default), progress (updates), resolution (fix details), activity (system)"
|
|
5830
|
+
}
|
|
5831
|
+
},
|
|
5832
|
+
required: ["todo_id", "content"]
|
|
5833
|
+
}
|
|
5834
|
+
},
|
|
5835
|
+
{
|
|
5836
|
+
name: "list_todo_comments",
|
|
5837
|
+
description: "List all comments and activity history for a specific todo.",
|
|
5838
|
+
inputSchema: {
|
|
5839
|
+
type: "object",
|
|
5840
|
+
properties: {
|
|
5841
|
+
todo_id: {
|
|
5842
|
+
type: "string",
|
|
5843
|
+
description: "Todo ID or short prefix (e.g., 'BOLT-1', 'MEM-2')"
|
|
5844
|
+
}
|
|
5845
|
+
},
|
|
5846
|
+
required: ["todo_id"]
|
|
5847
|
+
}
|
|
5848
|
+
},
|
|
5849
|
+
{
|
|
5850
|
+
name: "update_todo_comment",
|
|
5851
|
+
description: "Update an existing comment on a todo.",
|
|
5852
|
+
inputSchema: {
|
|
5853
|
+
type: "object",
|
|
5854
|
+
properties: {
|
|
5855
|
+
todo_id: {
|
|
5856
|
+
type: "string",
|
|
5857
|
+
description: "Todo ID or short prefix"
|
|
5858
|
+
},
|
|
5859
|
+
comment_id: {
|
|
5860
|
+
type: "string",
|
|
5861
|
+
description: "Comment ID (UUID)"
|
|
5862
|
+
},
|
|
5863
|
+
content: {
|
|
5864
|
+
type: "string",
|
|
5865
|
+
description: "New comment content"
|
|
5866
|
+
}
|
|
5867
|
+
},
|
|
5868
|
+
required: ["todo_id", "comment_id", "content"]
|
|
5869
|
+
}
|
|
5870
|
+
},
|
|
5871
|
+
{
|
|
5872
|
+
name: "delete_todo_comment",
|
|
5873
|
+
description: "Delete a comment from a todo.",
|
|
5874
|
+
inputSchema: {
|
|
5875
|
+
type: "object",
|
|
5876
|
+
properties: {
|
|
5877
|
+
todo_id: {
|
|
5878
|
+
type: "string",
|
|
5879
|
+
description: "Todo ID or short prefix"
|
|
5880
|
+
},
|
|
5881
|
+
comment_id: {
|
|
5882
|
+
type: "string",
|
|
5883
|
+
description: "Comment ID (UUID)"
|
|
5884
|
+
}
|
|
5885
|
+
},
|
|
5886
|
+
required: ["todo_id", "comment_id"]
|
|
5887
|
+
}
|
|
5888
|
+
},
|
|
5889
|
+
{
|
|
5890
|
+
name: "read_memory",
|
|
5891
|
+
description: "Read the FULL content of a specific memory by ID. Use this when you need to see the complete text of a memory that was truncated in search results.",
|
|
5892
|
+
inputSchema: {
|
|
5893
|
+
type: "object",
|
|
5894
|
+
properties: {
|
|
5895
|
+
memory_id: {
|
|
5896
|
+
type: "string",
|
|
5897
|
+
description: "The memory ID (full UUID or short prefix like '5581cd02')"
|
|
5898
|
+
}
|
|
5899
|
+
},
|
|
5900
|
+
required: ["memory_id"]
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
]
|
|
5904
|
+
};
|
|
5905
|
+
});
|
|
5906
|
+
function autoStreamContext(toolName, args) {
|
|
5907
|
+
if (["proactive_context", "streaming_status", "token_status", "reset_token_session"].includes(toolName))
|
|
5908
|
+
return;
|
|
5909
|
+
let context = "";
|
|
5910
|
+
if (args.query && typeof args.query === "string") {
|
|
5911
|
+
context = `Query: ${args.query}`;
|
|
5912
|
+
} else if (args.content && typeof args.content === "string") {
|
|
5913
|
+
context = args.content;
|
|
5914
|
+
} else if (args.context && typeof args.context === "string") {
|
|
5915
|
+
context = args.context;
|
|
5916
|
+
}
|
|
5917
|
+
if (context.length >= 20) {
|
|
5918
|
+
streamMemory(context, ["auto-context", toolName], "user");
|
|
5919
|
+
}
|
|
5920
|
+
}
|
|
5921
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
5922
|
+
const { name, arguments: args } = request.params;
|
|
5923
|
+
if (STREAM_ENABLED && (!streamSocket || streamSocket.readyState !== WebSocket.OPEN)) {
|
|
5924
|
+
connectStream().catch(() => {});
|
|
5925
|
+
}
|
|
5926
|
+
autoStreamContext(name, args);
|
|
5927
|
+
const serverUp = await isServerAvailable();
|
|
5928
|
+
if (!serverUp) {
|
|
5929
|
+
return {
|
|
5930
|
+
content: [
|
|
5931
|
+
{
|
|
5932
|
+
type: "text",
|
|
5933
|
+
text: `Memory server unavailable at ${API_URL}. Please ensure shodh-memory-server is running.
|
|
5934
|
+
|
|
5935
|
+
To start: cd shodh-memory && cargo run`
|
|
5936
|
+
}
|
|
5937
|
+
],
|
|
5938
|
+
isError: true
|
|
5939
|
+
};
|
|
5940
|
+
}
|
|
5941
|
+
const executeTool = async () => {
|
|
5942
|
+
switch (name) {
|
|
5943
|
+
case "remember": {
|
|
5944
|
+
const {
|
|
5945
|
+
content,
|
|
5946
|
+
type = "Observation",
|
|
5947
|
+
tags = [],
|
|
5948
|
+
created_at,
|
|
5949
|
+
emotional_valence,
|
|
5950
|
+
emotional_arousal,
|
|
5951
|
+
emotion,
|
|
5952
|
+
source_type,
|
|
5953
|
+
credibility,
|
|
5954
|
+
episode_id,
|
|
5955
|
+
sequence_number,
|
|
5956
|
+
preceding_memory_id
|
|
5957
|
+
} = args;
|
|
5958
|
+
const result = await apiCall("/api/remember", "POST", {
|
|
5959
|
+
user_id: USER_ID,
|
|
5960
|
+
content,
|
|
5961
|
+
memory_type: type,
|
|
5962
|
+
tags,
|
|
5963
|
+
...created_at && { created_at },
|
|
5964
|
+
...emotional_valence !== undefined && { emotional_valence },
|
|
5965
|
+
...emotional_arousal !== undefined && { emotional_arousal },
|
|
5966
|
+
...emotion && { emotion },
|
|
5967
|
+
...source_type && { source_type },
|
|
5968
|
+
...credibility !== undefined && { credibility },
|
|
5969
|
+
...episode_id && { episode_id },
|
|
5970
|
+
...sequence_number !== undefined && { sequence_number },
|
|
5971
|
+
...preceding_memory_id && { preceding_memory_id }
|
|
5972
|
+
});
|
|
5973
|
+
let response = `\uD83D\uDC18 Memory Stored
|
|
5974
|
+
`;
|
|
5975
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5976
|
+
`;
|
|
5977
|
+
response += `\uD83D\uDCDD ${content.slice(0, 60)}${content.length > 60 ? "..." : ""}
|
|
5978
|
+
`;
|
|
5979
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5980
|
+
`;
|
|
5981
|
+
response += `Type: ${type}`;
|
|
5982
|
+
if (tags.length > 0) {
|
|
5983
|
+
response += ` │ Tags: ${tags.join(", ")}`;
|
|
5984
|
+
}
|
|
5985
|
+
response += `
|
|
5986
|
+
ID: ${result.id.slice(0, 8)}...`;
|
|
5987
|
+
return {
|
|
5988
|
+
content: [{ type: "text", text: response }]
|
|
5989
|
+
};
|
|
5990
|
+
}
|
|
5991
|
+
case "recall": {
|
|
5992
|
+
const { query, limit = 5, mode = "hybrid" } = args;
|
|
5993
|
+
const result = await apiCall("/api/recall", "POST", {
|
|
5994
|
+
user_id: USER_ID,
|
|
5995
|
+
query,
|
|
5996
|
+
limit,
|
|
5997
|
+
mode
|
|
5998
|
+
});
|
|
5999
|
+
const memories = result.memories || [];
|
|
6000
|
+
const todos = result.todos || [];
|
|
6001
|
+
const stats = result.retrieval_stats;
|
|
6002
|
+
if (memories.length === 0 && todos.length === 0) {
|
|
6003
|
+
return {
|
|
6004
|
+
content: [
|
|
6005
|
+
{
|
|
6006
|
+
type: "text",
|
|
6007
|
+
text: `\uD83D\uDC18 No memories or todos found for: "${query}"
|
|
6008
|
+
Mode: ${mode}`
|
|
6009
|
+
}
|
|
6010
|
+
]
|
|
6011
|
+
};
|
|
6012
|
+
}
|
|
6013
|
+
const totalCount = memories.length + todos.length;
|
|
6014
|
+
let response = `\uD83D\uDC18 Recalled ${totalCount} Results`;
|
|
6015
|
+
if (memories.length > 0 && todos.length > 0) {
|
|
6016
|
+
response += ` (${memories.length} memories, ${todos.length} todos)`;
|
|
6017
|
+
}
|
|
6018
|
+
response += `
|
|
6019
|
+
`;
|
|
6020
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6021
|
+
`;
|
|
6022
|
+
response += `Query: "${query.slice(0, 40)}${query.length > 40 ? "..." : ""}" │ Mode: ${mode}
|
|
6023
|
+
`;
|
|
6024
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6025
|
+
|
|
6026
|
+
`;
|
|
6027
|
+
const formatTime = (ts) => {
|
|
6028
|
+
if (!ts)
|
|
6029
|
+
return "";
|
|
6030
|
+
const d = new Date(ts);
|
|
6031
|
+
const now = new Date;
|
|
6032
|
+
const diffMs = now.getTime() - d.getTime();
|
|
6033
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
6034
|
+
if (diffDays === 0) {
|
|
6035
|
+
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
6036
|
+
} else if (diffDays === 1) {
|
|
6037
|
+
return "Yesterday";
|
|
6038
|
+
} else if (diffDays < 7) {
|
|
6039
|
+
return `${diffDays}d ago`;
|
|
6040
|
+
} else {
|
|
6041
|
+
return d.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
6042
|
+
}
|
|
6043
|
+
};
|
|
6044
|
+
if (memories.length > 0) {
|
|
6045
|
+
response += `\uD83D\uDCDD MEMORIES
|
|
6046
|
+
`;
|
|
6047
|
+
for (let i = 0;i < memories.length; i++) {
|
|
6048
|
+
const m = memories[i];
|
|
6049
|
+
const content = getContent(m);
|
|
6050
|
+
const score = ((m.score || 0) * 100).toFixed(0);
|
|
6051
|
+
const filled = Math.max(0, Math.min(10, Math.round((m.score || 0) * 10)));
|
|
6052
|
+
const matchBar = "█".repeat(filled) + "░".repeat(10 - filled);
|
|
6053
|
+
const timeStr = formatTime(m.created_at);
|
|
6054
|
+
response += `• ${matchBar} ${score}% │ ${timeStr}
|
|
6055
|
+
`;
|
|
6056
|
+
response += ` ${content.slice(0, 200)}${content.length > 200 ? "..." : ""}
|
|
6057
|
+
`;
|
|
6058
|
+
response += ` ┗━ ${getType(m)} │ ${m.id.slice(0, 8)}...
|
|
6059
|
+
`;
|
|
6060
|
+
if (i < memories.length - 1)
|
|
6061
|
+
response += `
|
|
6062
|
+
`;
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
if (todos.length > 0) {
|
|
6066
|
+
if (memories.length > 0)
|
|
6067
|
+
response += `
|
|
6068
|
+
`;
|
|
6069
|
+
response += `✅ TODOS
|
|
6070
|
+
`;
|
|
6071
|
+
for (let i = 0;i < todos.length; i++) {
|
|
6072
|
+
const t = todos[i];
|
|
6073
|
+
const score = ((t.score || 0) * 100).toFixed(0);
|
|
6074
|
+
const filled = Math.max(0, Math.min(10, Math.round((t.score || 0) * 10)));
|
|
6075
|
+
const matchBar = "█".repeat(filled) + "░".repeat(10 - filled);
|
|
6076
|
+
const statusIcon = t.status === "done" ? "✓" : t.status === "in_progress" ? "▶" : t.status === "blocked" ? "⊗" : "○";
|
|
6077
|
+
const timeStr = formatTime(t.created_at);
|
|
6078
|
+
response += `• ${matchBar} ${score}% │ ${timeStr}
|
|
6079
|
+
`;
|
|
6080
|
+
response += ` ${statusIcon} ${t.content.slice(0, 180)}${t.content.length > 180 ? "..." : ""}
|
|
6081
|
+
`;
|
|
6082
|
+
response += ` ┗━ ${t.short_id} │ ${t.status} │ ${t.priority}`;
|
|
6083
|
+
if (t.project)
|
|
6084
|
+
response += ` │ ${t.project}`;
|
|
6085
|
+
response += `
|
|
6086
|
+
`;
|
|
6087
|
+
if (i < todos.length - 1)
|
|
6088
|
+
response += `
|
|
6089
|
+
`;
|
|
6090
|
+
}
|
|
6091
|
+
}
|
|
6092
|
+
if (stats && (mode === "associative" || mode === "hybrid")) {
|
|
6093
|
+
response += `
|
|
6094
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6095
|
+
`;
|
|
6096
|
+
response += `\uD83D\uDCCA Retrieval Stats
|
|
6097
|
+
`;
|
|
6098
|
+
const graphPct = (stats.graph_weight * 100).toFixed(0);
|
|
6099
|
+
const semPct = (stats.semantic_weight * 100).toFixed(0);
|
|
6100
|
+
response += ` Graph: ${graphPct}% │ Semantic: ${semPct}% │ Density: ${stats.graph_density.toFixed(2)}
|
|
6101
|
+
`;
|
|
6102
|
+
response += ` Candidates: ${stats.graph_candidates} graph + ${stats.semantic_candidates} semantic
|
|
6103
|
+
`;
|
|
6104
|
+
response += ` Entities: ${stats.entities_activated} │ Time: ${(stats.retrieval_time_us / 1000).toFixed(1)}ms`;
|
|
6105
|
+
}
|
|
6106
|
+
return {
|
|
6107
|
+
content: [{ type: "text", text: response }]
|
|
6108
|
+
};
|
|
6109
|
+
}
|
|
6110
|
+
case "context_summary": {
|
|
6111
|
+
const {
|
|
6112
|
+
include_decisions = true,
|
|
6113
|
+
include_learnings = true,
|
|
6114
|
+
include_context = true,
|
|
6115
|
+
max_items = 5
|
|
6116
|
+
} = args;
|
|
6117
|
+
const result = await apiCall("/api/memories", "POST", {
|
|
6118
|
+
user_id: USER_ID
|
|
6119
|
+
});
|
|
6120
|
+
const memories = result.memories || [];
|
|
5673
6121
|
if (memories.length === 0) {
|
|
6122
|
+
let response2 = `\uD83D\uDC18 Context Summary
|
|
6123
|
+
`;
|
|
6124
|
+
response2 += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6125
|
+
`;
|
|
6126
|
+
response2 += `No memories stored yet.
|
|
6127
|
+
`;
|
|
6128
|
+
response2 += `Start remembering to build context!`;
|
|
5674
6129
|
return {
|
|
5675
|
-
content: [
|
|
5676
|
-
{
|
|
5677
|
-
type: "text",
|
|
5678
|
-
text: "No memories stored yet."
|
|
5679
|
-
}
|
|
5680
|
-
]
|
|
6130
|
+
content: [{ type: "text", text: response2 }]
|
|
5681
6131
|
};
|
|
5682
6132
|
}
|
|
5683
|
-
const
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
6133
|
+
const decisions = [];
|
|
6134
|
+
const learnings = [];
|
|
6135
|
+
const context = [];
|
|
6136
|
+
const patterns = [];
|
|
6137
|
+
const errors2 = [];
|
|
6138
|
+
for (const m of memories) {
|
|
6139
|
+
const type = getType(m);
|
|
6140
|
+
switch (type) {
|
|
6141
|
+
case "Decision":
|
|
6142
|
+
decisions.push(m);
|
|
6143
|
+
break;
|
|
6144
|
+
case "Learning":
|
|
6145
|
+
learnings.push(m);
|
|
6146
|
+
break;
|
|
6147
|
+
case "Context":
|
|
6148
|
+
context.push(m);
|
|
6149
|
+
break;
|
|
6150
|
+
case "Pattern":
|
|
6151
|
+
patterns.push(m);
|
|
6152
|
+
break;
|
|
6153
|
+
case "Error":
|
|
6154
|
+
errors2.push(m);
|
|
6155
|
+
break;
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
let response = `\uD83D\uDC18 Context Summary
|
|
6159
|
+
`;
|
|
6160
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6161
|
+
`;
|
|
6162
|
+
response += `Total: ${memories.length} memories │ `;
|
|
6163
|
+
response += `\uD83D\uDCCB ${decisions.length} │ \uD83D\uDCA1 ${learnings.length} │ \uD83D\uDCC1 ${context.length} │ \uD83D\uDD04 ${patterns.length} │ ⚠️ ${errors2.length}
|
|
6164
|
+
`;
|
|
6165
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5688
6166
|
|
|
5689
|
-
|
|
6167
|
+
`;
|
|
6168
|
+
if (include_context && context.length > 0) {
|
|
6169
|
+
response += `\uD83D\uDCC1 PROJECT CONTEXT
|
|
6170
|
+
`;
|
|
6171
|
+
for (const m of context.slice(0, max_items)) {
|
|
6172
|
+
response += ` • ${getContent(m).slice(0, 70)}${getContent(m).length > 70 ? "..." : ""}
|
|
6173
|
+
`;
|
|
6174
|
+
}
|
|
6175
|
+
response += `
|
|
6176
|
+
`;
|
|
6177
|
+
}
|
|
6178
|
+
if (include_decisions && decisions.length > 0) {
|
|
6179
|
+
response += `\uD83D\uDCCB DECISIONS
|
|
6180
|
+
`;
|
|
6181
|
+
for (const m of decisions.slice(0, max_items)) {
|
|
6182
|
+
response += ` • ${getContent(m).slice(0, 70)}${getContent(m).length > 70 ? "..." : ""}
|
|
6183
|
+
`;
|
|
6184
|
+
}
|
|
6185
|
+
response += `
|
|
6186
|
+
`;
|
|
6187
|
+
}
|
|
6188
|
+
if (include_learnings && learnings.length > 0) {
|
|
6189
|
+
response += `\uD83D\uDCA1 LEARNINGS
|
|
6190
|
+
`;
|
|
6191
|
+
for (const m of learnings.slice(0, max_items)) {
|
|
6192
|
+
response += ` • ${getContent(m).slice(0, 70)}${getContent(m).length > 70 ? "..." : ""}
|
|
6193
|
+
`;
|
|
6194
|
+
}
|
|
6195
|
+
response += `
|
|
6196
|
+
`;
|
|
6197
|
+
}
|
|
6198
|
+
if (patterns.length > 0) {
|
|
6199
|
+
response += `\uD83D\uDD04 PATTERNS
|
|
6200
|
+
`;
|
|
6201
|
+
for (const m of patterns.slice(0, max_items)) {
|
|
6202
|
+
response += ` • ${getContent(m).slice(0, 70)}${getContent(m).length > 70 ? "..." : ""}
|
|
6203
|
+
`;
|
|
6204
|
+
}
|
|
6205
|
+
response += `
|
|
6206
|
+
`;
|
|
6207
|
+
}
|
|
6208
|
+
if (errors2.length > 0) {
|
|
6209
|
+
response += `⚠️ ERRORS TO AVOID
|
|
6210
|
+
`;
|
|
6211
|
+
for (const m of errors2.slice(0, Math.min(3, max_items))) {
|
|
6212
|
+
response += ` • ${getContent(m).slice(0, 70)}${getContent(m).length > 70 ? "..." : ""}
|
|
6213
|
+
`;
|
|
6214
|
+
}
|
|
6215
|
+
}
|
|
6216
|
+
if (decisions.length === 0 && learnings.length === 0 && context.length === 0) {
|
|
6217
|
+
response += `ℹ️ Tip: Use types like Decision, Learning, Context when remembering
|
|
6218
|
+
`;
|
|
6219
|
+
response += ` to build richer context summaries.`;
|
|
6220
|
+
}
|
|
5690
6221
|
return {
|
|
5691
|
-
content: [
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
6222
|
+
content: [{ type: "text", text: response.trimEnd() }]
|
|
6223
|
+
};
|
|
6224
|
+
}
|
|
6225
|
+
case "list_memories": {
|
|
6226
|
+
const { limit = 20 } = args;
|
|
6227
|
+
const result = await apiCall("/api/memories", "POST", {
|
|
6228
|
+
user_id: USER_ID
|
|
6229
|
+
});
|
|
6230
|
+
const memories = (result.memories || []).slice(0, limit);
|
|
6231
|
+
if (memories.length === 0) {
|
|
6232
|
+
let response2 = `\uD83D\uDC18 Memory List
|
|
6233
|
+
`;
|
|
6234
|
+
response2 += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6235
|
+
`;
|
|
6236
|
+
response2 += `No memories stored yet.`;
|
|
6237
|
+
return {
|
|
6238
|
+
content: [{ type: "text", text: response2 }]
|
|
6239
|
+
};
|
|
6240
|
+
}
|
|
6241
|
+
const typeCounts = {};
|
|
6242
|
+
for (const m of memories) {
|
|
6243
|
+
const type = getType(m);
|
|
6244
|
+
typeCounts[type] = (typeCounts[type] || 0) + 1;
|
|
6245
|
+
}
|
|
6246
|
+
let response = `\uD83D\uDC18 Memory List
|
|
6247
|
+
`;
|
|
6248
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6249
|
+
`;
|
|
6250
|
+
response += `Showing ${memories.length} memories
|
|
6251
|
+
`;
|
|
6252
|
+
response += `Types: ${Object.entries(typeCounts).map(([t, c]) => `${t}(${c})`).join(" │ ")}
|
|
6253
|
+
`;
|
|
6254
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5695
6255
|
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
]
|
|
6256
|
+
`;
|
|
6257
|
+
for (let i = 0;i < memories.length; i++) {
|
|
6258
|
+
const m = memories[i];
|
|
6259
|
+
const content = getContent(m);
|
|
6260
|
+
const typeIcon = {
|
|
6261
|
+
Decision: "\uD83D\uDCCB",
|
|
6262
|
+
Learning: "\uD83D\uDCA1",
|
|
6263
|
+
Context: "\uD83D\uDCC1",
|
|
6264
|
+
Pattern: "\uD83D\uDD04",
|
|
6265
|
+
Error: "⚠️",
|
|
6266
|
+
Observation: "\uD83D\uDC41️",
|
|
6267
|
+
Discovery: "\uD83D\uDD0D",
|
|
6268
|
+
Task: "✅",
|
|
6269
|
+
CodeEdit: "\uD83D\uDCDD",
|
|
6270
|
+
FileAccess: "\uD83D\uDCC4",
|
|
6271
|
+
Search: "\uD83D\uDD0E",
|
|
6272
|
+
Command: "⚡",
|
|
6273
|
+
Conversation: "\uD83D\uDCAC"
|
|
6274
|
+
}[getType(m)] || "\uD83D\uDCE6";
|
|
6275
|
+
response += `${String(i + 1).padStart(2)}. ${typeIcon} ${content.slice(0, 150)}${content.length > 150 ? "..." : ""}
|
|
6276
|
+
`;
|
|
6277
|
+
response += ` ┗━ ${getType(m)} │ ${m.id.slice(0, 8)}...
|
|
6278
|
+
`;
|
|
6279
|
+
}
|
|
6280
|
+
return {
|
|
6281
|
+
content: [{ type: "text", text: response.trimEnd() }]
|
|
5699
6282
|
};
|
|
5700
6283
|
}
|
|
5701
6284
|
case "forget": {
|
|
5702
6285
|
const { id } = args;
|
|
5703
6286
|
await apiCall(`/api/memory/${id}?user_id=${USER_ID}`, "DELETE");
|
|
6287
|
+
let response = `\uD83D\uDC18 Memory Deleted
|
|
6288
|
+
`;
|
|
6289
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6290
|
+
`;
|
|
6291
|
+
response += `✓ Removed: ${id.slice(0, 8)}...`;
|
|
5704
6292
|
return {
|
|
5705
|
-
content: [
|
|
5706
|
-
{
|
|
5707
|
-
type: "text",
|
|
5708
|
-
text: `Deleted memory: ${id}`
|
|
5709
|
-
}
|
|
5710
|
-
]
|
|
6293
|
+
content: [{ type: "text", text: response }]
|
|
5711
6294
|
};
|
|
5712
6295
|
}
|
|
5713
6296
|
case "memory_stats": {
|
|
5714
6297
|
const result = await apiCall(`/api/users/${USER_ID}/stats`, "GET");
|
|
6298
|
+
const indexedCount = result.vector_index_count ?? result.indexed_vectors ?? 0;
|
|
6299
|
+
const avgImportance = result.average_importance ?? result.avg_importance ?? 0;
|
|
6300
|
+
let response = `\uD83D\uDC18 Memory Statistics
|
|
6301
|
+
`;
|
|
6302
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6303
|
+
`;
|
|
6304
|
+
response += `Total Memories: ${result.total_memories || 0}
|
|
6305
|
+
`;
|
|
6306
|
+
response += `Graph: ${result.graph_nodes || 0} nodes │ ${result.graph_edges || 0} edges
|
|
6307
|
+
`;
|
|
6308
|
+
response += `Indexed Vectors: ${indexedCount}
|
|
6309
|
+
`;
|
|
6310
|
+
response += `Avg Importance: ${avgImportance.toFixed(2)}
|
|
6311
|
+
`;
|
|
6312
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6313
|
+
`;
|
|
6314
|
+
if (result.memory_types && Object.keys(result.memory_types).length > 0) {
|
|
6315
|
+
response += `
|
|
6316
|
+
By Type:
|
|
6317
|
+
`;
|
|
6318
|
+
const typeIcons = {
|
|
6319
|
+
Decision: "\uD83D\uDCCB",
|
|
6320
|
+
Learning: "\uD83D\uDCA1",
|
|
6321
|
+
Context: "\uD83D\uDCC1",
|
|
6322
|
+
Pattern: "\uD83D\uDD04",
|
|
6323
|
+
Error: "⚠️",
|
|
6324
|
+
Observation: "\uD83D\uDC41️",
|
|
6325
|
+
Discovery: "\uD83D\uDD0D",
|
|
6326
|
+
Task: "✅",
|
|
6327
|
+
CodeEdit: "\uD83D\uDCDD",
|
|
6328
|
+
FileAccess: "\uD83D\uDCC4",
|
|
6329
|
+
Search: "\uD83D\uDD0E",
|
|
6330
|
+
Command: "⚡",
|
|
6331
|
+
Conversation: "\uD83D\uDCAC"
|
|
6332
|
+
};
|
|
6333
|
+
for (const [type, count] of Object.entries(result.memory_types)) {
|
|
6334
|
+
const icon = typeIcons[type] || "\uD83D\uDCE6";
|
|
6335
|
+
const bar = "█".repeat(Math.min(20, Math.round(count / (result.total_memories || 1) * 20)));
|
|
6336
|
+
response += ` ${icon} ${type.padEnd(12)} ${bar} ${count}
|
|
6337
|
+
`;
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
5715
6340
|
return {
|
|
5716
|
-
content: [
|
|
5717
|
-
{
|
|
5718
|
-
type: "text",
|
|
5719
|
-
text: `Memory Statistics:
|
|
5720
|
-
${JSON.stringify(result, null, 2)}`
|
|
5721
|
-
}
|
|
5722
|
-
]
|
|
6341
|
+
content: [{ type: "text", text: response.trimEnd() }]
|
|
5723
6342
|
};
|
|
5724
6343
|
}
|
|
5725
6344
|
case "verify_index": {
|
|
5726
6345
|
const result = await apiCall("/api/index/verify", "POST", {
|
|
5727
6346
|
user_id: USER_ID
|
|
5728
6347
|
});
|
|
5729
|
-
const statusIcon = result.is_healthy ? "✓" : "
|
|
5730
|
-
const healthText = result.is_healthy ? "
|
|
5731
|
-
let response =
|
|
6348
|
+
const statusIcon = result.is_healthy ? "✓" : "⚠️";
|
|
6349
|
+
const healthText = result.is_healthy ? "HEALTHY" : "UNHEALTHY";
|
|
6350
|
+
let response = `\uD83D\uDC18 Index Verification
|
|
5732
6351
|
`;
|
|
5733
|
-
response +=
|
|
6352
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5734
6353
|
`;
|
|
5735
|
-
response +=
|
|
6354
|
+
response += `${statusIcon} ${healthText}
|
|
5736
6355
|
`;
|
|
5737
|
-
response +=
|
|
6356
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5738
6357
|
`;
|
|
5739
|
-
response += `
|
|
6358
|
+
response += `Storage: ${result.total_storage} memories
|
|
5740
6359
|
`;
|
|
5741
|
-
response += `
|
|
6360
|
+
response += `Indexed: ${result.total_indexed} vectors
|
|
6361
|
+
`;
|
|
6362
|
+
response += `Orphaned: ${result.orphaned_count}
|
|
5742
6363
|
`;
|
|
5743
6364
|
if (result.orphaned_count > 0) {
|
|
5744
|
-
response +=
|
|
5745
|
-
|
|
6365
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6366
|
+
`;
|
|
6367
|
+
response += `⚠️ Run repair_index to fix orphaned memories`;
|
|
5746
6368
|
}
|
|
5747
6369
|
return {
|
|
5748
|
-
content: [
|
|
5749
|
-
{
|
|
5750
|
-
type: "text",
|
|
5751
|
-
text: response
|
|
5752
|
-
}
|
|
5753
|
-
]
|
|
6370
|
+
content: [{ type: "text", text: response }]
|
|
5754
6371
|
};
|
|
5755
6372
|
}
|
|
5756
6373
|
case "repair_index": {
|
|
5757
6374
|
const result = await apiCall("/api/index/repair", "POST", {
|
|
5758
6375
|
user_id: USER_ID
|
|
5759
6376
|
});
|
|
5760
|
-
const statusIcon = result.is_healthy ? "✓" : "
|
|
5761
|
-
|
|
6377
|
+
const statusIcon = result.is_healthy ? "✓" : "⚠️";
|
|
6378
|
+
const statusText = result.success ? "SUCCESS" : "PARTIAL";
|
|
6379
|
+
let response = `\uD83D\uDC18 Index Repair
|
|
5762
6380
|
`;
|
|
5763
|
-
response +=
|
|
6381
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5764
6382
|
`;
|
|
5765
|
-
response +=
|
|
6383
|
+
response += `${statusIcon} ${statusText}
|
|
5766
6384
|
`;
|
|
5767
|
-
response +=
|
|
6385
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5768
6386
|
`;
|
|
5769
|
-
response += `
|
|
6387
|
+
response += `Storage: ${result.total_storage} memories
|
|
6388
|
+
`;
|
|
6389
|
+
response += `Indexed: ${result.total_indexed} vectors
|
|
5770
6390
|
`;
|
|
5771
6391
|
response += `Repaired: ${result.repaired}
|
|
5772
6392
|
`;
|
|
5773
6393
|
response += `Failed: ${result.failed}
|
|
5774
|
-
`;
|
|
5775
|
-
response += `Index healthy: ${result.is_healthy ? "Yes" : "No"}
|
|
5776
6394
|
`;
|
|
5777
6395
|
if (result.failed > 0) {
|
|
5778
|
-
response +=
|
|
5779
|
-
|
|
6396
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6397
|
+
`;
|
|
6398
|
+
response += `⚠️ ${result.failed} could not be repaired`;
|
|
5780
6399
|
}
|
|
5781
6400
|
return {
|
|
5782
|
-
content: [
|
|
5783
|
-
{
|
|
5784
|
-
type: "text",
|
|
5785
|
-
text: response
|
|
5786
|
-
}
|
|
5787
|
-
]
|
|
6401
|
+
content: [{ type: "text", text: response }]
|
|
5788
6402
|
};
|
|
5789
6403
|
}
|
|
5790
|
-
case "
|
|
5791
|
-
const
|
|
5792
|
-
|
|
5793
|
-
user_id: USER_ID,
|
|
5794
|
-
tags,
|
|
5795
|
-
limit
|
|
6404
|
+
case "backup_create": {
|
|
6405
|
+
const result = await apiCall("/api/backup/create", "POST", {
|
|
6406
|
+
user_id: USER_ID
|
|
5796
6407
|
});
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
6408
|
+
let response = `\uD83D\uDC18 Backup Created
|
|
6409
|
+
`;
|
|
6410
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6411
|
+
`;
|
|
6412
|
+
if (result.success && result.backup) {
|
|
6413
|
+
const b = result.backup;
|
|
6414
|
+
const sizeMB = (b.size_bytes / (1024 * 1024)).toFixed(2);
|
|
6415
|
+
response += `✓ Backup ID: ${b.backup_id}
|
|
6416
|
+
`;
|
|
6417
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6418
|
+
`;
|
|
6419
|
+
response += `Type: ${b.backup_type}
|
|
6420
|
+
`;
|
|
6421
|
+
response += `Memories: ${b.memory_count}
|
|
6422
|
+
`;
|
|
6423
|
+
response += `Size: ${sizeMB} MB
|
|
6424
|
+
`;
|
|
6425
|
+
response += `Checksum: ${b.checksum.slice(0, 16)}...
|
|
6426
|
+
`;
|
|
6427
|
+
response += `Created: ${new Date(b.created_at).toLocaleString()}
|
|
6428
|
+
`;
|
|
6429
|
+
} else {
|
|
6430
|
+
response += `✗ Failed: ${result.message}
|
|
6431
|
+
`;
|
|
5807
6432
|
}
|
|
5808
|
-
const formatted = memories.map((m, i) => {
|
|
5809
|
-
const content = getContent(m);
|
|
5810
|
-
return `${i + 1}. ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}
|
|
5811
|
-
Type: ${getType(m)} | ID: ${m.id.slice(0, 8)}...`;
|
|
5812
|
-
}).join(`
|
|
5813
|
-
|
|
5814
|
-
`);
|
|
5815
6433
|
return {
|
|
5816
|
-
content: [
|
|
5817
|
-
{
|
|
5818
|
-
type: "text",
|
|
5819
|
-
text: `Found ${memories.length} memories with tags [${tags.join(", ")}]:
|
|
5820
|
-
|
|
5821
|
-
${formatted}`
|
|
5822
|
-
}
|
|
5823
|
-
]
|
|
6434
|
+
content: [{ type: "text", text: response }]
|
|
5824
6435
|
};
|
|
5825
6436
|
}
|
|
5826
|
-
case "
|
|
5827
|
-
const
|
|
5828
|
-
|
|
5829
|
-
user_id: USER_ID,
|
|
5830
|
-
start,
|
|
5831
|
-
end,
|
|
5832
|
-
limit
|
|
6437
|
+
case "backup_list": {
|
|
6438
|
+
const result = await apiCall("/api/backups", "POST", {
|
|
6439
|
+
user_id: USER_ID
|
|
5833
6440
|
});
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
}
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
const content = getContent(m);
|
|
5847
|
-
return `${i + 1}. ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}
|
|
5848
|
-
Type: ${getType(m)} | Created: ${m.created_at || "unknown"}`;
|
|
5849
|
-
}).join(`
|
|
6441
|
+
let response = `\uD83D\uDC18 Available Backups
|
|
6442
|
+
`;
|
|
6443
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6444
|
+
`;
|
|
6445
|
+
if (result.backups.length === 0) {
|
|
6446
|
+
response += `No backups available.
|
|
6447
|
+
`;
|
|
6448
|
+
response += `Use backup_create to create your first backup.`;
|
|
6449
|
+
} else {
|
|
6450
|
+
response += `Found: ${result.count} backup(s)
|
|
6451
|
+
`;
|
|
6452
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
5850
6453
|
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
6454
|
+
`;
|
|
6455
|
+
for (const b of result.backups) {
|
|
6456
|
+
const sizeMB = (b.size_bytes / (1024 * 1024)).toFixed(2);
|
|
6457
|
+
const date = new Date(b.created_at).toLocaleString();
|
|
6458
|
+
response += `\uD83D\uDCE6 Backup #${b.backup_id}
|
|
6459
|
+
`;
|
|
6460
|
+
response += ` Type: ${b.backup_type} │ Memories: ${b.memory_count} │ Size: ${sizeMB} MB
|
|
6461
|
+
`;
|
|
6462
|
+
response += ` Created: ${date}
|
|
5857
6463
|
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
6464
|
+
`;
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
return {
|
|
6468
|
+
content: [{ type: "text", text: response.trimEnd() }]
|
|
5861
6469
|
};
|
|
5862
6470
|
}
|
|
5863
|
-
case "
|
|
5864
|
-
const {
|
|
5865
|
-
const result = await apiCall("/api/
|
|
6471
|
+
case "backup_verify": {
|
|
6472
|
+
const { backup_id } = args;
|
|
6473
|
+
const result = await apiCall("/api/backup/verify", "POST", {
|
|
5866
6474
|
user_id: USER_ID,
|
|
5867
|
-
|
|
6475
|
+
backup_id
|
|
5868
6476
|
});
|
|
6477
|
+
const statusIcon = result.is_valid ? "✓" : "✗";
|
|
6478
|
+
const statusText = result.is_valid ? "VALID" : "INVALID";
|
|
6479
|
+
let response = `\uD83D\uDC18 Backup Verification
|
|
6480
|
+
`;
|
|
6481
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6482
|
+
`;
|
|
6483
|
+
response += `${statusIcon} Backup #${backup_id}: ${statusText}
|
|
6484
|
+
`;
|
|
6485
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6486
|
+
`;
|
|
6487
|
+
response += result.message;
|
|
5869
6488
|
return {
|
|
5870
|
-
content: [
|
|
5871
|
-
{
|
|
5872
|
-
type: "text",
|
|
5873
|
-
text: `Deleted ${result.deleted_count} memories with tags: ${tags.join(", ")}`
|
|
5874
|
-
}
|
|
5875
|
-
]
|
|
6489
|
+
content: [{ type: "text", text: response }]
|
|
5876
6490
|
};
|
|
5877
6491
|
}
|
|
5878
|
-
case "
|
|
5879
|
-
const {
|
|
5880
|
-
const result = await apiCall("/api/
|
|
6492
|
+
case "backup_purge": {
|
|
6493
|
+
const { keep_count = 7 } = args;
|
|
6494
|
+
const result = await apiCall("/api/backups/purge", "POST", {
|
|
5881
6495
|
user_id: USER_ID,
|
|
5882
|
-
|
|
5883
|
-
end
|
|
6496
|
+
keep_count
|
|
5884
6497
|
});
|
|
6498
|
+
let response = `\uD83D\uDC18 Backup Purge
|
|
6499
|
+
`;
|
|
6500
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6501
|
+
`;
|
|
6502
|
+
if (result.purged_count === 0) {
|
|
6503
|
+
response += `No backups purged (keeping ${keep_count}, none exceeded limit)`;
|
|
6504
|
+
} else {
|
|
6505
|
+
response += `✓ Purged ${result.purged_count} old backup(s)
|
|
6506
|
+
`;
|
|
6507
|
+
response += `Kept ${keep_count} most recent backup(s)`;
|
|
6508
|
+
}
|
|
5885
6509
|
return {
|
|
5886
|
-
content: [
|
|
5887
|
-
{
|
|
5888
|
-
type: "text",
|
|
5889
|
-
text: `Deleted ${result.deleted_count} memories between ${start} and ${end}`
|
|
5890
|
-
}
|
|
5891
|
-
]
|
|
6510
|
+
content: [{ type: "text", text: response }]
|
|
5892
6511
|
};
|
|
5893
6512
|
}
|
|
5894
6513
|
case "proactive_context": {
|
|
@@ -5948,132 +6567,601 @@ ${entityList}
|
|
|
5948
6567
|
]
|
|
5949
6568
|
};
|
|
5950
6569
|
}
|
|
5951
|
-
const
|
|
6570
|
+
const entitySummary = entities.length > 0 ? `
|
|
6571
|
+
|
|
6572
|
+
Detected entities: ${entities.map((e) => `"${e.text}" (${e.entity_type})`).join(", ")}` : "";
|
|
6573
|
+
let reminderBlock = "";
|
|
6574
|
+
try {
|
|
6575
|
+
const dueResult = await apiCall("/api/reminders/due", "POST", {
|
|
6576
|
+
user_id: USER_ID,
|
|
6577
|
+
mark_triggered: false
|
|
6578
|
+
});
|
|
6579
|
+
const contextResult = await apiCall("/api/reminders/context", "POST", {
|
|
6580
|
+
user_id: USER_ID,
|
|
6581
|
+
context,
|
|
6582
|
+
mark_triggered: false
|
|
6583
|
+
});
|
|
6584
|
+
const allReminders = [...dueResult.reminders, ...contextResult.reminders];
|
|
6585
|
+
const uniqueReminders = allReminders.filter((r, i, arr) => arr.findIndex((x) => x.id === r.id) === i);
|
|
6586
|
+
if (uniqueReminders.length > 0) {
|
|
6587
|
+
reminderBlock = `
|
|
6588
|
+
|
|
6589
|
+
`;
|
|
6590
|
+
reminderBlock += `\uD83D\uDC18━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\uD83E\uDDE0
|
|
6591
|
+
`;
|
|
6592
|
+
reminderBlock += `┃ SHODH MEMORY REMINDERS (${String(uniqueReminders.length).padStart(2)}) ┃
|
|
6593
|
+
`;
|
|
6594
|
+
reminderBlock += `┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
6595
|
+
`;
|
|
6596
|
+
for (const r of uniqueReminders) {
|
|
6597
|
+
const icon = r.overdue_seconds && r.overdue_seconds > 0 ? "⏰" : "\uD83D\uDCCC";
|
|
6598
|
+
const contentText = r.content.slice(0, 38);
|
|
6599
|
+
reminderBlock += `┃ ${icon} ${contentText.padEnd(44)} [${r.id.slice(0, 8)}] ┃
|
|
6600
|
+
`;
|
|
6601
|
+
if (r.overdue_seconds && r.overdue_seconds > 0) {
|
|
6602
|
+
const mins = Math.round(r.overdue_seconds / 60);
|
|
6603
|
+
const overdueText = mins > 60 ? `⚠️ OVERDUE by ${Math.round(mins / 60)}h ${mins % 60}m` : `⚠️ OVERDUE by ${mins}m`;
|
|
6604
|
+
reminderBlock += `┃ ${overdueText.padEnd(47)} ┃
|
|
6605
|
+
`;
|
|
6606
|
+
} else if (r.due_at) {
|
|
6607
|
+
const dueText = `Due: ${new Date(r.due_at).toLocaleString()}`;
|
|
6608
|
+
reminderBlock += `┃ ${dueText.padEnd(47)} ┃
|
|
6609
|
+
`;
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
reminderBlock += `┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
6613
|
+
`;
|
|
6614
|
+
reminderBlock += `
|
|
6615
|
+
\uD83D\uDCA1 Use dismiss_reminder with the [id] shown above`;
|
|
6616
|
+
}
|
|
6617
|
+
} catch (e) {
|
|
6618
|
+
console.error("[proactive_context] Reminder check failed:", e);
|
|
6619
|
+
}
|
|
6620
|
+
let todoBlock = "";
|
|
6621
|
+
try {
|
|
6622
|
+
const dueTodosResult = await apiCall("/api/todos/due", "POST", {
|
|
6623
|
+
user_id: USER_ID
|
|
6624
|
+
});
|
|
6625
|
+
if (dueTodosResult.count > 0) {
|
|
6626
|
+
todoBlock = `
|
|
6627
|
+
|
|
6628
|
+
` + dueTodosResult.formatted;
|
|
6629
|
+
}
|
|
6630
|
+
} catch (e) {
|
|
6631
|
+
console.error("[proactive_context] Todo check failed:", e);
|
|
6632
|
+
}
|
|
6633
|
+
const now = new Date;
|
|
6634
|
+
const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
6635
|
+
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
6636
|
+
const temporalHeader = `\uD83D\uDCC5 ${dayNames[now.getDay()]}, ${monthNames[now.getMonth()]} ${now.getDate()}, ${now.getFullYear()} at ${now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
|
|
6637
|
+
|
|
6638
|
+
`;
|
|
6639
|
+
const formattedWithTime = memories.map((m, i) => {
|
|
5952
6640
|
const score = (m.relevance_score * 100).toFixed(0);
|
|
5953
6641
|
const entityMatchStr = m.matched_entities && m.matched_entities.length > 0 ? `
|
|
5954
6642
|
Entity matches: ${m.matched_entities.join(", ")}` : "";
|
|
5955
6643
|
const semScore = (m.semantic_similarity * 100).toFixed(0);
|
|
5956
|
-
|
|
6644
|
+
let timeStr = "";
|
|
6645
|
+
if (m.created_at) {
|
|
6646
|
+
const d = new Date(m.created_at);
|
|
6647
|
+
const diffMs = now.getTime() - d.getTime();
|
|
6648
|
+
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
|
6649
|
+
if (diffDays === 0) {
|
|
6650
|
+
timeStr = ` (today at ${d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })})`;
|
|
6651
|
+
} else if (diffDays === 1) {
|
|
6652
|
+
timeStr = ` (yesterday)`;
|
|
6653
|
+
} else if (diffDays < 7) {
|
|
6654
|
+
timeStr = ` (${diffDays}d ago)`;
|
|
6655
|
+
} else {
|
|
6656
|
+
timeStr = ` (${d.toLocaleDateString([], { month: "short", day: "numeric" })})`;
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
return `• [${score}%]${timeStr} ${m.content.slice(0, 100)}${m.content.length > 100 ? "..." : ""}
|
|
5957
6660
|
Type: ${m.memory_type} | semantic=${semScore}% | reason: ${m.relevance_reason}${entityMatchStr}`;
|
|
5958
6661
|
}).join(`
|
|
5959
6662
|
|
|
5960
6663
|
`);
|
|
5961
|
-
|
|
6664
|
+
return {
|
|
6665
|
+
content: [
|
|
6666
|
+
{
|
|
6667
|
+
type: "text",
|
|
6668
|
+
text: `${temporalHeader}Surfaced ${memories.length} relevant memories:
|
|
5962
6669
|
|
|
5963
|
-
|
|
6670
|
+
${formattedWithTime}${entitySummary}${reminderBlock}${todoBlock}
|
|
6671
|
+
|
|
6672
|
+
[Latency: ${result.latency_ms.toFixed(1)}ms | Threshold: ${(semantic_threshold * 100).toFixed(0)}%]`
|
|
6673
|
+
}
|
|
6674
|
+
]
|
|
6675
|
+
};
|
|
6676
|
+
}
|
|
6677
|
+
case "token_status": {
|
|
6678
|
+
const status = getTokenStatus();
|
|
6679
|
+
const sessionDuration = Math.round((Date.now() - sessionStartTime) / 1000 / 60);
|
|
6680
|
+
const remaining = status.budget - status.tokens;
|
|
6681
|
+
const percentUsed = Math.round(status.percent * 100);
|
|
6682
|
+
const barLength = 20;
|
|
6683
|
+
const filledLength = Math.round(percentUsed / 100 * barLength);
|
|
6684
|
+
const bar = "█".repeat(filledLength) + "░".repeat(barLength - filledLength);
|
|
6685
|
+
let response = `\uD83D\uDC18 Token Status
|
|
6686
|
+
`;
|
|
6687
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6688
|
+
`;
|
|
6689
|
+
response += `${bar} ${percentUsed}%
|
|
6690
|
+
`;
|
|
6691
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6692
|
+
`;
|
|
6693
|
+
response += `Used: ${status.tokens.toLocaleString()} tokens
|
|
6694
|
+
`;
|
|
6695
|
+
response += `Budget: ${status.budget.toLocaleString()} tokens
|
|
6696
|
+
`;
|
|
6697
|
+
response += `Remaining: ${remaining.toLocaleString()} tokens
|
|
6698
|
+
`;
|
|
6699
|
+
response += `Session: ${sessionDuration} min
|
|
6700
|
+
`;
|
|
6701
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6702
|
+
`;
|
|
6703
|
+
response += status.alert ? `⚠️ ALERT: ${percentUsed}% used - Consider new session` : `✓ Context window healthy`;
|
|
6704
|
+
return {
|
|
6705
|
+
content: [{ type: "text", text: response }]
|
|
6706
|
+
};
|
|
6707
|
+
}
|
|
6708
|
+
case "reset_token_session": {
|
|
6709
|
+
const previousTokens = sessionTokens;
|
|
6710
|
+
resetTokenSession();
|
|
6711
|
+
let response = `\uD83D\uDC18 Token Session Reset
|
|
6712
|
+
`;
|
|
6713
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6714
|
+
`;
|
|
6715
|
+
response += `Previous: ${previousTokens.toLocaleString()} tokens
|
|
6716
|
+
`;
|
|
6717
|
+
response += `Current: 0 tokens
|
|
6718
|
+
`;
|
|
6719
|
+
response += `Budget: ${TOKEN_BUDGET.toLocaleString()} tokens
|
|
6720
|
+
`;
|
|
6721
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6722
|
+
`;
|
|
6723
|
+
response += `✓ Counter cleared`;
|
|
6724
|
+
return {
|
|
6725
|
+
content: [{ type: "text", text: response }]
|
|
6726
|
+
};
|
|
6727
|
+
}
|
|
6728
|
+
case "consolidation_report": {
|
|
6729
|
+
const { since, until } = args;
|
|
6730
|
+
const result = await apiCall("/api/consolidation/report", "POST", {
|
|
6731
|
+
user_id: USER_ID,
|
|
6732
|
+
since,
|
|
6733
|
+
until
|
|
6734
|
+
});
|
|
6735
|
+
const stats = result.statistics;
|
|
6736
|
+
const eventCount = result.strengthened_memories.length + result.decayed_memories.length + result.formed_associations.length + result.strengthened_associations.length + result.potentiated_associations.length + result.pruned_associations.length + result.extracted_facts.length + result.reinforced_facts.length;
|
|
6737
|
+
const startDate = new Date(result.period.start).toLocaleString();
|
|
6738
|
+
const endDate = new Date(result.period.end).toLocaleString();
|
|
6739
|
+
let response = `\uD83D\uDC18 Consolidation Report
|
|
6740
|
+
`;
|
|
6741
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6742
|
+
`;
|
|
6743
|
+
response += `Period: ${startDate} → ${endDate}
|
|
6744
|
+
`;
|
|
6745
|
+
response += `Events: ${eventCount} │ Memories: ${stats.total_memories}
|
|
6746
|
+
`;
|
|
6747
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6748
|
+
|
|
6749
|
+
`;
|
|
6750
|
+
if (stats.memories_strengthened > 0 || stats.memories_decayed > 0 || stats.memories_at_risk > 0) {
|
|
6751
|
+
response += `\uD83E\uDDE0 MEMORY DYNAMICS
|
|
6752
|
+
`;
|
|
6753
|
+
if (stats.memories_strengthened > 0)
|
|
6754
|
+
response += ` ↑ ${stats.memories_strengthened} strengthened
|
|
6755
|
+
`;
|
|
6756
|
+
if (stats.memories_decayed > 0)
|
|
6757
|
+
response += ` ↓ ${stats.memories_decayed} decayed
|
|
6758
|
+
`;
|
|
6759
|
+
if (stats.memories_at_risk > 0)
|
|
6760
|
+
response += ` ⚠️ ${stats.memories_at_risk} at risk
|
|
6761
|
+
`;
|
|
6762
|
+
response += `
|
|
6763
|
+
`;
|
|
6764
|
+
}
|
|
6765
|
+
if (stats.edges_formed > 0 || stats.edges_strengthened > 0 || stats.edges_potentiated > 0 || stats.edges_pruned > 0) {
|
|
6766
|
+
response += `\uD83D\uDD17 ASSOCIATIONS (Hebbian)
|
|
6767
|
+
`;
|
|
6768
|
+
if (stats.edges_formed > 0)
|
|
6769
|
+
response += ` + ${stats.edges_formed} formed
|
|
6770
|
+
`;
|
|
6771
|
+
if (stats.edges_strengthened > 0)
|
|
6772
|
+
response += ` ↑ ${stats.edges_strengthened} strengthened
|
|
6773
|
+
`;
|
|
6774
|
+
if (stats.edges_potentiated > 0)
|
|
6775
|
+
response += ` ★ ${stats.edges_potentiated} permanent (LTP)
|
|
6776
|
+
`;
|
|
6777
|
+
if (stats.edges_pruned > 0)
|
|
6778
|
+
response += ` ✂ ${stats.edges_pruned} pruned
|
|
6779
|
+
`;
|
|
6780
|
+
response += `
|
|
6781
|
+
`;
|
|
6782
|
+
}
|
|
6783
|
+
if (stats.facts_extracted > 0 || stats.facts_reinforced > 0) {
|
|
6784
|
+
response += `\uD83D\uDCDA FACTS
|
|
6785
|
+
`;
|
|
6786
|
+
if (stats.facts_extracted > 0)
|
|
6787
|
+
response += ` + ${stats.facts_extracted} extracted
|
|
6788
|
+
`;
|
|
6789
|
+
if (stats.facts_reinforced > 0)
|
|
6790
|
+
response += ` ↑ ${stats.facts_reinforced} reinforced
|
|
6791
|
+
`;
|
|
6792
|
+
response += `
|
|
6793
|
+
`;
|
|
6794
|
+
}
|
|
6795
|
+
if (stats.maintenance_cycles > 0) {
|
|
6796
|
+
const durationSec = (stats.total_maintenance_duration_ms / 1000).toFixed(2);
|
|
6797
|
+
response += `⚙️ MAINTENANCE: ${stats.maintenance_cycles} cycles (${durationSec}s)
|
|
6798
|
+
`;
|
|
6799
|
+
}
|
|
6800
|
+
if (eventCount === 0) {
|
|
6801
|
+
response += `ℹ️ No consolidation activity in this period.
|
|
6802
|
+
`;
|
|
6803
|
+
response += ` Store and access memories to trigger learning.`;
|
|
6804
|
+
}
|
|
6805
|
+
return {
|
|
6806
|
+
content: [{ type: "text", text: response.trimEnd() }]
|
|
6807
|
+
};
|
|
6808
|
+
}
|
|
6809
|
+
case "set_reminder": {
|
|
6810
|
+
const { content, trigger_type, trigger_at, after_seconds, keywords, priority = 3, tags = [] } = args;
|
|
6811
|
+
let trigger;
|
|
6812
|
+
switch (trigger_type) {
|
|
6813
|
+
case "time":
|
|
6814
|
+
if (!trigger_at) {
|
|
6815
|
+
return {
|
|
6816
|
+
content: [{ type: "text", text: "Error: 'trigger_at' is required for time-based reminders" }],
|
|
6817
|
+
isError: true
|
|
6818
|
+
};
|
|
6819
|
+
}
|
|
6820
|
+
trigger = { type: "time", at: trigger_at };
|
|
6821
|
+
break;
|
|
6822
|
+
case "duration":
|
|
6823
|
+
if (!after_seconds || after_seconds <= 0) {
|
|
6824
|
+
return {
|
|
6825
|
+
content: [{ type: "text", text: "Error: 'after_seconds' must be positive for duration-based reminders" }],
|
|
6826
|
+
isError: true
|
|
6827
|
+
};
|
|
6828
|
+
}
|
|
6829
|
+
trigger = { type: "duration", after_seconds };
|
|
6830
|
+
break;
|
|
6831
|
+
case "context":
|
|
6832
|
+
if (!keywords || keywords.length === 0) {
|
|
6833
|
+
return {
|
|
6834
|
+
content: [{ type: "text", text: "Error: 'keywords' is required for context-based reminders" }],
|
|
6835
|
+
isError: true
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
trigger = { type: "context", keywords, threshold: 0.7 };
|
|
6839
|
+
break;
|
|
6840
|
+
default:
|
|
6841
|
+
return {
|
|
6842
|
+
content: [{ type: "text", text: `Error: Invalid trigger_type: ${trigger_type}` }],
|
|
6843
|
+
isError: true
|
|
6844
|
+
};
|
|
6845
|
+
}
|
|
6846
|
+
const result = await apiCall("/api/remind", "POST", {
|
|
6847
|
+
user_id: USER_ID,
|
|
6848
|
+
content,
|
|
6849
|
+
trigger,
|
|
6850
|
+
priority,
|
|
6851
|
+
tags
|
|
6852
|
+
});
|
|
6853
|
+
let response = `\uD83D\uDC18 Reminder Set
|
|
6854
|
+
`;
|
|
6855
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6856
|
+
`;
|
|
6857
|
+
response += `ID: ${result.id.slice(0, 8)}...
|
|
6858
|
+
`;
|
|
6859
|
+
response += `Content: ${content}
|
|
6860
|
+
`;
|
|
6861
|
+
response += `Trigger: ${trigger_type}`;
|
|
6862
|
+
if (trigger_type === "time" && result.due_at) {
|
|
6863
|
+
response += ` (${new Date(result.due_at).toLocaleString()})`;
|
|
6864
|
+
} else if (trigger_type === "duration" && after_seconds) {
|
|
6865
|
+
const mins = Math.round(after_seconds / 60);
|
|
6866
|
+
response += ` (in ${mins > 60 ? Math.round(mins / 60) + "h" : mins + "m"})`;
|
|
6867
|
+
} else if (trigger_type === "context" && keywords) {
|
|
6868
|
+
response += ` (keywords: ${keywords.join(", ")})`;
|
|
6869
|
+
}
|
|
6870
|
+
response += `
|
|
6871
|
+
`;
|
|
6872
|
+
if (priority !== 3) {
|
|
6873
|
+
response += `Priority: ${"★".repeat(priority)}${"☆".repeat(5 - priority)}
|
|
6874
|
+
`;
|
|
6875
|
+
}
|
|
6876
|
+
return {
|
|
6877
|
+
content: [{ type: "text", text: response }]
|
|
6878
|
+
};
|
|
6879
|
+
}
|
|
6880
|
+
case "list_reminders": {
|
|
6881
|
+
const { status = "pending" } = args;
|
|
6882
|
+
const result = await apiCall("/api/reminders", "POST", {
|
|
6883
|
+
user_id: USER_ID,
|
|
6884
|
+
status: status === "all" ? null : status
|
|
6885
|
+
});
|
|
6886
|
+
if (result.count === 0) {
|
|
6887
|
+
return {
|
|
6888
|
+
content: [{ type: "text", text: `No ${status === "all" ? "" : status + " "}reminders found.` }]
|
|
6889
|
+
};
|
|
6890
|
+
}
|
|
6891
|
+
let response = `\uD83D\uDC18 SHODH REMINDERS (${result.count})
|
|
6892
|
+
`;
|
|
6893
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6894
|
+
`;
|
|
6895
|
+
for (const r of result.reminders) {
|
|
6896
|
+
const icon = r.overdue_seconds && r.overdue_seconds > 0 ? "⏰" : "\uD83D\uDCCC";
|
|
6897
|
+
const statusBadge = r.status === "triggered" ? " [TRIGGERED]" : "";
|
|
6898
|
+
response += `${icon} ${r.content.slice(0, 50)}${r.content.length > 50 ? "..." : ""}${statusBadge}
|
|
6899
|
+
`;
|
|
6900
|
+
response += ` Type: ${r.trigger_type} | Priority: ${"★".repeat(r.priority)} | ID: ${r.id.slice(0, 8)}...
|
|
6901
|
+
`;
|
|
6902
|
+
if (r.due_at) {
|
|
6903
|
+
response += ` Due: ${new Date(r.due_at).toLocaleString()}
|
|
6904
|
+
`;
|
|
6905
|
+
}
|
|
6906
|
+
if (r.overdue_seconds && r.overdue_seconds > 0) {
|
|
6907
|
+
const mins = Math.round(r.overdue_seconds / 60);
|
|
6908
|
+
response += ` ⚠️ Overdue by ${mins > 60 ? Math.round(mins / 60) + "h" : mins + "m"}
|
|
6909
|
+
`;
|
|
6910
|
+
}
|
|
6911
|
+
response += `
|
|
6912
|
+
`;
|
|
6913
|
+
}
|
|
6914
|
+
return {
|
|
6915
|
+
content: [{ type: "text", text: response }]
|
|
6916
|
+
};
|
|
6917
|
+
}
|
|
6918
|
+
case "dismiss_reminder": {
|
|
6919
|
+
const { reminder_id } = args;
|
|
6920
|
+
const result = await apiCall(`/api/reminders/${reminder_id}/dismiss`, "POST", {
|
|
6921
|
+
user_id: USER_ID
|
|
6922
|
+
});
|
|
6923
|
+
return {
|
|
6924
|
+
content: [
|
|
6925
|
+
{
|
|
6926
|
+
type: "text",
|
|
6927
|
+
text: result.success ? `✓ Reminder dismissed: ${reminder_id.slice(0, 8)}...` : `⚠️ ${result.message}`
|
|
6928
|
+
}
|
|
6929
|
+
]
|
|
6930
|
+
};
|
|
6931
|
+
}
|
|
6932
|
+
case "add_todo": {
|
|
6933
|
+
const {
|
|
6934
|
+
content: todoContent,
|
|
6935
|
+
status = "todo",
|
|
6936
|
+
priority = "medium",
|
|
6937
|
+
project,
|
|
6938
|
+
contexts = [],
|
|
6939
|
+
due_date,
|
|
6940
|
+
tags = [],
|
|
6941
|
+
blocked_on,
|
|
6942
|
+
notes,
|
|
6943
|
+
recurrence
|
|
6944
|
+
} = args;
|
|
6945
|
+
const result = await apiCall("/api/todos/add", "POST", {
|
|
6946
|
+
user_id: USER_ID,
|
|
6947
|
+
content: todoContent,
|
|
6948
|
+
status,
|
|
6949
|
+
priority,
|
|
6950
|
+
project,
|
|
6951
|
+
contexts,
|
|
6952
|
+
due_date,
|
|
6953
|
+
tags,
|
|
6954
|
+
blocked_on,
|
|
6955
|
+
notes,
|
|
6956
|
+
recurrence
|
|
6957
|
+
});
|
|
6958
|
+
return {
|
|
6959
|
+
content: [{ type: "text", text: result.formatted }]
|
|
6960
|
+
};
|
|
6961
|
+
}
|
|
6962
|
+
case "list_todos": {
|
|
6963
|
+
const {
|
|
6964
|
+
query,
|
|
6965
|
+
status: statusFilter,
|
|
6966
|
+
project,
|
|
6967
|
+
context,
|
|
6968
|
+
priority,
|
|
6969
|
+
due,
|
|
6970
|
+
limit = 50,
|
|
6971
|
+
offset = 0
|
|
6972
|
+
} = args;
|
|
6973
|
+
const result = await apiCall("/api/todos/list", "POST", {
|
|
6974
|
+
user_id: USER_ID,
|
|
6975
|
+
query,
|
|
6976
|
+
status: statusFilter,
|
|
6977
|
+
project,
|
|
6978
|
+
context,
|
|
6979
|
+
priority,
|
|
6980
|
+
due,
|
|
6981
|
+
limit,
|
|
6982
|
+
offset
|
|
6983
|
+
});
|
|
6984
|
+
return {
|
|
6985
|
+
content: [{ type: "text", text: result.formatted }]
|
|
6986
|
+
};
|
|
6987
|
+
}
|
|
6988
|
+
case "update_todo": {
|
|
6989
|
+
const {
|
|
6990
|
+
todo_id,
|
|
6991
|
+
content: newContent,
|
|
6992
|
+
status,
|
|
6993
|
+
priority,
|
|
6994
|
+
project,
|
|
6995
|
+
contexts,
|
|
6996
|
+
due_date,
|
|
6997
|
+
blocked_on,
|
|
6998
|
+
notes,
|
|
6999
|
+
tags,
|
|
7000
|
+
parent_id
|
|
7001
|
+
} = args;
|
|
7002
|
+
const result = await apiCall(`/api/todos/${todo_id}/update`, "POST", {
|
|
7003
|
+
user_id: USER_ID,
|
|
7004
|
+
content: newContent,
|
|
7005
|
+
status,
|
|
7006
|
+
priority,
|
|
7007
|
+
project,
|
|
7008
|
+
contexts,
|
|
7009
|
+
due_date,
|
|
7010
|
+
blocked_on,
|
|
7011
|
+
notes,
|
|
7012
|
+
tags,
|
|
7013
|
+
parent_id
|
|
7014
|
+
});
|
|
7015
|
+
return {
|
|
7016
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7017
|
+
};
|
|
7018
|
+
}
|
|
7019
|
+
case "complete_todo": {
|
|
7020
|
+
const { todo_id } = args;
|
|
7021
|
+
const result = await apiCall(`/api/todos/${todo_id}/complete`, "POST", {
|
|
7022
|
+
user_id: USER_ID
|
|
7023
|
+
});
|
|
7024
|
+
return {
|
|
7025
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7026
|
+
};
|
|
7027
|
+
}
|
|
7028
|
+
case "delete_todo": {
|
|
7029
|
+
const { todo_id } = args;
|
|
7030
|
+
const result = await apiCall(`/api/todos/${todo_id}?user_id=${USER_ID}`, "DELETE");
|
|
7031
|
+
return {
|
|
7032
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7033
|
+
};
|
|
7034
|
+
}
|
|
7035
|
+
case "reorder_todo": {
|
|
7036
|
+
const { todo_id, direction } = args;
|
|
7037
|
+
const result = await apiCall(`/api/todos/${todo_id}/reorder`, "POST", {
|
|
7038
|
+
user_id: USER_ID,
|
|
7039
|
+
direction
|
|
7040
|
+
});
|
|
7041
|
+
return {
|
|
7042
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7043
|
+
};
|
|
7044
|
+
}
|
|
7045
|
+
case "add_project": {
|
|
7046
|
+
const { name: name2, prefix, description, parent } = args;
|
|
7047
|
+
const result = await apiCall("/api/projects", "POST", {
|
|
7048
|
+
user_id: USER_ID,
|
|
7049
|
+
name: name2,
|
|
7050
|
+
prefix,
|
|
7051
|
+
description,
|
|
7052
|
+
parent
|
|
7053
|
+
});
|
|
7054
|
+
return {
|
|
7055
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7056
|
+
};
|
|
7057
|
+
}
|
|
7058
|
+
case "list_projects": {
|
|
7059
|
+
const result = await apiCall("/api/projects/list", "POST", {
|
|
7060
|
+
user_id: USER_ID
|
|
7061
|
+
});
|
|
7062
|
+
return {
|
|
7063
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7064
|
+
};
|
|
7065
|
+
}
|
|
7066
|
+
case "archive_project": {
|
|
7067
|
+
const { project } = args;
|
|
7068
|
+
const result = await apiCall(`/api/projects/${encodeURIComponent(project)}/update`, "POST", {
|
|
7069
|
+
user_id: USER_ID,
|
|
7070
|
+
status: "archived"
|
|
7071
|
+
});
|
|
7072
|
+
return {
|
|
7073
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7074
|
+
};
|
|
7075
|
+
}
|
|
7076
|
+
case "delete_project": {
|
|
7077
|
+
const { project, delete_todos } = args;
|
|
7078
|
+
const result = await apiCall(`/api/projects/${encodeURIComponent(project)}`, "DELETE", {
|
|
7079
|
+
user_id: USER_ID,
|
|
7080
|
+
delete_todos: delete_todos ?? false
|
|
7081
|
+
});
|
|
7082
|
+
return {
|
|
7083
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7084
|
+
};
|
|
7085
|
+
}
|
|
7086
|
+
case "todo_stats": {
|
|
7087
|
+
const result = await apiCall("/api/todos/stats", "POST", {
|
|
7088
|
+
user_id: USER_ID
|
|
7089
|
+
});
|
|
7090
|
+
return {
|
|
7091
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7092
|
+
};
|
|
7093
|
+
}
|
|
7094
|
+
case "list_subtasks": {
|
|
7095
|
+
const { parent_id } = args;
|
|
7096
|
+
const result = await apiCall(`/api/todos/${parent_id}/subtasks?user_id=${USER_ID}`, "GET");
|
|
7097
|
+
return {
|
|
7098
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
case "add_todo_comment": {
|
|
7102
|
+
const { todo_id, content, comment_type } = args;
|
|
7103
|
+
const result = await apiCall(`/api/todos/${todo_id}/comments`, "POST", {
|
|
7104
|
+
user_id: USER_ID,
|
|
7105
|
+
content,
|
|
7106
|
+
comment_type
|
|
7107
|
+
});
|
|
5964
7108
|
return {
|
|
5965
|
-
content: [
|
|
5966
|
-
{
|
|
5967
|
-
type: "text",
|
|
5968
|
-
text: `Surfaced ${memories.length} relevant memories:
|
|
5969
|
-
|
|
5970
|
-
${formatted}${entitySummary}
|
|
5971
|
-
|
|
5972
|
-
[Latency: ${result.latency_ms.toFixed(1)}ms | Threshold: ${(semantic_threshold * 100).toFixed(0)}%]`
|
|
5973
|
-
}
|
|
5974
|
-
]
|
|
7109
|
+
content: [{ type: "text", text: result.formatted }]
|
|
5975
7110
|
};
|
|
5976
7111
|
}
|
|
5977
|
-
case "
|
|
5978
|
-
const
|
|
5979
|
-
const
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
enabled: STREAM_ENABLED,
|
|
5983
|
-
ws_url: WS_URL,
|
|
5984
|
-
socket_state: stateName,
|
|
5985
|
-
handshake_complete: streamHandshakeComplete,
|
|
5986
|
-
buffer_size: streamBuffer.length,
|
|
5987
|
-
connecting: streamConnecting,
|
|
5988
|
-
reconnect_pending: streamReconnectTimer !== null
|
|
7112
|
+
case "list_todo_comments": {
|
|
7113
|
+
const { todo_id } = args;
|
|
7114
|
+
const result = await apiCall(`/api/todos/${todo_id}/comments?user_id=${USER_ID}`, "GET");
|
|
7115
|
+
return {
|
|
7116
|
+
content: [{ type: "text", text: result.formatted }]
|
|
5989
7117
|
};
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
}
|
|
7118
|
+
}
|
|
7119
|
+
case "update_todo_comment": {
|
|
7120
|
+
const { todo_id, comment_id, content } = args;
|
|
7121
|
+
const result = await apiCall(`/api/todos/${todo_id}/comments/${comment_id}/update`, "POST", {
|
|
7122
|
+
user_id: USER_ID,
|
|
7123
|
+
content
|
|
7124
|
+
});
|
|
5993
7125
|
return {
|
|
5994
|
-
content: [
|
|
5995
|
-
{
|
|
5996
|
-
type: "text",
|
|
5997
|
-
text: `Streaming Status:
|
|
5998
|
-
|
|
5999
|
-
` + `Enabled: ${status.enabled}
|
|
6000
|
-
` + `WebSocket URL: ${status.ws_url}
|
|
6001
|
-
` + `Socket State: ${status.socket_state}
|
|
6002
|
-
` + `Handshake Complete: ${status.handshake_complete}
|
|
6003
|
-
` + `Buffer Size: ${status.buffer_size}
|
|
6004
|
-
` + `Currently Connecting: ${status.connecting}
|
|
6005
|
-
` + `Reconnect Pending: ${status.reconnect_pending}
|
|
6006
|
-
|
|
6007
|
-
` + (status.handshake_complete ? "✓ Streaming is ACTIVE" : "✗ Streaming is NOT ACTIVE - attempting reconnect...")
|
|
6008
|
-
}
|
|
6009
|
-
]
|
|
7126
|
+
content: [{ type: "text", text: result.formatted }]
|
|
6010
7127
|
};
|
|
6011
7128
|
}
|
|
6012
|
-
case "
|
|
6013
|
-
const {
|
|
6014
|
-
const result = await apiCall(
|
|
7129
|
+
case "delete_todo_comment": {
|
|
7130
|
+
const { todo_id, comment_id } = args;
|
|
7131
|
+
const result = await apiCall(`/api/todos/${todo_id}/comments/${comment_id}?user_id=${USER_ID}`, "DELETE");
|
|
7132
|
+
return {
|
|
7133
|
+
content: [{ type: "text", text: result.formatted }]
|
|
7134
|
+
};
|
|
7135
|
+
}
|
|
7136
|
+
case "read_memory": {
|
|
7137
|
+
const { memory_id } = args;
|
|
7138
|
+
const result = await apiCall("/api/recall", "POST", {
|
|
6015
7139
|
user_id: USER_ID,
|
|
6016
|
-
|
|
6017
|
-
|
|
7140
|
+
query: memory_id,
|
|
7141
|
+
limit: 20
|
|
6018
7142
|
});
|
|
6019
|
-
const
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
sections.push("=".repeat(50));
|
|
6025
|
-
if (stats.memories_strengthened > 0 || stats.memories_decayed > 0 || stats.memories_at_risk > 0) {
|
|
6026
|
-
const memoryLines = [];
|
|
6027
|
-
if (stats.memories_strengthened > 0)
|
|
6028
|
-
memoryLines.push(` + ${stats.memories_strengthened} memories strengthened`);
|
|
6029
|
-
if (stats.memories_decayed > 0)
|
|
6030
|
-
memoryLines.push(` - ${stats.memories_decayed} memories decayed`);
|
|
6031
|
-
if (stats.memories_at_risk > 0)
|
|
6032
|
-
memoryLines.push(` ! ${stats.memories_at_risk} memories at risk of forgetting`);
|
|
6033
|
-
sections.push(`MEMORY CHANGES:
|
|
6034
|
-
${memoryLines.join(`
|
|
6035
|
-
`)}`);
|
|
6036
|
-
}
|
|
6037
|
-
if (stats.edges_formed > 0 || stats.edges_strengthened > 0 || stats.edges_potentiated > 0 || stats.edges_pruned > 0) {
|
|
6038
|
-
const edgeLines = [];
|
|
6039
|
-
if (stats.edges_formed > 0)
|
|
6040
|
-
edgeLines.push(` + ${stats.edges_formed} new associations formed`);
|
|
6041
|
-
if (stats.edges_strengthened > 0)
|
|
6042
|
-
edgeLines.push(` + ${stats.edges_strengthened} associations strengthened`);
|
|
6043
|
-
if (stats.edges_potentiated > 0)
|
|
6044
|
-
edgeLines.push(` * ${stats.edges_potentiated} associations became permanent (LTP)`);
|
|
6045
|
-
if (stats.edges_pruned > 0)
|
|
6046
|
-
edgeLines.push(` - ${stats.edges_pruned} weak associations pruned`);
|
|
6047
|
-
sections.push(`ASSOCIATIONS (Hebbian Learning):
|
|
6048
|
-
${edgeLines.join(`
|
|
6049
|
-
`)}`);
|
|
6050
|
-
}
|
|
6051
|
-
if (stats.facts_extracted > 0 || stats.facts_reinforced > 0) {
|
|
6052
|
-
const factLines = [];
|
|
6053
|
-
if (stats.facts_extracted > 0)
|
|
6054
|
-
factLines.push(` + ${stats.facts_extracted} facts extracted`);
|
|
6055
|
-
if (stats.facts_reinforced > 0)
|
|
6056
|
-
factLines.push(` + ${stats.facts_reinforced} facts reinforced`);
|
|
6057
|
-
sections.push(`FACTS:
|
|
6058
|
-
${factLines.join(`
|
|
6059
|
-
`)}`);
|
|
6060
|
-
}
|
|
6061
|
-
if (stats.maintenance_cycles > 0) {
|
|
6062
|
-
const durationSec = (stats.total_maintenance_duration_ms / 1000).toFixed(2);
|
|
6063
|
-
sections.push(`MAINTENANCE: ${stats.maintenance_cycles} cycle(s) completed (${durationSec}s total)`);
|
|
6064
|
-
}
|
|
6065
|
-
if (eventCount === 0) {
|
|
6066
|
-
sections.push("No consolidation activity in this period. Store and access memories to trigger learning.");
|
|
7143
|
+
const memory = result.memories.find((m) => m.id.toLowerCase().startsWith(memory_id.toLowerCase()));
|
|
7144
|
+
if (!memory) {
|
|
7145
|
+
return {
|
|
7146
|
+
content: [{ type: "text", text: `❌ Memory not found: ${memory_id}` }]
|
|
7147
|
+
};
|
|
6067
7148
|
}
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
7149
|
+
const tags = memory.experience.tags?.join(", ") || "none";
|
|
7150
|
+
const created = new Date(memory.created_at).toLocaleString();
|
|
7151
|
+
let response = `\uD83D\uDC18 Memory: ${memory.id}
|
|
7152
|
+
`;
|
|
7153
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
7154
|
+
`;
|
|
7155
|
+
response += `Type: ${memory.experience.memory_type} │ Tags: ${tags}
|
|
7156
|
+
`;
|
|
7157
|
+
response += `Created: ${created} │ Importance: ${(memory.importance * 100).toFixed(0)}%
|
|
7158
|
+
`;
|
|
7159
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6073
7160
|
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
7161
|
+
`;
|
|
7162
|
+
response += memory.experience.content;
|
|
7163
|
+
return {
|
|
7164
|
+
content: [{ type: "text", text: response }]
|
|
6077
7165
|
};
|
|
6078
7166
|
}
|
|
6079
7167
|
default:
|
|
@@ -6085,6 +7173,9 @@ ${factLines.join(`
|
|
|
6085
7173
|
const resultText = result.content.map((c) => c.text).join(`
|
|
6086
7174
|
`);
|
|
6087
7175
|
streamToolCall(name, args, resultText);
|
|
7176
|
+
const responseTokens = estimateTokens(resultText);
|
|
7177
|
+
sessionTokens += responseTokens;
|
|
7178
|
+
const tokenStatus = getTokenStatus();
|
|
6088
7179
|
if (PROACTIVE_SURFACING && !["remember", "recall", "forget", "list_memories", "proactive_context", "context_summary", "memory_stats"].includes(name)) {
|
|
6089
7180
|
const contextParts = [];
|
|
6090
7181
|
if (args && typeof args === "object") {
|
|
@@ -6103,7 +7194,19 @@ ${factLines.join(`
|
|
|
6103
7194
|
}
|
|
6104
7195
|
}
|
|
6105
7196
|
}
|
|
6106
|
-
|
|
7197
|
+
if (tokenStatus.alert) {
|
|
7198
|
+
const percentUsed = Math.round(tokenStatus.percent * 100);
|
|
7199
|
+
const warning = `⚠️ CONTEXT ALERT: ${percentUsed}% of token budget used (${tokenStatus.tokens.toLocaleString()}/${tokenStatus.budget.toLocaleString()}). Consider starting a new session or running consolidation.
|
|
7200
|
+
|
|
7201
|
+
`;
|
|
7202
|
+
result.content[0].text = warning + result.content[0].text;
|
|
7203
|
+
}
|
|
7204
|
+
return {
|
|
7205
|
+
...result,
|
|
7206
|
+
_meta: {
|
|
7207
|
+
token_status: tokenStatus
|
|
7208
|
+
}
|
|
7209
|
+
};
|
|
6107
7210
|
} catch (error) {
|
|
6108
7211
|
const message = error instanceof Error ? error.message : String(error);
|
|
6109
7212
|
let helpText = "";
|
|
@@ -6133,30 +7236,191 @@ Endpoint not found. The server may be running an older version.`;
|
|
|
6133
7236
|
}
|
|
6134
7237
|
});
|
|
6135
7238
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
7239
|
+
const staticResources = [
|
|
7240
|
+
{
|
|
7241
|
+
uri: "shodh://commands",
|
|
7242
|
+
name: "Available Commands",
|
|
7243
|
+
mimeType: "text/markdown",
|
|
7244
|
+
description: "List all shodh-memory commands and their usage"
|
|
7245
|
+
},
|
|
7246
|
+
{
|
|
7247
|
+
uri: "shodh://summary",
|
|
7248
|
+
name: "Session Summary",
|
|
7249
|
+
mimeType: "text/plain",
|
|
7250
|
+
description: "Recent learnings, decisions, and context"
|
|
7251
|
+
},
|
|
7252
|
+
{
|
|
7253
|
+
uri: "shodh://todos",
|
|
7254
|
+
name: "Pending Work",
|
|
7255
|
+
mimeType: "text/plain",
|
|
7256
|
+
description: "Your todo list and incomplete tasks"
|
|
7257
|
+
},
|
|
7258
|
+
{
|
|
7259
|
+
uri: "shodh://stats",
|
|
7260
|
+
name: "Memory Stats",
|
|
7261
|
+
mimeType: "application/json",
|
|
7262
|
+
description: "Memory system statistics and health"
|
|
7263
|
+
}
|
|
7264
|
+
];
|
|
6136
7265
|
try {
|
|
6137
7266
|
const result = await apiCall("/api/memories", "POST", {
|
|
6138
7267
|
user_id: USER_ID
|
|
6139
7268
|
});
|
|
6140
7269
|
const memories = result.memories || [];
|
|
7270
|
+
const memoryResources = memories.slice(0, 30).map((m) => {
|
|
7271
|
+
const content = getContent(m);
|
|
7272
|
+
return {
|
|
7273
|
+
uri: `memory://${m.id}`,
|
|
7274
|
+
name: content.slice(0, 50) + (content.length > 50 ? "..." : ""),
|
|
7275
|
+
mimeType: "text/plain",
|
|
7276
|
+
description: `Type: ${getType(m)}`
|
|
7277
|
+
};
|
|
7278
|
+
});
|
|
6141
7279
|
return {
|
|
6142
|
-
resources:
|
|
6143
|
-
const content = getContent(m);
|
|
6144
|
-
return {
|
|
6145
|
-
uri: `memory://${m.id}`,
|
|
6146
|
-
name: content.slice(0, 50) + (content.length > 50 ? "..." : ""),
|
|
6147
|
-
mimeType: "text/plain",
|
|
6148
|
-
description: `Type: ${getType(m)}`
|
|
6149
|
-
};
|
|
6150
|
-
})
|
|
7280
|
+
resources: [...staticResources, ...memoryResources]
|
|
6151
7281
|
};
|
|
6152
7282
|
} catch {
|
|
6153
|
-
return { resources:
|
|
7283
|
+
return { resources: staticResources };
|
|
6154
7284
|
}
|
|
6155
7285
|
});
|
|
6156
7286
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
6157
7287
|
const uri = request.params.uri;
|
|
6158
|
-
const memoryId = uri.replace("memory://", "");
|
|
6159
7288
|
try {
|
|
7289
|
+
if (uri.startsWith("shodh://")) {
|
|
7290
|
+
const resource = uri.replace("shodh://", "");
|
|
7291
|
+
switch (resource) {
|
|
7292
|
+
case "commands": {
|
|
7293
|
+
const commandList = `# Shodh-Memory Commands
|
|
7294
|
+
|
|
7295
|
+
## Memory Tools
|
|
7296
|
+
- **remember** - Store a memory (observation, decision, learning, etc.)
|
|
7297
|
+
- **recall** - Search memories (semantic, associative, or hybrid mode)
|
|
7298
|
+
- **recall_by_tags** - Find memories by tags
|
|
7299
|
+
- **recall_by_date** - Find memories in a date range
|
|
7300
|
+
- **forget** - Delete a specific memory
|
|
7301
|
+
- **context_summary** - Get recent learnings, decisions, and context
|
|
7302
|
+
- **proactive_context** - Surface relevant memories for current context
|
|
7303
|
+
- **list_memories** - List all stored memories
|
|
7304
|
+
- **memory_stats** - Get memory system statistics
|
|
7305
|
+
|
|
7306
|
+
## Todo Tools
|
|
7307
|
+
- **add_todo** - Add a task to your todo list
|
|
7308
|
+
- **list_todos** - View pending tasks
|
|
7309
|
+
- **update_todo** - Modify a todo
|
|
7310
|
+
- **complete_todo** - Mark a todo as done
|
|
7311
|
+
- **delete_todo** - Remove a todo
|
|
7312
|
+
- **list_projects** - View project hierarchy
|
|
7313
|
+
- **add_project** - Create a new project
|
|
7314
|
+
- **todo_stats** - Get todo statistics
|
|
7315
|
+
|
|
7316
|
+
## System Tools
|
|
7317
|
+
- **verify_index** - Check memory index health
|
|
7318
|
+
- **repair_index** - Fix orphaned memories
|
|
7319
|
+
- **streaming_status** - Check streaming connection
|
|
7320
|
+
- **token_status** - Check context window usage
|
|
7321
|
+
- **reset_token_session** - Reset token tracking
|
|
7322
|
+
|
|
7323
|
+
## Reminders
|
|
7324
|
+
- **set_reminder** - Set a future reminder
|
|
7325
|
+
- **list_reminders** - View pending reminders
|
|
7326
|
+
- **dismiss_reminder** - Mark reminder as handled
|
|
7327
|
+
|
|
7328
|
+
## Slash Commands (type / in chat)
|
|
7329
|
+
- **/mcp__shodh-memory__quick_recall** - Search memories
|
|
7330
|
+
- **/mcp__shodh-memory__session_summary** - Session overview
|
|
7331
|
+
- **/mcp__shodh-memory__what_i_know** - Everything about a topic
|
|
7332
|
+
- **/mcp__shodh-memory__pending_work** - View todos
|
|
7333
|
+
- **/mcp__shodh-memory__recent_memories** - Recent memories
|
|
7334
|
+
- **/mcp__shodh-memory__memory_health** - System status
|
|
7335
|
+
`;
|
|
7336
|
+
return {
|
|
7337
|
+
contents: [{ uri, mimeType: "text/markdown", text: commandList }]
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7340
|
+
case "summary": {
|
|
7341
|
+
const result2 = await apiCall("/api/context_summary", "POST", {
|
|
7342
|
+
user_id: USER_ID,
|
|
7343
|
+
include_learnings: true,
|
|
7344
|
+
include_decisions: true,
|
|
7345
|
+
include_context: true,
|
|
7346
|
+
max_items: 5
|
|
7347
|
+
});
|
|
7348
|
+
const parts = [`Session Summary
|
|
7349
|
+
`];
|
|
7350
|
+
if (result2.learnings?.length) {
|
|
7351
|
+
parts.push(`
|
|
7352
|
+
Recent Learnings:`);
|
|
7353
|
+
result2.learnings.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7354
|
+
}
|
|
7355
|
+
if (result2.decisions?.length) {
|
|
7356
|
+
parts.push(`
|
|
7357
|
+
Recent Decisions:`);
|
|
7358
|
+
result2.decisions.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7359
|
+
}
|
|
7360
|
+
if (result2.context?.length) {
|
|
7361
|
+
parts.push(`
|
|
7362
|
+
Current Context:`);
|
|
7363
|
+
result2.context.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7364
|
+
}
|
|
7365
|
+
return {
|
|
7366
|
+
contents: [{
|
|
7367
|
+
uri,
|
|
7368
|
+
mimeType: "text/plain",
|
|
7369
|
+
text: parts.length > 1 ? parts.join(`
|
|
7370
|
+
`) : "No recent memories."
|
|
7371
|
+
}]
|
|
7372
|
+
};
|
|
7373
|
+
}
|
|
7374
|
+
case "todos": {
|
|
7375
|
+
const result2 = await apiCall("/api/todos", "POST", {
|
|
7376
|
+
user_id: USER_ID,
|
|
7377
|
+
status: ["backlog", "todo", "in_progress", "blocked"]
|
|
7378
|
+
});
|
|
7379
|
+
const todos = result2.todos || [];
|
|
7380
|
+
if (todos.length === 0) {
|
|
7381
|
+
return {
|
|
7382
|
+
contents: [{ uri, mimeType: "text/plain", text: "No pending tasks." }]
|
|
7383
|
+
};
|
|
7384
|
+
}
|
|
7385
|
+
const byStatus = {};
|
|
7386
|
+
todos.forEach((t) => {
|
|
7387
|
+
if (!byStatus[t.status])
|
|
7388
|
+
byStatus[t.status] = [];
|
|
7389
|
+
byStatus[t.status].push(t);
|
|
7390
|
+
});
|
|
7391
|
+
const parts = [`Pending Work
|
|
7392
|
+
`];
|
|
7393
|
+
["in_progress", "blocked", "todo", "backlog"].forEach((status) => {
|
|
7394
|
+
if (byStatus[status]?.length) {
|
|
7395
|
+
parts.push(`
|
|
7396
|
+
${status.replace("_", " ").toUpperCase()}:`);
|
|
7397
|
+
byStatus[status].forEach((t) => {
|
|
7398
|
+
const priority = t.priority !== "medium" ? ` [${t.priority}]` : "";
|
|
7399
|
+
const project = t.project ? ` (${t.project})` : "";
|
|
7400
|
+
parts.push(`- ${t.content}${priority}${project}`);
|
|
7401
|
+
});
|
|
7402
|
+
}
|
|
7403
|
+
});
|
|
7404
|
+
return {
|
|
7405
|
+
contents: [{ uri, mimeType: "text/plain", text: parts.join(`
|
|
7406
|
+
`) }]
|
|
7407
|
+
};
|
|
7408
|
+
}
|
|
7409
|
+
case "stats": {
|
|
7410
|
+
const stats = await apiCall(`/api/users/${USER_ID}/stats`, "GET");
|
|
7411
|
+
return {
|
|
7412
|
+
contents: [{
|
|
7413
|
+
uri,
|
|
7414
|
+
mimeType: "application/json",
|
|
7415
|
+
text: JSON.stringify(stats, null, 2)
|
|
7416
|
+
}]
|
|
7417
|
+
};
|
|
7418
|
+
}
|
|
7419
|
+
default:
|
|
7420
|
+
throw new Error(`Unknown resource: ${resource}`);
|
|
7421
|
+
}
|
|
7422
|
+
}
|
|
7423
|
+
const memoryId = uri.replace("memory://", "");
|
|
6160
7424
|
const result = await apiCall("/api/memories", "POST", {
|
|
6161
7425
|
user_id: USER_ID
|
|
6162
7426
|
});
|
|
@@ -6180,9 +7444,352 @@ ID: ${memory.id}`
|
|
|
6180
7444
|
};
|
|
6181
7445
|
} catch (error) {
|
|
6182
7446
|
const message = error instanceof Error ? error.message : String(error);
|
|
6183
|
-
throw new Error(`Failed to read
|
|
7447
|
+
throw new Error(`Failed to read resource: ${message}`);
|
|
7448
|
+
}
|
|
7449
|
+
});
|
|
7450
|
+
var SHODH_PROMPTS = [
|
|
7451
|
+
{
|
|
7452
|
+
name: "quick_recall",
|
|
7453
|
+
description: "Search your memories for relevant context",
|
|
7454
|
+
arguments: [
|
|
7455
|
+
{
|
|
7456
|
+
name: "query",
|
|
7457
|
+
description: "What to search for in memories",
|
|
7458
|
+
required: true
|
|
7459
|
+
}
|
|
7460
|
+
]
|
|
7461
|
+
},
|
|
7462
|
+
{
|
|
7463
|
+
name: "session_summary",
|
|
7464
|
+
description: "Get a summary of recent learnings, decisions, and context",
|
|
7465
|
+
arguments: []
|
|
7466
|
+
},
|
|
7467
|
+
{
|
|
7468
|
+
name: "what_i_know",
|
|
7469
|
+
description: "Surface everything related to a topic",
|
|
7470
|
+
arguments: [
|
|
7471
|
+
{
|
|
7472
|
+
name: "topic",
|
|
7473
|
+
description: "The topic to explore",
|
|
7474
|
+
required: true
|
|
7475
|
+
}
|
|
7476
|
+
]
|
|
7477
|
+
},
|
|
7478
|
+
{
|
|
7479
|
+
name: "pending_work",
|
|
7480
|
+
description: "Show todos and incomplete tasks",
|
|
7481
|
+
arguments: []
|
|
7482
|
+
},
|
|
7483
|
+
{
|
|
7484
|
+
name: "recent_memories",
|
|
7485
|
+
description: "Show recently created memories",
|
|
7486
|
+
arguments: [
|
|
7487
|
+
{
|
|
7488
|
+
name: "count",
|
|
7489
|
+
description: "Number of memories (default: 10)",
|
|
7490
|
+
required: false
|
|
7491
|
+
}
|
|
7492
|
+
]
|
|
7493
|
+
},
|
|
7494
|
+
{
|
|
7495
|
+
name: "memory_health",
|
|
7496
|
+
description: "Check memory system status and statistics",
|
|
7497
|
+
arguments: []
|
|
7498
|
+
}
|
|
7499
|
+
];
|
|
7500
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
7501
|
+
return {
|
|
7502
|
+
prompts: SHODH_PROMPTS.map((p) => ({
|
|
7503
|
+
name: p.name,
|
|
7504
|
+
description: p.description,
|
|
7505
|
+
arguments: p.arguments
|
|
7506
|
+
}))
|
|
7507
|
+
};
|
|
7508
|
+
});
|
|
7509
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
7510
|
+
const promptName = request.params.name;
|
|
7511
|
+
const args = request.params.arguments || {};
|
|
7512
|
+
try {
|
|
7513
|
+
switch (promptName) {
|
|
7514
|
+
case "quick_recall": {
|
|
7515
|
+
const query = args.query;
|
|
7516
|
+
if (!query) {
|
|
7517
|
+
return {
|
|
7518
|
+
messages: [
|
|
7519
|
+
{
|
|
7520
|
+
role: "user",
|
|
7521
|
+
content: { type: "text", text: "Please provide a search query." }
|
|
7522
|
+
}
|
|
7523
|
+
]
|
|
7524
|
+
};
|
|
7525
|
+
}
|
|
7526
|
+
const result = await apiCall("/api/recall", "POST", {
|
|
7527
|
+
user_id: USER_ID,
|
|
7528
|
+
query,
|
|
7529
|
+
mode: "hybrid",
|
|
7530
|
+
limit: 5
|
|
7531
|
+
});
|
|
7532
|
+
const memories = result.memories || [];
|
|
7533
|
+
const memoryText = memories.length > 0 ? memories.map((m) => `- ${getContent(m)} (${getType(m)})`).join(`
|
|
7534
|
+
`) : "No memories found.";
|
|
7535
|
+
return {
|
|
7536
|
+
messages: [
|
|
7537
|
+
{
|
|
7538
|
+
role: "user",
|
|
7539
|
+
content: {
|
|
7540
|
+
type: "text",
|
|
7541
|
+
text: `Here's what I found about "${query}":
|
|
7542
|
+
|
|
7543
|
+
${memoryText}`
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7546
|
+
]
|
|
7547
|
+
};
|
|
7548
|
+
}
|
|
7549
|
+
case "session_summary": {
|
|
7550
|
+
const result = await apiCall("/api/context_summary", "POST", {
|
|
7551
|
+
user_id: USER_ID,
|
|
7552
|
+
include_learnings: true,
|
|
7553
|
+
include_decisions: true,
|
|
7554
|
+
include_context: true,
|
|
7555
|
+
max_items: 5
|
|
7556
|
+
});
|
|
7557
|
+
const parts = [];
|
|
7558
|
+
if (result.learnings?.length) {
|
|
7559
|
+
parts.push("**Recent Learnings:**");
|
|
7560
|
+
result.learnings.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7561
|
+
}
|
|
7562
|
+
if (result.decisions?.length) {
|
|
7563
|
+
parts.push(`
|
|
7564
|
+
**Recent Decisions:**`);
|
|
7565
|
+
result.decisions.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7566
|
+
}
|
|
7567
|
+
if (result.context?.length) {
|
|
7568
|
+
parts.push(`
|
|
7569
|
+
**Current Context:**`);
|
|
7570
|
+
result.context.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7571
|
+
}
|
|
7572
|
+
const summaryText = parts.length > 0 ? parts.join(`
|
|
7573
|
+
`) : "No recent memories.";
|
|
7574
|
+
return {
|
|
7575
|
+
messages: [
|
|
7576
|
+
{
|
|
7577
|
+
role: "user",
|
|
7578
|
+
content: { type: "text", text: `Session Summary:
|
|
7579
|
+
|
|
7580
|
+
${summaryText}` }
|
|
7581
|
+
}
|
|
7582
|
+
]
|
|
7583
|
+
};
|
|
7584
|
+
}
|
|
7585
|
+
case "what_i_know": {
|
|
7586
|
+
const topic = args.topic;
|
|
7587
|
+
if (!topic) {
|
|
7588
|
+
return {
|
|
7589
|
+
messages: [
|
|
7590
|
+
{
|
|
7591
|
+
role: "user",
|
|
7592
|
+
content: { type: "text", text: "Please specify a topic to explore." }
|
|
7593
|
+
}
|
|
7594
|
+
]
|
|
7595
|
+
};
|
|
7596
|
+
}
|
|
7597
|
+
const result = await apiCall("/api/recall", "POST", {
|
|
7598
|
+
user_id: USER_ID,
|
|
7599
|
+
query: topic,
|
|
7600
|
+
mode: "hybrid",
|
|
7601
|
+
limit: 10
|
|
7602
|
+
});
|
|
7603
|
+
const memories = result.memories || [];
|
|
7604
|
+
const grouped = {};
|
|
7605
|
+
memories.forEach((m) => {
|
|
7606
|
+
const type = getType(m);
|
|
7607
|
+
if (!grouped[type])
|
|
7608
|
+
grouped[type] = [];
|
|
7609
|
+
grouped[type].push(m);
|
|
7610
|
+
});
|
|
7611
|
+
const parts = [`Everything I know about "${topic}":
|
|
7612
|
+
`];
|
|
7613
|
+
Object.entries(grouped).forEach(([type, mems]) => {
|
|
7614
|
+
parts.push(`
|
|
7615
|
+
**${type}s:**`);
|
|
7616
|
+
mems.forEach((m) => parts.push(`- ${getContent(m)}`));
|
|
7617
|
+
});
|
|
7618
|
+
return {
|
|
7619
|
+
messages: [
|
|
7620
|
+
{
|
|
7621
|
+
role: "user",
|
|
7622
|
+
content: {
|
|
7623
|
+
type: "text",
|
|
7624
|
+
text: memories.length > 0 ? parts.join(`
|
|
7625
|
+
`) : `No memories found about "${topic}".`
|
|
7626
|
+
}
|
|
7627
|
+
}
|
|
7628
|
+
]
|
|
7629
|
+
};
|
|
7630
|
+
}
|
|
7631
|
+
case "pending_work": {
|
|
7632
|
+
const result = await apiCall("/api/todos", "POST", {
|
|
7633
|
+
user_id: USER_ID,
|
|
7634
|
+
status: ["backlog", "todo", "in_progress", "blocked"]
|
|
7635
|
+
});
|
|
7636
|
+
const todos = result.todos || [];
|
|
7637
|
+
if (todos.length === 0) {
|
|
7638
|
+
return {
|
|
7639
|
+
messages: [
|
|
7640
|
+
{
|
|
7641
|
+
role: "user",
|
|
7642
|
+
content: { type: "text", text: "No pending tasks. You're all caught up!" }
|
|
7643
|
+
}
|
|
7644
|
+
]
|
|
7645
|
+
};
|
|
7646
|
+
}
|
|
7647
|
+
const byStatus = {};
|
|
7648
|
+
todos.forEach((t) => {
|
|
7649
|
+
if (!byStatus[t.status])
|
|
7650
|
+
byStatus[t.status] = [];
|
|
7651
|
+
byStatus[t.status].push(t);
|
|
7652
|
+
});
|
|
7653
|
+
const parts = [`**Pending Work:**
|
|
7654
|
+
`];
|
|
7655
|
+
["in_progress", "blocked", "todo", "backlog"].forEach((status) => {
|
|
7656
|
+
if (byStatus[status]?.length) {
|
|
7657
|
+
parts.push(`
|
|
7658
|
+
*${status.replace("_", " ").toUpperCase()}:*`);
|
|
7659
|
+
byStatus[status].forEach((t) => {
|
|
7660
|
+
const priority = t.priority !== "medium" ? ` [${t.priority}]` : "";
|
|
7661
|
+
const project = t.project ? ` (${t.project})` : "";
|
|
7662
|
+
parts.push(`- ${t.content}${priority}${project}`);
|
|
7663
|
+
});
|
|
7664
|
+
}
|
|
7665
|
+
});
|
|
7666
|
+
return {
|
|
7667
|
+
messages: [
|
|
7668
|
+
{
|
|
7669
|
+
role: "user",
|
|
7670
|
+
content: { type: "text", text: parts.join(`
|
|
7671
|
+
`) }
|
|
7672
|
+
}
|
|
7673
|
+
]
|
|
7674
|
+
};
|
|
7675
|
+
}
|
|
7676
|
+
case "recent_memories": {
|
|
7677
|
+
const count = parseInt(args.count || "10", 10);
|
|
7678
|
+
const result = await apiCall("/api/memories", "POST", {
|
|
7679
|
+
user_id: USER_ID,
|
|
7680
|
+
limit: count
|
|
7681
|
+
});
|
|
7682
|
+
const memories = result.memories || [];
|
|
7683
|
+
if (memories.length === 0) {
|
|
7684
|
+
return {
|
|
7685
|
+
messages: [
|
|
7686
|
+
{
|
|
7687
|
+
role: "user",
|
|
7688
|
+
content: { type: "text", text: "No memories found." }
|
|
7689
|
+
}
|
|
7690
|
+
]
|
|
7691
|
+
};
|
|
7692
|
+
}
|
|
7693
|
+
const parts = [`**${memories.length} Recent Memories:**
|
|
7694
|
+
`];
|
|
7695
|
+
memories.forEach((m) => {
|
|
7696
|
+
const content = getContent(m);
|
|
7697
|
+
const type = getType(m);
|
|
7698
|
+
const preview = content.length > 100 ? content.slice(0, 100) + "..." : content;
|
|
7699
|
+
parts.push(`- [${type}] ${preview}`);
|
|
7700
|
+
});
|
|
7701
|
+
return {
|
|
7702
|
+
messages: [
|
|
7703
|
+
{
|
|
7704
|
+
role: "user",
|
|
7705
|
+
content: { type: "text", text: parts.join(`
|
|
7706
|
+
`) }
|
|
7707
|
+
}
|
|
7708
|
+
]
|
|
7709
|
+
};
|
|
7710
|
+
}
|
|
7711
|
+
case "memory_health": {
|
|
7712
|
+
const statsResult = await apiCall(`/api/users/${USER_ID}/stats`, "GET");
|
|
7713
|
+
const verifyResult = await apiCall("/api/index/verify", "POST", { user_id: USER_ID });
|
|
7714
|
+
const parts = [`**Memory System Health:**
|
|
7715
|
+
`];
|
|
7716
|
+
parts.push(`Total memories: ${statsResult.total_memories || 0}`);
|
|
7717
|
+
parts.push(`Last 24h: ${statsResult.memories_last_24h || 0}`);
|
|
7718
|
+
parts.push(`Last 7 days: ${statsResult.memories_last_7d || 0}`);
|
|
7719
|
+
parts.push(`
|
|
7720
|
+
Index status: ${verifyResult.healthy ? "✓ Healthy" : "⚠ Needs repair"}`);
|
|
7721
|
+
if (verifyResult.orphaned_count > 0) {
|
|
7722
|
+
parts.push(`Orphaned entries: ${verifyResult.orphaned_count}`);
|
|
7723
|
+
}
|
|
7724
|
+
if (statsResult.memories_by_type) {
|
|
7725
|
+
parts.push(`
|
|
7726
|
+
**By Type:**`);
|
|
7727
|
+
Object.entries(statsResult.memories_by_type).forEach(([type, count]) => {
|
|
7728
|
+
parts.push(`- ${type}: ${count}`);
|
|
7729
|
+
});
|
|
7730
|
+
}
|
|
7731
|
+
return {
|
|
7732
|
+
messages: [
|
|
7733
|
+
{
|
|
7734
|
+
role: "user",
|
|
7735
|
+
content: { type: "text", text: parts.join(`
|
|
7736
|
+
`) }
|
|
7737
|
+
}
|
|
7738
|
+
]
|
|
7739
|
+
};
|
|
7740
|
+
}
|
|
7741
|
+
default:
|
|
7742
|
+
return {
|
|
7743
|
+
messages: [
|
|
7744
|
+
{
|
|
7745
|
+
role: "user",
|
|
7746
|
+
content: { type: "text", text: `Unknown prompt: ${promptName}` }
|
|
7747
|
+
}
|
|
7748
|
+
]
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
7751
|
+
} catch (error) {
|
|
7752
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7753
|
+
return {
|
|
7754
|
+
messages: [
|
|
7755
|
+
{
|
|
7756
|
+
role: "user",
|
|
7757
|
+
content: { type: "text", text: `Error: ${message}` }
|
|
7758
|
+
}
|
|
7759
|
+
]
|
|
7760
|
+
};
|
|
6184
7761
|
}
|
|
6185
7762
|
});
|
|
7763
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
7764
|
+
return {
|
|
7765
|
+
resourceTemplates: [
|
|
7766
|
+
{
|
|
7767
|
+
uriTemplate: "memory://{id}",
|
|
7768
|
+
name: "Memory by ID",
|
|
7769
|
+
description: "Access a specific memory by its ID",
|
|
7770
|
+
mimeType: "text/plain"
|
|
7771
|
+
},
|
|
7772
|
+
{
|
|
7773
|
+
uriTemplate: "shodh://stats",
|
|
7774
|
+
name: "Memory Statistics",
|
|
7775
|
+
description: "Current memory system statistics",
|
|
7776
|
+
mimeType: "application/json"
|
|
7777
|
+
},
|
|
7778
|
+
{
|
|
7779
|
+
uriTemplate: "shodh://todos",
|
|
7780
|
+
name: "Todo List",
|
|
7781
|
+
description: "Your pending tasks and work items",
|
|
7782
|
+
mimeType: "text/plain"
|
|
7783
|
+
},
|
|
7784
|
+
{
|
|
7785
|
+
uriTemplate: "shodh://search/{query}",
|
|
7786
|
+
name: "Search Memories",
|
|
7787
|
+
description: "Search memories for a specific query",
|
|
7788
|
+
mimeType: "text/plain"
|
|
7789
|
+
}
|
|
7790
|
+
]
|
|
7791
|
+
};
|
|
7792
|
+
});
|
|
6186
7793
|
var AUTO_SPAWN_ENABLED = process.env.SHODH_NO_AUTO_SPAWN !== "true";
|
|
6187
7794
|
var serverProcess = null;
|
|
6188
7795
|
function getBinaryPath() {
|