devsh-memory-mcp 0.3.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-BKOJ5TNO.js → chunk-R25AQROL.js} +978 -27
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Logged items are reviewed by team leads and may be promoted to active orchestrat
|
|
|
125
125
|
|----------|-------------|
|
|
126
126
|
| `CMUX_TASK_RUN_JWT` | JWT for authenticating orchestration API calls |
|
|
127
127
|
| `CMUX_ORCHESTRATION_ID` | Current orchestration session ID |
|
|
128
|
-
| `CMUX_API_BASE_URL` | API base URL (default: https://cmux.
|
|
128
|
+
| `CMUX_API_BASE_URL` | API base URL (default: https://cmux-www.karldigi.dev) |
|
|
129
129
|
|
|
130
130
|
## Memory Directory Structure
|
|
131
131
|
|
|
@@ -23,6 +23,7 @@ function extractTeamIdFromJwt(jwt) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
var DEFAULT_MEMORY_DIR = "/root/lifecycle/memory";
|
|
26
|
+
var DEFAULT_CMUX_API_BASE_URL = "https://cmux-www.karldigi.dev";
|
|
26
27
|
function createMemoryMcpServer(config) {
|
|
27
28
|
const memoryDir = config?.memoryDir ?? DEFAULT_MEMORY_DIR;
|
|
28
29
|
const resolvedAgentName = config?.agentName ?? process.env.CMUX_AGENT_NAME;
|
|
@@ -37,9 +38,316 @@ function createMemoryMcpServer(config) {
|
|
|
37
38
|
const orchestrationDir = path.join(memoryDir, "orchestration");
|
|
38
39
|
const mailboxPath = path.join(memoryDir, "MAILBOX.json");
|
|
39
40
|
const tasksPath = path.join(memoryDir, "TASKS.json");
|
|
41
|
+
const usageStatsPath = path.join(memoryDir, "USAGE_STATS.json");
|
|
40
42
|
const planPath = path.join(orchestrationDir, "PLAN.json");
|
|
41
43
|
const agentsPath = path.join(orchestrationDir, "AGENTS.json");
|
|
42
44
|
const eventsPath = path.join(orchestrationDir, "EVENTS.jsonl");
|
|
45
|
+
function readUsageStats() {
|
|
46
|
+
const content = readFile(usageStatsPath);
|
|
47
|
+
if (!content) return { version: 1, entries: {} };
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
} catch {
|
|
51
|
+
return { version: 1, entries: {} };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function writeUsageStats(stats) {
|
|
55
|
+
return writeFile(usageStatsPath, JSON.stringify(stats, null, 2));
|
|
56
|
+
}
|
|
57
|
+
function trackRead(memoryType) {
|
|
58
|
+
const stats = readUsageStats();
|
|
59
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
60
|
+
if (!stats.entries[memoryType]) {
|
|
61
|
+
stats.entries[memoryType] = {
|
|
62
|
+
readCount: 0,
|
|
63
|
+
lastRead: now,
|
|
64
|
+
createdAt: now
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
stats.entries[memoryType].readCount++;
|
|
68
|
+
stats.entries[memoryType].lastRead = now;
|
|
69
|
+
writeUsageStats(stats);
|
|
70
|
+
}
|
|
71
|
+
function trackWrite(memoryType) {
|
|
72
|
+
const stats = readUsageStats();
|
|
73
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
74
|
+
if (!stats.entries[memoryType]) {
|
|
75
|
+
stats.entries[memoryType] = {
|
|
76
|
+
readCount: 0,
|
|
77
|
+
lastRead: now,
|
|
78
|
+
createdAt: now
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
stats.entries[memoryType].lastWrite = now;
|
|
82
|
+
writeUsageStats(stats);
|
|
83
|
+
}
|
|
84
|
+
let syncConfig = null;
|
|
85
|
+
let syncConfigResolved = false;
|
|
86
|
+
const debounceMap = /* @__PURE__ */ new Map();
|
|
87
|
+
const retryQueue = [];
|
|
88
|
+
const DEBOUNCE_MS = 2e3;
|
|
89
|
+
const MAX_RETRIES = 3;
|
|
90
|
+
const SYNC_CONFIG_CACHE_PATH = path.join(memoryDir, ".sync-config.json");
|
|
91
|
+
const SYNC_FILE_MAP = {
|
|
92
|
+
append_daily_log: {
|
|
93
|
+
memoryType: "daily",
|
|
94
|
+
getFilePath: () => path.join(dailyDir, `${getTodayDateString()}.md`),
|
|
95
|
+
getFileName: (date) => `daily/${date ?? getTodayDateString()}.md`,
|
|
96
|
+
getDate: () => getTodayDateString()
|
|
97
|
+
},
|
|
98
|
+
update_knowledge: {
|
|
99
|
+
memoryType: "knowledge",
|
|
100
|
+
getFilePath: () => path.join(knowledgeDir, "MEMORY.md"),
|
|
101
|
+
getFileName: () => "knowledge/MEMORY.md"
|
|
102
|
+
},
|
|
103
|
+
add_task: {
|
|
104
|
+
memoryType: "tasks",
|
|
105
|
+
getFilePath: () => tasksPath,
|
|
106
|
+
getFileName: () => "TASKS.json"
|
|
107
|
+
},
|
|
108
|
+
update_task: {
|
|
109
|
+
memoryType: "tasks",
|
|
110
|
+
getFilePath: () => tasksPath,
|
|
111
|
+
getFileName: () => "TASKS.json"
|
|
112
|
+
},
|
|
113
|
+
send_message: {
|
|
114
|
+
memoryType: "mailbox",
|
|
115
|
+
getFilePath: () => mailboxPath,
|
|
116
|
+
getFileName: () => "MAILBOX.json"
|
|
117
|
+
},
|
|
118
|
+
mark_read: {
|
|
119
|
+
memoryType: "mailbox",
|
|
120
|
+
getFilePath: () => mailboxPath,
|
|
121
|
+
getFileName: () => "MAILBOX.json"
|
|
122
|
+
},
|
|
123
|
+
append_event: {
|
|
124
|
+
memoryType: "events",
|
|
125
|
+
getFilePath: () => eventsPath,
|
|
126
|
+
getFileName: () => "orchestration/EVENTS.jsonl"
|
|
127
|
+
},
|
|
128
|
+
update_plan_task: {
|
|
129
|
+
memoryType: "events",
|
|
130
|
+
getFilePath: () => eventsPath,
|
|
131
|
+
getFileName: () => "orchestration/EVENTS.jsonl"
|
|
132
|
+
},
|
|
133
|
+
check_stale_entries: {
|
|
134
|
+
memoryType: "knowledge",
|
|
135
|
+
getFilePath: () => path.join(knowledgeDir, "MEMORY.md"),
|
|
136
|
+
getFileName: () => "knowledge/MEMORY.md"
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function readDotEnv(envPath) {
|
|
140
|
+
try {
|
|
141
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
142
|
+
const vars = {};
|
|
143
|
+
for (const line of content.split("\n")) {
|
|
144
|
+
const trimmed = line.trim();
|
|
145
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
146
|
+
const eqIndex = trimmed.indexOf("=");
|
|
147
|
+
if (eqIndex === -1) continue;
|
|
148
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
149
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
150
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
151
|
+
value = value.slice(1, -1);
|
|
152
|
+
}
|
|
153
|
+
vars[key] = value;
|
|
154
|
+
}
|
|
155
|
+
return vars;
|
|
156
|
+
} catch {
|
|
157
|
+
return {};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function resolveSyncConfig() {
|
|
161
|
+
const envJwt = process.env.CMUX_TASK_RUN_JWT;
|
|
162
|
+
const envCallbackUrl = process.env.CMUX_CALLBACK_URL;
|
|
163
|
+
if (envJwt && envCallbackUrl) {
|
|
164
|
+
console.error(`[devsh-memory-mcp] Sync enabled via env vars: ${envCallbackUrl}`);
|
|
165
|
+
return { syncUrl: envCallbackUrl, authToken: envJwt };
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const cachedRaw = fs.readFileSync(SYNC_CONFIG_CACHE_PATH, "utf-8");
|
|
169
|
+
const cached = JSON.parse(cachedRaw);
|
|
170
|
+
if (cached.jwt && cached.syncUrl && cached.expiresAt > Date.now()) {
|
|
171
|
+
console.error(`[devsh-memory-mcp] Sync enabled via cached config: ${cached.syncUrl}`);
|
|
172
|
+
return { syncUrl: cached.syncUrl, authToken: cached.jwt };
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
}
|
|
176
|
+
const envPaths = [
|
|
177
|
+
path.join(memoryDir, "..", "..", ".env"),
|
|
178
|
+
"/root/workspace/.env",
|
|
179
|
+
path.join(process.env.HOME ?? "/root", "workspace", ".env")
|
|
180
|
+
];
|
|
181
|
+
let jwtSecret;
|
|
182
|
+
let convexUrl;
|
|
183
|
+
let teamId;
|
|
184
|
+
for (const envPath of envPaths) {
|
|
185
|
+
const vars = readDotEnv(envPath);
|
|
186
|
+
jwtSecret = jwtSecret ?? vars.CMUX_TASK_RUN_JWT_SECRET;
|
|
187
|
+
convexUrl = convexUrl ?? vars.CONVEX_SITE_URL ?? vars.CONVEX_SELF_HOSTED_URL;
|
|
188
|
+
teamId = teamId ?? vars.CMUX_TEAM_ID;
|
|
189
|
+
}
|
|
190
|
+
teamId = teamId ?? process.env.CMUX_TEAM_ID;
|
|
191
|
+
if (!jwtSecret || !convexUrl) {
|
|
192
|
+
console.error("[devsh-memory-mcp] Sync disabled: no JWT or Convex URL found");
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
if (!teamId) {
|
|
196
|
+
console.error("[devsh-memory-mcp] Sync disabled: no teamId found for bootstrap");
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const response = await fetch(`${convexUrl}/api/memory/bootstrap`, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: {
|
|
203
|
+
"Content-Type": "application/json",
|
|
204
|
+
"x-cmux-bootstrap-key": jwtSecret
|
|
205
|
+
},
|
|
206
|
+
body: JSON.stringify({
|
|
207
|
+
teamId,
|
|
208
|
+
agentName
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
const errText = await response.text().catch(() => "");
|
|
213
|
+
console.error(`[devsh-memory-mcp] Bootstrap failed: ${response.status} ${errText}`);
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
const result = await response.json();
|
|
217
|
+
if (!result.ok || !result.jwt) {
|
|
218
|
+
console.error("[devsh-memory-mcp] Bootstrap returned invalid response");
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const cacheData = {
|
|
222
|
+
jwt: result.jwt,
|
|
223
|
+
taskRunId: result.taskRunId,
|
|
224
|
+
syncUrl: convexUrl,
|
|
225
|
+
expiresAt: Date.now() + 6 * 24 * 60 * 60 * 1e3
|
|
226
|
+
};
|
|
227
|
+
try {
|
|
228
|
+
fs.writeFileSync(SYNC_CONFIG_CACHE_PATH, JSON.stringify(cacheData), "utf-8");
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
console.error(
|
|
232
|
+
`[devsh-memory-mcp] Sync enabled via bootstrap: ${convexUrl} (reused=${result.reused})`
|
|
233
|
+
);
|
|
234
|
+
return { syncUrl: convexUrl, authToken: result.jwt };
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error("[devsh-memory-mcp] Bootstrap error:", error);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
async function doSync(memoryType, content, fileName, date) {
|
|
241
|
+
if (!syncConfig) return;
|
|
242
|
+
const url = `${syncConfig.syncUrl}/api/memory/sync`;
|
|
243
|
+
const body = {
|
|
244
|
+
files: [
|
|
245
|
+
{
|
|
246
|
+
memoryType,
|
|
247
|
+
content,
|
|
248
|
+
fileName,
|
|
249
|
+
date
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(url, {
|
|
255
|
+
method: "POST",
|
|
256
|
+
headers: {
|
|
257
|
+
"Content-Type": "application/json",
|
|
258
|
+
"x-cmux-token": syncConfig.authToken
|
|
259
|
+
},
|
|
260
|
+
body: JSON.stringify(body)
|
|
261
|
+
});
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
const errText = await response.text().catch(() => "");
|
|
264
|
+
console.error(
|
|
265
|
+
`[devsh-memory-mcp] Sync failed for ${memoryType}: ${response.status} ${errText}`
|
|
266
|
+
);
|
|
267
|
+
enqueueRetry(memoryType, content, fileName, date);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
drainRetryQueue();
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error(
|
|
273
|
+
`[devsh-memory-mcp] Sync error for ${memoryType}:`,
|
|
274
|
+
error instanceof Error ? error.message : error
|
|
275
|
+
);
|
|
276
|
+
enqueueRetry(memoryType, content, fileName, date);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function enqueueRetry(memoryType, content, fileName, date) {
|
|
280
|
+
const key = date ? `${memoryType}:${date}` : memoryType;
|
|
281
|
+
const existingIdx = retryQueue.findIndex(
|
|
282
|
+
(r) => (r.date ? `${r.memoryType}:${r.date}` : r.memoryType) === key
|
|
283
|
+
);
|
|
284
|
+
if (existingIdx >= 0) {
|
|
285
|
+
const existing = retryQueue[existingIdx];
|
|
286
|
+
if (existing.retryCount >= MAX_RETRIES) {
|
|
287
|
+
console.error(
|
|
288
|
+
`[devsh-memory-mcp] Dropping retry for ${memoryType} after ${MAX_RETRIES} attempts`
|
|
289
|
+
);
|
|
290
|
+
retryQueue.splice(existingIdx, 1);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
retryQueue[existingIdx] = {
|
|
294
|
+
memoryType,
|
|
295
|
+
content,
|
|
296
|
+
fileName,
|
|
297
|
+
date,
|
|
298
|
+
retryCount: existing.retryCount + 1
|
|
299
|
+
};
|
|
300
|
+
} else {
|
|
301
|
+
retryQueue.push({ memoryType, content, fileName, date, retryCount: 1 });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function drainRetryQueue() {
|
|
305
|
+
if (!syncConfig || retryQueue.length === 0) return;
|
|
306
|
+
const items = [...retryQueue];
|
|
307
|
+
retryQueue.length = 0;
|
|
308
|
+
for (const item of items) {
|
|
309
|
+
if (item.retryCount >= MAX_RETRIES) {
|
|
310
|
+
console.error(
|
|
311
|
+
`[devsh-memory-mcp] Dropping retry for ${item.memoryType} after ${MAX_RETRIES} attempts`
|
|
312
|
+
);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
await doSync(item.memoryType, item.content, item.fileName, item.date);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function scheduleSyncForFile(toolName) {
|
|
319
|
+
if (!syncConfigResolved) return;
|
|
320
|
+
if (!syncConfig) return;
|
|
321
|
+
const mapping = SYNC_FILE_MAP[toolName];
|
|
322
|
+
if (!mapping) return;
|
|
323
|
+
const { memoryType } = mapping;
|
|
324
|
+
const date = mapping.getDate?.();
|
|
325
|
+
const debounceKey = date ? `${memoryType}:${date}` : memoryType;
|
|
326
|
+
let entry = debounceMap.get(debounceKey);
|
|
327
|
+
if (!entry) {
|
|
328
|
+
entry = { timer: null };
|
|
329
|
+
debounceMap.set(debounceKey, entry);
|
|
330
|
+
}
|
|
331
|
+
if (entry.timer) {
|
|
332
|
+
clearTimeout(entry.timer);
|
|
333
|
+
}
|
|
334
|
+
entry.timer = setTimeout(() => {
|
|
335
|
+
const filePath = mapping.getFilePath();
|
|
336
|
+
const content = readFile(filePath);
|
|
337
|
+
if (!content) return;
|
|
338
|
+
const fileName = mapping.getFileName(date);
|
|
339
|
+
doSync(memoryType, content, fileName, date).catch((err) => {
|
|
340
|
+
console.error("[devsh-memory-mcp] Sync error in debounce callback:", err);
|
|
341
|
+
});
|
|
342
|
+
}, DEBOUNCE_MS);
|
|
343
|
+
}
|
|
344
|
+
resolveSyncConfig().then((config2) => {
|
|
345
|
+
syncConfig = config2;
|
|
346
|
+
syncConfigResolved = true;
|
|
347
|
+
}).catch((err) => {
|
|
348
|
+
console.error("[devsh-memory-mcp] Sync config resolution failed:", err);
|
|
349
|
+
syncConfigResolved = true;
|
|
350
|
+
});
|
|
43
351
|
function readFile(filePath) {
|
|
44
352
|
try {
|
|
45
353
|
return fs.readFileSync(filePath, "utf-8");
|
|
@@ -79,6 +387,9 @@ function createMemoryMcpServer(config) {
|
|
|
79
387
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
80
388
|
}
|
|
81
389
|
}
|
|
390
|
+
function getCmuxApiBaseUrl() {
|
|
391
|
+
return process.env.CMUX_API_BASE_URL ?? DEFAULT_CMUX_API_BASE_URL;
|
|
392
|
+
}
|
|
82
393
|
function readPlan() {
|
|
83
394
|
const content = readFile(planPath);
|
|
84
395
|
if (!content) return null;
|
|
@@ -184,7 +495,7 @@ function createMemoryMcpServer(config) {
|
|
|
184
495
|
tools: [
|
|
185
496
|
{
|
|
186
497
|
name: "read_memory",
|
|
187
|
-
description: 'Read a memory file. Type can be "knowledge", "tasks", or "mailbox".',
|
|
498
|
+
description: 'Read a memory file. Type can be "knowledge", "tasks", or "mailbox". For tasks, by default only returns open tasks (pending/in_progress) to prevent context bloat.',
|
|
188
499
|
inputSchema: {
|
|
189
500
|
type: "object",
|
|
190
501
|
properties: {
|
|
@@ -192,6 +503,14 @@ function createMemoryMcpServer(config) {
|
|
|
192
503
|
type: "string",
|
|
193
504
|
enum: ["knowledge", "tasks", "mailbox"],
|
|
194
505
|
description: "The type of memory to read"
|
|
506
|
+
},
|
|
507
|
+
includeCompleted: {
|
|
508
|
+
type: "boolean",
|
|
509
|
+
description: "For tasks: include completed tasks (default: false, only open tasks)"
|
|
510
|
+
},
|
|
511
|
+
limit: {
|
|
512
|
+
type: "number",
|
|
513
|
+
description: "For tasks: maximum number of tasks to return (default: 50)"
|
|
195
514
|
}
|
|
196
515
|
},
|
|
197
516
|
required: ["type"]
|
|
@@ -233,9 +552,34 @@ function createMemoryMcpServer(config) {
|
|
|
233
552
|
required: ["query"]
|
|
234
553
|
}
|
|
235
554
|
},
|
|
555
|
+
{
|
|
556
|
+
name: "get_memory_usage",
|
|
557
|
+
description: "Get usage statistics for memory files. Shows read/write counts and timestamps for freshness tracking.",
|
|
558
|
+
inputSchema: {
|
|
559
|
+
type: "object",
|
|
560
|
+
properties: {}
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
name: "archive_stale_memories",
|
|
565
|
+
description: "Archive stale memory entries from MEMORY.md. Moves low-freshness entries to an archive section. Use dryRun=true to preview without changes.",
|
|
566
|
+
inputSchema: {
|
|
567
|
+
type: "object",
|
|
568
|
+
properties: {
|
|
569
|
+
dryRun: {
|
|
570
|
+
type: "boolean",
|
|
571
|
+
description: "Preview changes without modifying files (default: true)"
|
|
572
|
+
},
|
|
573
|
+
maxScoreToArchive: {
|
|
574
|
+
type: "number",
|
|
575
|
+
description: "Archive entries with freshness score below this threshold (default: 30)"
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
},
|
|
236
580
|
{
|
|
237
581
|
name: "send_message",
|
|
238
|
-
description: 'Send a message to another agent on the same task. Use "*" to broadcast to all agents.',
|
|
582
|
+
description: 'Send a message to another agent on the same task. Use "*" to broadcast to all agents. Aligned with Claude Code SendMessage pattern.',
|
|
239
583
|
inputSchema: {
|
|
240
584
|
type: "object",
|
|
241
585
|
properties: {
|
|
@@ -249,8 +593,25 @@ function createMemoryMcpServer(config) {
|
|
|
249
593
|
},
|
|
250
594
|
type: {
|
|
251
595
|
type: "string",
|
|
252
|
-
enum: ["handoff", "request", "status"],
|
|
253
|
-
description: "Message type: handoff (work transfer), request (ask to do something), status (progress update)"
|
|
596
|
+
enum: ["handoff", "request", "status", "response"],
|
|
597
|
+
description: "Message type: handoff (work transfer), request (ask to do something), status (progress update), response (reply to a request)"
|
|
598
|
+
},
|
|
599
|
+
correlationId: {
|
|
600
|
+
type: "string",
|
|
601
|
+
description: "Optional correlation ID for request-response tracking. Use to link related messages."
|
|
602
|
+
},
|
|
603
|
+
replyTo: {
|
|
604
|
+
type: "string",
|
|
605
|
+
description: "Optional message ID this is responding to (for response type messages)."
|
|
606
|
+
},
|
|
607
|
+
priority: {
|
|
608
|
+
type: "string",
|
|
609
|
+
enum: ["high", "normal", "low"],
|
|
610
|
+
description: "Message priority (default: normal). High priority messages appear first."
|
|
611
|
+
},
|
|
612
|
+
metadata: {
|
|
613
|
+
type: "object",
|
|
614
|
+
description: "Optional additional context as key-value pairs."
|
|
254
615
|
}
|
|
255
616
|
},
|
|
256
617
|
required: ["to", "message"]
|
|
@@ -258,13 +619,22 @@ function createMemoryMcpServer(config) {
|
|
|
258
619
|
},
|
|
259
620
|
{
|
|
260
621
|
name: "get_my_messages",
|
|
261
|
-
description: "Get all messages addressed to this agent (including broadcasts). Returns unread messages first.",
|
|
622
|
+
description: "Get all messages addressed to this agent (including broadcasts). Returns high-priority and unread messages first.",
|
|
262
623
|
inputSchema: {
|
|
263
624
|
type: "object",
|
|
264
625
|
properties: {
|
|
265
626
|
includeRead: {
|
|
266
627
|
type: "boolean",
|
|
267
628
|
description: "Include messages already marked as read (default: false)"
|
|
629
|
+
},
|
|
630
|
+
correlationId: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "Filter messages by correlation ID (for tracking request-response chains)"
|
|
633
|
+
},
|
|
634
|
+
type: {
|
|
635
|
+
type: "string",
|
|
636
|
+
enum: ["handoff", "request", "status", "response"],
|
|
637
|
+
description: "Filter messages by type"
|
|
268
638
|
}
|
|
269
639
|
}
|
|
270
640
|
}
|
|
@@ -569,6 +939,14 @@ function createMemoryMcpServer(config) {
|
|
|
569
939
|
properties: {}
|
|
570
940
|
}
|
|
571
941
|
},
|
|
942
|
+
{
|
|
943
|
+
name: "get_context_health",
|
|
944
|
+
description: "Get context health summary for the current task run. Returns context window usage, warning state, and recent compaction events. Use this to monitor context pressure and decide when to summarize or archive context.",
|
|
945
|
+
inputSchema: {
|
|
946
|
+
type: "object",
|
|
947
|
+
properties: {}
|
|
948
|
+
}
|
|
949
|
+
},
|
|
572
950
|
{
|
|
573
951
|
name: "bind_provider_session",
|
|
574
952
|
description: "Bind a provider-specific session ID to the current task. Use to enable session resume on task retry. Claude agents should call this with their session ID, Codex agents with their thread ID.",
|
|
@@ -716,6 +1094,56 @@ function createMemoryMcpServer(config) {
|
|
|
716
1094
|
}
|
|
717
1095
|
}
|
|
718
1096
|
}
|
|
1097
|
+
},
|
|
1098
|
+
// Operator Input Queue Tools (Active-Turn Steering)
|
|
1099
|
+
{
|
|
1100
|
+
name: "get_pending_operator_inputs",
|
|
1101
|
+
description: "Check if there are pending operator steering inputs in the queue. Call this at turn boundaries to see if the operator has sent guidance. Returns queue status without draining.",
|
|
1102
|
+
inputSchema: {
|
|
1103
|
+
type: "object",
|
|
1104
|
+
properties: {
|
|
1105
|
+
orchestrationId: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
description: "The orchestration ID (optional, uses CMUX_ORCHESTRATION_ID env var if not provided)"
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
name: "acknowledge_operator_inputs",
|
|
1114
|
+
description: "Drain and acknowledge all pending operator inputs. Returns merged content (newline-separated, priority-ordered). Call this at turn boundaries to process operator steering.",
|
|
1115
|
+
inputSchema: {
|
|
1116
|
+
type: "object",
|
|
1117
|
+
properties: {
|
|
1118
|
+
orchestrationId: {
|
|
1119
|
+
type: "string",
|
|
1120
|
+
description: "The orchestration ID (optional, uses CMUX_ORCHESTRATION_ID env var if not provided)"
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
name: "queue_operator_input",
|
|
1127
|
+
description: "Queue an operator steering input for the agent. Use this as a head agent to send guidance to workers, or for testing. Returns QUEUE_FULL error if at capacity.",
|
|
1128
|
+
inputSchema: {
|
|
1129
|
+
type: "object",
|
|
1130
|
+
properties: {
|
|
1131
|
+
orchestrationId: {
|
|
1132
|
+
type: "string",
|
|
1133
|
+
description: "The orchestration ID (optional, uses CMUX_ORCHESTRATION_ID env var if not provided)"
|
|
1134
|
+
},
|
|
1135
|
+
content: {
|
|
1136
|
+
type: "string",
|
|
1137
|
+
description: "The steering instruction content"
|
|
1138
|
+
},
|
|
1139
|
+
priority: {
|
|
1140
|
+
type: "string",
|
|
1141
|
+
enum: ["high", "normal", "low"],
|
|
1142
|
+
description: "Input priority (default: normal). High for interrupts, low for background guidance."
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
required: ["content"]
|
|
1146
|
+
}
|
|
719
1147
|
}
|
|
720
1148
|
]
|
|
721
1149
|
}));
|
|
@@ -723,12 +1151,35 @@ function createMemoryMcpServer(config) {
|
|
|
723
1151
|
const { name, arguments: args } = request.params;
|
|
724
1152
|
switch (name) {
|
|
725
1153
|
case "read_memory": {
|
|
726
|
-
const type = args
|
|
1154
|
+
const { type, includeCompleted, limit } = args;
|
|
1155
|
+
trackRead(type);
|
|
727
1156
|
let content = null;
|
|
728
1157
|
if (type === "knowledge") {
|
|
729
1158
|
content = readFile(path.join(knowledgeDir, "MEMORY.md"));
|
|
730
1159
|
} else if (type === "tasks") {
|
|
731
|
-
|
|
1160
|
+
const tasksFile = readTasks();
|
|
1161
|
+
const maxTasks = limit ?? 50;
|
|
1162
|
+
const showCompleted = includeCompleted ?? false;
|
|
1163
|
+
let filteredTasks = showCompleted ? tasksFile.tasks : tasksFile.tasks.filter((t) => t.status === "pending" || t.status === "in_progress");
|
|
1164
|
+
filteredTasks = filteredTasks.sort((a, b) => {
|
|
1165
|
+
const aNum = parseInt(a.id.replace(/\D/g, ""), 10) || 0;
|
|
1166
|
+
const bNum = parseInt(b.id.replace(/\D/g, ""), 10) || 0;
|
|
1167
|
+
return bNum - aNum;
|
|
1168
|
+
});
|
|
1169
|
+
const truncated = filteredTasks.length > maxTasks;
|
|
1170
|
+
filteredTasks = filteredTasks.slice(0, maxTasks);
|
|
1171
|
+
const result = {
|
|
1172
|
+
version: tasksFile.version,
|
|
1173
|
+
tasks: filteredTasks,
|
|
1174
|
+
_meta: {
|
|
1175
|
+
totalTasks: tasksFile.tasks.length,
|
|
1176
|
+
openTasks: tasksFile.tasks.filter((t) => t.status === "pending" || t.status === "in_progress").length,
|
|
1177
|
+
returnedTasks: filteredTasks.length,
|
|
1178
|
+
truncated,
|
|
1179
|
+
includeCompleted: showCompleted
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
content = JSON.stringify(result, null, 2);
|
|
732
1183
|
} else if (type === "mailbox") {
|
|
733
1184
|
content = readFile(mailboxPath);
|
|
734
1185
|
}
|
|
@@ -758,8 +1209,122 @@ function createMemoryMcpServer(config) {
|
|
|
758
1209
|
const formatted = results.map((r) => `[${r.source}${r.line ? `:${r.line}` : ""}] ${r.content}`).join("\n");
|
|
759
1210
|
return { content: [{ type: "text", text: formatted }] };
|
|
760
1211
|
}
|
|
1212
|
+
case "get_memory_usage": {
|
|
1213
|
+
const stats = readUsageStats();
|
|
1214
|
+
if (Object.keys(stats.entries).length === 0) {
|
|
1215
|
+
return { content: [{ type: "text", text: "No usage statistics recorded yet." }] };
|
|
1216
|
+
}
|
|
1217
|
+
const now = Date.now();
|
|
1218
|
+
const entries = Object.entries(stats.entries).map(([key, value]) => {
|
|
1219
|
+
const daysSinceRead = Math.floor((now - new Date(value.lastRead).getTime()) / (1e3 * 60 * 60 * 24));
|
|
1220
|
+
const daysSinceWrite = value.lastWrite ? Math.floor((now - new Date(value.lastWrite).getTime()) / (1e3 * 60 * 60 * 24)) : null;
|
|
1221
|
+
const recencyScore = Math.max(0, 100 - daysSinceRead * 3);
|
|
1222
|
+
const usageScore = Math.min(100, value.readCount * 10);
|
|
1223
|
+
const priorityScore = key.includes("P0") ? 100 : key.includes("P1") ? 50 : key.includes("P2") ? 25 : 50;
|
|
1224
|
+
const freshnessScore = Math.round(recencyScore * 0.4 + usageScore * 0.4 + priorityScore * 0.2);
|
|
1225
|
+
let recommendation;
|
|
1226
|
+
if (freshnessScore >= 60) recommendation = "keep";
|
|
1227
|
+
else if (freshnessScore >= 30) recommendation = "review";
|
|
1228
|
+
else recommendation = "archive";
|
|
1229
|
+
return {
|
|
1230
|
+
type: key,
|
|
1231
|
+
readCount: value.readCount,
|
|
1232
|
+
daysSinceRead,
|
|
1233
|
+
daysSinceWrite,
|
|
1234
|
+
freshnessScore,
|
|
1235
|
+
recommendation
|
|
1236
|
+
};
|
|
1237
|
+
});
|
|
1238
|
+
const keepCount = entries.filter((e) => e.recommendation === "keep").length;
|
|
1239
|
+
const reviewCount = entries.filter((e) => e.recommendation === "review").length;
|
|
1240
|
+
const archiveCount = entries.filter((e) => e.recommendation === "archive").length;
|
|
1241
|
+
return {
|
|
1242
|
+
content: [{
|
|
1243
|
+
type: "text",
|
|
1244
|
+
text: JSON.stringify({
|
|
1245
|
+
summary: { keep: keepCount, review: reviewCount, archive: archiveCount },
|
|
1246
|
+
entries
|
|
1247
|
+
}, null, 2)
|
|
1248
|
+
}]
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
case "archive_stale_memories": {
|
|
1252
|
+
const { dryRun = true, maxScoreToArchive = 30 } = args;
|
|
1253
|
+
const knowledgePath = path.join(knowledgeDir, "MEMORY.md");
|
|
1254
|
+
const content = readFile(knowledgePath);
|
|
1255
|
+
if (!content) {
|
|
1256
|
+
return { content: [{ type: "text", text: "No MEMORY.md file found." }] };
|
|
1257
|
+
}
|
|
1258
|
+
const lines = content.split("\n");
|
|
1259
|
+
const dateEntryPattern = /^- \[(\d{4}-\d{2}-\d{2})\] (.+)$/;
|
|
1260
|
+
const now = Date.now();
|
|
1261
|
+
const entriesToArchive = [];
|
|
1262
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1263
|
+
const match = lines[i].match(dateEntryPattern);
|
|
1264
|
+
if (match) {
|
|
1265
|
+
const entryDate = new Date(match[1]).getTime();
|
|
1266
|
+
const daysSince = Math.floor((now - entryDate) / (1e3 * 60 * 60 * 24));
|
|
1267
|
+
const score = Math.max(0, 100 - daysSince * 2);
|
|
1268
|
+
if (score < maxScoreToArchive) {
|
|
1269
|
+
entriesToArchive.push({
|
|
1270
|
+
line: i,
|
|
1271
|
+
date: match[1],
|
|
1272
|
+
text: match[2],
|
|
1273
|
+
score
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (entriesToArchive.length === 0) {
|
|
1279
|
+
return { content: [{ type: "text", text: "No stale entries found to archive." }] };
|
|
1280
|
+
}
|
|
1281
|
+
if (dryRun) {
|
|
1282
|
+
return {
|
|
1283
|
+
content: [{
|
|
1284
|
+
type: "text",
|
|
1285
|
+
text: JSON.stringify({
|
|
1286
|
+
dryRun: true,
|
|
1287
|
+
wouldArchive: entriesToArchive.length,
|
|
1288
|
+
entries: entriesToArchive,
|
|
1289
|
+
message: "Set dryRun=false to actually archive these entries."
|
|
1290
|
+
}, null, 2)
|
|
1291
|
+
}]
|
|
1292
|
+
};
|
|
1293
|
+
}
|
|
1294
|
+
const linesToRemove = new Set(entriesToArchive.map((e) => e.line));
|
|
1295
|
+
const newLines = lines.filter((_, i) => !linesToRemove.has(i));
|
|
1296
|
+
const archiveHeader = "## Archive (Stale Entries)";
|
|
1297
|
+
let archiveContent = newLines.join("\n");
|
|
1298
|
+
if (!archiveContent.includes(archiveHeader)) {
|
|
1299
|
+
archiveContent += `
|
|
1300
|
+
|
|
1301
|
+
${archiveHeader}
|
|
1302
|
+
<!-- Entries moved here by archive_stale_memories tool -->
|
|
1303
|
+
`;
|
|
1304
|
+
}
|
|
1305
|
+
const archiveEntries = entriesToArchive.map((e) => `- [${e.date}] [archived] ${e.text}`).join("\n");
|
|
1306
|
+
archiveContent = archiveContent.replace(
|
|
1307
|
+
archiveHeader,
|
|
1308
|
+
`${archiveHeader}
|
|
1309
|
+
${archiveEntries}`
|
|
1310
|
+
);
|
|
1311
|
+
if (writeFile(knowledgePath, archiveContent)) {
|
|
1312
|
+
trackWrite("knowledge.archive");
|
|
1313
|
+
scheduleSyncForFile("update_knowledge");
|
|
1314
|
+
return {
|
|
1315
|
+
content: [{
|
|
1316
|
+
type: "text",
|
|
1317
|
+
text: JSON.stringify({
|
|
1318
|
+
archived: entriesToArchive.length,
|
|
1319
|
+
entries: entriesToArchive.map((e) => e.text.slice(0, 50) + "...")
|
|
1320
|
+
}, null, 2)
|
|
1321
|
+
}]
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
return { content: [{ type: "text", text: "Failed to write archived content." }] };
|
|
1325
|
+
}
|
|
761
1326
|
case "send_message": {
|
|
762
|
-
const { to, message, type } = args;
|
|
1327
|
+
const { to, message, type, correlationId, replyTo, priority, metadata } = args;
|
|
763
1328
|
const mailbox = readMailbox();
|
|
764
1329
|
const newMessage = {
|
|
765
1330
|
id: generateMessageId(),
|
|
@@ -768,23 +1333,48 @@ function createMemoryMcpServer(config) {
|
|
|
768
1333
|
type: type ?? "request",
|
|
769
1334
|
message,
|
|
770
1335
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
771
|
-
read: false
|
|
1336
|
+
read: false,
|
|
1337
|
+
correlationId,
|
|
1338
|
+
replyTo,
|
|
1339
|
+
priority,
|
|
1340
|
+
metadata
|
|
772
1341
|
};
|
|
773
1342
|
mailbox.messages.push(newMessage);
|
|
774
1343
|
writeMailbox(mailbox);
|
|
775
|
-
|
|
1344
|
+
scheduleSyncForFile("send_message");
|
|
1345
|
+
const responseText = correlationId ? `Message sent successfully. ID: ${newMessage.id}, correlationId: ${correlationId}` : `Message sent successfully. ID: ${newMessage.id}`;
|
|
1346
|
+
return { content: [{ type: "text", text: responseText }] };
|
|
776
1347
|
}
|
|
777
1348
|
case "get_my_messages": {
|
|
778
|
-
const includeRead = args
|
|
1349
|
+
const { includeRead, correlationId, type: filterType } = args;
|
|
779
1350
|
const mailbox = readMailbox();
|
|
780
|
-
|
|
1351
|
+
let myMessages = mailbox.messages.filter(
|
|
781
1352
|
(m) => m.to === agentName || m.to === "*"
|
|
782
1353
|
);
|
|
783
|
-
|
|
784
|
-
|
|
1354
|
+
if (!includeRead) {
|
|
1355
|
+
myMessages = myMessages.filter((m) => !m.read);
|
|
1356
|
+
}
|
|
1357
|
+
if (correlationId) {
|
|
1358
|
+
myMessages = myMessages.filter((m) => m.correlationId === correlationId);
|
|
1359
|
+
}
|
|
1360
|
+
if (filterType) {
|
|
1361
|
+
myMessages = myMessages.filter((m) => m.type === filterType);
|
|
1362
|
+
}
|
|
1363
|
+
const priorityOrder = { high: 0, normal: 1, low: 2, undefined: 1 };
|
|
1364
|
+
myMessages.sort((a, b) => {
|
|
1365
|
+
const pa = priorityOrder[a.priority ?? "normal"];
|
|
1366
|
+
const pb = priorityOrder[b.priority ?? "normal"];
|
|
1367
|
+
if (pa !== pb) return pa - pb;
|
|
1368
|
+
return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
1369
|
+
});
|
|
1370
|
+
if (myMessages.length === 0) {
|
|
785
1371
|
return { content: [{ type: "text", text: "No messages for you." }] };
|
|
786
1372
|
}
|
|
787
|
-
const formatted =
|
|
1373
|
+
const formatted = myMessages.map((m) => {
|
|
1374
|
+
const priorityTag = m.priority === "high" ? "[HIGH] " : m.priority === "low" ? "[low] " : "";
|
|
1375
|
+
const corrTag = m.correlationId ? ` (corr: ${m.correlationId})` : "";
|
|
1376
|
+
return `${priorityTag}[${m.id}] ${m.type ?? "message"} from ${m.from}${corrTag}: ${m.message}`;
|
|
1377
|
+
}).join("\n\n");
|
|
788
1378
|
return { content: [{ type: "text", text: formatted }] };
|
|
789
1379
|
}
|
|
790
1380
|
case "mark_read": {
|
|
@@ -796,6 +1386,7 @@ function createMemoryMcpServer(config) {
|
|
|
796
1386
|
}
|
|
797
1387
|
message.read = true;
|
|
798
1388
|
writeMailbox(mailbox);
|
|
1389
|
+
scheduleSyncForFile("mark_read");
|
|
799
1390
|
return { content: [{ type: "text", text: `Message ${messageId} marked as read.` }] };
|
|
800
1391
|
}
|
|
801
1392
|
// Write tool handlers
|
|
@@ -814,12 +1405,25 @@ function createMemoryMcpServer(config) {
|
|
|
814
1405
|
const newContent = existing + `
|
|
815
1406
|
- [${timestamp}] ${content}`;
|
|
816
1407
|
if (writeFile(logPath, newContent)) {
|
|
1408
|
+
scheduleSyncForFile("append_daily_log");
|
|
817
1409
|
return { content: [{ type: "text", text: `Appended to daily/${today}.md` }] };
|
|
818
1410
|
}
|
|
819
1411
|
return { content: [{ type: "text", text: `Failed to append to daily log` }] };
|
|
820
1412
|
}
|
|
821
1413
|
case "update_knowledge": {
|
|
822
1414
|
const { section, content } = args;
|
|
1415
|
+
if (content.length > 200) {
|
|
1416
|
+
return {
|
|
1417
|
+
content: [{
|
|
1418
|
+
type: "text",
|
|
1419
|
+
text: `Entry too long (${content.length} chars). Keep memory entries concise (max 200 chars). Consider splitting into multiple entries or being more specific.`
|
|
1420
|
+
}]
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
if (!content.trim()) {
|
|
1424
|
+
return { content: [{ type: "text", text: "Cannot add empty memory entry." }] };
|
|
1425
|
+
}
|
|
1426
|
+
trackWrite(`knowledge.${section}`);
|
|
823
1427
|
ensureDir(knowledgeDir);
|
|
824
1428
|
const knowledgePath = path.join(knowledgeDir, "MEMORY.md");
|
|
825
1429
|
let existing = readFile(knowledgePath);
|
|
@@ -854,6 +1458,45 @@ function createMemoryMcpServer(config) {
|
|
|
854
1458
|
if (headerIndex === -1) {
|
|
855
1459
|
return { content: [{ type: "text", text: `Section ${section} not found in MEMORY.md` }] };
|
|
856
1460
|
}
|
|
1461
|
+
const contentLower = content.toLowerCase();
|
|
1462
|
+
const existingEntries = existing.match(/- \[\d{4}-\d{2}-\d{2}\] .+/g) || [];
|
|
1463
|
+
for (const entry of existingEntries) {
|
|
1464
|
+
const entryText = entry.replace(/- \[\d{4}-\d{2}-\d{2}\] /, "").toLowerCase();
|
|
1465
|
+
if (entryText === contentLower || entryText.includes(contentLower) || contentLower.includes(entryText)) {
|
|
1466
|
+
return {
|
|
1467
|
+
content: [{
|
|
1468
|
+
type: "text",
|
|
1469
|
+
text: `Duplicate detected. Similar entry already exists: "${entry.slice(0, 80)}...". Update the existing entry instead of adding a duplicate.`
|
|
1470
|
+
}]
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
if (section === "P0") {
|
|
1475
|
+
const p0Section = existing.slice(headerIndex, existing.indexOf("## P1"));
|
|
1476
|
+
const usesPattern = /uses?\s+(\w+)/gi;
|
|
1477
|
+
const newUses = [...contentLower.matchAll(usesPattern)].map((m) => m[1]);
|
|
1478
|
+
const existingUses = [...p0Section.toLowerCase().matchAll(usesPattern)].map((m) => m[1]);
|
|
1479
|
+
const alternatives = {
|
|
1480
|
+
bun: ["npm", "yarn", "pnpm"],
|
|
1481
|
+
npm: ["bun", "yarn", "pnpm"],
|
|
1482
|
+
yarn: ["npm", "bun", "pnpm"],
|
|
1483
|
+
vitest: ["jest", "mocha"],
|
|
1484
|
+
jest: ["vitest", "mocha"]
|
|
1485
|
+
};
|
|
1486
|
+
for (const newTool of newUses) {
|
|
1487
|
+
const conflicts = alternatives[newTool] || [];
|
|
1488
|
+
for (const existingTool of existingUses) {
|
|
1489
|
+
if (conflicts.includes(existingTool)) {
|
|
1490
|
+
return {
|
|
1491
|
+
content: [{
|
|
1492
|
+
type: "text",
|
|
1493
|
+
text: `Potential P0 contradiction: New entry mentions "${newTool}" but P0 already contains "${existingTool}". These are typically alternatives. Verify this is intentional before adding.`
|
|
1494
|
+
}]
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
857
1500
|
const afterHeader = existing.slice(headerIndex + header.length);
|
|
858
1501
|
const nextSectionMatch = afterHeader.match(/\n## /);
|
|
859
1502
|
const insertPoint = nextSectionMatch ? headerIndex + header.length + (nextSectionMatch.index ?? afterHeader.length) : existing.length;
|
|
@@ -862,6 +1505,7 @@ function createMemoryMcpServer(config) {
|
|
|
862
1505
|
const actualInsertPoint = Math.min(commentEnd, insertPoint);
|
|
863
1506
|
const updated = existing.slice(0, actualInsertPoint) + newEntry + "\n" + existing.slice(actualInsertPoint);
|
|
864
1507
|
if (writeFile(knowledgePath, updated)) {
|
|
1508
|
+
scheduleSyncForFile("update_knowledge");
|
|
865
1509
|
return { content: [{ type: "text", text: `Added entry to ${section} section in MEMORY.md` }] };
|
|
866
1510
|
}
|
|
867
1511
|
return { content: [{ type: "text", text: `Failed to update MEMORY.md` }] };
|
|
@@ -880,6 +1524,7 @@ function createMemoryMcpServer(config) {
|
|
|
880
1524
|
};
|
|
881
1525
|
tasks.tasks.push(newTask);
|
|
882
1526
|
if (writeTasks(tasks)) {
|
|
1527
|
+
scheduleSyncForFile("add_task");
|
|
883
1528
|
return { content: [{ type: "text", text: `Task created with ID: ${newTask.id}` }] };
|
|
884
1529
|
}
|
|
885
1530
|
return { content: [{ type: "text", text: `Failed to create task` }] };
|
|
@@ -894,6 +1539,7 @@ function createMemoryMcpServer(config) {
|
|
|
894
1539
|
task.status = status;
|
|
895
1540
|
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
896
1541
|
if (writeTasks(tasks)) {
|
|
1542
|
+
scheduleSyncForFile("update_task");
|
|
897
1543
|
return { content: [{ type: "text", text: `Task ${taskId} updated to status: ${status}` }] };
|
|
898
1544
|
}
|
|
899
1545
|
return { content: [{ type: "text", text: `Failed to update task` }] };
|
|
@@ -923,6 +1569,7 @@ function createMemoryMcpServer(config) {
|
|
|
923
1569
|
if (agentName2) eventObj.agentName = agentName2;
|
|
924
1570
|
if (taskRunId) eventObj.taskRunId = taskRunId;
|
|
925
1571
|
if (appendEvent(eventObj)) {
|
|
1572
|
+
scheduleSyncForFile("append_event");
|
|
926
1573
|
return { content: [{ type: "text", text: `Event appended to EVENTS.jsonl` }] };
|
|
927
1574
|
}
|
|
928
1575
|
return { content: [{ type: "text", text: `Failed to append event` }] };
|
|
@@ -947,6 +1594,7 @@ function createMemoryMcpServer(config) {
|
|
|
947
1594
|
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
948
1595
|
}
|
|
949
1596
|
if (writePlan(plan)) {
|
|
1597
|
+
scheduleSyncForFile("update_plan_task");
|
|
950
1598
|
return { content: [{ type: "text", text: `Plan task ${taskId} updated to status: ${status}` }] };
|
|
951
1599
|
}
|
|
952
1600
|
return { content: [{ type: "text", text: `Failed to update plan task` }] };
|
|
@@ -955,7 +1603,7 @@ function createMemoryMcpServer(config) {
|
|
|
955
1603
|
const { orchestrationId: argOrchId } = args;
|
|
956
1604
|
const orchestrationId = argOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
957
1605
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
958
|
-
const apiBaseUrl =
|
|
1606
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
959
1607
|
if (!orchestrationId) {
|
|
960
1608
|
return {
|
|
961
1609
|
content: [{
|
|
@@ -1066,7 +1714,7 @@ function createMemoryMcpServer(config) {
|
|
|
1066
1714
|
const { orchestrationId: argOrchId, headAgentStatus, message, taskIds } = args;
|
|
1067
1715
|
const orchestrationId = argOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
1068
1716
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1069
|
-
const apiBaseUrl =
|
|
1717
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1070
1718
|
if (!orchestrationId) {
|
|
1071
1719
|
return {
|
|
1072
1720
|
content: [{
|
|
@@ -1403,7 +2051,7 @@ function createMemoryMcpServer(config) {
|
|
|
1403
2051
|
case "list_spawned_agents": {
|
|
1404
2052
|
const { status: filterStatus } = args;
|
|
1405
2053
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1406
|
-
const apiBaseUrl =
|
|
2054
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1407
2055
|
const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
|
|
1408
2056
|
if (!jwt) {
|
|
1409
2057
|
return {
|
|
@@ -1462,7 +2110,7 @@ function createMemoryMcpServer(config) {
|
|
|
1462
2110
|
case "cancel_agent": {
|
|
1463
2111
|
const { orchestrationTaskId, cascade = false } = args;
|
|
1464
2112
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1465
|
-
const apiBaseUrl =
|
|
2113
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1466
2114
|
if (!jwt) {
|
|
1467
2115
|
return {
|
|
1468
2116
|
content: [{
|
|
@@ -1530,7 +2178,7 @@ function createMemoryMcpServer(config) {
|
|
|
1530
2178
|
}
|
|
1531
2179
|
case "get_orchestration_summary": {
|
|
1532
2180
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1533
|
-
const apiBaseUrl =
|
|
2181
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1534
2182
|
const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
|
|
1535
2183
|
if (!jwt) {
|
|
1536
2184
|
return {
|
|
@@ -1600,10 +2248,77 @@ function createMemoryMcpServer(config) {
|
|
|
1600
2248
|
};
|
|
1601
2249
|
}
|
|
1602
2250
|
}
|
|
2251
|
+
case "get_context_health": {
|
|
2252
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
2253
|
+
const callbackUrl = process.env.CMUX_CALLBACK_URL;
|
|
2254
|
+
if (!jwt) {
|
|
2255
|
+
return {
|
|
2256
|
+
content: [{
|
|
2257
|
+
type: "text",
|
|
2258
|
+
text: "CMUX_TASK_RUN_JWT environment variable not set. This tool requires JWT authentication."
|
|
2259
|
+
}]
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
if (!callbackUrl) {
|
|
2263
|
+
return {
|
|
2264
|
+
content: [{
|
|
2265
|
+
type: "text",
|
|
2266
|
+
text: "CMUX_CALLBACK_URL environment variable not set."
|
|
2267
|
+
}]
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
try {
|
|
2271
|
+
const url = `${callbackUrl}/api/v1/cmux/orchestration/context-health`;
|
|
2272
|
+
const response = await fetch(url, {
|
|
2273
|
+
method: "GET",
|
|
2274
|
+
headers: {
|
|
2275
|
+
"x-cmux-token": jwt
|
|
2276
|
+
}
|
|
2277
|
+
});
|
|
2278
|
+
if (!response.ok) {
|
|
2279
|
+
const errorData = await response.json().catch(() => ({}));
|
|
2280
|
+
return {
|
|
2281
|
+
content: [{
|
|
2282
|
+
type: "text",
|
|
2283
|
+
text: `Error getting context health: ${response.status} ${errorData.message ?? response.statusText}`
|
|
2284
|
+
}]
|
|
2285
|
+
};
|
|
2286
|
+
}
|
|
2287
|
+
const data = await response.json();
|
|
2288
|
+
const usageStr = data.contextWindow ? `${data.totalInputTokens.toLocaleString()} / ${data.contextWindow.toLocaleString()} tokens (${data.usagePercent ?? 0}%)` : `${data.totalInputTokens.toLocaleString()} input tokens`;
|
|
2289
|
+
const warningStr = data.latestWarningSeverity ? `${data.latestWarningSeverity.toUpperCase()} - ${data.topWarningReasons[0] ?? "No details"}` : "None";
|
|
2290
|
+
const summary = [
|
|
2291
|
+
`Provider: ${data.provider}`,
|
|
2292
|
+
`Context Usage: ${usageStr}`,
|
|
2293
|
+
`Output Tokens: ${data.totalOutputTokens.toLocaleString()}`,
|
|
2294
|
+
`Warning State: ${warningStr}`,
|
|
2295
|
+
`Warning Count: ${data.warningCount}`,
|
|
2296
|
+
`Compaction Count: ${data.recentCompactionCount}`
|
|
2297
|
+
].join("\n");
|
|
2298
|
+
return {
|
|
2299
|
+
content: [{
|
|
2300
|
+
type: "text",
|
|
2301
|
+
text: `Context Health Summary:
|
|
2302
|
+
${summary}
|
|
2303
|
+
|
|
2304
|
+
Raw Data:
|
|
2305
|
+
${JSON.stringify(data, null, 2)}`
|
|
2306
|
+
}]
|
|
2307
|
+
};
|
|
2308
|
+
} catch (error) {
|
|
2309
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2310
|
+
return {
|
|
2311
|
+
content: [{
|
|
2312
|
+
type: "text",
|
|
2313
|
+
text: `Error getting context health: ${errorMsg}`
|
|
2314
|
+
}]
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
1603
2318
|
case "bind_provider_session": {
|
|
1604
2319
|
const { providerSessionId, providerThreadId, replyChannel } = args;
|
|
1605
2320
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1606
|
-
const apiBaseUrl =
|
|
2321
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1607
2322
|
const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
|
|
1608
2323
|
const taskRunId = process.env.CMUX_TASK_RUN_ID;
|
|
1609
2324
|
if (!jwt) {
|
|
@@ -1666,7 +2381,7 @@ function createMemoryMcpServer(config) {
|
|
|
1666
2381
|
case "get_provider_session": {
|
|
1667
2382
|
const { taskId: providedTaskId } = args;
|
|
1668
2383
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1669
|
-
const apiBaseUrl =
|
|
2384
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1670
2385
|
const taskId = providedTaskId ?? process.env.CMUX_TASK_RUN_ID;
|
|
1671
2386
|
if (!jwt) {
|
|
1672
2387
|
return {
|
|
@@ -1744,7 +2459,7 @@ function createMemoryMcpServer(config) {
|
|
|
1744
2459
|
case "wait_for_events": {
|
|
1745
2460
|
const { orchestrationId, eventTypes = [], timeout = 3e4 } = args;
|
|
1746
2461
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1747
|
-
const apiBaseUrl =
|
|
2462
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1748
2463
|
if (!jwt) {
|
|
1749
2464
|
return {
|
|
1750
2465
|
content: [{
|
|
@@ -1859,7 +2574,7 @@ function createMemoryMcpServer(config) {
|
|
|
1859
2574
|
case "get_pending_approvals": {
|
|
1860
2575
|
const { orchestrationId: providedOrchId } = args;
|
|
1861
2576
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1862
|
-
const apiBaseUrl =
|
|
2577
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1863
2578
|
const orchestrationId = providedOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
1864
2579
|
if (!jwt) {
|
|
1865
2580
|
return {
|
|
@@ -1919,7 +2634,7 @@ function createMemoryMcpServer(config) {
|
|
|
1919
2634
|
case "resolve_approval": {
|
|
1920
2635
|
const { requestId, resolution, note } = args;
|
|
1921
2636
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
1922
|
-
const apiBaseUrl =
|
|
2637
|
+
const apiBaseUrl = getCmuxApiBaseUrl();
|
|
1923
2638
|
if (!jwt) {
|
|
1924
2639
|
return {
|
|
1925
2640
|
content: [{
|
|
@@ -2151,7 +2866,7 @@ function createMemoryMcpServer(config) {
|
|
|
2151
2866
|
}
|
|
2152
2867
|
case "log_learning": {
|
|
2153
2868
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
2154
|
-
const apiBase =
|
|
2869
|
+
const apiBase = getCmuxApiBaseUrl();
|
|
2155
2870
|
if (!jwt) {
|
|
2156
2871
|
return {
|
|
2157
2872
|
content: [{
|
|
@@ -2211,7 +2926,7 @@ function createMemoryMcpServer(config) {
|
|
|
2211
2926
|
}
|
|
2212
2927
|
case "get_active_orchestration_rules": {
|
|
2213
2928
|
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
2214
|
-
const apiBase =
|
|
2929
|
+
const apiBase = getCmuxApiBaseUrl();
|
|
2215
2930
|
if (!jwt) {
|
|
2216
2931
|
return {
|
|
2217
2932
|
content: [{
|
|
@@ -2279,6 +2994,242 @@ function createMemoryMcpServer(config) {
|
|
|
2279
2994
|
};
|
|
2280
2995
|
}
|
|
2281
2996
|
}
|
|
2997
|
+
// Operator Input Queue Tools (Active-Turn Steering)
|
|
2998
|
+
case "get_pending_operator_inputs": {
|
|
2999
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
3000
|
+
const apiBase = getCmuxApiBaseUrl();
|
|
3001
|
+
if (!jwt) {
|
|
3002
|
+
return {
|
|
3003
|
+
content: [{
|
|
3004
|
+
type: "text",
|
|
3005
|
+
text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
|
|
3006
|
+
}]
|
|
3007
|
+
};
|
|
3008
|
+
}
|
|
3009
|
+
const { orchestrationId: inputOrchId } = args;
|
|
3010
|
+
const orchestrationId = inputOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
3011
|
+
if (!orchestrationId) {
|
|
3012
|
+
return {
|
|
3013
|
+
content: [{
|
|
3014
|
+
type: "text",
|
|
3015
|
+
text: "Error: No orchestration ID provided and CMUX_ORCHESTRATION_ID not set."
|
|
3016
|
+
}]
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
try {
|
|
3020
|
+
const response = await fetch(
|
|
3021
|
+
`${apiBase}/api/orchestrate/input/${orchestrationId}/status`,
|
|
3022
|
+
{
|
|
3023
|
+
method: "GET",
|
|
3024
|
+
headers: {
|
|
3025
|
+
"x-cmux-token": jwt
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
);
|
|
3029
|
+
if (!response.ok) {
|
|
3030
|
+
const errText = await response.text();
|
|
3031
|
+
return {
|
|
3032
|
+
content: [{
|
|
3033
|
+
type: "text",
|
|
3034
|
+
text: `Error getting queue status: ${response.status} ${errText}`
|
|
3035
|
+
}]
|
|
3036
|
+
};
|
|
3037
|
+
}
|
|
3038
|
+
const status = await response.json();
|
|
3039
|
+
return {
|
|
3040
|
+
content: [{
|
|
3041
|
+
type: "text",
|
|
3042
|
+
text: JSON.stringify({
|
|
3043
|
+
hasPendingInputs: status.hasPendingInputs,
|
|
3044
|
+
depth: status.depth,
|
|
3045
|
+
capacity: status.capacity,
|
|
3046
|
+
oldestInputAt: status.oldestInputAt,
|
|
3047
|
+
message: status.hasPendingInputs ? `${status.depth} pending operator input(s). Call acknowledge_operator_inputs to process.` : "No pending operator inputs."
|
|
3048
|
+
}, null, 2)
|
|
3049
|
+
}]
|
|
3050
|
+
};
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3053
|
+
return {
|
|
3054
|
+
content: [{
|
|
3055
|
+
type: "text",
|
|
3056
|
+
text: `Error checking operator inputs: ${errorMsg}`
|
|
3057
|
+
}]
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
case "acknowledge_operator_inputs": {
|
|
3062
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
3063
|
+
const apiBase = getCmuxApiBaseUrl();
|
|
3064
|
+
if (!jwt) {
|
|
3065
|
+
return {
|
|
3066
|
+
content: [{
|
|
3067
|
+
type: "text",
|
|
3068
|
+
text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
|
|
3069
|
+
}]
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
const { orchestrationId: ackOrchId } = args;
|
|
3073
|
+
const orchestrationId = ackOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
3074
|
+
if (!orchestrationId) {
|
|
3075
|
+
return {
|
|
3076
|
+
content: [{
|
|
3077
|
+
type: "text",
|
|
3078
|
+
text: "Error: No orchestration ID provided and CMUX_ORCHESTRATION_ID not set."
|
|
3079
|
+
}]
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
try {
|
|
3083
|
+
const response = await fetch(
|
|
3084
|
+
`${apiBase}/api/orchestrate/input/${orchestrationId}/drain`,
|
|
3085
|
+
{
|
|
3086
|
+
method: "POST",
|
|
3087
|
+
headers: {
|
|
3088
|
+
"Content-Type": "application/json",
|
|
3089
|
+
"x-cmux-token": jwt
|
|
3090
|
+
},
|
|
3091
|
+
body: JSON.stringify({})
|
|
3092
|
+
}
|
|
3093
|
+
);
|
|
3094
|
+
if (!response.ok) {
|
|
3095
|
+
const errText = await response.text();
|
|
3096
|
+
return {
|
|
3097
|
+
content: [{
|
|
3098
|
+
type: "text",
|
|
3099
|
+
text: `Error draining operator inputs: ${response.status} ${errText}`
|
|
3100
|
+
}]
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
const result = await response.json();
|
|
3104
|
+
if (result.count === 0) {
|
|
3105
|
+
return {
|
|
3106
|
+
content: [{
|
|
3107
|
+
type: "text",
|
|
3108
|
+
text: JSON.stringify({
|
|
3109
|
+
count: 0,
|
|
3110
|
+
content: "",
|
|
3111
|
+
batchId: result.batchId,
|
|
3112
|
+
message: "No operator inputs to process."
|
|
3113
|
+
}, null, 2)
|
|
3114
|
+
}]
|
|
3115
|
+
};
|
|
3116
|
+
}
|
|
3117
|
+
return {
|
|
3118
|
+
content: [{
|
|
3119
|
+
type: "text",
|
|
3120
|
+
text: JSON.stringify({
|
|
3121
|
+
count: result.count,
|
|
3122
|
+
content: result.content,
|
|
3123
|
+
batchId: result.batchId,
|
|
3124
|
+
inputIds: result.inputIds,
|
|
3125
|
+
message: `Processed ${result.count} operator input(s). Content merged above.`
|
|
3126
|
+
}, null, 2)
|
|
3127
|
+
}]
|
|
3128
|
+
};
|
|
3129
|
+
} catch (error) {
|
|
3130
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3131
|
+
return {
|
|
3132
|
+
content: [{
|
|
3133
|
+
type: "text",
|
|
3134
|
+
text: `Error acknowledging operator inputs: ${errorMsg}`
|
|
3135
|
+
}]
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
case "queue_operator_input": {
|
|
3140
|
+
const jwt = process.env.CMUX_TASK_RUN_JWT;
|
|
3141
|
+
const apiBase = getCmuxApiBaseUrl();
|
|
3142
|
+
if (!jwt) {
|
|
3143
|
+
return {
|
|
3144
|
+
content: [{
|
|
3145
|
+
type: "text",
|
|
3146
|
+
text: "Error: CMUX_TASK_RUN_JWT not set. This tool requires authentication."
|
|
3147
|
+
}]
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
const {
|
|
3151
|
+
orchestrationId: queueOrchId,
|
|
3152
|
+
content,
|
|
3153
|
+
priority
|
|
3154
|
+
} = args;
|
|
3155
|
+
const orchestrationId = queueOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
|
|
3156
|
+
if (!orchestrationId) {
|
|
3157
|
+
return {
|
|
3158
|
+
content: [{
|
|
3159
|
+
type: "text",
|
|
3160
|
+
text: "Error: No orchestration ID provided and CMUX_ORCHESTRATION_ID not set."
|
|
3161
|
+
}]
|
|
3162
|
+
};
|
|
3163
|
+
}
|
|
3164
|
+
if (!content || content.trim().length === 0) {
|
|
3165
|
+
return {
|
|
3166
|
+
content: [{
|
|
3167
|
+
type: "text",
|
|
3168
|
+
text: "Error: Content is required."
|
|
3169
|
+
}]
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
try {
|
|
3173
|
+
const response = await fetch(
|
|
3174
|
+
`${apiBase}/api/orchestrate/input/${orchestrationId}`,
|
|
3175
|
+
{
|
|
3176
|
+
method: "POST",
|
|
3177
|
+
headers: {
|
|
3178
|
+
"Content-Type": "application/json",
|
|
3179
|
+
"x-cmux-token": jwt
|
|
3180
|
+
},
|
|
3181
|
+
body: JSON.stringify({
|
|
3182
|
+
content: content.trim(),
|
|
3183
|
+
priority: priority ?? "normal"
|
|
3184
|
+
})
|
|
3185
|
+
}
|
|
3186
|
+
);
|
|
3187
|
+
if (!response.ok) {
|
|
3188
|
+
const errText = await response.text();
|
|
3189
|
+
return {
|
|
3190
|
+
content: [{
|
|
3191
|
+
type: "text",
|
|
3192
|
+
text: `Error queuing input: ${response.status} ${errText}`
|
|
3193
|
+
}]
|
|
3194
|
+
};
|
|
3195
|
+
}
|
|
3196
|
+
const result = await response.json();
|
|
3197
|
+
if (!result.success) {
|
|
3198
|
+
return {
|
|
3199
|
+
content: [{
|
|
3200
|
+
type: "text",
|
|
3201
|
+
text: JSON.stringify({
|
|
3202
|
+
queued: false,
|
|
3203
|
+
error: result.error,
|
|
3204
|
+
queueDepth: result.queueDepth,
|
|
3205
|
+
queueCapacity: result.queueCapacity,
|
|
3206
|
+
message: `Queue is full (${result.queueDepth}/${result.queueCapacity}). Wait for agent to drain before sending more.`
|
|
3207
|
+
}, null, 2)
|
|
3208
|
+
}]
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
return {
|
|
3212
|
+
content: [{
|
|
3213
|
+
type: "text",
|
|
3214
|
+
text: JSON.stringify({
|
|
3215
|
+
queued: true,
|
|
3216
|
+
inputId: result.inputId,
|
|
3217
|
+
queueDepth: result.queueDepth,
|
|
3218
|
+
priority: priority ?? "normal",
|
|
3219
|
+
message: `Input queued successfully (${result.queueDepth} in queue).`
|
|
3220
|
+
}, null, 2)
|
|
3221
|
+
}]
|
|
3222
|
+
};
|
|
3223
|
+
} catch (error) {
|
|
3224
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
3225
|
+
return {
|
|
3226
|
+
content: [{
|
|
3227
|
+
type: "text",
|
|
3228
|
+
text: `Error queuing operator input: ${errorMsg}`
|
|
3229
|
+
}]
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
2282
3233
|
default:
|
|
2283
3234
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
2284
3235
|
}
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devsh-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "MCP server for devsh/cmux agent memory - enables Claude Desktop and external clients to access sandbox memory and orchestrate multi-agent workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsup",
|
|
13
13
|
"dev": "tsup --watch",
|
|
14
|
+
"test": "bun test",
|
|
14
15
|
"typecheck": "tsc --noEmit",
|
|
15
16
|
"prepublishOnly": "npm run build"
|
|
16
17
|
},
|