@getmarrow/mcp 3.8.3 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1200 -1109
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -113,771 +113,861 @@ ${MARROW_BLOCK_END}`;
|
|
|
113
113
|
process.exit(0);
|
|
114
114
|
}
|
|
115
115
|
const cliArgs = parseArgs();
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
function formatKeyMaterialWarning() {
|
|
117
|
+
return 'Copy this key now. Marrow will only show the full plaintext key once.';
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else if (cliArgs.setup) {
|
|
123
|
-
runSetup();
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
119
|
+
// ─── Standalone CLI: key management ───
|
|
120
|
+
if (process.argv[2] === 'keys') {
|
|
121
|
+
const cmd = process.argv[3];
|
|
126
122
|
const API_KEY = cliArgs.apiKey || process.env.MARROW_API_KEY || '';
|
|
127
|
-
// [SECURITY #3] Validate BASE_URL — require HTTPS to prevent SSRF / credential leakage
|
|
128
|
-
const rawBaseUrl = process.env.MARROW_BASE_URL || 'https://api.getmarrow.ai';
|
|
129
|
-
const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
|
|
130
|
-
const SESSION_ID = process.env.MARROW_SESSION_ID || undefined;
|
|
131
|
-
const FLEET_AGENT_ID = process.env.MARROW_FLEET_AGENT_ID || undefined; // V5: agent UUID for X-Marrow-Agent-Id header
|
|
132
|
-
const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
|
|
133
|
-
const AGENT_ID = process.env.MARROW_AGENT_ID || `${require('os').hostname()}-${Date.now().toString(36)}`;
|
|
134
123
|
if (!API_KEY) {
|
|
135
|
-
process.stderr.write('Error: MARROW_API_KEY
|
|
136
|
-
process.stderr.write('Usage: MARROW_API_KEY=mrw_yourkey npx @getmarrow/mcp\n');
|
|
137
|
-
process.stderr.write(' or: npx @getmarrow/mcp --key mrw_yourkey\n');
|
|
124
|
+
process.stderr.write('Error: MARROW_API_KEY required. Use --key or set MARROW_API_KEY env var.\n');
|
|
138
125
|
process.exit(1);
|
|
139
126
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
127
|
+
const getFlag = (name, short) => {
|
|
128
|
+
const idx = process.argv.findIndex(a => a === `--${name}` || (short ? a === `-${short}` : false));
|
|
129
|
+
return idx >= 0 ? process.argv[idx + 1] : undefined;
|
|
130
|
+
};
|
|
131
|
+
const getFlagList = (name) => {
|
|
132
|
+
const val = getFlag(name);
|
|
133
|
+
return val ? val.split(',').map(s => s.trim()) : [];
|
|
134
|
+
};
|
|
135
|
+
const runCli = async () => {
|
|
136
|
+
try {
|
|
137
|
+
if (cmd === 'create') {
|
|
138
|
+
const name = getFlag('name', 'n');
|
|
139
|
+
if (!name) {
|
|
140
|
+
process.stderr.write('Error: --name required\n');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
const result = await (0, index_1.marrowCreateKey)(API_KEY, 'https://api.getmarrow.ai', {
|
|
144
|
+
name,
|
|
145
|
+
key_type: (getFlag('type', 't') || 'live'),
|
|
146
|
+
scopes: getFlagList('scopes'),
|
|
147
|
+
agent_ids: getFlagList('agents'),
|
|
148
|
+
expires_at: getFlag('expires'),
|
|
149
|
+
}, undefined, undefined);
|
|
150
|
+
process.stdout.write(JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) + '\n');
|
|
151
|
+
}
|
|
152
|
+
else if (cmd === 'list') {
|
|
153
|
+
const result = await (0, index_1.marrowListKeys)(API_KEY, 'https://api.getmarrow.ai', undefined, undefined);
|
|
154
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
155
|
+
}
|
|
156
|
+
else if (cmd === 'get') {
|
|
157
|
+
const id = getFlag('id', 'i') || process.argv[4];
|
|
158
|
+
if (!id) {
|
|
159
|
+
process.stderr.write('Error: --id required\n');
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
const result = await (0, index_1.marrowGetKey)(API_KEY, 'https://api.getmarrow.ai', id, undefined, undefined);
|
|
163
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
164
|
+
}
|
|
165
|
+
else if (cmd === 'rotate') {
|
|
166
|
+
const id = getFlag('id', 'i') || process.argv[4];
|
|
167
|
+
if (!id) {
|
|
168
|
+
process.stderr.write('Error: --id required\n');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
const result = await (0, index_1.marrowRotateKey)(API_KEY, 'https://api.getmarrow.ai', id, undefined, undefined);
|
|
172
|
+
process.stdout.write(JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) + '\n');
|
|
173
|
+
}
|
|
174
|
+
else if (cmd === 'revoke') {
|
|
175
|
+
const id = getFlag('id', 'i') || process.argv[4];
|
|
176
|
+
if (!id) {
|
|
177
|
+
process.stderr.write('Error: --id required\n');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
const result = await (0, index_1.marrowRevokeKey)(API_KEY, 'https://api.getmarrow.ai', id, undefined, undefined);
|
|
181
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
182
|
+
}
|
|
183
|
+
else if (cmd === 'audit') {
|
|
184
|
+
const limit = parseInt(getFlag('limit', 'l') || '20', 10);
|
|
185
|
+
const result = await (0, index_1.marrowGetKeyAudit)(API_KEY, 'https://api.getmarrow.ai', { limit }, undefined, undefined);
|
|
186
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
process.stderr.write(`Usage: npx @getmarrow/mcp keys <create|list|get|rotate|revoke|audit> [options]\n\n`);
|
|
190
|
+
process.stderr.write(` create --name <name> [--type live|test] [--scopes scope1,scope2] [--agents id1,id2] [--expires ISO]\n`);
|
|
191
|
+
process.stderr.write(` list\n`);
|
|
192
|
+
process.stderr.write(` get --id <key-id>\n`);
|
|
193
|
+
process.stderr.write(` rotate --id <key-id>\n`);
|
|
194
|
+
process.stderr.write(` revoke --id <key-id>\n`);
|
|
195
|
+
process.stderr.write(` audit [--limit <n>]\n`);
|
|
196
|
+
process.stderr.write(`\nOptions: --key <api-key>\n`);
|
|
197
|
+
process.exit(1);
|
|
166
198
|
}
|
|
167
199
|
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
process.stderr.write(`Error: ${e.message || e}\n`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
void runCli().then(() => process.exit(0));
|
|
206
|
+
}
|
|
207
|
+
// Only start MCP server if not handling a CLI command
|
|
208
|
+
if (process.argv[2] !== 'keys') {
|
|
209
|
+
if (cliArgs.hook) {
|
|
210
|
+
void (0, hook_1.runHookCommand)();
|
|
168
211
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
|
|
212
|
+
else if (cliArgs.contextHook) {
|
|
213
|
+
void (0, hook_context_1.runContextHookCommand)();
|
|
172
214
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
215
|
+
else if (cliArgs.setup) {
|
|
216
|
+
runSetup();
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
const API_KEY = cliArgs.apiKey || process.env.MARROW_API_KEY || '';
|
|
220
|
+
// [SECURITY #3] Validate BASE_URL — require HTTPS to prevent SSRF / credential leakage
|
|
221
|
+
const rawBaseUrl = process.env.MARROW_BASE_URL || 'https://api.getmarrow.ai';
|
|
222
|
+
const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
|
|
223
|
+
const SESSION_ID = process.env.MARROW_SESSION_ID || undefined;
|
|
224
|
+
const FLEET_AGENT_ID = process.env.MARROW_FLEET_AGENT_ID || undefined; // V5: agent UUID for X-Marrow-Agent-Id header
|
|
225
|
+
const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
|
|
226
|
+
const AGENT_ID = process.env.MARROW_AGENT_ID || `${require('os').hostname()}-${Date.now().toString(36)}`;
|
|
227
|
+
if (!API_KEY) {
|
|
228
|
+
process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
|
|
229
|
+
process.stderr.write('Usage: MARROW_API_KEY=mrw_yourkey npx @getmarrow/mcp\n');
|
|
230
|
+
process.stderr.write(' or: npx @getmarrow/mcp --key mrw_yourkey\n');
|
|
231
|
+
process.exit(1);
|
|
178
232
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
process.stderr.write(
|
|
233
|
+
// [SECURITY #12] Warn if API key is visible in process args
|
|
234
|
+
if (cliArgs.apiKey) {
|
|
235
|
+
process.stderr.write('[marrow] Warning: --key flag exposes API key in process list. Use MARROW_API_KEY env var for production.\n');
|
|
182
236
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
237
|
+
// Auto-orient on startup — cache warnings, inject into EVERY marrow_think response
|
|
238
|
+
let cachedOrientWarnings = [];
|
|
239
|
+
let thinkCallCount = 0;
|
|
240
|
+
let orientCallCount = 0;
|
|
241
|
+
let initialized = false;
|
|
242
|
+
const pendingDecisions = new Map();
|
|
243
|
+
const PENDING_TTL_MS = 30 * 60 * 1000; // 30 min TTL
|
|
244
|
+
function actionHash(action) {
|
|
245
|
+
const normalized = action.toLowerCase().trim().replace(/\s+/g, ' ');
|
|
246
|
+
let h = 5381;
|
|
247
|
+
for (let i = 0; i < normalized.length; i++) {
|
|
248
|
+
h = ((h << 5) + h) ^ normalized.charCodeAt(i);
|
|
249
|
+
h = h >>> 0;
|
|
250
|
+
}
|
|
251
|
+
return h.toString(36) + '_' + normalized.slice(0, 32);
|
|
188
252
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
253
|
+
// [FIX #11] Actually call cleanupPending to prevent unbounded map growth
|
|
254
|
+
function cleanupPending() {
|
|
255
|
+
const now = Date.now();
|
|
256
|
+
for (const [key, val] of pendingDecisions) {
|
|
257
|
+
if (now - val.timestamp > PENDING_TTL_MS) {
|
|
258
|
+
pendingDecisions.delete(key);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function formatWarningActionably(w) {
|
|
263
|
+
const pct = Math.round(w.failureRate * 100);
|
|
264
|
+
return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
|
|
265
|
+
}
|
|
266
|
+
// [FIX #4] Log orient refresh failures instead of silently ignoring
|
|
267
|
+
async function refreshOrientWarnings() {
|
|
196
268
|
try {
|
|
197
|
-
await (0, index_1.
|
|
198
|
-
|
|
199
|
-
success: false,
|
|
200
|
-
outcome: 'Session ended without explicit commit',
|
|
201
|
-
}, SESSION_ID);
|
|
269
|
+
const r = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
270
|
+
cachedOrientWarnings = r.warnings;
|
|
202
271
|
}
|
|
203
272
|
catch (err) {
|
|
204
273
|
const msg = err instanceof Error ? err.message : String(err);
|
|
205
|
-
process.stderr.write(`[marrow] Warning:
|
|
274
|
+
process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
|
|
206
275
|
}
|
|
207
276
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
277
|
+
// Initial orient
|
|
278
|
+
refreshOrientWarnings().then(() => {
|
|
279
|
+
if (cachedOrientWarnings.some((w) => w.failureRate > 0.4)) {
|
|
280
|
+
process.stderr.write(`[marrow] ⚠️ High failure rate detected on startup — call marrow_orient for details before acting\n`);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
// Auto-commit tracking for session close
|
|
284
|
+
let lastDecisionId = null;
|
|
285
|
+
let lastCommitted = false;
|
|
286
|
+
// [FIX #5] Log auto-commit failures instead of silently ignoring; remove broken AbortController
|
|
287
|
+
async function autoCommitOnClose() {
|
|
288
|
+
if (lastDecisionId && !lastCommitted) {
|
|
289
|
+
try {
|
|
290
|
+
await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
291
|
+
decision_id: lastDecisionId,
|
|
292
|
+
success: false,
|
|
293
|
+
outcome: 'Session ended without explicit commit',
|
|
294
|
+
}, SESSION_ID);
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
298
|
+
process.stderr.write(`[marrow] Warning: auto-commit on close failed: ${msg}\n`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
232
301
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
302
|
+
// [FIX #10] Handle both SIGTERM and SIGINT for clean shutdown
|
|
303
|
+
async function gracefulShutdown() {
|
|
304
|
+
const forceExit = setTimeout(() => process.exit(0), 5000);
|
|
305
|
+
forceExit.unref();
|
|
306
|
+
await autoCommitOnClose();
|
|
307
|
+
process.exit(0);
|
|
308
|
+
}
|
|
309
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
310
|
+
process.on('SIGINT', gracefulShutdown);
|
|
311
|
+
function send(response) {
|
|
312
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
313
|
+
}
|
|
314
|
+
function success(id, result) {
|
|
315
|
+
send({ jsonrpc: '2.0', id, result });
|
|
316
|
+
}
|
|
317
|
+
function error(id, code, message) {
|
|
318
|
+
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
319
|
+
}
|
|
320
|
+
// [FIX #9] Runtime validation helper for required string params
|
|
321
|
+
function requireString(args, name) {
|
|
322
|
+
const val = args[name];
|
|
323
|
+
if (typeof val !== 'string' || !val.trim()) {
|
|
324
|
+
throw new Error(`"${name}" is required and must be a non-empty string`);
|
|
244
325
|
}
|
|
245
|
-
|
|
246
|
-
throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
|
|
326
|
+
return val;
|
|
247
327
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
328
|
+
// [FIX #6 & #7] Safe JSON response helper for memory API functions
|
|
329
|
+
async function safeMemoryResponse(res) {
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
let detail = '';
|
|
332
|
+
try {
|
|
333
|
+
detail = await res.text();
|
|
334
|
+
}
|
|
335
|
+
catch { /* ignore */ }
|
|
336
|
+
throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
|
|
337
|
+
}
|
|
338
|
+
const json = await res.json();
|
|
339
|
+
if (json.error) {
|
|
340
|
+
throw new Error(json.error);
|
|
341
|
+
}
|
|
342
|
+
return json;
|
|
251
343
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
description: 'Enable active intervention: scans recent failures, returns HIGH/MEDIUM/LOW severity warnings with recommendations. Recommended: true.',
|
|
344
|
+
// Memory API functions — all patched with safeMemoryResponse and validatePathParam
|
|
345
|
+
async function marrowListMemories(apiKey, baseUrl, params, sessionId) {
|
|
346
|
+
const qs = new URLSearchParams();
|
|
347
|
+
if (params?.status)
|
|
348
|
+
qs.set('status', params.status);
|
|
349
|
+
if (params?.query)
|
|
350
|
+
qs.set('query', params.query);
|
|
351
|
+
if (params?.limit)
|
|
352
|
+
qs.set('limit', String(params.limit));
|
|
353
|
+
if (params?.agentId)
|
|
354
|
+
qs.set('agent_id', params.agentId);
|
|
355
|
+
const res = await fetch(`${baseUrl}/v1/memories?${qs.toString()}`, {
|
|
356
|
+
headers: {
|
|
357
|
+
Authorization: `Bearer ${apiKey}`,
|
|
358
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
const json = await safeMemoryResponse(res);
|
|
362
|
+
return json.data?.memories || [];
|
|
363
|
+
}
|
|
364
|
+
async function marrowGetMemory(apiKey, baseUrl, id, sessionId) {
|
|
365
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
366
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
367
|
+
headers: {
|
|
368
|
+
Authorization: `Bearer ${apiKey}`,
|
|
369
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
370
|
+
},
|
|
371
|
+
});
|
|
372
|
+
const json = await safeMemoryResponse(res);
|
|
373
|
+
return json.data?.memory || null;
|
|
374
|
+
}
|
|
375
|
+
async function marrowUpdateMemory(apiKey, baseUrl, id, patch, sessionId) {
|
|
376
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
377
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
378
|
+
method: 'PATCH',
|
|
379
|
+
headers: {
|
|
380
|
+
Authorization: `Bearer ${apiKey}`,
|
|
381
|
+
'Content-Type': 'application/json',
|
|
382
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
383
|
+
},
|
|
384
|
+
body: JSON.stringify(patch),
|
|
385
|
+
});
|
|
386
|
+
const json = await safeMemoryResponse(res);
|
|
387
|
+
return json.data?.memory;
|
|
388
|
+
}
|
|
389
|
+
async function marrowDeleteMemory(apiKey, baseUrl, id, meta, sessionId) {
|
|
390
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
391
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
392
|
+
method: 'DELETE',
|
|
393
|
+
headers: {
|
|
394
|
+
Authorization: `Bearer ${apiKey}`,
|
|
395
|
+
'Content-Type': 'application/json',
|
|
396
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
397
|
+
},
|
|
398
|
+
body: JSON.stringify(meta || {}),
|
|
399
|
+
});
|
|
400
|
+
const json = await safeMemoryResponse(res);
|
|
401
|
+
return json.data?.memory;
|
|
402
|
+
}
|
|
403
|
+
async function marrowMarkOutdated(apiKey, baseUrl, id, meta, sessionId) {
|
|
404
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
405
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/outdated`, {
|
|
406
|
+
method: 'POST',
|
|
407
|
+
headers: {
|
|
408
|
+
Authorization: `Bearer ${apiKey}`,
|
|
409
|
+
'Content-Type': 'application/json',
|
|
410
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
411
|
+
},
|
|
412
|
+
body: JSON.stringify(meta || {}),
|
|
413
|
+
});
|
|
414
|
+
const json = await safeMemoryResponse(res);
|
|
415
|
+
return json.data?.memory;
|
|
416
|
+
}
|
|
417
|
+
async function marrowSupersedeMemory(apiKey, baseUrl, id, replacement, sessionId) {
|
|
418
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
419
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/supersede`, {
|
|
420
|
+
method: 'POST',
|
|
421
|
+
headers: {
|
|
422
|
+
Authorization: `Bearer ${apiKey}`,
|
|
423
|
+
'Content-Type': 'application/json',
|
|
424
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
425
|
+
},
|
|
426
|
+
body: JSON.stringify(replacement),
|
|
427
|
+
});
|
|
428
|
+
const json = await safeMemoryResponse(res);
|
|
429
|
+
return json.data;
|
|
430
|
+
}
|
|
431
|
+
async function marrowShareMemory(apiKey, baseUrl, id, agentIds, actor, sessionId) {
|
|
432
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
433
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/share`, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: {
|
|
436
|
+
Authorization: `Bearer ${apiKey}`,
|
|
437
|
+
'Content-Type': 'application/json',
|
|
438
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
439
|
+
},
|
|
440
|
+
body: JSON.stringify({ agent_ids: agentIds, actor }),
|
|
441
|
+
});
|
|
442
|
+
const json = await safeMemoryResponse(res);
|
|
443
|
+
return json.data?.memory;
|
|
444
|
+
}
|
|
445
|
+
async function marrowExportMemories(apiKey, baseUrl, params, sessionId) {
|
|
446
|
+
const qs = new URLSearchParams();
|
|
447
|
+
if (params?.format)
|
|
448
|
+
qs.set('format', params.format);
|
|
449
|
+
if (params?.status)
|
|
450
|
+
qs.set('status', params.status);
|
|
451
|
+
if (params?.tags)
|
|
452
|
+
qs.set('tags', params.tags);
|
|
453
|
+
const res = await fetch(`${baseUrl}/v1/memories/export?${qs.toString()}`, {
|
|
454
|
+
headers: {
|
|
455
|
+
Authorization: `Bearer ${apiKey}`,
|
|
456
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
const json = await safeMemoryResponse(res);
|
|
460
|
+
return json.data;
|
|
461
|
+
}
|
|
462
|
+
async function marrowImportMemories(apiKey, baseUrl, memories, mode, sessionId) {
|
|
463
|
+
const res = await fetch(`${baseUrl}/v1/memories/import`, {
|
|
464
|
+
method: 'POST',
|
|
465
|
+
headers: {
|
|
466
|
+
Authorization: `Bearer ${apiKey}`,
|
|
467
|
+
'Content-Type': 'application/json',
|
|
468
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
469
|
+
},
|
|
470
|
+
body: JSON.stringify({ memories, mode }),
|
|
471
|
+
});
|
|
472
|
+
const json = await safeMemoryResponse(res);
|
|
473
|
+
return json.data;
|
|
474
|
+
}
|
|
475
|
+
async function marrowRetrieveMemories(apiKey, baseUrl, query, params, sessionId) {
|
|
476
|
+
const qs = new URLSearchParams();
|
|
477
|
+
qs.set('q', query);
|
|
478
|
+
if (params?.limit)
|
|
479
|
+
qs.set('limit', String(params.limit));
|
|
480
|
+
if (params?.from)
|
|
481
|
+
qs.set('from', params.from);
|
|
482
|
+
if (params?.to)
|
|
483
|
+
qs.set('to', params.to);
|
|
484
|
+
if (params?.tags)
|
|
485
|
+
qs.set('tags', params.tags);
|
|
486
|
+
if (params?.source)
|
|
487
|
+
qs.set('source', params.source);
|
|
488
|
+
if (params?.status)
|
|
489
|
+
qs.set('status', params.status);
|
|
490
|
+
if (params?.shared !== undefined)
|
|
491
|
+
qs.set('shared', String(params.shared));
|
|
492
|
+
const res = await fetch(`${baseUrl}/v1/memories/retrieve?${qs.toString()}`, {
|
|
493
|
+
headers: {
|
|
494
|
+
Authorization: `Bearer ${apiKey}`,
|
|
495
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
const json = await safeMemoryResponse(res);
|
|
499
|
+
return json.data;
|
|
500
|
+
}
|
|
501
|
+
// Tool definitions (unchanged)
|
|
502
|
+
const TOOLS = [
|
|
503
|
+
{
|
|
504
|
+
name: 'marrow_orient',
|
|
505
|
+
description: '⚡ CALL THIS FIRST — every session, before any other tool. ' +
|
|
506
|
+
'Returns failure warnings from your history so you avoid known mistakes immediately. ' +
|
|
507
|
+
'If shouldPause=true, review lessons before acting. ' +
|
|
508
|
+
'orient() reads from the hive. think() writes to it. Both are required — this is what makes Marrow compound.',
|
|
509
|
+
inputSchema: {
|
|
510
|
+
type: 'object',
|
|
511
|
+
properties: {
|
|
512
|
+
taskType: {
|
|
513
|
+
type: 'string',
|
|
514
|
+
enum: ['implementation', 'security', 'architecture', 'process', 'general'],
|
|
515
|
+
description: 'Optional: filter warnings to a specific task type you are about to perform',
|
|
516
|
+
},
|
|
517
|
+
autoWarn: {
|
|
518
|
+
type: 'boolean',
|
|
519
|
+
description: 'Enable active intervention: scans recent failures, returns HIGH/MEDIUM/LOW severity warnings with recommendations. Recommended: true.',
|
|
520
|
+
},
|
|
430
521
|
},
|
|
522
|
+
required: [],
|
|
431
523
|
},
|
|
432
|
-
required: [],
|
|
433
524
|
},
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
525
|
+
{
|
|
526
|
+
name: 'marrow_think',
|
|
527
|
+
description: 'Log intent and get collective intelligence before acting. ' +
|
|
528
|
+
'Call this before every meaningful action. ' +
|
|
529
|
+
'Returns pattern insights, similar past decisions, failure detection, and a recommendedNext field — follow it. ' +
|
|
530
|
+
'Pass previous_outcome to auto-commit the last decision and open a new one. ' +
|
|
531
|
+
'Response MAY include: onboarding_hint (new accounts), intelligence.collective (cross-account patterns), intelligence.team_context (recent decisions from other sessions).',
|
|
532
|
+
inputSchema: {
|
|
533
|
+
type: 'object',
|
|
534
|
+
properties: {
|
|
535
|
+
action: { type: 'string', description: 'What the agent is about to do' },
|
|
536
|
+
type: {
|
|
537
|
+
type: 'string',
|
|
538
|
+
enum: ['implementation', 'security', 'architecture', 'process', 'general'],
|
|
539
|
+
description: 'Type of action (default: general)',
|
|
540
|
+
},
|
|
541
|
+
context: { type: 'object', description: 'Optional metadata about the current situation' },
|
|
542
|
+
previous_decision_id: { type: 'string', description: 'decision_id from previous think() call — auto-commits that session' },
|
|
543
|
+
previous_success: { type: 'boolean', description: 'Did the previous action succeed?' },
|
|
544
|
+
previous_outcome: { type: 'string', description: 'What happened in the previous action (required if previous_decision_id provided)' },
|
|
545
|
+
checkLoop: { type: 'boolean', description: 'Enable loop detection: warns if you are about to retry a failed approach. Recommended: true.' },
|
|
450
546
|
},
|
|
451
|
-
|
|
452
|
-
previous_decision_id: { type: 'string', description: 'decision_id from previous think() call — auto-commits that session' },
|
|
453
|
-
previous_success: { type: 'boolean', description: 'Did the previous action succeed?' },
|
|
454
|
-
previous_outcome: { type: 'string', description: 'What happened in the previous action (required if previous_decision_id provided)' },
|
|
455
|
-
checkLoop: { type: 'boolean', description: 'Enable loop detection: warns if you are about to retry a failed approach. Recommended: true.' },
|
|
547
|
+
required: ['action'],
|
|
456
548
|
},
|
|
457
|
-
required: ['action'],
|
|
458
549
|
},
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
550
|
+
{
|
|
551
|
+
name: 'marrow_commit',
|
|
552
|
+
description: 'Explicitly commit the result of an action to Marrow. ' +
|
|
553
|
+
'Optional — marrow_think() auto-commits if you pass previous_outcome. ' +
|
|
554
|
+
'Use when you need explicit control over commit timing.',
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: 'object',
|
|
557
|
+
properties: {
|
|
558
|
+
decision_id: { type: 'string', description: 'decision_id from the marrow_think call' },
|
|
559
|
+
success: { type: 'boolean', description: 'Did the action succeed?' },
|
|
560
|
+
outcome: { type: 'string', description: 'What happened — be specific, this trains the hive' },
|
|
561
|
+
caused_by: { type: 'string', description: 'Optional: what caused this action' },
|
|
562
|
+
},
|
|
563
|
+
required: ['decision_id', 'success', 'outcome'],
|
|
472
564
|
},
|
|
473
|
-
required: ['decision_id', 'success', 'outcome'],
|
|
474
565
|
},
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
566
|
+
{
|
|
567
|
+
name: 'marrow_run',
|
|
568
|
+
description: 'Zero-ceremony memory logging. Single call handles orient → think → commit automatically. ' +
|
|
569
|
+
'Use this instead of chaining marrow_think + marrow_commit when you want Marrow to just work without managing the loop yourself.',
|
|
570
|
+
inputSchema: {
|
|
571
|
+
type: 'object',
|
|
572
|
+
properties: {
|
|
573
|
+
description: { type: 'string', description: 'What the agent did' },
|
|
574
|
+
success: { type: 'boolean', description: 'Whether it succeeded' },
|
|
575
|
+
outcome: { type: 'string', description: 'One-line summary of what happened' },
|
|
576
|
+
type: {
|
|
577
|
+
type: 'string',
|
|
578
|
+
enum: ['implementation', 'security', 'architecture', 'process', 'general'],
|
|
579
|
+
description: 'Type of action (default: general)',
|
|
580
|
+
},
|
|
490
581
|
},
|
|
582
|
+
required: ['description', 'success', 'outcome'],
|
|
491
583
|
},
|
|
492
|
-
required: ['description', 'success', 'outcome'],
|
|
493
584
|
},
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
585
|
+
{
|
|
586
|
+
name: 'marrow_auto',
|
|
587
|
+
description: 'Zero-friction Marrow logging. One call for any action — Marrow handles everything in the background without blocking. ' +
|
|
588
|
+
'Pass what you are about to do. Optionally pass outcome if already done. ' +
|
|
589
|
+
'Use for ANY action: deploys, file writes, API calls, external sends. ' +
|
|
590
|
+
'If you only have time for one call: pass action + outcome + success together — done in one shot.',
|
|
591
|
+
inputSchema: {
|
|
592
|
+
type: 'object',
|
|
593
|
+
properties: {
|
|
594
|
+
action: { type: 'string', description: 'What you are about to do or just did' },
|
|
595
|
+
outcome: { type: 'string', description: 'What happened (if already done). Omit to log intent only.' },
|
|
596
|
+
success: { type: 'boolean', description: 'Did it succeed (default: true)' },
|
|
597
|
+
type: {
|
|
598
|
+
type: 'string',
|
|
599
|
+
enum: ['implementation', 'security', 'architecture', 'process', 'general'],
|
|
600
|
+
description: 'Type of action (default: general)',
|
|
601
|
+
},
|
|
511
602
|
},
|
|
603
|
+
required: ['action'],
|
|
512
604
|
},
|
|
513
|
-
required: ['action'],
|
|
514
605
|
},
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
606
|
+
{
|
|
607
|
+
name: 'marrow_ask',
|
|
608
|
+
description: 'Query the collective hive in plain English. ' +
|
|
609
|
+
'Ask about failure patterns, what worked, what broke, or get a recommendation before acting. ' +
|
|
610
|
+
'Returns direct answer + supporting evidence.',
|
|
611
|
+
inputSchema: {
|
|
612
|
+
type: 'object',
|
|
613
|
+
properties: {
|
|
614
|
+
query: { type: 'string', description: 'Plain English question about your decision history' },
|
|
615
|
+
},
|
|
616
|
+
required: ['query'],
|
|
525
617
|
},
|
|
526
|
-
required: ['query'],
|
|
527
618
|
},
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
619
|
+
{
|
|
620
|
+
name: 'marrow_status',
|
|
621
|
+
description: 'Check Marrow platform health and status.',
|
|
622
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: 'marrow_create_key',
|
|
626
|
+
description: 'Create a new API key. Full plaintext key is returned once — copy it now.',
|
|
627
|
+
inputSchema: {
|
|
628
|
+
type: 'object',
|
|
629
|
+
properties: {
|
|
630
|
+
name: { type: 'string', description: 'Human-readable key name' },
|
|
631
|
+
key_type: { type: 'string', enum: ['live', 'test'], description: 'Key type (default: live)' },
|
|
632
|
+
scopes: { type: 'array', items: { type: 'string' }, description: 'Allowed scopes' },
|
|
633
|
+
agent_ids: { type: 'array', items: { type: 'string' }, description: 'Optional agent bindings' },
|
|
634
|
+
expires_at: { type: 'string', description: 'Optional ISO-8601 expiry' },
|
|
635
|
+
},
|
|
636
|
+
required: ['name'],
|
|
545
637
|
},
|
|
546
|
-
required: ['name'],
|
|
547
638
|
},
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
639
|
+
{
|
|
640
|
+
name: 'marrow_list_keys',
|
|
641
|
+
description: 'List API keys. Keys are masked here by design.',
|
|
642
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
name: 'marrow_get_key',
|
|
646
|
+
description: 'Get a single API key by ID. The key value is masked after creation.',
|
|
647
|
+
inputSchema: {
|
|
648
|
+
type: 'object',
|
|
649
|
+
properties: {
|
|
650
|
+
id: { type: 'string', description: 'API key ID' },
|
|
651
|
+
},
|
|
652
|
+
required: ['id'],
|
|
561
653
|
},
|
|
562
|
-
required: ['id'],
|
|
563
654
|
},
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
655
|
+
{
|
|
656
|
+
name: 'marrow_revoke_key',
|
|
657
|
+
description: 'Revoke an API key by ID.',
|
|
658
|
+
inputSchema: {
|
|
659
|
+
type: 'object',
|
|
660
|
+
properties: {
|
|
661
|
+
id: { type: 'string', description: 'API key ID' },
|
|
662
|
+
},
|
|
663
|
+
required: ['id'],
|
|
572
664
|
},
|
|
573
|
-
required: ['id'],
|
|
574
665
|
},
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
666
|
+
{
|
|
667
|
+
name: 'marrow_rotate_key',
|
|
668
|
+
description: 'Rotate an API key by ID. Full plaintext key is returned once — copy it now.',
|
|
669
|
+
inputSchema: {
|
|
670
|
+
type: 'object',
|
|
671
|
+
properties: {
|
|
672
|
+
id: { type: 'string', description: 'API key ID' },
|
|
673
|
+
},
|
|
674
|
+
required: ['id'],
|
|
583
675
|
},
|
|
584
|
-
required: ['id'],
|
|
585
676
|
},
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
677
|
+
{
|
|
678
|
+
name: 'marrow_list_memories',
|
|
679
|
+
description: 'List memories with optional filters (status, query, limit, agent_id for shared memories).',
|
|
680
|
+
inputSchema: {
|
|
681
|
+
type: 'object',
|
|
682
|
+
properties: {
|
|
683
|
+
status: { type: 'string', enum: ['active', 'outdated', 'deleted'], description: 'Filter by status' },
|
|
684
|
+
query: { type: 'string', description: 'Search query' },
|
|
685
|
+
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
686
|
+
agentId: { type: 'string', description: 'Agent ID for shared memories' },
|
|
687
|
+
},
|
|
688
|
+
required: [],
|
|
597
689
|
},
|
|
598
|
-
required: [],
|
|
599
690
|
},
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
691
|
+
{
|
|
692
|
+
name: 'marrow_get_memory',
|
|
693
|
+
description: 'Get a single memory by ID.',
|
|
694
|
+
inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'Memory ID' } }, required: ['id'] },
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
name: 'marrow_update_memory',
|
|
698
|
+
description: 'Update memory text, tags, or metadata.',
|
|
699
|
+
inputSchema: {
|
|
700
|
+
type: 'object',
|
|
701
|
+
properties: {
|
|
702
|
+
id: { type: 'string', description: 'Memory ID' },
|
|
703
|
+
text: { type: 'string', description: 'New text' },
|
|
704
|
+
source: { type: 'string', description: 'Source' },
|
|
705
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
|
|
706
|
+
actor: { type: 'string', description: 'Actor name' },
|
|
707
|
+
note: { type: 'string', description: 'Audit note' },
|
|
708
|
+
},
|
|
709
|
+
required: ['id'],
|
|
618
710
|
},
|
|
619
|
-
required: ['id'],
|
|
620
711
|
},
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
712
|
+
{
|
|
713
|
+
name: 'marrow_delete_memory',
|
|
714
|
+
description: 'Soft delete a memory.',
|
|
715
|
+
inputSchema: {
|
|
716
|
+
type: 'object',
|
|
717
|
+
properties: {
|
|
718
|
+
id: { type: 'string', description: 'Memory ID' },
|
|
719
|
+
actor: { type: 'string', description: 'Actor name' },
|
|
720
|
+
note: { type: 'string', description: 'Audit note' },
|
|
721
|
+
},
|
|
722
|
+
required: ['id'],
|
|
631
723
|
},
|
|
632
|
-
required: ['id'],
|
|
633
724
|
},
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
725
|
+
{
|
|
726
|
+
name: 'marrow_mark_outdated',
|
|
727
|
+
description: 'Mark a memory as outdated.',
|
|
728
|
+
inputSchema: {
|
|
729
|
+
type: 'object',
|
|
730
|
+
properties: {
|
|
731
|
+
id: { type: 'string', description: 'Memory ID' },
|
|
732
|
+
actor: { type: 'string', description: 'Actor name' },
|
|
733
|
+
note: { type: 'string', description: 'Audit note' },
|
|
734
|
+
},
|
|
735
|
+
required: ['id'],
|
|
644
736
|
},
|
|
645
|
-
required: ['id'],
|
|
646
737
|
},
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
738
|
+
{
|
|
739
|
+
name: 'marrow_supersede_memory',
|
|
740
|
+
description: 'Atomically replace a memory with a new version.',
|
|
741
|
+
inputSchema: {
|
|
742
|
+
type: 'object',
|
|
743
|
+
properties: {
|
|
744
|
+
id: { type: 'string', description: 'Memory ID to supersede' },
|
|
745
|
+
text: { type: 'string', description: 'New memory text' },
|
|
746
|
+
source: { type: 'string', description: 'Source' },
|
|
747
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
|
|
748
|
+
actor: { type: 'string', description: 'Actor name' },
|
|
749
|
+
note: { type: 'string', description: 'Audit note' },
|
|
750
|
+
},
|
|
751
|
+
required: ['id', 'text'],
|
|
660
752
|
},
|
|
661
|
-
required: ['id', 'text'],
|
|
662
753
|
},
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
754
|
+
{
|
|
755
|
+
name: 'marrow_share_memory',
|
|
756
|
+
description: 'Share a memory with specific agents.',
|
|
757
|
+
inputSchema: {
|
|
758
|
+
type: 'object',
|
|
759
|
+
properties: {
|
|
760
|
+
id: { type: 'string', description: 'Memory ID' },
|
|
761
|
+
agentIds: { type: 'array', items: { type: 'string' }, description: 'Agent IDs to share with' },
|
|
762
|
+
actor: { type: 'string', description: 'Actor name' },
|
|
763
|
+
},
|
|
764
|
+
required: ['id', 'agentIds'],
|
|
673
765
|
},
|
|
674
|
-
required: ['id', 'agentIds'],
|
|
675
766
|
},
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
767
|
+
{
|
|
768
|
+
name: 'marrow_export_memories',
|
|
769
|
+
description: 'Export memories to JSON or CSV.',
|
|
770
|
+
inputSchema: {
|
|
771
|
+
type: 'object',
|
|
772
|
+
properties: {
|
|
773
|
+
format: { type: 'string', enum: ['json', 'csv'], description: 'Export format' },
|
|
774
|
+
status: { type: 'string', enum: ['active', 'all'], description: 'Filter by status' },
|
|
775
|
+
tags: { type: 'string', description: 'Comma-separated tags' },
|
|
776
|
+
},
|
|
777
|
+
required: [],
|
|
686
778
|
},
|
|
687
|
-
required: [],
|
|
688
779
|
},
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
780
|
+
{
|
|
781
|
+
name: 'marrow_import_memories',
|
|
782
|
+
description: 'Import memories with merge (dedup) or replace mode.',
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: 'object',
|
|
785
|
+
properties: {
|
|
786
|
+
memories: { type: 'array', items: { type: 'object', properties: { text: { type: 'string' }, source: { type: 'string' }, tags: { type: 'array', items: { type: 'string' } } } }, description: 'Memories to import' },
|
|
787
|
+
mode: { type: 'string', enum: ['merge', 'replace'], description: 'Import mode' },
|
|
788
|
+
},
|
|
789
|
+
required: ['memories', 'mode'],
|
|
698
790
|
},
|
|
699
|
-
required: ['memories', 'mode'],
|
|
700
791
|
},
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
792
|
+
{
|
|
793
|
+
name: 'marrow_retrieve_memories',
|
|
794
|
+
description: 'Full-text search memories with filters (from, to, tags, source, status, shared).',
|
|
795
|
+
inputSchema: {
|
|
796
|
+
type: 'object',
|
|
797
|
+
properties: {
|
|
798
|
+
query: { type: 'string', description: 'Search query' },
|
|
799
|
+
limit: { type: 'number', description: 'Max results' },
|
|
800
|
+
from: { type: 'string', description: 'From date (ISO-8601)' },
|
|
801
|
+
to: { type: 'string', description: 'To date (ISO-8601)' },
|
|
802
|
+
tags: { type: 'string', description: 'Comma-separated tags' },
|
|
803
|
+
source: { type: 'string', description: 'Source filter' },
|
|
804
|
+
status: { type: 'string', enum: ['active', 'outdated', 'deleted'], description: 'Status filter' },
|
|
805
|
+
shared: { type: 'boolean', description: 'Include shared memories' },
|
|
806
|
+
},
|
|
807
|
+
required: ['query'],
|
|
716
808
|
},
|
|
717
|
-
required: ['query'],
|
|
718
809
|
},
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
810
|
+
{
|
|
811
|
+
name: 'marrow_workflow',
|
|
812
|
+
description: 'Interact with Marrow Workflow Registry. Register, start, and advance multi-step workflows. ' +
|
|
813
|
+
'Actions: register (create workflow template), list (show all), get (details), start (begin instance), ' +
|
|
814
|
+
'advance (complete a step), instances (list runs).',
|
|
815
|
+
inputSchema: {
|
|
816
|
+
type: 'object',
|
|
817
|
+
properties: {
|
|
818
|
+
action: { type: 'string', enum: ['register', 'list', 'get', 'update', 'start', 'advance', 'instances'], description: 'Workflow action to perform' },
|
|
819
|
+
workflowId: { type: 'string', description: 'Workflow ID (required for get/start/advance/instances)' },
|
|
820
|
+
instanceId: { type: 'string', description: 'Instance ID (required for advance)' },
|
|
821
|
+
name: { type: 'string', description: 'Workflow name (for register)' },
|
|
822
|
+
description: { type: 'string', description: 'Workflow description (for register/update)' },
|
|
823
|
+
steps: { type: 'array', description: 'Step definitions (for register)', items: { type: 'object', properties: { step: { type: 'number', description: 'Step order (1, 2, 3...)' }, agent_role: { type: 'string', description: 'Expected agent role (e.g., "builder", "auditor")' }, action_type: { type: 'string', description: 'Action type (e.g., "build", "audit", "patch")' }, description: { type: 'string', description: 'Step description' } }, required: ['step', 'description'] } },
|
|
824
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Tags (for register)' },
|
|
825
|
+
agentId: { type: 'string', description: 'Agent ID starting the workflow (for start)' },
|
|
826
|
+
context: { type: 'object', description: 'Workflow context (for start)' },
|
|
827
|
+
inputs: { type: 'object', description: 'Workflow inputs (for start)' },
|
|
828
|
+
stepCompleted: { type: 'number', description: 'Step number completed (for advance)' },
|
|
829
|
+
outcome: { type: 'string', description: 'Step outcome (for advance)' },
|
|
830
|
+
nextAgentId: { type: 'string', description: 'Next agent for the following step (for advance)' },
|
|
831
|
+
contextUpdate: { type: 'object', description: 'Context changes (for advance)' },
|
|
832
|
+
status: { type: 'string', enum: ['running', 'completed', 'failed', 'cancelled', 'active', 'archived'], description: 'Filter by status (for list/instances)' },
|
|
833
|
+
},
|
|
834
|
+
required: ['action'],
|
|
743
835
|
},
|
|
744
|
-
required: ['action'],
|
|
745
836
|
},
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
837
|
+
{
|
|
838
|
+
name: 'marrow_dashboard',
|
|
839
|
+
description: 'Get operator dashboard — account health, top failures, workflow status, recent activity, Marrow\'s saves metric. ' +
|
|
840
|
+
'One call returns everything an operator needs to see.',
|
|
841
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
name: 'marrow_digest',
|
|
845
|
+
description: 'Get periodic summary of agent activity and Marrow impact (default 7-day period). ' +
|
|
846
|
+
'Shows decision counts, success rate trend vs previous period, saves, top improvements and risks.',
|
|
847
|
+
inputSchema: {
|
|
848
|
+
type: 'object',
|
|
849
|
+
properties: {
|
|
850
|
+
period: { type: 'string', description: 'Time period: 7d (default), 14d, or 30d' },
|
|
851
|
+
},
|
|
852
|
+
required: [],
|
|
761
853
|
},
|
|
762
|
-
required: [],
|
|
763
854
|
},
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
855
|
+
{
|
|
856
|
+
name: 'marrow_session_end',
|
|
857
|
+
description: 'Explicitly end the current session. Optionally auto-commits any open decision. ' +
|
|
858
|
+
'Prevents orphaned decisions when an agent finishes a task.',
|
|
859
|
+
inputSchema: {
|
|
860
|
+
type: 'object',
|
|
861
|
+
properties: {
|
|
862
|
+
autoCommitOpen: { type: 'boolean', description: 'Whether to auto-commit any open decision (default: false)' },
|
|
863
|
+
},
|
|
864
|
+
required: [],
|
|
773
865
|
},
|
|
774
|
-
required: [],
|
|
775
866
|
},
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
867
|
+
{
|
|
868
|
+
name: 'marrow_accept_detected',
|
|
869
|
+
description: 'Convert a detected decision pattern into an enforced workflow. ' +
|
|
870
|
+
'The pattern ID comes from suggested_workflows in the orient() response.',
|
|
871
|
+
inputSchema: {
|
|
872
|
+
type: 'object',
|
|
873
|
+
properties: {
|
|
874
|
+
detectedId: { type: 'string', description: 'ID of the detected pattern to accept' },
|
|
875
|
+
},
|
|
876
|
+
required: ['detectedId'],
|
|
785
877
|
},
|
|
786
|
-
required: ['detectedId'],
|
|
787
878
|
},
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
879
|
+
{
|
|
880
|
+
name: 'marrow_list_templates',
|
|
881
|
+
description: 'Browse pre-built workflow templates. Filter by industry (insurance, healthcare, ecommerce, legal, saas, fintech, media, enterprise) or category. ' +
|
|
882
|
+
'Use to discover available workflows before installing.',
|
|
883
|
+
inputSchema: {
|
|
884
|
+
type: 'object',
|
|
885
|
+
properties: {
|
|
886
|
+
industry: { type: 'string', description: 'Filter by industry (e.g., insurance, healthcare, saas)' },
|
|
887
|
+
category: { type: 'string', description: 'Filter by category (e.g., claims, engineering, support)' },
|
|
888
|
+
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
889
|
+
},
|
|
890
|
+
required: [],
|
|
799
891
|
},
|
|
800
|
-
required: [],
|
|
801
892
|
},
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
893
|
+
{
|
|
894
|
+
name: 'marrow_install_template',
|
|
895
|
+
description: 'Install a workflow template into your fleet as an active workflow. ' +
|
|
896
|
+
'Use after marrow_list_templates to pick one.',
|
|
897
|
+
inputSchema: {
|
|
898
|
+
type: 'object',
|
|
899
|
+
properties: {
|
|
900
|
+
slug: { type: 'string', description: 'Template slug to install (e.g., code-review-deploy, claims-triage)' },
|
|
901
|
+
},
|
|
902
|
+
required: ['slug'],
|
|
811
903
|
},
|
|
812
|
-
required: ['slug'],
|
|
813
904
|
},
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
error(id, -32002, 'Server not initialized. Send initialize first.');
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
try {
|
|
825
|
-
if (method === 'initialize') {
|
|
826
|
-
initialized = true;
|
|
827
|
-
success(id, {
|
|
828
|
-
protocolVersion: '2024-11-05',
|
|
829
|
-
capabilities: { tools: {}, prompts: {} },
|
|
830
|
-
serverInfo: { name: 'marrow', version: '3.8.0' },
|
|
831
|
-
});
|
|
832
|
-
// Auto-enroll: emit enrollment notification on connection
|
|
833
|
-
if (AUTO_ENROLL) {
|
|
834
|
-
send({
|
|
835
|
-
jsonrpc: '2.0',
|
|
836
|
-
method: 'notifications/message',
|
|
837
|
-
params: {
|
|
838
|
-
level: 'info',
|
|
839
|
-
logger: 'marrow',
|
|
840
|
-
data: {
|
|
841
|
-
type: 'auto_enroll',
|
|
842
|
-
message: 'Marrow auto-enroll active. Call marrow_orient FIRST, then marrow_think before acting, marrow_commit after. Or use marrow_auto / marrow_run for one-call logging.',
|
|
843
|
-
agentId: AGENT_ID || 'auto',
|
|
844
|
-
},
|
|
845
|
-
},
|
|
846
|
-
});
|
|
847
|
-
}
|
|
905
|
+
];
|
|
906
|
+
// Request handler
|
|
907
|
+
async function handleRequest(req) {
|
|
908
|
+
const { id, method, params } = req;
|
|
909
|
+
// [FIX #15] Enforce initialize-first per MCP spec
|
|
910
|
+
if (!initialized && method !== 'initialize') {
|
|
911
|
+
error(id, -32002, 'Server not initialized. Send initialize first.');
|
|
848
912
|
return;
|
|
849
913
|
}
|
|
850
|
-
|
|
851
|
-
if (
|
|
914
|
+
try {
|
|
915
|
+
if (method === 'initialize') {
|
|
916
|
+
initialized = true;
|
|
852
917
|
success(id, {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
description: 'Always-on Marrow memory loop. Instructs the agent to orient at session start, log intent before meaningful actions, and commit outcomes after completion. Install once — works automatically.',
|
|
857
|
-
arguments: [],
|
|
858
|
-
},
|
|
859
|
-
],
|
|
918
|
+
protocolVersion: '2024-11-05',
|
|
919
|
+
capabilities: { tools: {}, prompts: {} },
|
|
920
|
+
serverInfo: { name: 'marrow', version: '3.8.0' },
|
|
860
921
|
});
|
|
922
|
+
// Auto-enroll: emit enrollment notification on connection
|
|
923
|
+
if (AUTO_ENROLL) {
|
|
924
|
+
send({
|
|
925
|
+
jsonrpc: '2.0',
|
|
926
|
+
method: 'notifications/message',
|
|
927
|
+
params: {
|
|
928
|
+
level: 'info',
|
|
929
|
+
logger: 'marrow',
|
|
930
|
+
data: {
|
|
931
|
+
type: 'auto_enroll',
|
|
932
|
+
message: 'Marrow auto-enroll active. Call marrow_orient FIRST, then marrow_think before acting, marrow_commit after. Or use marrow_auto / marrow_run for one-call logging.',
|
|
933
|
+
agentId: AGENT_ID || 'auto',
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
return;
|
|
861
939
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
940
|
+
if (method === 'prompts/list') {
|
|
941
|
+
if (AUTO_ENROLL) {
|
|
942
|
+
success(id, {
|
|
943
|
+
prompts: [
|
|
944
|
+
{
|
|
945
|
+
name: 'marrow-always-on',
|
|
946
|
+
description: 'Always-on Marrow memory loop. Instructs the agent to orient at session start, log intent before meaningful actions, and commit outcomes after completion. Install once — works automatically.',
|
|
947
|
+
arguments: [],
|
|
948
|
+
},
|
|
949
|
+
],
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
else {
|
|
953
|
+
success(id, { prompts: [] });
|
|
954
|
+
}
|
|
871
955
|
return;
|
|
872
956
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
957
|
+
if (method === 'prompts/get') {
|
|
958
|
+
const promptName = params?.name;
|
|
959
|
+
if (promptName !== 'marrow-always-on' || !AUTO_ENROLL) {
|
|
960
|
+
error(id, -32602, 'Unknown prompt');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
success(id, {
|
|
964
|
+
description: 'Always-on Marrow memory loop — automatic, zero-config',
|
|
965
|
+
messages: [
|
|
966
|
+
{
|
|
967
|
+
role: 'user',
|
|
968
|
+
content: {
|
|
969
|
+
type: 'text',
|
|
970
|
+
text: `You have Marrow — a memory layer that tells you what went wrong last time before you do it again.
|
|
881
971
|
|
|
882
972
|
## PRIMARY: Use marrow_auto for everything
|
|
883
973
|
|
|
@@ -943,468 +1033,469 @@ Every decision you log compounds. Failure rates drop. Patterns emerge. The next
|
|
|
943
1033
|
Marrow tells you what went wrong last time before you do it again. But only if you logged it.
|
|
944
1034
|
|
|
945
1035
|
This is not optional overhead — it's how you stop repeating the same failures.`,
|
|
1036
|
+
},
|
|
946
1037
|
},
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1038
|
+
],
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (method === 'tools/list') {
|
|
1043
|
+
success(id, { tools: TOOLS });
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (method === 'tools/call') {
|
|
1047
|
+
const toolName = params?.name;
|
|
1048
|
+
const args = (params?.arguments || {});
|
|
1049
|
+
if (toolName === 'marrow_orient') {
|
|
1050
|
+
orientCallCount++;
|
|
1051
|
+
let result;
|
|
1052
|
+
const wantAutoWarn = args.autoWarn ?? true;
|
|
1053
|
+
try {
|
|
1054
|
+
result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: wantAutoWarn }, SESSION_ID);
|
|
1055
|
+
}
|
|
1056
|
+
catch (e) {
|
|
1057
|
+
// autoWarn endpoint may not be deployed yet — fall back to legacy orient
|
|
1058
|
+
if (wantAutoWarn) {
|
|
1059
|
+
process.stderr.write(`[marrow] autoWarn orient not available, falling back to legacy\n`);
|
|
1060
|
+
result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: false }, SESSION_ID);
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
throw e;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
if (AUTO_ENROLL && orientCallCount === 1) {
|
|
1067
|
+
const enrollmentText = `\n\n📋 **Marrow Auto-Enroll Active**\n\n` +
|
|
1068
|
+
`You have Marrow — a collective memory that tells you what went wrong last time before you do it again.\n\n` +
|
|
1069
|
+
`## How it works (2 tools max):\n\n` +
|
|
1070
|
+
`1. **Session start:** Call marrow_orient FIRST. You just did — it surfaces failure warnings.\n` +
|
|
1071
|
+
`2. **Before any action:** Call marrow_think({ action: "..." }) — returns pattern insights + recommendedNext.\n` +
|
|
1072
|
+
`3. **After acting:** Call marrow_commit({ decision_id, success, outcome }) — or pass previous_outcome to auto-commit.\n\n` +
|
|
1073
|
+
`## Quick mode (one tool):\n` +
|
|
1074
|
+
`- marrow_run({ description, success, outcome }) — handles orient → think → commit in one call.\n` +
|
|
1075
|
+
`- marrow_auto({ action, outcome?, success? }) — fire-and-forget background logging.\n\n` +
|
|
1076
|
+
`## Why you must not skip this:\n` +
|
|
1077
|
+
`Every decision you log compounds. Failure rates drop. The next session starts smarter.\n` +
|
|
1078
|
+
`Marrow tells you what went wrong last time — but only if you logged it.\n`;
|
|
1079
|
+
const orientText = JSON.stringify(result, null, 2);
|
|
1080
|
+
success(id, {
|
|
1081
|
+
content: [{ type: 'text', text: enrollmentText + orientText }],
|
|
1082
|
+
});
|
|
971
1083
|
}
|
|
972
1084
|
else {
|
|
973
|
-
|
|
1085
|
+
success(id, {
|
|
1086
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1087
|
+
});
|
|
974
1088
|
}
|
|
1089
|
+
return;
|
|
975
1090
|
}
|
|
976
|
-
if (
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
|
|
1091
|
+
if (toolName === 'marrow_think') {
|
|
1092
|
+
// [FIX #9] Validate required param
|
|
1093
|
+
const action = requireString(args, 'action');
|
|
1094
|
+
const result = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1095
|
+
action,
|
|
1096
|
+
type: args.type,
|
|
1097
|
+
context: args.context,
|
|
1098
|
+
previous_decision_id: args.previous_decision_id,
|
|
1099
|
+
previous_success: args.previous_success,
|
|
1100
|
+
previous_outcome: args.previous_outcome,
|
|
1101
|
+
checkLoop: args.checkLoop ?? true,
|
|
1102
|
+
}, SESSION_ID);
|
|
1103
|
+
// Refresh orient warnings every 5th think call
|
|
1104
|
+
thinkCallCount++;
|
|
1105
|
+
if (thinkCallCount % 5 === 0) {
|
|
1106
|
+
refreshOrientWarnings();
|
|
1107
|
+
}
|
|
1108
|
+
// Inject cached orient warnings into intelligence.insights
|
|
1109
|
+
if (cachedOrientWarnings.length > 0) {
|
|
1110
|
+
const existingInsights = result.intelligence?.insights || [];
|
|
1111
|
+
result.intelligence.insights = [
|
|
1112
|
+
...cachedOrientWarnings.map((w) => ({
|
|
1113
|
+
type: 'failure_pattern',
|
|
1114
|
+
summary: w.message,
|
|
1115
|
+
action: `Review past ${w.type} failures before proceeding`,
|
|
1116
|
+
severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
|
|
1117
|
+
count: 0,
|
|
1118
|
+
})),
|
|
1119
|
+
...existingInsights,
|
|
1120
|
+
];
|
|
1121
|
+
}
|
|
1122
|
+
lastDecisionId = result.decision_id;
|
|
1123
|
+
lastCommitted = false;
|
|
995
1124
|
success(id, {
|
|
996
1125
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
997
1126
|
});
|
|
1127
|
+
return;
|
|
998
1128
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
previous_success: args.previous_success,
|
|
1010
|
-
previous_outcome: args.previous_outcome,
|
|
1011
|
-
checkLoop: args.checkLoop ?? true,
|
|
1012
|
-
}, SESSION_ID);
|
|
1013
|
-
// Refresh orient warnings every 5th think call
|
|
1014
|
-
thinkCallCount++;
|
|
1015
|
-
if (thinkCallCount % 5 === 0) {
|
|
1016
|
-
refreshOrientWarnings();
|
|
1017
|
-
}
|
|
1018
|
-
// Inject cached orient warnings into intelligence.insights
|
|
1019
|
-
if (cachedOrientWarnings.length > 0) {
|
|
1020
|
-
const existingInsights = result.intelligence?.insights || [];
|
|
1021
|
-
result.intelligence.insights = [
|
|
1022
|
-
...cachedOrientWarnings.map((w) => ({
|
|
1023
|
-
type: 'failure_pattern',
|
|
1024
|
-
summary: w.message,
|
|
1025
|
-
action: `Review past ${w.type} failures before proceeding`,
|
|
1026
|
-
severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
|
|
1027
|
-
count: 0,
|
|
1028
|
-
})),
|
|
1029
|
-
...existingInsights,
|
|
1030
|
-
];
|
|
1031
|
-
}
|
|
1032
|
-
lastDecisionId = result.decision_id;
|
|
1033
|
-
lastCommitted = false;
|
|
1034
|
-
success(id, {
|
|
1035
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1036
|
-
});
|
|
1037
|
-
return;
|
|
1038
|
-
}
|
|
1039
|
-
if (toolName === 'marrow_commit') {
|
|
1040
|
-
// [FIX #9] Validate required params
|
|
1041
|
-
const decision_id = requireString(args, 'decision_id');
|
|
1042
|
-
const outcome = requireString(args, 'outcome');
|
|
1043
|
-
if (typeof args.success !== 'boolean') {
|
|
1044
|
-
throw new Error('"success" is required and must be a boolean');
|
|
1045
|
-
}
|
|
1046
|
-
const result = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1047
|
-
decision_id,
|
|
1048
|
-
success: args.success,
|
|
1049
|
-
outcome,
|
|
1050
|
-
caused_by: args.caused_by,
|
|
1051
|
-
}, SESSION_ID);
|
|
1052
|
-
const commitResult = { ...result, narrative: result.narrative ?? null };
|
|
1053
|
-
lastCommitted = true;
|
|
1054
|
-
lastDecisionId = null;
|
|
1055
|
-
success(id, {
|
|
1056
|
-
content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
|
|
1057
|
-
});
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
if (toolName === 'marrow_run') {
|
|
1061
|
-
// [FIX #9] Validate required params
|
|
1062
|
-
const description = requireString(args, 'description');
|
|
1063
|
-
const outcome = requireString(args, 'outcome');
|
|
1064
|
-
// [FIX #16] Handle partial failures — return think result even if commit fails
|
|
1065
|
-
let thinkResult = null;
|
|
1066
|
-
try {
|
|
1067
|
-
await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
1068
|
-
}
|
|
1069
|
-
catch (err) {
|
|
1070
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1071
|
-
process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
|
|
1072
|
-
}
|
|
1073
|
-
thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1074
|
-
action: description,
|
|
1075
|
-
type: args.type || 'general',
|
|
1076
|
-
}, SESSION_ID);
|
|
1077
|
-
let commitResult = null;
|
|
1078
|
-
try {
|
|
1079
|
-
commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1080
|
-
decision_id: thinkResult.decision_id,
|
|
1081
|
-
success: args.success ?? true,
|
|
1129
|
+
if (toolName === 'marrow_commit') {
|
|
1130
|
+
// [FIX #9] Validate required params
|
|
1131
|
+
const decision_id = requireString(args, 'decision_id');
|
|
1132
|
+
const outcome = requireString(args, 'outcome');
|
|
1133
|
+
if (typeof args.success !== 'boolean') {
|
|
1134
|
+
throw new Error('"success" is required and must be a boolean');
|
|
1135
|
+
}
|
|
1136
|
+
const result = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1137
|
+
decision_id,
|
|
1138
|
+
success: args.success,
|
|
1082
1139
|
outcome,
|
|
1140
|
+
caused_by: args.caused_by,
|
|
1083
1141
|
}, SESSION_ID);
|
|
1142
|
+
const commitResult = { ...result, narrative: result.narrative ?? null };
|
|
1143
|
+
lastCommitted = true;
|
|
1144
|
+
lastDecisionId = null;
|
|
1145
|
+
success(id, {
|
|
1146
|
+
content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
|
|
1147
|
+
});
|
|
1148
|
+
return;
|
|
1084
1149
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1150
|
+
if (toolName === 'marrow_run') {
|
|
1151
|
+
// [FIX #9] Validate required params
|
|
1152
|
+
const description = requireString(args, 'description');
|
|
1153
|
+
const outcome = requireString(args, 'outcome');
|
|
1154
|
+
// [FIX #16] Handle partial failures — return think result even if commit fails
|
|
1155
|
+
let thinkResult = null;
|
|
1156
|
+
try {
|
|
1157
|
+
await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
1158
|
+
}
|
|
1159
|
+
catch (err) {
|
|
1160
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1161
|
+
process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
|
|
1162
|
+
}
|
|
1163
|
+
thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1164
|
+
action: description,
|
|
1165
|
+
type: args.type || 'general',
|
|
1166
|
+
}, SESSION_ID);
|
|
1167
|
+
let commitResult = null;
|
|
1168
|
+
try {
|
|
1169
|
+
commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1170
|
+
decision_id: thinkResult.decision_id,
|
|
1171
|
+
success: args.success ?? true,
|
|
1172
|
+
outcome,
|
|
1173
|
+
}, SESSION_ID);
|
|
1174
|
+
}
|
|
1175
|
+
catch (err) {
|
|
1176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1177
|
+
process.stderr.write(`[marrow] marrow_run commit failed: ${msg}\n`);
|
|
1178
|
+
success(id, {
|
|
1179
|
+
content: [{
|
|
1180
|
+
type: 'text',
|
|
1181
|
+
text: JSON.stringify({
|
|
1182
|
+
think: thinkResult,
|
|
1183
|
+
commit: null,
|
|
1184
|
+
commit_error: msg,
|
|
1185
|
+
decision_id: thinkResult.decision_id,
|
|
1186
|
+
}, null, 2),
|
|
1187
|
+
}],
|
|
1188
|
+
});
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1088
1191
|
success(id, {
|
|
1089
1192
|
content: [{
|
|
1090
1193
|
type: 'text',
|
|
1091
|
-
text: JSON.stringify({
|
|
1092
|
-
think: thinkResult,
|
|
1093
|
-
commit: null,
|
|
1094
|
-
commit_error: msg,
|
|
1095
|
-
decision_id: thinkResult.decision_id,
|
|
1096
|
-
}, null, 2),
|
|
1194
|
+
text: JSON.stringify({ think: thinkResult, commit: commitResult }, null, 2),
|
|
1097
1195
|
}],
|
|
1098
1196
|
});
|
|
1099
1197
|
return;
|
|
1100
1198
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
try {
|
|
1127
|
-
if (!outcome) {
|
|
1128
|
-
await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
|
|
1199
|
+
if (toolName === 'marrow_auto') {
|
|
1200
|
+
// [FIX #9] Validate required param
|
|
1201
|
+
const action = requireString(args, 'action');
|
|
1202
|
+
const outcome = args.outcome;
|
|
1203
|
+
const outcomeSuccess = args.success ?? true;
|
|
1204
|
+
const type = args.type || 'general';
|
|
1205
|
+
// [FIX #11] Cleanup pending decisions on each auto call
|
|
1206
|
+
cleanupPending();
|
|
1207
|
+
// [FIX #8] Include pending flag so agent knows logging is deferred
|
|
1208
|
+
const response = {
|
|
1209
|
+
action,
|
|
1210
|
+
outcome: outcome || 'pending',
|
|
1211
|
+
warnings: cachedOrientWarnings.map(formatWarningActionably),
|
|
1212
|
+
logging: 'deferred',
|
|
1213
|
+
};
|
|
1214
|
+
// Fire-and-forget the actual API calls
|
|
1215
|
+
(async () => {
|
|
1216
|
+
try {
|
|
1217
|
+
if (!outcome) {
|
|
1218
|
+
await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
const thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
|
|
1222
|
+
await (0, index_1.marrowCommit)(API_KEY, BASE_URL, { decision_id: thinkResult.decision_id, success: outcomeSuccess, outcome }, SESSION_ID);
|
|
1223
|
+
}
|
|
1129
1224
|
}
|
|
1130
|
-
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1225
|
+
catch (err) {
|
|
1226
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1227
|
+
process.stderr.write(`[marrow] marrow_auto background logging failed: ${msg}\n`);
|
|
1133
1228
|
}
|
|
1229
|
+
})();
|
|
1230
|
+
success(id, {
|
|
1231
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
1232
|
+
});
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (toolName === 'marrow_ask') {
|
|
1236
|
+
const query = requireString(args, 'query');
|
|
1237
|
+
const result = await (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID);
|
|
1238
|
+
success(id, {
|
|
1239
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1240
|
+
});
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (toolName === 'marrow_status') {
|
|
1244
|
+
const result = await (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1245
|
+
success(id, {
|
|
1246
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1247
|
+
});
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
if (toolName === 'marrow_create_key') {
|
|
1251
|
+
const name = requireString(args, 'name');
|
|
1252
|
+
const result = await (0, index_1.marrowCreateKey)(API_KEY, BASE_URL, {
|
|
1253
|
+
name,
|
|
1254
|
+
key_type: args.key_type,
|
|
1255
|
+
scopes: args.scopes,
|
|
1256
|
+
agent_ids: args.agent_ids,
|
|
1257
|
+
expires_at: args.expires_at,
|
|
1258
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1259
|
+
success(id, {
|
|
1260
|
+
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
if (toolName === 'marrow_list_keys') {
|
|
1265
|
+
const result = await (0, index_1.marrowListKeys)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1266
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (toolName === 'marrow_get_key') {
|
|
1270
|
+
const keyId = requireString(args, 'id');
|
|
1271
|
+
const result = await (0, index_1.marrowGetKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1272
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
if (toolName === 'marrow_revoke_key') {
|
|
1276
|
+
const keyId = requireString(args, 'id');
|
|
1277
|
+
const result = await (0, index_1.marrowRevokeKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1278
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
if (toolName === 'marrow_rotate_key') {
|
|
1282
|
+
const keyId = requireString(args, 'id');
|
|
1283
|
+
const result = await (0, index_1.marrowRotateKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1284
|
+
success(id, {
|
|
1285
|
+
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1286
|
+
});
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
// Memory control tools — all use requireString for id validation
|
|
1290
|
+
if (toolName === 'marrow_list_memories') {
|
|
1291
|
+
const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
|
|
1292
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
if (toolName === 'marrow_get_memory') {
|
|
1296
|
+
const memId = requireString(args, 'id');
|
|
1297
|
+
const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
|
|
1298
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
if (toolName === 'marrow_update_memory') {
|
|
1302
|
+
const memId = requireString(args, 'id');
|
|
1303
|
+
const result = await marrowUpdateMemory(API_KEY, BASE_URL, memId, { text: args.text, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
|
|
1304
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
if (toolName === 'marrow_delete_memory') {
|
|
1308
|
+
const memId = requireString(args, 'id');
|
|
1309
|
+
const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1310
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
if (toolName === 'marrow_mark_outdated') {
|
|
1314
|
+
const memId = requireString(args, 'id');
|
|
1315
|
+
const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1316
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (toolName === 'marrow_supersede_memory') {
|
|
1320
|
+
const memId = requireString(args, 'id');
|
|
1321
|
+
const newText = requireString(args, 'text');
|
|
1322
|
+
const result = await marrowSupersedeMemory(API_KEY, BASE_URL, memId, { text: newText, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
|
|
1323
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
if (toolName === 'marrow_share_memory') {
|
|
1327
|
+
const memId = requireString(args, 'id');
|
|
1328
|
+
const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
|
|
1329
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
if (toolName === 'marrow_export_memories') {
|
|
1333
|
+
const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
|
|
1334
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
if (toolName === 'marrow_import_memories') {
|
|
1338
|
+
const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
|
|
1339
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
if (toolName === 'marrow_retrieve_memories') {
|
|
1343
|
+
const query = requireString(args, 'query');
|
|
1344
|
+
const result = await marrowRetrieveMemories(API_KEY, BASE_URL, query, { limit: args.limit, from: args.from, to: args.to, tags: args.tags, source: args.source, status: args.status, shared: args.shared }, SESSION_ID);
|
|
1345
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
if (toolName === 'marrow_workflow') {
|
|
1349
|
+
const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
|
|
1350
|
+
action: args.action,
|
|
1351
|
+
workflowId: args.workflowId,
|
|
1352
|
+
instanceId: args.instanceId,
|
|
1353
|
+
name: args.name,
|
|
1354
|
+
description: args.description,
|
|
1355
|
+
steps: args.steps,
|
|
1356
|
+
tags: args.tags,
|
|
1357
|
+
agentId: args.agentId,
|
|
1358
|
+
context: args.context,
|
|
1359
|
+
inputs: args.inputs,
|
|
1360
|
+
stepCompleted: args.stepCompleted,
|
|
1361
|
+
outcome: args.outcome,
|
|
1362
|
+
nextAgentId: args.nextAgentId,
|
|
1363
|
+
contextUpdate: args.contextUpdate,
|
|
1364
|
+
status: args.status,
|
|
1365
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1366
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (toolName === 'marrow_dashboard') {
|
|
1370
|
+
const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1371
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
if (toolName === 'marrow_digest') {
|
|
1375
|
+
const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
|
|
1376
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (toolName === 'marrow_session_end') {
|
|
1380
|
+
const result = await (0, index_1.marrowSessionEnd)(API_KEY, BASE_URL, Boolean(args.autoCommitOpen), SESSION_ID, FLEET_AGENT_ID);
|
|
1381
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (toolName === 'marrow_accept_detected') {
|
|
1385
|
+
const detectedId = args.detectedId;
|
|
1386
|
+
if (!detectedId) {
|
|
1387
|
+
error(id, -32602, 'detectedId is required');
|
|
1388
|
+
return;
|
|
1134
1389
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
process.stderr.write(`[marrow] marrow_auto background logging failed: ${msg}\n`);
|
|
1138
|
-
}
|
|
1139
|
-
})();
|
|
1140
|
-
success(id, {
|
|
1141
|
-
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
1142
|
-
});
|
|
1143
|
-
return;
|
|
1144
|
-
}
|
|
1145
|
-
if (toolName === 'marrow_ask') {
|
|
1146
|
-
const query = requireString(args, 'query');
|
|
1147
|
-
const result = await (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID);
|
|
1148
|
-
success(id, {
|
|
1149
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1150
|
-
});
|
|
1151
|
-
return;
|
|
1152
|
-
}
|
|
1153
|
-
if (toolName === 'marrow_status') {
|
|
1154
|
-
const result = await (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1155
|
-
success(id, {
|
|
1156
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1157
|
-
});
|
|
1158
|
-
return;
|
|
1159
|
-
}
|
|
1160
|
-
if (toolName === 'marrow_create_key') {
|
|
1161
|
-
const name = requireString(args, 'name');
|
|
1162
|
-
const result = await (0, index_1.marrowCreateKey)(API_KEY, BASE_URL, {
|
|
1163
|
-
name,
|
|
1164
|
-
key_type: args.key_type,
|
|
1165
|
-
scopes: args.scopes,
|
|
1166
|
-
agent_ids: args.agent_ids,
|
|
1167
|
-
expires_at: args.expires_at,
|
|
1168
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1169
|
-
success(id, {
|
|
1170
|
-
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1171
|
-
});
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
if (toolName === 'marrow_list_keys') {
|
|
1175
|
-
const result = await (0, index_1.marrowListKeys)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1176
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
if (toolName === 'marrow_get_key') {
|
|
1180
|
-
const keyId = requireString(args, 'id');
|
|
1181
|
-
const result = await (0, index_1.marrowGetKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1182
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
if (toolName === 'marrow_revoke_key') {
|
|
1186
|
-
const keyId = requireString(args, 'id');
|
|
1187
|
-
const result = await (0, index_1.marrowRevokeKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1188
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
if (toolName === 'marrow_rotate_key') {
|
|
1192
|
-
const keyId = requireString(args, 'id');
|
|
1193
|
-
const result = await (0, index_1.marrowRotateKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1194
|
-
success(id, {
|
|
1195
|
-
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1196
|
-
});
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
// Memory control tools — all use requireString for id validation
|
|
1200
|
-
if (toolName === 'marrow_list_memories') {
|
|
1201
|
-
const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
|
|
1202
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1203
|
-
return;
|
|
1204
|
-
}
|
|
1205
|
-
if (toolName === 'marrow_get_memory') {
|
|
1206
|
-
const memId = requireString(args, 'id');
|
|
1207
|
-
const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
|
|
1208
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
if (toolName === 'marrow_update_memory') {
|
|
1212
|
-
const memId = requireString(args, 'id');
|
|
1213
|
-
const result = await marrowUpdateMemory(API_KEY, BASE_URL, memId, { text: args.text, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
|
|
1214
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1215
|
-
return;
|
|
1216
|
-
}
|
|
1217
|
-
if (toolName === 'marrow_delete_memory') {
|
|
1218
|
-
const memId = requireString(args, 'id');
|
|
1219
|
-
const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1220
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1221
|
-
return;
|
|
1222
|
-
}
|
|
1223
|
-
if (toolName === 'marrow_mark_outdated') {
|
|
1224
|
-
const memId = requireString(args, 'id');
|
|
1225
|
-
const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1226
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
if (toolName === 'marrow_supersede_memory') {
|
|
1230
|
-
const memId = requireString(args, 'id');
|
|
1231
|
-
const newText = requireString(args, 'text');
|
|
1232
|
-
const result = await marrowSupersedeMemory(API_KEY, BASE_URL, memId, { text: newText, source: args.source, tags: args.tags, actor: args.actor, note: args.note }, SESSION_ID);
|
|
1233
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1234
|
-
return;
|
|
1235
|
-
}
|
|
1236
|
-
if (toolName === 'marrow_share_memory') {
|
|
1237
|
-
const memId = requireString(args, 'id');
|
|
1238
|
-
const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
|
|
1239
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
if (toolName === 'marrow_export_memories') {
|
|
1243
|
-
const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
|
|
1244
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1245
|
-
return;
|
|
1246
|
-
}
|
|
1247
|
-
if (toolName === 'marrow_import_memories') {
|
|
1248
|
-
const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
|
|
1249
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
if (toolName === 'marrow_retrieve_memories') {
|
|
1253
|
-
const query = requireString(args, 'query');
|
|
1254
|
-
const result = await marrowRetrieveMemories(API_KEY, BASE_URL, query, { limit: args.limit, from: args.from, to: args.to, tags: args.tags, source: args.source, status: args.status, shared: args.shared }, SESSION_ID);
|
|
1255
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1256
|
-
return;
|
|
1257
|
-
}
|
|
1258
|
-
if (toolName === 'marrow_workflow') {
|
|
1259
|
-
const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
|
|
1260
|
-
action: args.action,
|
|
1261
|
-
workflowId: args.workflowId,
|
|
1262
|
-
instanceId: args.instanceId,
|
|
1263
|
-
name: args.name,
|
|
1264
|
-
description: args.description,
|
|
1265
|
-
steps: args.steps,
|
|
1266
|
-
tags: args.tags,
|
|
1267
|
-
agentId: args.agentId,
|
|
1268
|
-
context: args.context,
|
|
1269
|
-
inputs: args.inputs,
|
|
1270
|
-
stepCompleted: args.stepCompleted,
|
|
1271
|
-
outcome: args.outcome,
|
|
1272
|
-
nextAgentId: args.nextAgentId,
|
|
1273
|
-
contextUpdate: args.contextUpdate,
|
|
1274
|
-
status: args.status,
|
|
1275
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1276
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
if (toolName === 'marrow_dashboard') {
|
|
1280
|
-
const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1281
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1282
|
-
return;
|
|
1283
|
-
}
|
|
1284
|
-
if (toolName === 'marrow_digest') {
|
|
1285
|
-
const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
|
|
1286
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
if (toolName === 'marrow_session_end') {
|
|
1290
|
-
const result = await (0, index_1.marrowSessionEnd)(API_KEY, BASE_URL, Boolean(args.autoCommitOpen), SESSION_ID, FLEET_AGENT_ID);
|
|
1291
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1292
|
-
return;
|
|
1293
|
-
}
|
|
1294
|
-
if (toolName === 'marrow_accept_detected') {
|
|
1295
|
-
const detectedId = args.detectedId;
|
|
1296
|
-
if (!detectedId) {
|
|
1297
|
-
error(id, -32602, 'detectedId is required');
|
|
1390
|
+
const result = await (0, index_1.marrowAcceptDetected)(API_KEY, BASE_URL, detectedId, SESSION_ID, FLEET_AGENT_ID);
|
|
1391
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1298
1392
|
return;
|
|
1299
1393
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1394
|
+
if (toolName === 'marrow_list_templates') {
|
|
1395
|
+
const result = await (0, index_1.marrowListTemplates)(API_KEY, BASE_URL, {
|
|
1396
|
+
industry: args.industry,
|
|
1397
|
+
category: args.category,
|
|
1398
|
+
limit: args.limit,
|
|
1399
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1400
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
if (toolName === 'marrow_install_template') {
|
|
1404
|
+
const slug = args.slug;
|
|
1405
|
+
if (!slug) {
|
|
1406
|
+
error(id, -32602, 'slug is required');
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const result = await (0, index_1.marrowInstallTemplate)(API_KEY, BASE_URL, slug, SESSION_ID, FLEET_AGENT_ID);
|
|
1410
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1317
1411
|
return;
|
|
1318
1412
|
}
|
|
1319
|
-
|
|
1320
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1413
|
+
error(id, -32601, `Method not found: ${toolName}`);
|
|
1321
1414
|
return;
|
|
1322
1415
|
}
|
|
1323
|
-
error(id, -32601, `Method not found: ${
|
|
1324
|
-
return;
|
|
1416
|
+
error(id, -32601, `Method not found: ${method}`);
|
|
1325
1417
|
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1330
|
-
error(id, -32000, message);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
// MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)
|
|
1334
|
-
let buffer = '';
|
|
1335
|
-
let pendingRequests = 0;
|
|
1336
|
-
let stdinEnded = false;
|
|
1337
|
-
function checkExit() {
|
|
1338
|
-
if (stdinEnded && pendingRequests === 0) {
|
|
1339
|
-
autoCommitOnClose().then(() => process.exit(0));
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
process.stdin.setEncoding('utf8');
|
|
1343
|
-
process.stdin.on('data', (chunk) => {
|
|
1344
|
-
buffer += chunk;
|
|
1345
|
-
const lines = buffer.split('\n');
|
|
1346
|
-
buffer = lines.pop() || ''; // keep incomplete line in buffer
|
|
1347
|
-
for (const line of lines) {
|
|
1348
|
-
const trimmed = line.trim();
|
|
1349
|
-
if (!trimmed)
|
|
1350
|
-
continue;
|
|
1351
|
-
// [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
|
|
1352
|
-
let msg;
|
|
1353
|
-
try {
|
|
1354
|
-
msg = JSON.parse(trimmed);
|
|
1418
|
+
catch (err) {
|
|
1419
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1420
|
+
error(id, -32000, message);
|
|
1355
1421
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1422
|
+
}
|
|
1423
|
+
// MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)
|
|
1424
|
+
let buffer = '';
|
|
1425
|
+
let pendingRequests = 0;
|
|
1426
|
+
let stdinEnded = false;
|
|
1427
|
+
function checkExit() {
|
|
1428
|
+
if (stdinEnded && pendingRequests === 0) {
|
|
1429
|
+
autoCommitOnClose().then(() => process.exit(0));
|
|
1360
1430
|
}
|
|
1361
|
-
// MCP notifications (no id) must be silently ignored per spec
|
|
1362
|
-
if (msg.id === undefined || msg.id === null)
|
|
1363
|
-
continue;
|
|
1364
|
-
pendingRequests++;
|
|
1365
|
-
handleRequest(msg)
|
|
1366
|
-
.catch((err) => {
|
|
1367
|
-
process.stderr.write(`[marrow] Handler error: ${err}\n`);
|
|
1368
|
-
})
|
|
1369
|
-
.finally(() => {
|
|
1370
|
-
pendingRequests--;
|
|
1371
|
-
checkExit();
|
|
1372
|
-
});
|
|
1373
1431
|
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1432
|
+
process.stdin.setEncoding('utf8');
|
|
1433
|
+
process.stdin.on('data', (chunk) => {
|
|
1434
|
+
buffer += chunk;
|
|
1435
|
+
const lines = buffer.split('\n');
|
|
1436
|
+
buffer = lines.pop() || ''; // keep incomplete line in buffer
|
|
1437
|
+
for (const line of lines) {
|
|
1438
|
+
const trimmed = line.trim();
|
|
1439
|
+
if (!trimmed)
|
|
1440
|
+
continue;
|
|
1441
|
+
// [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
|
|
1442
|
+
let msg;
|
|
1443
|
+
try {
|
|
1444
|
+
msg = JSON.parse(trimmed);
|
|
1445
|
+
}
|
|
1446
|
+
catch (parseErr) {
|
|
1447
|
+
process.stderr.write(`[marrow] JSON parse error: ${parseErr}\n`);
|
|
1448
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
// MCP notifications (no id) must be silently ignored per spec
|
|
1452
|
+
if (msg.id === undefined || msg.id === null)
|
|
1453
|
+
continue;
|
|
1454
|
+
pendingRequests++;
|
|
1455
|
+
handleRequest(msg)
|
|
1456
|
+
.catch((err) => {
|
|
1457
|
+
process.stderr.write(`[marrow] Handler error: ${err}\n`);
|
|
1458
|
+
})
|
|
1459
|
+
.finally(() => {
|
|
1460
|
+
pendingRequests--;
|
|
1461
|
+
checkExit();
|
|
1462
|
+
});
|
|
1381
1463
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1464
|
+
});
|
|
1465
|
+
process.stdin.on('end', () => {
|
|
1466
|
+
stdinEnded = true;
|
|
1467
|
+
if (buffer.trim()) {
|
|
1468
|
+
let msg;
|
|
1469
|
+
try {
|
|
1470
|
+
msg = JSON.parse(buffer.trim());
|
|
1471
|
+
}
|
|
1472
|
+
catch (err) {
|
|
1473
|
+
process.stderr.write(`[marrow] JSON parse error on remaining buffer: ${err}\n`);
|
|
1474
|
+
checkExit();
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
if (msg.id === undefined || msg.id === null) {
|
|
1478
|
+
checkExit();
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
pendingRequests++;
|
|
1482
|
+
handleRequest(msg)
|
|
1483
|
+
.catch((err) => {
|
|
1484
|
+
process.stderr.write(`[marrow] Handler error on remaining: ${err}\n`);
|
|
1485
|
+
})
|
|
1486
|
+
.finally(() => {
|
|
1487
|
+
pendingRequests--;
|
|
1488
|
+
checkExit();
|
|
1489
|
+
});
|
|
1386
1490
|
}
|
|
1387
|
-
|
|
1491
|
+
else {
|
|
1388
1492
|
checkExit();
|
|
1389
|
-
return;
|
|
1390
1493
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
pendingRequests--;
|
|
1398
|
-
checkExit();
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
else {
|
|
1402
|
-
checkExit();
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
process.stdin.on('error', (err) => {
|
|
1406
|
-
process.stderr.write(`[marrow] stdin error: ${err}\n`);
|
|
1407
|
-
process.exit(1);
|
|
1408
|
-
});
|
|
1494
|
+
});
|
|
1495
|
+
process.stdin.on('error', (err) => {
|
|
1496
|
+
process.stderr.write(`[marrow] stdin error: ${err}\n`);
|
|
1497
|
+
process.exit(1);
|
|
1498
|
+
});
|
|
1499
|
+
} // Close the if (process.argv[2] !== 'keys') block
|
|
1409
1500
|
}
|
|
1410
1501
|
//# sourceMappingURL=cli.js.map
|