devsh-memory-mcp 0.3.9 → 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 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.sh) |
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;
@@ -241,6 +552,31 @@ function createMemoryMcpServer(config) {
241
552
  required: ["query"]
242
553
  }
243
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
+ },
244
580
  {
245
581
  name: "send_message",
246
582
  description: 'Send a message to another agent on the same task. Use "*" to broadcast to all agents. Aligned with Claude Code SendMessage pattern.',
@@ -603,6 +939,14 @@ function createMemoryMcpServer(config) {
603
939
  properties: {}
604
940
  }
605
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
+ },
606
950
  {
607
951
  name: "bind_provider_session",
608
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.",
@@ -750,6 +1094,56 @@ function createMemoryMcpServer(config) {
750
1094
  }
751
1095
  }
752
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
+ }
753
1147
  }
754
1148
  ]
755
1149
  }));
@@ -758,6 +1152,7 @@ function createMemoryMcpServer(config) {
758
1152
  switch (name) {
759
1153
  case "read_memory": {
760
1154
  const { type, includeCompleted, limit } = args;
1155
+ trackRead(type);
761
1156
  let content = null;
762
1157
  if (type === "knowledge") {
763
1158
  content = readFile(path.join(knowledgeDir, "MEMORY.md"));
@@ -814,6 +1209,120 @@ function createMemoryMcpServer(config) {
814
1209
  const formatted = results.map((r) => `[${r.source}${r.line ? `:${r.line}` : ""}] ${r.content}`).join("\n");
815
1210
  return { content: [{ type: "text", text: formatted }] };
816
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
+ }
817
1326
  case "send_message": {
818
1327
  const { to, message, type, correlationId, replyTo, priority, metadata } = args;
819
1328
  const mailbox = readMailbox();
@@ -832,6 +1341,7 @@ function createMemoryMcpServer(config) {
832
1341
  };
833
1342
  mailbox.messages.push(newMessage);
834
1343
  writeMailbox(mailbox);
1344
+ scheduleSyncForFile("send_message");
835
1345
  const responseText = correlationId ? `Message sent successfully. ID: ${newMessage.id}, correlationId: ${correlationId}` : `Message sent successfully. ID: ${newMessage.id}`;
836
1346
  return { content: [{ type: "text", text: responseText }] };
837
1347
  }
@@ -876,6 +1386,7 @@ function createMemoryMcpServer(config) {
876
1386
  }
877
1387
  message.read = true;
878
1388
  writeMailbox(mailbox);
1389
+ scheduleSyncForFile("mark_read");
879
1390
  return { content: [{ type: "text", text: `Message ${messageId} marked as read.` }] };
880
1391
  }
881
1392
  // Write tool handlers
@@ -894,12 +1405,25 @@ function createMemoryMcpServer(config) {
894
1405
  const newContent = existing + `
895
1406
  - [${timestamp}] ${content}`;
896
1407
  if (writeFile(logPath, newContent)) {
1408
+ scheduleSyncForFile("append_daily_log");
897
1409
  return { content: [{ type: "text", text: `Appended to daily/${today}.md` }] };
898
1410
  }
899
1411
  return { content: [{ type: "text", text: `Failed to append to daily log` }] };
900
1412
  }
901
1413
  case "update_knowledge": {
902
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}`);
903
1427
  ensureDir(knowledgeDir);
904
1428
  const knowledgePath = path.join(knowledgeDir, "MEMORY.md");
905
1429
  let existing = readFile(knowledgePath);
@@ -934,6 +1458,45 @@ function createMemoryMcpServer(config) {
934
1458
  if (headerIndex === -1) {
935
1459
  return { content: [{ type: "text", text: `Section ${section} not found in MEMORY.md` }] };
936
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
+ }
937
1500
  const afterHeader = existing.slice(headerIndex + header.length);
938
1501
  const nextSectionMatch = afterHeader.match(/\n## /);
939
1502
  const insertPoint = nextSectionMatch ? headerIndex + header.length + (nextSectionMatch.index ?? afterHeader.length) : existing.length;
@@ -942,6 +1505,7 @@ function createMemoryMcpServer(config) {
942
1505
  const actualInsertPoint = Math.min(commentEnd, insertPoint);
943
1506
  const updated = existing.slice(0, actualInsertPoint) + newEntry + "\n" + existing.slice(actualInsertPoint);
944
1507
  if (writeFile(knowledgePath, updated)) {
1508
+ scheduleSyncForFile("update_knowledge");
945
1509
  return { content: [{ type: "text", text: `Added entry to ${section} section in MEMORY.md` }] };
946
1510
  }
947
1511
  return { content: [{ type: "text", text: `Failed to update MEMORY.md` }] };
@@ -960,6 +1524,7 @@ function createMemoryMcpServer(config) {
960
1524
  };
961
1525
  tasks.tasks.push(newTask);
962
1526
  if (writeTasks(tasks)) {
1527
+ scheduleSyncForFile("add_task");
963
1528
  return { content: [{ type: "text", text: `Task created with ID: ${newTask.id}` }] };
964
1529
  }
965
1530
  return { content: [{ type: "text", text: `Failed to create task` }] };
@@ -974,6 +1539,7 @@ function createMemoryMcpServer(config) {
974
1539
  task.status = status;
975
1540
  task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
976
1541
  if (writeTasks(tasks)) {
1542
+ scheduleSyncForFile("update_task");
977
1543
  return { content: [{ type: "text", text: `Task ${taskId} updated to status: ${status}` }] };
978
1544
  }
979
1545
  return { content: [{ type: "text", text: `Failed to update task` }] };
@@ -1003,6 +1569,7 @@ function createMemoryMcpServer(config) {
1003
1569
  if (agentName2) eventObj.agentName = agentName2;
1004
1570
  if (taskRunId) eventObj.taskRunId = taskRunId;
1005
1571
  if (appendEvent(eventObj)) {
1572
+ scheduleSyncForFile("append_event");
1006
1573
  return { content: [{ type: "text", text: `Event appended to EVENTS.jsonl` }] };
1007
1574
  }
1008
1575
  return { content: [{ type: "text", text: `Failed to append event` }] };
@@ -1027,6 +1594,7 @@ function createMemoryMcpServer(config) {
1027
1594
  task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
1028
1595
  }
1029
1596
  if (writePlan(plan)) {
1597
+ scheduleSyncForFile("update_plan_task");
1030
1598
  return { content: [{ type: "text", text: `Plan task ${taskId} updated to status: ${status}` }] };
1031
1599
  }
1032
1600
  return { content: [{ type: "text", text: `Failed to update plan task` }] };
@@ -1035,7 +1603,7 @@ function createMemoryMcpServer(config) {
1035
1603
  const { orchestrationId: argOrchId } = args;
1036
1604
  const orchestrationId = argOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
1037
1605
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1038
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1606
+ const apiBaseUrl = getCmuxApiBaseUrl();
1039
1607
  if (!orchestrationId) {
1040
1608
  return {
1041
1609
  content: [{
@@ -1146,7 +1714,7 @@ function createMemoryMcpServer(config) {
1146
1714
  const { orchestrationId: argOrchId, headAgentStatus, message, taskIds } = args;
1147
1715
  const orchestrationId = argOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
1148
1716
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1149
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
1717
+ const apiBaseUrl = getCmuxApiBaseUrl();
1150
1718
  if (!orchestrationId) {
1151
1719
  return {
1152
1720
  content: [{
@@ -1483,7 +2051,7 @@ function createMemoryMcpServer(config) {
1483
2051
  case "list_spawned_agents": {
1484
2052
  const { status: filterStatus } = args;
1485
2053
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1486
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2054
+ const apiBaseUrl = getCmuxApiBaseUrl();
1487
2055
  const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
1488
2056
  if (!jwt) {
1489
2057
  return {
@@ -1542,7 +2110,7 @@ function createMemoryMcpServer(config) {
1542
2110
  case "cancel_agent": {
1543
2111
  const { orchestrationTaskId, cascade = false } = args;
1544
2112
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1545
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2113
+ const apiBaseUrl = getCmuxApiBaseUrl();
1546
2114
  if (!jwt) {
1547
2115
  return {
1548
2116
  content: [{
@@ -1610,7 +2178,7 @@ function createMemoryMcpServer(config) {
1610
2178
  }
1611
2179
  case "get_orchestration_summary": {
1612
2180
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1613
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2181
+ const apiBaseUrl = getCmuxApiBaseUrl();
1614
2182
  const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
1615
2183
  if (!jwt) {
1616
2184
  return {
@@ -1680,10 +2248,77 @@ function createMemoryMcpServer(config) {
1680
2248
  };
1681
2249
  }
1682
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
+ }
1683
2318
  case "bind_provider_session": {
1684
2319
  const { providerSessionId, providerThreadId, replyChannel } = args;
1685
2320
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1686
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2321
+ const apiBaseUrl = getCmuxApiBaseUrl();
1687
2322
  const orchestrationId = process.env.CMUX_ORCHESTRATION_ID;
1688
2323
  const taskRunId = process.env.CMUX_TASK_RUN_ID;
1689
2324
  if (!jwt) {
@@ -1746,7 +2381,7 @@ function createMemoryMcpServer(config) {
1746
2381
  case "get_provider_session": {
1747
2382
  const { taskId: providedTaskId } = args;
1748
2383
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1749
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2384
+ const apiBaseUrl = getCmuxApiBaseUrl();
1750
2385
  const taskId = providedTaskId ?? process.env.CMUX_TASK_RUN_ID;
1751
2386
  if (!jwt) {
1752
2387
  return {
@@ -1824,7 +2459,7 @@ function createMemoryMcpServer(config) {
1824
2459
  case "wait_for_events": {
1825
2460
  const { orchestrationId, eventTypes = [], timeout = 3e4 } = args;
1826
2461
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1827
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2462
+ const apiBaseUrl = getCmuxApiBaseUrl();
1828
2463
  if (!jwt) {
1829
2464
  return {
1830
2465
  content: [{
@@ -1939,7 +2574,7 @@ function createMemoryMcpServer(config) {
1939
2574
  case "get_pending_approvals": {
1940
2575
  const { orchestrationId: providedOrchId } = args;
1941
2576
  const jwt = process.env.CMUX_TASK_RUN_JWT;
1942
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2577
+ const apiBaseUrl = getCmuxApiBaseUrl();
1943
2578
  const orchestrationId = providedOrchId ?? process.env.CMUX_ORCHESTRATION_ID;
1944
2579
  if (!jwt) {
1945
2580
  return {
@@ -1999,7 +2634,7 @@ function createMemoryMcpServer(config) {
1999
2634
  case "resolve_approval": {
2000
2635
  const { requestId, resolution, note } = args;
2001
2636
  const jwt = process.env.CMUX_TASK_RUN_JWT;
2002
- const apiBaseUrl = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2637
+ const apiBaseUrl = getCmuxApiBaseUrl();
2003
2638
  if (!jwt) {
2004
2639
  return {
2005
2640
  content: [{
@@ -2231,7 +2866,7 @@ function createMemoryMcpServer(config) {
2231
2866
  }
2232
2867
  case "log_learning": {
2233
2868
  const jwt = process.env.CMUX_TASK_RUN_JWT;
2234
- const apiBase = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2869
+ const apiBase = getCmuxApiBaseUrl();
2235
2870
  if (!jwt) {
2236
2871
  return {
2237
2872
  content: [{
@@ -2291,7 +2926,7 @@ function createMemoryMcpServer(config) {
2291
2926
  }
2292
2927
  case "get_active_orchestration_rules": {
2293
2928
  const jwt = process.env.CMUX_TASK_RUN_JWT;
2294
- const apiBase = process.env.CMUX_API_BASE_URL ?? "https://cmux.sh";
2929
+ const apiBase = getCmuxApiBaseUrl();
2295
2930
  if (!jwt) {
2296
2931
  return {
2297
2932
  content: [{
@@ -2359,6 +2994,242 @@ function createMemoryMcpServer(config) {
2359
2994
  };
2360
2995
  }
2361
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
+ }
2362
3233
  default:
2363
3234
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
2364
3235
  }
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runServer
4
- } from "./chunk-4CUOHQV6.js";
4
+ } from "./chunk-R25AQROL.js";
5
5
 
6
6
  // src/cli.ts
7
7
  function parseArgs() {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createMemoryMcpServer,
3
3
  runServer
4
- } from "./chunk-4CUOHQV6.js";
4
+ } from "./chunk-R25AQROL.js";
5
5
  export {
6
6
  createMemoryMcpServer,
7
7
  runServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devsh-memory-mcp",
3
- "version": "0.3.9",
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",