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