@getmarrow/mcp 3.9.102 → 3.9.106
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 +71 -24
- package/dist/approval-reply.d.ts +2 -0
- package/dist/approval-reply.d.ts.map +1 -0
- package/dist/approval-reply.js +18 -0
- package/dist/approval-reply.js.map +1 -0
- package/dist/cli.js +1716 -1644
- package/dist/cli.js.map +1 -1
- package/dist/codex-plugin-pins.d.ts +19 -0
- package/dist/codex-plugin-pins.d.ts.map +1 -0
- package/dist/codex-plugin-pins.js +61 -0
- package/dist/codex-plugin-pins.js.map +1 -0
- package/dist/codex-plugins.d.ts +34 -0
- package/dist/codex-plugins.d.ts.map +1 -0
- package/dist/codex-plugins.js +240 -0
- package/dist/codex-plugins.js.map +1 -0
- package/dist/env.d.ts +8 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +83 -4
- package/dist/env.js.map +1 -1
- package/dist/hook-context.d.ts.map +1 -1
- package/dist/hook-context.js +25 -0
- package/dist/hook-context.js.map +1 -1
- package/dist/hook-contract.d.ts +4 -3
- package/dist/hook-contract.d.ts.map +1 -1
- package/dist/hook-contract.js +43 -5
- package/dist/hook-contract.js.map +1 -1
- package/dist/hook-pre-action.d.ts +80 -3
- package/dist/hook-pre-action.d.ts.map +1 -1
- package/dist/hook-pre-action.js +435 -29
- package/dist/hook-pre-action.js.map +1 -1
- package/dist/hook-tool-policy.d.ts +73 -4
- package/dist/hook-tool-policy.d.ts.map +1 -1
- package/dist/hook-tool-policy.js +287 -18
- package/dist/hook-tool-policy.js.map +1 -1
- package/dist/hook.d.ts +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +15 -7
- package/dist/hook.js.map +1 -1
- package/dist/host-approval-state.d.ts +7 -0
- package/dist/host-approval-state.d.ts.map +1 -1
- package/dist/host-approval-state.js +15 -0
- package/dist/host-approval-state.js.map +1 -1
- package/dist/host-approval.d.ts +22 -1
- package/dist/host-approval.d.ts.map +1 -1
- package/dist/host-approval.js +65 -4
- package/dist/host-approval.js.map +1 -1
- package/dist/host-session.d.ts +11 -0
- package/dist/host-session.d.ts.map +1 -1
- package/dist/host-session.js +49 -0
- package/dist/host-session.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -11
- package/dist/index.js.map +1 -1
- package/dist/keyless-server.d.ts +53 -0
- package/dist/keyless-server.d.ts.map +1 -0
- package/dist/keyless-server.js +230 -0
- package/dist/keyless-server.js.map +1 -0
- package/dist/normalized-action.d.ts +31 -6
- package/dist/normalized-action.d.ts.map +1 -1
- package/dist/normalized-action.js +550 -105
- package/dist/normalized-action.js.map +1 -1
- package/dist/own-server-identity.d.ts +142 -0
- package/dist/own-server-identity.d.ts.map +1 -0
- package/dist/own-server-identity.js +1123 -0
- package/dist/own-server-identity.js.map +1 -0
- package/dist/own-tool.d.ts +21 -9
- package/dist/own-tool.d.ts.map +1 -1
- package/dist/own-tool.js +25 -10
- package/dist/own-tool.js.map +1 -1
- package/dist/redact.d.ts +23 -0
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +181 -8
- package/dist/redact.js.map +1 -1
- package/dist/seatbelt/classifier-pin.d.ts +15 -0
- package/dist/seatbelt/classifier-pin.d.ts.map +1 -0
- package/dist/seatbelt/classifier-pin.js +26 -0
- package/dist/seatbelt/classifier-pin.js.map +1 -0
- package/dist/seatbelt/env-dump.d.ts +45 -0
- package/dist/seatbelt/env-dump.d.ts.map +1 -0
- package/dist/seatbelt/env-dump.js +308 -0
- package/dist/seatbelt/env-dump.js.map +1 -0
- package/dist/seatbelt/fallback-copy.d.ts +16 -0
- package/dist/seatbelt/fallback-copy.d.ts.map +1 -0
- package/dist/seatbelt/fallback-copy.js +68 -0
- package/dist/seatbelt/fallback-copy.js.map +1 -0
- package/dist/seatbelt/guards.d.ts +10 -0
- package/dist/seatbelt/guards.d.ts.map +1 -0
- package/dist/seatbelt/guards.js +509 -0
- package/dist/seatbelt/guards.js.map +1 -0
- package/dist/seatbelt/hook.d.ts +91 -0
- package/dist/seatbelt/hook.d.ts.map +1 -0
- package/dist/seatbelt/hook.js +396 -0
- package/dist/seatbelt/hook.js.map +1 -0
- package/dist/seatbelt/longopts.d.ts +68 -0
- package/dist/seatbelt/longopts.d.ts.map +1 -0
- package/dist/seatbelt/longopts.js +377 -0
- package/dist/seatbelt/longopts.js.map +1 -0
- package/dist/seatbelt/mapper.d.ts +83 -0
- package/dist/seatbelt/mapper.d.ts.map +1 -0
- package/dist/seatbelt/mapper.js +10581 -0
- package/dist/seatbelt/mapper.js.map +1 -0
- package/dist/seatbelt/record.d.ts +94 -0
- package/dist/seatbelt/record.d.ts.map +1 -0
- package/dist/seatbelt/record.js +776 -0
- package/dist/seatbelt/record.js.map +1 -0
- package/dist/seatbelt/tables.d.ts +82 -0
- package/dist/seatbelt/tables.d.ts.map +1 -0
- package/dist/seatbelt/tables.js +352 -0
- package/dist/seatbelt/tables.js.map +1 -0
- package/dist/seatbelt-deploy.d.ts +100 -0
- package/dist/seatbelt-deploy.d.ts.map +1 -0
- package/dist/seatbelt-deploy.js +393 -0
- package/dist/seatbelt-deploy.js.map +1 -0
- package/dist/seatbelt-fs.d.ts +30 -0
- package/dist/seatbelt-fs.d.ts.map +1 -0
- package/dist/seatbelt-fs.js +94 -0
- package/dist/seatbelt-fs.js.map +1 -0
- package/dist/seatbelt-git.d.ts +212 -0
- package/dist/seatbelt-git.d.ts.map +1 -0
- package/dist/seatbelt-git.js +844 -0
- package/dist/seatbelt-git.js.map +1 -0
- package/dist/seatbelt-mode.d.ts +77 -0
- package/dist/seatbelt-mode.d.ts.map +1 -0
- package/dist/seatbelt-mode.js +496 -0
- package/dist/seatbelt-mode.js.map +1 -0
- package/dist/seatbelt-path.d.ts +123 -0
- package/dist/seatbelt-path.d.ts.map +1 -0
- package/dist/seatbelt-path.js +482 -0
- package/dist/seatbelt-path.js.map +1 -0
- package/dist/seatbelt-scripts.d.ts +109 -0
- package/dist/seatbelt-scripts.d.ts.map +1 -0
- package/dist/seatbelt-scripts.js +1060 -0
- package/dist/seatbelt-scripts.js.map +1 -0
- package/dist/secret-paths.d.ts +17 -0
- package/dist/secret-paths.d.ts.map +1 -0
- package/dist/secret-paths.js +32 -0
- package/dist/secret-paths.js.map +1 -0
- package/dist/session-loop-guard.d.ts.map +1 -1
- package/dist/session-loop-guard.js +15 -5
- package/dist/session-loop-guard.js.map +1 -1
- package/dist/tool-catalog.js +2 -2
- package/dist/tool-catalog.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/server.json +3 -3
- package/vendor/classifier/MANIFEST.json +25 -0
- package/vendor/classifier/classifier.js +1356 -0
- package/vendor/classifier/policy-baseline.js +187 -0
- package/vendor/classifier/redact.js +59 -0
- package/vendor/classifier/shell-parse.js +455 -0
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,7 @@ const hook_session_1 = require("./hook-session");
|
|
|
21
21
|
const hook_pre_action_1 = require("./hook-pre-action");
|
|
22
22
|
const node_child_process_1 = require("node:child_process");
|
|
23
23
|
const env_1 = require("./env");
|
|
24
|
+
const keyless_server_1 = require("./keyless-server");
|
|
24
25
|
const lifecycle_spool_1 = require("./lifecycle-spool");
|
|
25
26
|
const spool_command_1 = require("./spool-command");
|
|
26
27
|
const guidance_cache_1 = require("./guidance-cache");
|
|
@@ -154,6 +155,7 @@ async function runPingCommand() {
|
|
|
154
155
|
return;
|
|
155
156
|
}
|
|
156
157
|
const resolved = (0, env_1.resolveMarrowEnv)({ trustedOnly: true });
|
|
158
|
+
(0, env_1.emitBaseUrlNotice)(resolved);
|
|
157
159
|
if (!resolved.apiKey) {
|
|
158
160
|
process.stdout.write(JSON.stringify({ ok: false, error: 'missing_key', exact_fix: resolved.exactFix }) + '\n');
|
|
159
161
|
process.exitCode = 1;
|
|
@@ -207,9 +209,10 @@ async function runSpoolCommand(drain) {
|
|
|
207
209
|
return;
|
|
208
210
|
}
|
|
209
211
|
const resolved = (0, env_1.resolveMarrowEnv)({ trustedOnly: true });
|
|
212
|
+
(0, env_1.emitBaseUrlNotice)(resolved);
|
|
210
213
|
const apiKey = resolved.apiKey || '';
|
|
211
214
|
if (!apiKey) {
|
|
212
|
-
process.stderr.write(
|
|
215
|
+
process.stderr.write(`${keylessKeyError(resolved.exactFix)}\n`);
|
|
213
216
|
process.exitCode = 1;
|
|
214
217
|
return;
|
|
215
218
|
}
|
|
@@ -228,7 +231,28 @@ async function runSpoolCommand(drain) {
|
|
|
228
231
|
}
|
|
229
232
|
const GET_A_KEY_URL = 'https://getmarrow.ai/signup';
|
|
230
233
|
const ACTIVATE_COMMAND = 'npx -y @getmarrow/install@latest activate';
|
|
231
|
-
|
|
234
|
+
/**
|
|
235
|
+
* The Claude Code plugin sets CLAUDE_PLUGIN_ROOT for the processes it starts (its launcher runs this
|
|
236
|
+
* server in-process, so the variable is the plugin's own marker). Plugin users add the key to
|
|
237
|
+
* ~/.marrow/env and reload; setup and activate would write a second copy of the hooks. Only its
|
|
238
|
+
* presence is read, never its value.
|
|
239
|
+
*/
|
|
240
|
+
function runningAsPlugin() {
|
|
241
|
+
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
242
|
+
return typeof root === 'string' && root.trim() !== '';
|
|
243
|
+
}
|
|
244
|
+
const PLUGIN_KEY_STEPS = 'Marrow plugin: put MARROW_API_KEY in ~/.marrow/env, run chmod 600 ~/.marrow/env, then /reload-plugins. The seatbelt hooks keep working without a key. Do not run marrow-mcp setup or activate for the plugin.';
|
|
245
|
+
const PLUGIN_KEY_ERROR = `Error: MARROW_API_KEY required. ${PLUGIN_KEY_STEPS}`;
|
|
246
|
+
const GET_A_KEY_LINE = `Get a key: ${GET_A_KEY_URL}. Day-one steps and commands: npx @getmarrow/mcp --help`;
|
|
247
|
+
// The server's no-key refusal, whole: written before exit 1, and by the keyless server when its first frame shows it was not started by an MCP host.
|
|
248
|
+
function serverKeyRequiredText(exactFix) {
|
|
249
|
+
const fix = runningAsPlugin() ? PLUGIN_KEY_ERROR : `Error: MARROW_API_KEY environment variable is required\n${exactFix}`;
|
|
250
|
+
return `${fix}\n${GET_A_KEY_LINE}\n`;
|
|
251
|
+
}
|
|
252
|
+
function keylessKeyError(exactFix) {
|
|
253
|
+
return runningAsPlugin() ? PLUGIN_KEY_ERROR : `Error: MARROW_API_KEY required. ${exactFix}`;
|
|
254
|
+
}
|
|
255
|
+
const ENFORCEMENT_SENTENCE = 'On the 30-day evaluation and Business, deploys and publishes are held in hooks and the governed runner. Team and Free warn. If Marrow is unreachable, hooks hold high-risk actions and let routine actions run with a warning, and the governed runner stops protected actions it cannot verify. Over MCP, the agent gets the decision and is expected to follow it.';
|
|
232
256
|
const HELP_TEXT = `Marrow MCP server and hook commands (@getmarrow/mcp)
|
|
233
257
|
|
|
234
258
|
Day one:
|
|
@@ -387,6 +411,11 @@ ${MARROW_BLOCK_END}`;
|
|
|
387
411
|
process.exit(0);
|
|
388
412
|
}
|
|
389
413
|
const cliArgs = parseArgs();
|
|
414
|
+
// `seatbelt-status`: one line of JSON for the installer's doctor. Read-only; it starts no server, hook or nudge.
|
|
415
|
+
if (process.argv[2] === 'seatbelt-status') {
|
|
416
|
+
(0, hook_pre_action_1.runSeatbeltStatusCommand)();
|
|
417
|
+
process.exit(0);
|
|
418
|
+
}
|
|
390
419
|
// Never for a hook entrypoint: a hook that sees -h must still run its check.
|
|
391
420
|
if (cliArgs.help && !(cliArgs.hook || cliArgs.contextHook || cliArgs.preActionHook || cliArgs.sessionHook || cliArgs.permissionRequestHook || cliArgs.backgroundNudge)) {
|
|
392
421
|
process.stdout.write(HELP_TEXT);
|
|
@@ -399,33 +428,64 @@ function formatKeyMaterialWarning() {
|
|
|
399
428
|
if (process.argv[2] === 'keys') {
|
|
400
429
|
const cmd = process.argv[3];
|
|
401
430
|
const resolvedEnv = (0, env_1.resolveMarrowEnv)();
|
|
431
|
+
(0, env_1.emitBaseUrlNotice)(resolvedEnv);
|
|
402
432
|
const API_KEY = cliArgs.apiKey || resolvedEnv.apiKey || '';
|
|
403
433
|
if (!API_KEY) {
|
|
404
|
-
process.stderr.write(
|
|
434
|
+
process.stderr.write(`${keylessKeyError(resolvedEnv.exactFix)}\n`);
|
|
405
435
|
process.exit(1);
|
|
406
436
|
}
|
|
407
437
|
const getFlag = (name, short) => {
|
|
408
438
|
const idx = process.argv.findIndex(a => a === `--${name}` || (short ? a === `-${short}` : false));
|
|
409
439
|
return idx >= 0 ? process.argv[idx + 1] : undefined;
|
|
410
440
|
};
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
441
|
+
// Absent flag: undefined, so the key is left out of the request (the server's default).
|
|
442
|
+
// Present flag: its non-empty parts. Present but empty is a usage error, never "omitted":
|
|
443
|
+
// omitted means full scopes / unrestricted agents. Repeated flags: the first one wins.
|
|
444
|
+
const getFlagList = (name, noun) => {
|
|
445
|
+
const idx = process.argv.findIndex(a => a === `--${name}` || a.startsWith(`--${name}=`));
|
|
446
|
+
if (idx < 0)
|
|
447
|
+
return undefined;
|
|
448
|
+
const arg = process.argv[idx];
|
|
449
|
+
const raw = arg.startsWith(`--${name}=`) ? arg.slice(name.length + 3) : process.argv[idx + 1];
|
|
450
|
+
const parts = (raw ?? '').split(',').map(s => s.replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g, '')).filter(Boolean);
|
|
451
|
+
if (!parts.length) {
|
|
452
|
+
process.stderr.write(`Error: list at least one ${noun}, or omit --${name} for the default\n`);
|
|
453
|
+
process.exit(2);
|
|
454
|
+
}
|
|
455
|
+
return parts;
|
|
456
|
+
};
|
|
457
|
+
// `keys create` single values, the same rule as getFlagList: absent is undefined (the default:
|
|
458
|
+
// live key, no expiry); `--name value`, `--name=value`, `-n value` and `-n=value` are present
|
|
459
|
+
// (the first occurrence wins); present but blank is a usage error (exit 2, nothing sent), never
|
|
460
|
+
// "absent" (an ignored `--expires=` would make a key that never expires). A value is sent as typed.
|
|
461
|
+
const getFlagValue = (name, short) => {
|
|
462
|
+
const forms = [`--${name}`, ...(short ? [`-${short}`] : [])];
|
|
463
|
+
const idx = process.argv.findIndex(a => forms.some(f => a === f || a.startsWith(`${f}=`)));
|
|
464
|
+
if (idx < 0)
|
|
465
|
+
return undefined;
|
|
466
|
+
const arg = process.argv[idx];
|
|
467
|
+
const form = forms.find(f => arg === f || arg.startsWith(`${f}=`));
|
|
468
|
+
const raw = arg === form ? process.argv[idx + 1] : arg.slice(form.length + 1);
|
|
469
|
+
if (!(raw ?? '').replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g, '')) {
|
|
470
|
+
process.stderr.write(`Error: --${name} needs a value, or omit --${name} for the default\n`);
|
|
471
|
+
process.exit(2);
|
|
472
|
+
}
|
|
473
|
+
return raw;
|
|
414
474
|
};
|
|
415
475
|
const runCli = async () => {
|
|
416
476
|
try {
|
|
417
477
|
if (cmd === 'create') {
|
|
418
|
-
const name =
|
|
478
|
+
const name = getFlagValue('name', 'n');
|
|
419
479
|
if (!name) {
|
|
420
480
|
process.stderr.write('Error: --name required\n');
|
|
421
481
|
process.exit(1);
|
|
422
482
|
}
|
|
423
483
|
const result = await (0, index_1.marrowCreateKey)(API_KEY, 'https://api.getmarrow.ai', {
|
|
424
484
|
name,
|
|
425
|
-
key_type: (
|
|
426
|
-
scopes: getFlagList('scopes'),
|
|
427
|
-
agent_ids: getFlagList('agents'),
|
|
428
|
-
expires_at:
|
|
485
|
+
key_type: (getFlagValue('type', 't') || 'live'),
|
|
486
|
+
scopes: getFlagList('scopes', 'scope'),
|
|
487
|
+
agent_ids: getFlagList('agents', 'agent'),
|
|
488
|
+
expires_at: getFlagValue('expires'),
|
|
429
489
|
}, undefined, undefined);
|
|
430
490
|
process.stdout.write(JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) + '\n');
|
|
431
491
|
}
|
|
@@ -525,758 +585,769 @@ if (process.argv[2] !== 'keys') {
|
|
|
525
585
|
}
|
|
526
586
|
else {
|
|
527
587
|
const resolvedEnv = (0, env_1.resolveMarrowEnv)();
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
// resolveMarrowEnv already gives MARROW_FLEET_AGENT_ID precedence over MARROW_AGENT_ID.
|
|
537
|
-
// When no identity is configured, omit it and let the API resolve the key's
|
|
538
|
-
// bound agent instead of inventing a process-local identity that changes on restart.
|
|
539
|
-
const FLEET_AGENT_ID = resolvedEnv.agentId || undefined;
|
|
540
|
-
const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
|
|
541
|
-
if (!API_KEY) {
|
|
542
|
-
process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
|
|
543
|
-
process.stderr.write(`${resolvedEnv.exactFix}\n`);
|
|
544
|
-
process.stderr.write(`Get a key: ${GET_A_KEY_URL}. Day-one steps and commands: npx @getmarrow/mcp --help\n`);
|
|
545
|
-
process.exit(1);
|
|
546
|
-
}
|
|
547
|
-
if (ENDPOINT_HOST) {
|
|
548
|
-
process.stderr.write(`[marrow] Using a non-default Marrow endpoint: ${ENDPOINT_HOST} (MARROW_BASE_URL).\n`);
|
|
549
|
-
}
|
|
550
|
-
// [SECURITY #12] Warn if API key is visible in process args
|
|
551
|
-
if (cliArgs.apiKey) {
|
|
552
|
-
process.stderr.write('[marrow] Warning: --key flag exposes API key in process list. Use MARROW_API_KEY env var for production.\n');
|
|
553
|
-
}
|
|
554
|
-
// Auto-orient on startup — cache warnings, inject into EVERY marrow_think response
|
|
555
|
-
let cachedOrientWarnings = [];
|
|
556
|
-
let thinkCallCount = 0;
|
|
557
|
-
let orientCallCount = 0;
|
|
558
|
-
let initialized = false;
|
|
559
|
-
// ---------------------------------------------------------------------------
|
|
560
|
-
// MCP elicitation: where the client can ask its user mid-task, a held
|
|
561
|
-
// marrow_auto action is approved in the client's own dialog (one click). The
|
|
562
|
-
// request goes to the client UI, never to the model; the answer is reported to
|
|
563
|
-
// Marrow as client-attested.
|
|
564
|
-
// ---------------------------------------------------------------------------
|
|
565
|
-
const ELICITATION_PROTOCOL_VERSIONS = ['2025-06-18', '2025-11-25'];
|
|
566
|
-
const ELICITATION_TIMEOUT_MS = 120_000;
|
|
567
|
-
let clientElicitation = false;
|
|
568
|
-
let clientHost = 'other';
|
|
569
|
-
let serverRequestSeq = 0;
|
|
570
|
-
const pendingClientRequests = new Map();
|
|
571
|
-
function noteClientCapabilities(params) {
|
|
572
|
-
const record = params && typeof params === 'object' ? params : {};
|
|
573
|
-
const capabilities = record.capabilities && typeof record.capabilities === 'object' ? record.capabilities : {};
|
|
574
|
-
const requested = typeof record.protocolVersion === 'string' ? record.protocolVersion : '';
|
|
575
|
-
const info = record.clientInfo && typeof record.clientInfo === 'object' ? record.clientInfo : {};
|
|
576
|
-
const name = typeof info.name === 'string' ? info.name.toLowerCase() : '';
|
|
577
|
-
clientHost = /claude/.test(name) ? 'claude-code' : /cursor/.test(name) ? 'cursor' : /hermes/.test(name) ? 'hermes'
|
|
578
|
-
: /codex/.test(name) ? 'codex' : /gemini/.test(name) ? 'gemini' : 'other';
|
|
579
|
-
clientElicitation = Boolean(capabilities.elicitation && typeof capabilities.elicitation === 'object')
|
|
580
|
-
&& ELICITATION_PROTOCOL_VERSIONS.includes(requested);
|
|
581
|
-
// Elicitation needs the protocol version that defines it; otherwise keep the version this server always spoke.
|
|
582
|
-
return clientElicitation ? requested : '2024-11-05';
|
|
583
|
-
}
|
|
584
|
-
function requestClient(method, params, timeoutMs) {
|
|
585
|
-
serverRequestSeq += 1;
|
|
586
|
-
const id = `marrow-server-${serverRequestSeq}`;
|
|
587
|
-
return new Promise((resolve) => {
|
|
588
|
-
const timer = setTimeout(() => {
|
|
589
|
-
pendingClientRequests.delete(id);
|
|
590
|
-
resolve({ error: { code: 'timeout' } });
|
|
591
|
-
}, timeoutMs);
|
|
592
|
-
timer.unref?.();
|
|
593
|
-
pendingClientRequests.set(id, { resolve, timer });
|
|
594
|
-
send({ jsonrpc: '2.0', id, method, params });
|
|
588
|
+
if ((0, keyless_server_1.keylessStart)(resolvedEnv, process.argv.slice(2), process.env)) {
|
|
589
|
+
// No Marrow key anywhere this server reads one: zero tools and get-a-key instructions instead of exit 1.
|
|
590
|
+
// Plugin or not, the key steps come from the same constants as the exit-1 text below.
|
|
591
|
+
const pluginSteps = runningAsPlugin() ? PLUGIN_KEY_STEPS : undefined;
|
|
592
|
+
(0, keyless_server_1.serveKeylessMcp)(hook_contract_2.MCP_ADAPTER_VERSION, {
|
|
593
|
+
instructions: (0, keyless_server_1.keylessInstructions)({ getKeyUrl: GET_A_KEY_URL, activateCommand: ACTIVATE_COMMAND, pluginSteps }),
|
|
594
|
+
notice: `${keyless_server_1.KEYLESS_NOTICE}\n${pluginSteps ?? resolvedEnv.exactFix}\n${GET_A_KEY_LINE}\n`,
|
|
595
|
+
refusal: serverKeyRequiredText(resolvedEnv.exactFix),
|
|
595
596
|
});
|
|
596
597
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
598
|
+
else {
|
|
599
|
+
(0, env_1.emitBaseUrlNotice)(resolvedEnv);
|
|
600
|
+
const API_KEY = cliArgs.apiKey || resolvedEnv.apiKey || '';
|
|
601
|
+
// [SECURITY #3] Validate BASE_URL — require HTTPS to prevent SSRF / credential leakage
|
|
602
|
+
const rawBaseUrl = resolvedEnv.baseUrl || 'https://api.getmarrow.ai';
|
|
603
|
+
const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
|
|
604
|
+
// A non-default endpoint receives every action; name its host wherever status is shown.
|
|
605
|
+
const ENDPOINT_HOST = (0, env_1.nonDefaultBaseUrlHost)(BASE_URL);
|
|
606
|
+
const SESSION_ID = resolvedEnv.sessionId || undefined;
|
|
607
|
+
// One identity must bind headers, bodies, queries, cache, and lifecycle events.
|
|
608
|
+
// resolveMarrowEnv already gives MARROW_FLEET_AGENT_ID precedence over MARROW_AGENT_ID.
|
|
609
|
+
// When no identity is configured, omit it and let the API resolve the key's
|
|
610
|
+
// bound agent instead of inventing a process-local identity that changes on restart.
|
|
611
|
+
const FLEET_AGENT_ID = resolvedEnv.agentId || undefined;
|
|
612
|
+
const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
|
|
613
|
+
if (!API_KEY) {
|
|
614
|
+
process.stderr.write(serverKeyRequiredText(resolvedEnv.exactFix));
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
if (ENDPOINT_HOST) {
|
|
618
|
+
process.stderr.write(`[marrow] Using a non-default Marrow endpoint: ${ENDPOINT_HOST} (MARROW_BASE_URL).\n`);
|
|
619
|
+
}
|
|
620
|
+
// [SECURITY #12] Warn if API key is visible in process args
|
|
621
|
+
if (cliArgs.apiKey) {
|
|
622
|
+
process.stderr.write('[marrow] Warning: --key flag exposes API key in process list. Use MARROW_API_KEY env var for production.\n');
|
|
623
|
+
}
|
|
624
|
+
// Auto-orient on startup — cache warnings, inject into EVERY marrow_think response
|
|
625
|
+
let cachedOrientWarnings = [];
|
|
626
|
+
let thinkCallCount = 0;
|
|
627
|
+
let orientCallCount = 0;
|
|
628
|
+
let initialized = false;
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
// MCP elicitation: where the client can ask its user mid-task, a held
|
|
631
|
+
// marrow_auto action is approved in the client's own dialog (one click). The
|
|
632
|
+
// request goes to the client UI, never to the model; the answer is reported to
|
|
633
|
+
// Marrow as client-attested.
|
|
634
|
+
// ---------------------------------------------------------------------------
|
|
635
|
+
const ELICITATION_PROTOCOL_VERSIONS = ['2025-06-18', '2025-11-25'];
|
|
636
|
+
const ELICITATION_TIMEOUT_MS = 120_000;
|
|
637
|
+
let clientElicitation = false;
|
|
638
|
+
let clientHost = 'other';
|
|
639
|
+
let serverRequestSeq = 0;
|
|
640
|
+
const pendingClientRequests = new Map();
|
|
641
|
+
function noteClientCapabilities(params) {
|
|
642
|
+
const record = params && typeof params === 'object' ? params : {};
|
|
643
|
+
const capabilities = record.capabilities && typeof record.capabilities === 'object' ? record.capabilities : {};
|
|
644
|
+
const requested = typeof record.protocolVersion === 'string' ? record.protocolVersion : '';
|
|
645
|
+
const info = record.clientInfo && typeof record.clientInfo === 'object' ? record.clientInfo : {};
|
|
646
|
+
const name = typeof info.name === 'string' ? info.name.toLowerCase() : '';
|
|
647
|
+
clientHost = /claude/.test(name) ? 'claude-code' : /cursor/.test(name) ? 'cursor' : /hermes/.test(name) ? 'hermes'
|
|
648
|
+
: /codex/.test(name) ? 'codex' : /gemini/.test(name) ? 'gemini' : 'other';
|
|
649
|
+
clientElicitation = Boolean(capabilities.elicitation && typeof capabilities.elicitation === 'object')
|
|
650
|
+
&& ELICITATION_PROTOCOL_VERSIONS.includes(requested);
|
|
651
|
+
// Elicitation needs the protocol version that defines it; otherwise keep the version this server always spoke.
|
|
652
|
+
return clientElicitation ? requested : '2024-11-05';
|
|
653
|
+
}
|
|
654
|
+
function requestClient(method, params, timeoutMs) {
|
|
655
|
+
serverRequestSeq += 1;
|
|
656
|
+
const id = `marrow-server-${serverRequestSeq}`;
|
|
657
|
+
return new Promise((resolve) => {
|
|
658
|
+
const timer = setTimeout(() => {
|
|
659
|
+
pendingClientRequests.delete(id);
|
|
660
|
+
resolve({ error: { code: 'timeout' } });
|
|
661
|
+
}, timeoutMs);
|
|
662
|
+
timer.unref?.();
|
|
663
|
+
pendingClientRequests.set(id, { resolve, timer });
|
|
664
|
+
send({ jsonrpc: '2.0', id, method, params });
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
/** A response from the client to one of this server's requests (elicitation). */
|
|
668
|
+
function settleClientResponse(message) {
|
|
669
|
+
if (typeof message.id !== 'string' || message.method !== undefined)
|
|
670
|
+
return false;
|
|
671
|
+
const pending = pendingClientRequests.get(message.id);
|
|
672
|
+
if (!pending)
|
|
673
|
+
return false;
|
|
674
|
+
pendingClientRequests.delete(message.id);
|
|
675
|
+
clearTimeout(pending.timer);
|
|
676
|
+
pending.resolve({ result: message.result, error: message.error });
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
/** Asks the user in the client's own dialog; approved, declined, or null (cancelled, timed out, unsupported). */
|
|
680
|
+
async function elicitApproval(request) {
|
|
681
|
+
if (!clientElicitation)
|
|
682
|
+
return null;
|
|
683
|
+
const answer = await requestClient('elicitation/create', {
|
|
684
|
+
message: request.message,
|
|
685
|
+
requestedSchema: {
|
|
686
|
+
type: 'object',
|
|
687
|
+
properties: {
|
|
688
|
+
decision: {
|
|
689
|
+
type: 'string',
|
|
690
|
+
title: 'Your answer',
|
|
691
|
+
description: 'Approve only if you authorize this exact action.',
|
|
692
|
+
enum: ['approve', 'decline'],
|
|
693
|
+
enumNames: ['Approve this action', 'Decline it'],
|
|
694
|
+
},
|
|
624
695
|
},
|
|
696
|
+
required: ['decision'],
|
|
625
697
|
},
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
return null;
|
|
636
|
-
const content = result.content && typeof result.content === 'object' ? result.content : {};
|
|
637
|
-
return content.decision === 'approve' ? 'approved' : content.decision === 'decline' ? 'declined' : null;
|
|
638
|
-
}
|
|
639
|
-
function autoElicitationOptions() {
|
|
640
|
-
if (!clientElicitation)
|
|
641
|
-
return undefined;
|
|
642
|
-
const hostSessionId = typeof SESSION_ID === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(SESSION_ID) ? SESSION_ID : `mcp-${process.pid}`;
|
|
643
|
-
return { elicitApproval, elicitHost: clientHost, elicitHostSessionId: hostSessionId };
|
|
644
|
-
}
|
|
645
|
-
function formatWarningActionably(w) {
|
|
646
|
-
const pct = Math.round(w.failureRate * 100);
|
|
647
|
-
return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
|
|
648
|
-
}
|
|
649
|
-
// [FIX #4] Log orient refresh failures instead of silently ignoring
|
|
650
|
-
async function refreshOrientWarnings() {
|
|
651
|
-
try {
|
|
652
|
-
const r = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
653
|
-
cachedOrientWarnings = r.warnings;
|
|
698
|
+
}, ELICITATION_TIMEOUT_MS);
|
|
699
|
+
const result = answer.result && typeof answer.result === 'object' ? answer.result : null;
|
|
700
|
+
if (!result)
|
|
701
|
+
return null;
|
|
702
|
+
if (result.action === 'decline')
|
|
703
|
+
return 'declined';
|
|
704
|
+
if (result.action !== 'accept')
|
|
705
|
+
return null;
|
|
706
|
+
const content = result.content && typeof result.content === 'object' ? result.content : {};
|
|
707
|
+
return content.decision === 'approve' ? 'approved' : content.decision === 'decline' ? 'declined' : null;
|
|
654
708
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
709
|
+
function autoElicitationOptions() {
|
|
710
|
+
if (!clientElicitation)
|
|
711
|
+
return undefined;
|
|
712
|
+
const hostSessionId = typeof SESSION_ID === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(SESSION_ID) ? SESSION_ID : `mcp-${process.pid}`;
|
|
713
|
+
return { elicitApproval, elicitHost: clientHost, elicitHostSessionId: hostSessionId };
|
|
658
714
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
if (lastDecisionId && !lastCommitted) {
|
|
715
|
+
function formatWarningActionably(w) {
|
|
716
|
+
const pct = Math.round(w.failureRate * 100);
|
|
717
|
+
return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
|
|
718
|
+
}
|
|
719
|
+
// [FIX #4] Log orient refresh failures instead of silently ignoring
|
|
720
|
+
async function refreshOrientWarnings() {
|
|
666
721
|
try {
|
|
667
|
-
await (0, index_1.
|
|
668
|
-
|
|
669
|
-
success: false,
|
|
670
|
-
outcome: 'Session ended without explicit commit',
|
|
671
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
722
|
+
const r = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
723
|
+
cachedOrientWarnings = r.warnings;
|
|
672
724
|
}
|
|
673
725
|
catch (err) {
|
|
674
726
|
const msg = err instanceof Error ? err.message : String(err);
|
|
675
|
-
process.stderr.write(`[marrow] Warning:
|
|
727
|
+
process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
|
|
676
728
|
}
|
|
677
729
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
696
|
-
}
|
|
697
|
-
function toolSuccess(id, value, isError = false) {
|
|
698
|
-
success(id, {
|
|
699
|
-
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
700
|
-
...(isError ? { isError: true } : {}),
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
/** Proof fields Marrow reported missing per decision or gate receipt, for a plain answer on a retry conflict. */
|
|
704
|
-
const commitProofMissing = new Map();
|
|
705
|
-
function proofFieldsGiven(proof) {
|
|
706
|
-
if (!proof || typeof proof !== 'object' || Array.isArray(proof))
|
|
707
|
-
return [];
|
|
708
|
-
return Object.entries(proof)
|
|
709
|
-
.filter(([, value]) => value !== null && value !== undefined && value !== ''
|
|
710
|
-
&& !(Array.isArray(value) && value.length === 0)
|
|
711
|
-
&& !(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0))
|
|
712
|
-
.map(([key]) => key);
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* A retry of marrow_commit that added proof, refused as a conflict with the
|
|
716
|
-
* outcome Marrow already holds (409 MARROW_OUTCOME_OBSERVATION_CONFLICT):
|
|
717
|
-
* say plainly which proof fields are still missing.
|
|
718
|
-
*/
|
|
719
|
-
function proofRetryConflict(keys, proof, requiredFromHold) {
|
|
720
|
-
const required = [...new Set([...keys.flatMap((key) => commitProofMissing.get(key) || []), ...requiredFromHold])];
|
|
721
|
-
const given = proofFieldsGiven(proof);
|
|
722
|
-
const stillMissing = required.filter((field) => !given.includes(field));
|
|
723
|
-
return {
|
|
724
|
-
committed: false,
|
|
725
|
-
code: 'MARROW_OUTCOME_OBSERVATION_CONFLICT',
|
|
726
|
-
proof_required: true,
|
|
727
|
-
failure_kind: 'validation',
|
|
728
|
-
still_missing: stillMissing,
|
|
729
|
-
exact_next_action: stillMissing.length
|
|
730
|
-
? `Still missing: ${stillMissing.join(', ')}. Call marrow_commit again with the same decision_id and gate_receipt_id, and a proof that has every field you sent plus these. Fill them from what really happened; do not invent values.`
|
|
731
|
-
: 'Marrow already holds an outcome for this action with different proof. Call marrow_commit again with the same decision_id and gate_receipt_id and the complete proof in one call; do not invent values.',
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
function toolFailure(toolName, failure) {
|
|
735
|
-
const result = (0, request_reliability_1.structuredRequestFailure)(failure);
|
|
736
|
-
const proofValidation = failure.code === 'proof_required';
|
|
737
|
-
const infrastructureFailure = !proofValidation && !['authentication_required', 'permission_denied'].includes(failure.code);
|
|
738
|
-
const supportsStale = ['marrow_agent_runtime', 'marrow_orient', 'marrow_ask', 'marrow_handoff_status', 'marrow_runtime_status', 'marrow_status'].includes(toolName || '');
|
|
739
|
-
const spool = reportLifecycleSpool({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
740
|
-
result.failure_kind = proofValidation ? 'validation' : infrastructureFailure ? 'infrastructure' : 'authorization';
|
|
741
|
-
result.control_path = (0, control_path_state_1.controlPathStats)(toolName || 'marrow_control');
|
|
742
|
-
result.lifecycle_spool = {
|
|
743
|
-
state: spool.failed > 0 || spool.pending > 0 ? spool.state : 'clear',
|
|
744
|
-
pending: spool.pending,
|
|
745
|
-
failed: spool.failed,
|
|
746
|
-
exact_fix: spool.failed > 0 || spool.pending > 0 ? spool.exact_fix : null,
|
|
747
|
-
drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
|
|
748
|
-
legacy_namespaces: spool.other_namespaces?.count || 0,
|
|
749
|
-
};
|
|
750
|
-
result.host_capability = mcpHostCapability();
|
|
751
|
-
if (proofValidation) {
|
|
752
|
-
result.proof_required = true;
|
|
753
|
-
result.exact_next_action = failure.exactFix;
|
|
754
|
-
}
|
|
755
|
-
if (infrastructureFailure && supportsStale) {
|
|
756
|
-
let cached = null;
|
|
757
|
-
try {
|
|
758
|
-
cached = (0, guidance_cache_1.readGuidanceCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
730
|
+
// Auto-commit tracking for session close
|
|
731
|
+
let lastDecisionId = null;
|
|
732
|
+
let lastCommitted = false;
|
|
733
|
+
// [FIX #5] Log auto-commit failures instead of silently ignoring; remove broken AbortController
|
|
734
|
+
async function autoCommitOnClose() {
|
|
735
|
+
if (lastDecisionId && !lastCommitted) {
|
|
736
|
+
try {
|
|
737
|
+
await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
738
|
+
decision_id: lastDecisionId,
|
|
739
|
+
success: false,
|
|
740
|
+
outcome: 'Session ended without explicit commit',
|
|
741
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
745
|
+
process.stderr.write(`[marrow] Warning: auto-commit on close failed: ${msg}\n`);
|
|
746
|
+
}
|
|
759
747
|
}
|
|
760
|
-
catch { /* owner-only cache is best effort */ }
|
|
761
|
-
result.stale_brief = cached?.context || [
|
|
762
|
-
'## Marrow control-path outage brief',
|
|
763
|
-
'- This is an infrastructure failure; Marrow returned no policy decision and did not deny the action.',
|
|
764
|
-
'- Preserve local evidence and continue only low-risk, reversible work under the owner\'s existing safeguards.',
|
|
765
|
-
'- Do not perform high-risk work until a fresh Marrow runtime gate returns allow, warn, review_required, or block.',
|
|
766
|
-
].join('\n');
|
|
767
|
-
result.stale_source = cached ? 'last_known_guidance' : 'local_outage_safety';
|
|
768
|
-
result.stale_ms = cached?.stale_ms ?? null;
|
|
769
|
-
result.authorization_state = 'unavailable';
|
|
770
|
-
result.gate_obtained = false;
|
|
771
|
-
result.stale_can_authorize_high_risk = false;
|
|
772
|
-
const retryAfterMs = failure.retryAfterMs;
|
|
773
|
-
result.exact_next_action = cached
|
|
774
|
-
? 'Use this last-known brief for low-risk context only. Obtain a fresh Marrow runtime gate before high-risk work.'
|
|
775
|
-
: retryAfterMs != null
|
|
776
|
-
? `Wait ${retryAfterMs} ms, then retry once. Do not perform high-risk work without a fresh Marrow runtime gate.`
|
|
777
|
-
: failure.exactFix;
|
|
778
748
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
749
|
+
// [FIX #10] Handle both SIGTERM and SIGINT for clean shutdown
|
|
750
|
+
async function gracefulShutdown() {
|
|
751
|
+
const forceExit = setTimeout(() => process.exit(0), 5000);
|
|
752
|
+
forceExit.unref();
|
|
753
|
+
await autoCommitOnClose();
|
|
754
|
+
process.exit(0);
|
|
755
|
+
}
|
|
756
|
+
process.on('SIGTERM', gracefulShutdown);
|
|
757
|
+
process.on('SIGINT', gracefulShutdown);
|
|
758
|
+
function send(response) {
|
|
759
|
+
process.stdout.write(JSON.stringify(response) + '\n');
|
|
760
|
+
}
|
|
761
|
+
function success(id, result) {
|
|
762
|
+
send({ jsonrpc: '2.0', id, result });
|
|
763
|
+
}
|
|
764
|
+
function error(id, code, message) {
|
|
765
|
+
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
766
|
+
}
|
|
767
|
+
function toolSuccess(id, value, isError = false) {
|
|
768
|
+
success(id, {
|
|
769
|
+
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
770
|
+
...(isError ? { isError: true } : {}),
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/** Proof fields Marrow reported missing per decision or gate receipt, for a plain answer on a retry conflict. */
|
|
774
|
+
const commitProofMissing = new Map();
|
|
775
|
+
function proofFieldsGiven(proof) {
|
|
776
|
+
if (!proof || typeof proof !== 'object' || Array.isArray(proof))
|
|
777
|
+
return [];
|
|
778
|
+
return Object.entries(proof)
|
|
779
|
+
.filter(([, value]) => value !== null && value !== undefined && value !== ''
|
|
780
|
+
&& !(Array.isArray(value) && value.length === 0)
|
|
781
|
+
&& !(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0))
|
|
782
|
+
.map(([key]) => key);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* A retry of marrow_commit that added proof, refused as a conflict with the
|
|
786
|
+
* outcome Marrow already holds (409 MARROW_OUTCOME_OBSERVATION_CONFLICT):
|
|
787
|
+
* say plainly which proof fields are still missing.
|
|
788
|
+
*/
|
|
789
|
+
function proofRetryConflict(keys, proof, requiredFromHold) {
|
|
790
|
+
const required = [...new Set([...keys.flatMap((key) => commitProofMissing.get(key) || []), ...requiredFromHold])];
|
|
791
|
+
const given = proofFieldsGiven(proof);
|
|
792
|
+
const stillMissing = required.filter((field) => !given.includes(field));
|
|
793
|
+
return {
|
|
794
|
+
committed: false,
|
|
795
|
+
code: 'MARROW_OUTCOME_OBSERVATION_CONFLICT',
|
|
796
|
+
proof_required: true,
|
|
797
|
+
failure_kind: 'validation',
|
|
798
|
+
still_missing: stillMissing,
|
|
799
|
+
exact_next_action: stillMissing.length
|
|
800
|
+
? `Still missing: ${stillMissing.join(', ')}. Call marrow_commit again with the same decision_id and gate_receipt_id, and a proof that has every field you sent plus these. Fill them from what really happened; do not invent values.`
|
|
801
|
+
: 'Marrow already holds an outcome for this action with different proof. Call marrow_commit again with the same decision_id and gate_receipt_id and the complete proof in one call; do not invent values.',
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function toolFailure(toolName, failure) {
|
|
805
|
+
const result = (0, request_reliability_1.structuredRequestFailure)(failure);
|
|
806
|
+
const proofValidation = failure.code === 'proof_required';
|
|
807
|
+
const infrastructureFailure = !proofValidation && !['authentication_required', 'permission_denied'].includes(failure.code);
|
|
808
|
+
const supportsStale = ['marrow_agent_runtime', 'marrow_orient', 'marrow_ask', 'marrow_handoff_status', 'marrow_runtime_status', 'marrow_status'].includes(toolName || '');
|
|
809
|
+
const spool = reportLifecycleSpool({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
810
|
+
result.failure_kind = proofValidation ? 'validation' : infrastructureFailure ? 'infrastructure' : 'authorization';
|
|
811
|
+
result.control_path = (0, control_path_state_1.controlPathStats)(toolName || 'marrow_control');
|
|
812
|
+
result.lifecycle_spool = {
|
|
804
813
|
state: spool.failed > 0 || spool.pending > 0 ? spool.state : 'clear',
|
|
805
814
|
pending: spool.pending,
|
|
806
815
|
failed: spool.failed,
|
|
807
816
|
exact_fix: spool.failed > 0 || spool.pending > 0 ? spool.exact_fix : null,
|
|
808
817
|
drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
|
|
809
818
|
legacy_namespaces: spool.other_namespaces?.count || 0,
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
: commitBudget ? 8_000 : options.highRisk || runtimeBudget ? 4_500 : 4_000;
|
|
841
|
-
const controller = new AbortController();
|
|
842
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
843
|
-
timer.unref?.();
|
|
844
|
-
const startedAt = Date.now();
|
|
845
|
-
try {
|
|
846
|
-
const result = await operation(controller.signal);
|
|
847
|
-
(0, control_path_state_1.recordControlPathSample)(toolName, Date.now() - startedAt, true);
|
|
819
|
+
};
|
|
820
|
+
result.host_capability = mcpHostCapability();
|
|
821
|
+
if (proofValidation) {
|
|
822
|
+
result.proof_required = true;
|
|
823
|
+
result.exact_next_action = failure.exactFix;
|
|
824
|
+
}
|
|
825
|
+
if (infrastructureFailure && supportsStale) {
|
|
826
|
+
let cached = null;
|
|
827
|
+
try {
|
|
828
|
+
cached = (0, guidance_cache_1.readGuidanceCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
829
|
+
}
|
|
830
|
+
catch { /* owner-only cache is best effort */ }
|
|
831
|
+
result.stale_brief = cached?.context || [
|
|
832
|
+
'## Marrow control-path outage brief',
|
|
833
|
+
'- This is an infrastructure failure; Marrow returned no policy decision and did not deny the action.',
|
|
834
|
+
'- Preserve local evidence and continue only low-risk, reversible work under the owner\'s existing safeguards.',
|
|
835
|
+
'- Do not perform high-risk work until a fresh Marrow runtime gate returns allow, warn, review_required, or block.',
|
|
836
|
+
].join('\n');
|
|
837
|
+
result.stale_source = cached ? 'last_known_guidance' : 'local_outage_safety';
|
|
838
|
+
result.stale_ms = cached?.stale_ms ?? null;
|
|
839
|
+
result.authorization_state = 'unavailable';
|
|
840
|
+
result.gate_obtained = false;
|
|
841
|
+
result.stale_can_authorize_high_risk = false;
|
|
842
|
+
const retryAfterMs = failure.retryAfterMs;
|
|
843
|
+
result.exact_next_action = cached
|
|
844
|
+
? 'Use this last-known brief for low-risk context only. Obtain a fresh Marrow runtime gate before high-risk work.'
|
|
845
|
+
: retryAfterMs != null
|
|
846
|
+
? `Wait ${retryAfterMs} ms, then retry once. Do not perform high-risk work without a fresh Marrow runtime gate.`
|
|
847
|
+
: failure.exactFix;
|
|
848
|
+
}
|
|
848
849
|
return result;
|
|
849
850
|
}
|
|
850
|
-
|
|
851
|
-
(0,
|
|
852
|
-
|
|
851
|
+
function mcpHostCapability() {
|
|
852
|
+
return (0, host_capability_1.resolveHostCapability)({
|
|
853
|
+
hostLabel: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || process.env.MARROW_AGENT_CLIENT,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
function clientOperationalPayload(toolName, value) {
|
|
857
|
+
const payload = value && typeof value === 'object' && !Array.isArray(value)
|
|
858
|
+
? value
|
|
859
|
+
: { data: value };
|
|
860
|
+
const spool = reportLifecycleSpool({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
861
|
+
const habitLoopCopy = (0, habit_loop_copy_1.formatHabitLoopCopy)(payload) || (0, habit_loop_copy_1.formatHabitLoopCopy)(payload.data);
|
|
862
|
+
return {
|
|
863
|
+
...payload,
|
|
864
|
+
...(habitLoopCopy ? { habit_loop_copy: habitLoopCopy } : {}),
|
|
865
|
+
host_capability: payload.host_capability || mcpHostCapability(),
|
|
866
|
+
client_update: payload.client_update
|
|
867
|
+
|| (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)
|
|
868
|
+
? payload.data.client_update
|
|
869
|
+
: null)
|
|
870
|
+
|| (0, request_reliability_1.localClientUpdate)(),
|
|
871
|
+
control_path: (0, control_path_state_1.controlPathStats)(toolName),
|
|
872
|
+
local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)),
|
|
873
|
+
lifecycle_spool: {
|
|
874
|
+
state: spool.failed > 0 || spool.pending > 0 ? spool.state : 'clear',
|
|
875
|
+
pending: spool.pending,
|
|
876
|
+
failed: spool.failed,
|
|
877
|
+
exact_fix: spool.failed > 0 || spool.pending > 0 ? spool.exact_fix : null,
|
|
878
|
+
drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
|
|
879
|
+
legacy_namespaces: spool.other_namespaces?.count || 0,
|
|
880
|
+
},
|
|
881
|
+
mcp_tool_profile: mcpToolProfileStatus(payload),
|
|
882
|
+
marrow_endpoint: { host: ENDPOINT_HOST || 'api.getmarrow.ai', non_default: Boolean(ENDPOINT_HOST) },
|
|
883
|
+
};
|
|
853
884
|
}
|
|
854
|
-
|
|
855
|
-
|
|
885
|
+
// [FIX #9] Runtime validation helper for required string params
|
|
886
|
+
function requireString(args, name) {
|
|
887
|
+
const val = args[name];
|
|
888
|
+
if (typeof val !== 'string' || !val.trim()) {
|
|
889
|
+
throw new Error(`"${name}" is required and must be a non-empty string`);
|
|
890
|
+
}
|
|
891
|
+
return val;
|
|
856
892
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
baseUrl: BASE_URL,
|
|
863
|
-
agentId: FLEET_AGENT_ID,
|
|
864
|
-
context: (0, hook_context_1.compactRuntimeContext)(runtime),
|
|
865
|
-
});
|
|
893
|
+
function requireBoolean(args, name) {
|
|
894
|
+
const value = args[name];
|
|
895
|
+
if (typeof value !== 'boolean')
|
|
896
|
+
throw new Error(`"${name}" is required and must be boolean`);
|
|
897
|
+
return value;
|
|
866
898
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
try {
|
|
871
|
-
(0, status_cache_1.writeStatusCache)({
|
|
872
|
-
apiKey: API_KEY,
|
|
873
|
-
baseUrl: BASE_URL,
|
|
874
|
-
agentId: FLEET_AGENT_ID,
|
|
875
|
-
status,
|
|
876
|
-
source,
|
|
877
|
-
});
|
|
899
|
+
const HIGH_RISK_ACTION = /\b(?:billing|credential|database|delete|deploy|destructive|financial|key|merge|migrat(?:e|ion)|payment|production|publish|release|remove|rollback|secret|security|token|truncate|wipe)\b/i;
|
|
900
|
+
function isHighRiskAction(action, type) {
|
|
901
|
+
return HIGH_RISK_ACTION.test(`${String(type || '')} ${String(action || '')}`);
|
|
878
902
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
903
|
+
async function withControlDeadline(operation, options = {}) {
|
|
904
|
+
const toolName = options.toolName || 'marrow_control';
|
|
905
|
+
const configuredTimeoutMs = Number(process.env.MARROW_REQUEST_TIMEOUT_MS);
|
|
906
|
+
const runtimeBudget = toolName === 'marrow_agent_runtime' || toolName === 'marrow_auto.runtime';
|
|
907
|
+
const commitBudget = toolName === 'marrow_commit';
|
|
908
|
+
const timeoutMs = Number.isFinite(configuredTimeoutMs)
|
|
909
|
+
? Math.min(10_000, Math.max(150, Math.floor(configuredTimeoutMs)))
|
|
910
|
+
: commitBudget ? 8_000 : options.highRisk || runtimeBudget ? 4_500 : 4_000;
|
|
911
|
+
const controller = new AbortController();
|
|
912
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
913
|
+
timer.unref?.();
|
|
914
|
+
const startedAt = Date.now();
|
|
890
915
|
try {
|
|
891
|
-
|
|
916
|
+
const result = await operation(controller.signal);
|
|
917
|
+
(0, control_path_state_1.recordControlPathSample)(toolName, Date.now() - startedAt, true);
|
|
918
|
+
return result;
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
(0, control_path_state_1.recordControlPathSample)(toolName, Date.now() - startedAt, false);
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
finally {
|
|
925
|
+
clearTimeout(timer);
|
|
892
926
|
}
|
|
893
|
-
catch { /* ignore */ }
|
|
894
|
-
throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
|
|
895
927
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
928
|
+
function storeRuntimeGuidance(runtime) {
|
|
929
|
+
try {
|
|
930
|
+
(0, guidance_cache_1.writeGuidanceCache)({
|
|
931
|
+
apiKey: API_KEY,
|
|
932
|
+
baseUrl: BASE_URL,
|
|
933
|
+
agentId: FLEET_AGENT_ID,
|
|
934
|
+
context: (0, hook_context_1.compactRuntimeContext)(runtime),
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
catch { /* owner-only cache is best effort */ }
|
|
899
938
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
if (params?.agentId)
|
|
912
|
-
qs.set('agent_id', params.agentId);
|
|
913
|
-
const res = await fetch(`${baseUrl}/v1/memories?${qs.toString()}`, {
|
|
914
|
-
headers: {
|
|
915
|
-
Authorization: `Bearer ${apiKey}`,
|
|
916
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
917
|
-
},
|
|
918
|
-
});
|
|
919
|
-
const json = await safeMemoryResponse(res);
|
|
920
|
-
return json.data?.memories || [];
|
|
921
|
-
}
|
|
922
|
-
async function marrowGetMemory(apiKey, baseUrl, id, sessionId) {
|
|
923
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
924
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
925
|
-
headers: {
|
|
926
|
-
Authorization: `Bearer ${apiKey}`,
|
|
927
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
928
|
-
},
|
|
929
|
-
});
|
|
930
|
-
const json = await safeMemoryResponse(res);
|
|
931
|
-
return json.data?.memory || null;
|
|
932
|
-
}
|
|
933
|
-
async function marrowUpdateMemory(apiKey, baseUrl, id, patch, sessionId) {
|
|
934
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
935
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
936
|
-
method: 'PATCH',
|
|
937
|
-
headers: {
|
|
938
|
-
Authorization: `Bearer ${apiKey}`,
|
|
939
|
-
'Content-Type': 'application/json',
|
|
940
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
941
|
-
},
|
|
942
|
-
body: JSON.stringify(patch),
|
|
943
|
-
});
|
|
944
|
-
const json = await safeMemoryResponse(res);
|
|
945
|
-
return json.data?.memory;
|
|
946
|
-
}
|
|
947
|
-
async function marrowDeleteMemory(apiKey, baseUrl, id, meta, sessionId) {
|
|
948
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
949
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
950
|
-
method: 'DELETE',
|
|
951
|
-
headers: {
|
|
952
|
-
Authorization: `Bearer ${apiKey}`,
|
|
953
|
-
'Content-Type': 'application/json',
|
|
954
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
955
|
-
},
|
|
956
|
-
body: JSON.stringify(meta || {}),
|
|
957
|
-
});
|
|
958
|
-
const json = await safeMemoryResponse(res);
|
|
959
|
-
return json.data?.memory;
|
|
960
|
-
}
|
|
961
|
-
async function marrowMarkOutdated(apiKey, baseUrl, id, meta, sessionId) {
|
|
962
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
963
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/outdated`, {
|
|
964
|
-
method: 'POST',
|
|
965
|
-
headers: {
|
|
966
|
-
Authorization: `Bearer ${apiKey}`,
|
|
967
|
-
'Content-Type': 'application/json',
|
|
968
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
969
|
-
},
|
|
970
|
-
body: JSON.stringify(meta || {}),
|
|
971
|
-
});
|
|
972
|
-
const json = await safeMemoryResponse(res);
|
|
973
|
-
return json.data?.memory;
|
|
974
|
-
}
|
|
975
|
-
async function marrowSupersedeMemory(apiKey, baseUrl, id, replacement, sessionId) {
|
|
976
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
977
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/supersede`, {
|
|
978
|
-
method: 'POST',
|
|
979
|
-
headers: {
|
|
980
|
-
Authorization: `Bearer ${apiKey}`,
|
|
981
|
-
'Content-Type': 'application/json',
|
|
982
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
983
|
-
},
|
|
984
|
-
body: JSON.stringify(replacement),
|
|
985
|
-
});
|
|
986
|
-
const json = await safeMemoryResponse(res);
|
|
987
|
-
return json.data;
|
|
988
|
-
}
|
|
989
|
-
async function marrowShareMemory(apiKey, baseUrl, id, agentIds, actor, sessionId) {
|
|
990
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
991
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/share`, {
|
|
992
|
-
method: 'POST',
|
|
993
|
-
headers: {
|
|
994
|
-
Authorization: `Bearer ${apiKey}`,
|
|
995
|
-
'Content-Type': 'application/json',
|
|
996
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
997
|
-
},
|
|
998
|
-
body: JSON.stringify({ agent_ids: agentIds, actor }),
|
|
999
|
-
});
|
|
1000
|
-
const json = await safeMemoryResponse(res);
|
|
1001
|
-
return json.data?.memory;
|
|
1002
|
-
}
|
|
1003
|
-
async function marrowExportMemories(apiKey, baseUrl, params, sessionId) {
|
|
1004
|
-
const qs = new URLSearchParams();
|
|
1005
|
-
if (params?.format)
|
|
1006
|
-
qs.set('format', params.format);
|
|
1007
|
-
if (params?.status)
|
|
1008
|
-
qs.set('status', params.status);
|
|
1009
|
-
if (params?.tags)
|
|
1010
|
-
qs.set('tags', params.tags);
|
|
1011
|
-
const res = await fetch(`${baseUrl}/v1/memories/export?${qs.toString()}`, {
|
|
1012
|
-
headers: {
|
|
1013
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1014
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1015
|
-
},
|
|
1016
|
-
});
|
|
1017
|
-
const json = await safeMemoryResponse(res);
|
|
1018
|
-
return json.data;
|
|
1019
|
-
}
|
|
1020
|
-
async function marrowImportMemories(apiKey, baseUrl, memories, mode, sessionId) {
|
|
1021
|
-
const res = await fetch(`${baseUrl}/v1/memories/import`, {
|
|
1022
|
-
method: 'POST',
|
|
1023
|
-
headers: {
|
|
1024
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1025
|
-
'Content-Type': 'application/json',
|
|
1026
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1027
|
-
},
|
|
1028
|
-
body: JSON.stringify({ memories, mode }),
|
|
1029
|
-
});
|
|
1030
|
-
const json = await safeMemoryResponse(res);
|
|
1031
|
-
return json.data;
|
|
1032
|
-
}
|
|
1033
|
-
async function marrowRetrieveMemories(apiKey, baseUrl, query, params, sessionId) {
|
|
1034
|
-
const qs = new URLSearchParams();
|
|
1035
|
-
qs.set('q', query);
|
|
1036
|
-
if (params?.limit)
|
|
1037
|
-
qs.set('limit', String(params.limit));
|
|
1038
|
-
if (params?.from)
|
|
1039
|
-
qs.set('from', params.from);
|
|
1040
|
-
if (params?.to)
|
|
1041
|
-
qs.set('to', params.to);
|
|
1042
|
-
if (params?.tags)
|
|
1043
|
-
qs.set('tags', params.tags);
|
|
1044
|
-
if (params?.source)
|
|
1045
|
-
qs.set('source', params.source);
|
|
1046
|
-
if (params?.status)
|
|
1047
|
-
qs.set('status', params.status);
|
|
1048
|
-
if (params?.shared !== undefined)
|
|
1049
|
-
qs.set('shared', String(params.shared));
|
|
1050
|
-
const res = await fetch(`${baseUrl}/v1/memories/retrieve?${qs.toString()}`, {
|
|
1051
|
-
headers: {
|
|
1052
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1053
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1054
|
-
},
|
|
1055
|
-
});
|
|
1056
|
-
const json = await safeMemoryResponse(res);
|
|
1057
|
-
return json.data;
|
|
1058
|
-
}
|
|
1059
|
-
function activeToolProfile() {
|
|
1060
|
-
const configured = process.env.MARROW_TOOL_PROFILE;
|
|
1061
|
-
if (configured === undefined || configured === 'primary')
|
|
1062
|
-
return 'primary';
|
|
1063
|
-
if (configured === 'core' || configured === 'full')
|
|
1064
|
-
return configured;
|
|
1065
|
-
throw new Error('Invalid MARROW_TOOL_PROFILE value. Set MARROW_TOOL_PROFILE=primary, core, or full, '
|
|
1066
|
-
+ 'or unset it for primary; then restart MCP.');
|
|
1067
|
-
}
|
|
1068
|
-
function toolsForProfile(profile) {
|
|
1069
|
-
if (profile === 'full')
|
|
1070
|
-
return tool_catalog_1.TOOLS;
|
|
1071
|
-
const visibleNames = profile === 'primary' ? tool_catalog_1.PRIMARY_TOOL_NAMES : tool_catalog_1.CORE_TOOL_NAMES;
|
|
1072
|
-
return tool_catalog_1.TOOLS.filter((tool) => visibleNames.has(tool.name));
|
|
1073
|
-
}
|
|
1074
|
-
function advertisedTools() {
|
|
1075
|
-
return toolsForProfile(activeToolProfile());
|
|
1076
|
-
}
|
|
1077
|
-
function toolAllowedByActiveProfile(toolName) {
|
|
1078
|
-
return Boolean(toolName && toolsForProfile(activeToolProfile()).some((tool) => tool.name === toolName));
|
|
1079
|
-
}
|
|
1080
|
-
function recordValue(value) {
|
|
1081
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1082
|
-
? value
|
|
1083
|
-
: null;
|
|
1084
|
-
}
|
|
1085
|
-
function nonNegativeInteger(value) {
|
|
1086
|
-
return Number.isInteger(value) && Number(value) >= 0;
|
|
1087
|
-
}
|
|
1088
|
-
function validatedPrimaryToolAvailability(value) {
|
|
1089
|
-
const projection = recordValue(value);
|
|
1090
|
-
const evidence = recordValue(projection?.entitlement_evidence);
|
|
1091
|
-
const counts = recordValue(projection?.counts);
|
|
1092
|
-
const tools = projection?.tools;
|
|
1093
|
-
const expectedNames = [...tool_catalog_1.PRIMARY_TOOL_NAMES];
|
|
1094
|
-
if (!projection
|
|
1095
|
-
|| projection.profile !== 'primary'
|
|
1096
|
-
|| (projection.current_plan !== null && typeof projection.current_plan !== 'string')
|
|
1097
|
-
|| typeof projection.owner_management_url !== 'string'
|
|
1098
|
-
|| !evidence
|
|
1099
|
-
|| (evidence.state !== 'available' && evidence.state !== 'unavailable')
|
|
1100
|
-
|| typeof evidence.source !== 'string'
|
|
1101
|
-
|| typeof evidence.authoritative !== 'boolean'
|
|
1102
|
-
|| evidence.authorizing !== false
|
|
1103
|
-
|| !counts
|
|
1104
|
-
|| !nonNegativeInteger(counts.total)
|
|
1105
|
-
|| !nonNegativeInteger(counts.entitled)
|
|
1106
|
-
|| !nonNegativeInteger(counts.upgrade_required)
|
|
1107
|
-
|| !nonNegativeInteger(counts.unavailable)
|
|
1108
|
-
|| counts.total !== expectedNames.length
|
|
1109
|
-
|| counts.entitled + counts.upgrade_required + counts.unavailable !== counts.total
|
|
1110
|
-
|| !Array.isArray(tools)
|
|
1111
|
-
|| tools.length !== expectedNames.length) {
|
|
1112
|
-
return null;
|
|
939
|
+
function storeLastKnownStatus(status, source) {
|
|
940
|
+
try {
|
|
941
|
+
(0, status_cache_1.writeStatusCache)({
|
|
942
|
+
apiKey: API_KEY,
|
|
943
|
+
baseUrl: BASE_URL,
|
|
944
|
+
agentId: FLEET_AGENT_ID,
|
|
945
|
+
status,
|
|
946
|
+
source,
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
catch { /* owner-only cache is best effort */ }
|
|
1113
950
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
951
|
+
function refreshStatusInBackground() {
|
|
952
|
+
void (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID)
|
|
953
|
+
.then((status) => storeLastKnownStatus(status, 'status'))
|
|
954
|
+
.catch(() => undefined);
|
|
955
|
+
}
|
|
956
|
+
// [FIX #6 & #7] Safe JSON response helper for memory API functions
|
|
957
|
+
async function safeMemoryResponse(res) {
|
|
958
|
+
if (!res.ok) {
|
|
959
|
+
let detail = '';
|
|
960
|
+
try {
|
|
961
|
+
detail = await res.text();
|
|
962
|
+
}
|
|
963
|
+
catch { /* ignore */ }
|
|
964
|
+
throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
|
|
965
|
+
}
|
|
966
|
+
const json = await res.json();
|
|
967
|
+
if (json.error) {
|
|
968
|
+
throw new Error(json.error);
|
|
969
|
+
}
|
|
970
|
+
return json;
|
|
971
|
+
}
|
|
972
|
+
// Memory API functions — all patched with safeMemoryResponse and validatePathParam
|
|
973
|
+
async function marrowListMemories(apiKey, baseUrl, params, sessionId) {
|
|
974
|
+
const qs = new URLSearchParams();
|
|
975
|
+
if (params?.status)
|
|
976
|
+
qs.set('status', params.status);
|
|
977
|
+
if (params?.query)
|
|
978
|
+
qs.set('query', params.query);
|
|
979
|
+
if (params?.limit)
|
|
980
|
+
qs.set('limit', String(params.limit));
|
|
981
|
+
if (params?.agentId)
|
|
982
|
+
qs.set('agent_id', params.agentId);
|
|
983
|
+
const res = await fetch(`${baseUrl}/v1/memories?${qs.toString()}`, {
|
|
984
|
+
headers: {
|
|
985
|
+
Authorization: `Bearer ${apiKey}`,
|
|
986
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
987
|
+
},
|
|
988
|
+
});
|
|
989
|
+
const json = await safeMemoryResponse(res);
|
|
990
|
+
return json.data?.memories || [];
|
|
991
|
+
}
|
|
992
|
+
async function marrowGetMemory(apiKey, baseUrl, id, sessionId) {
|
|
993
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
994
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
995
|
+
headers: {
|
|
996
|
+
Authorization: `Bearer ${apiKey}`,
|
|
997
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
998
|
+
},
|
|
999
|
+
});
|
|
1000
|
+
const json = await safeMemoryResponse(res);
|
|
1001
|
+
return json.data?.memory || null;
|
|
1002
|
+
}
|
|
1003
|
+
async function marrowUpdateMemory(apiKey, baseUrl, id, patch, sessionId) {
|
|
1004
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1005
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
1006
|
+
method: 'PATCH',
|
|
1007
|
+
headers: {
|
|
1008
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1009
|
+
'Content-Type': 'application/json',
|
|
1010
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1011
|
+
},
|
|
1012
|
+
body: JSON.stringify(patch),
|
|
1013
|
+
});
|
|
1014
|
+
const json = await safeMemoryResponse(res);
|
|
1015
|
+
return json.data?.memory;
|
|
1016
|
+
}
|
|
1017
|
+
async function marrowDeleteMemory(apiKey, baseUrl, id, meta, sessionId) {
|
|
1018
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1019
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
1020
|
+
method: 'DELETE',
|
|
1021
|
+
headers: {
|
|
1022
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1023
|
+
'Content-Type': 'application/json',
|
|
1024
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify(meta || {}),
|
|
1027
|
+
});
|
|
1028
|
+
const json = await safeMemoryResponse(res);
|
|
1029
|
+
return json.data?.memory;
|
|
1030
|
+
}
|
|
1031
|
+
async function marrowMarkOutdated(apiKey, baseUrl, id, meta, sessionId) {
|
|
1032
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1033
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/outdated`, {
|
|
1034
|
+
method: 'POST',
|
|
1035
|
+
headers: {
|
|
1036
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1037
|
+
'Content-Type': 'application/json',
|
|
1038
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1039
|
+
},
|
|
1040
|
+
body: JSON.stringify(meta || {}),
|
|
1041
|
+
});
|
|
1042
|
+
const json = await safeMemoryResponse(res);
|
|
1043
|
+
return json.data?.memory;
|
|
1044
|
+
}
|
|
1045
|
+
async function marrowSupersedeMemory(apiKey, baseUrl, id, replacement, sessionId) {
|
|
1046
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1047
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/supersede`, {
|
|
1048
|
+
method: 'POST',
|
|
1049
|
+
headers: {
|
|
1050
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1051
|
+
'Content-Type': 'application/json',
|
|
1052
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1053
|
+
},
|
|
1054
|
+
body: JSON.stringify(replacement),
|
|
1055
|
+
});
|
|
1056
|
+
const json = await safeMemoryResponse(res);
|
|
1057
|
+
return json.data;
|
|
1058
|
+
}
|
|
1059
|
+
async function marrowShareMemory(apiKey, baseUrl, id, agentIds, actor, sessionId) {
|
|
1060
|
+
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1061
|
+
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/share`, {
|
|
1062
|
+
method: 'POST',
|
|
1063
|
+
headers: {
|
|
1064
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1065
|
+
'Content-Type': 'application/json',
|
|
1066
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1067
|
+
},
|
|
1068
|
+
body: JSON.stringify({ agent_ids: agentIds, actor }),
|
|
1069
|
+
});
|
|
1070
|
+
const json = await safeMemoryResponse(res);
|
|
1071
|
+
return json.data?.memory;
|
|
1072
|
+
}
|
|
1073
|
+
async function marrowExportMemories(apiKey, baseUrl, params, sessionId) {
|
|
1074
|
+
const qs = new URLSearchParams();
|
|
1075
|
+
if (params?.format)
|
|
1076
|
+
qs.set('format', params.format);
|
|
1077
|
+
if (params?.status)
|
|
1078
|
+
qs.set('status', params.status);
|
|
1079
|
+
if (params?.tags)
|
|
1080
|
+
qs.set('tags', params.tags);
|
|
1081
|
+
const res = await fetch(`${baseUrl}/v1/memories/export?${qs.toString()}`, {
|
|
1082
|
+
headers: {
|
|
1083
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1084
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1085
|
+
},
|
|
1086
|
+
});
|
|
1087
|
+
const json = await safeMemoryResponse(res);
|
|
1088
|
+
return json.data;
|
|
1089
|
+
}
|
|
1090
|
+
async function marrowImportMemories(apiKey, baseUrl, memories, mode, sessionId) {
|
|
1091
|
+
const res = await fetch(`${baseUrl}/v1/memories/import`, {
|
|
1092
|
+
method: 'POST',
|
|
1093
|
+
headers: {
|
|
1094
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1095
|
+
'Content-Type': 'application/json',
|
|
1096
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1097
|
+
},
|
|
1098
|
+
body: JSON.stringify({ memories, mode }),
|
|
1099
|
+
});
|
|
1100
|
+
const json = await safeMemoryResponse(res);
|
|
1101
|
+
return json.data;
|
|
1102
|
+
}
|
|
1103
|
+
async function marrowRetrieveMemories(apiKey, baseUrl, query, params, sessionId) {
|
|
1104
|
+
const qs = new URLSearchParams();
|
|
1105
|
+
// The query leaves the process redacted, like every other tool's text.
|
|
1106
|
+
qs.set('q', (0, redact_1.redactSensitiveText)(query));
|
|
1107
|
+
if (params?.limit)
|
|
1108
|
+
qs.set('limit', String(params.limit));
|
|
1109
|
+
if (params?.from)
|
|
1110
|
+
qs.set('from', params.from);
|
|
1111
|
+
if (params?.to)
|
|
1112
|
+
qs.set('to', params.to);
|
|
1113
|
+
if (params?.tags)
|
|
1114
|
+
qs.set('tags', params.tags);
|
|
1115
|
+
if (params?.source)
|
|
1116
|
+
qs.set('source', params.source);
|
|
1117
|
+
if (params?.status)
|
|
1118
|
+
qs.set('status', params.status);
|
|
1119
|
+
if (params?.shared !== undefined)
|
|
1120
|
+
qs.set('shared', String(params.shared));
|
|
1121
|
+
const res = await fetch(`${baseUrl}/v1/memories/retrieve?${qs.toString()}`, {
|
|
1122
|
+
headers: {
|
|
1123
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1124
|
+
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1125
|
+
},
|
|
1126
|
+
});
|
|
1127
|
+
const json = await safeMemoryResponse(res);
|
|
1128
|
+
return json.data;
|
|
1129
|
+
}
|
|
1130
|
+
function activeToolProfile() {
|
|
1131
|
+
const configured = process.env.MARROW_TOOL_PROFILE;
|
|
1132
|
+
if (configured === undefined || configured === 'primary')
|
|
1133
|
+
return 'primary';
|
|
1134
|
+
if (configured === 'core' || configured === 'full')
|
|
1135
|
+
return configured;
|
|
1136
|
+
throw new Error('Invalid MARROW_TOOL_PROFILE value. Set MARROW_TOOL_PROFILE=primary, core, or full, '
|
|
1137
|
+
+ 'or unset it for primary; then restart MCP.');
|
|
1138
|
+
}
|
|
1139
|
+
function toolsForProfile(profile) {
|
|
1140
|
+
if (profile === 'full')
|
|
1141
|
+
return tool_catalog_1.TOOLS;
|
|
1142
|
+
const visibleNames = profile === 'primary' ? tool_catalog_1.PRIMARY_TOOL_NAMES : tool_catalog_1.CORE_TOOL_NAMES;
|
|
1143
|
+
return tool_catalog_1.TOOLS.filter((tool) => visibleNames.has(tool.name));
|
|
1144
|
+
}
|
|
1145
|
+
function advertisedTools() {
|
|
1146
|
+
return toolsForProfile(activeToolProfile());
|
|
1147
|
+
}
|
|
1148
|
+
function toolAllowedByActiveProfile(toolName) {
|
|
1149
|
+
return Boolean(toolName && toolsForProfile(activeToolProfile()).some((tool) => tool.name === toolName));
|
|
1150
|
+
}
|
|
1151
|
+
function recordValue(value) {
|
|
1152
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1153
|
+
? value
|
|
1154
|
+
: null;
|
|
1155
|
+
}
|
|
1156
|
+
function nonNegativeInteger(value) {
|
|
1157
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
1117
1158
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
const
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
||
|
|
1126
|
-
|| (
|
|
1127
|
-
||
|
|
1128
|
-
||
|
|
1159
|
+
function validatedPrimaryToolAvailability(value) {
|
|
1160
|
+
const projection = recordValue(value);
|
|
1161
|
+
const evidence = recordValue(projection?.entitlement_evidence);
|
|
1162
|
+
const counts = recordValue(projection?.counts);
|
|
1163
|
+
const tools = projection?.tools;
|
|
1164
|
+
const expectedNames = [...tool_catalog_1.PRIMARY_TOOL_NAMES];
|
|
1165
|
+
if (!projection
|
|
1166
|
+
|| projection.profile !== 'primary'
|
|
1167
|
+
|| (projection.current_plan !== null && typeof projection.current_plan !== 'string')
|
|
1168
|
+
|| typeof projection.owner_management_url !== 'string'
|
|
1169
|
+
|| !evidence
|
|
1170
|
+
|| (evidence.state !== 'available' && evidence.state !== 'unavailable')
|
|
1171
|
+
|| typeof evidence.source !== 'string'
|
|
1172
|
+
|| typeof evidence.authoritative !== 'boolean'
|
|
1173
|
+
|| evidence.authorizing !== false
|
|
1174
|
+
|| !counts
|
|
1175
|
+
|| !nonNegativeInteger(counts.total)
|
|
1176
|
+
|| !nonNegativeInteger(counts.entitled)
|
|
1177
|
+
|| !nonNegativeInteger(counts.upgrade_required)
|
|
1178
|
+
|| !nonNegativeInteger(counts.unavailable)
|
|
1179
|
+
|| counts.total !== expectedNames.length
|
|
1180
|
+
|| counts.entitled + counts.upgrade_required + counts.unavailable !== counts.total
|
|
1181
|
+
|| !Array.isArray(tools)
|
|
1182
|
+
|| tools.length !== expectedNames.length) {
|
|
1129
1183
|
return null;
|
|
1130
1184
|
}
|
|
1131
|
-
|
|
1185
|
+
if ((evidence.state === 'available' && evidence.authoritative !== true)
|
|
1186
|
+
|| (evidence.state === 'unavailable' && evidence.authoritative !== false)) {
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
const derivedCounts = { entitled: 0, upgrade_required: 0, unavailable: 0 };
|
|
1190
|
+
for (let index = 0; index < tools.length; index++) {
|
|
1191
|
+
const tool = recordValue(tools[index]);
|
|
1192
|
+
const state = tool?.state;
|
|
1193
|
+
if (!tool
|
|
1194
|
+
|| tool.name !== expectedNames[index]
|
|
1195
|
+
|| (state !== 'entitled' && state !== 'upgrade_required' && state !== 'unavailable')
|
|
1196
|
+
|| typeof tool.always_available !== 'boolean'
|
|
1197
|
+
|| (tool.plan_feature !== null && typeof tool.plan_feature !== 'string')
|
|
1198
|
+
|| (tool.minimum_plan !== null && typeof tool.minimum_plan !== 'string')
|
|
1199
|
+
|| typeof tool.owner_management_url !== 'string') {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
derivedCounts[state]++;
|
|
1203
|
+
}
|
|
1204
|
+
if (derivedCounts.entitled !== counts.entitled
|
|
1205
|
+
|| derivedCounts.upgrade_required !== counts.upgrade_required
|
|
1206
|
+
|| derivedCounts.unavailable !== counts.unavailable) {
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
return {
|
|
1210
|
+
projection,
|
|
1211
|
+
evidenceState: evidence.state,
|
|
1212
|
+
};
|
|
1132
1213
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1214
|
+
function backendPrimaryToolAvailability(value) {
|
|
1215
|
+
const payload = recordValue(value);
|
|
1216
|
+
if (!payload)
|
|
1217
|
+
return null;
|
|
1218
|
+
const data = recordValue(payload.data);
|
|
1219
|
+
const status = recordValue(payload.status);
|
|
1220
|
+
const entitlements = recordValue(payload.entitlements);
|
|
1221
|
+
const entitlementProjection = recordValue(payload.entitlement_projection);
|
|
1222
|
+
const statusEntitlements = recordValue(status?.entitlements);
|
|
1223
|
+
const statusProjection = recordValue(status?.entitlement_projection);
|
|
1224
|
+
const candidates = [
|
|
1225
|
+
payload.primary_tool_availability,
|
|
1226
|
+
data?.primary_tool_availability,
|
|
1227
|
+
status?.primary_tool_availability,
|
|
1228
|
+
entitlements?.primary_tool_availability,
|
|
1229
|
+
entitlementProjection?.primary_tool_availability,
|
|
1230
|
+
statusEntitlements?.primary_tool_availability,
|
|
1231
|
+
statusProjection?.primary_tool_availability,
|
|
1232
|
+
];
|
|
1233
|
+
for (const candidate of candidates) {
|
|
1234
|
+
const validated = validatedPrimaryToolAvailability(candidate);
|
|
1235
|
+
if (validated)
|
|
1236
|
+
return validated;
|
|
1237
|
+
}
|
|
1136
1238
|
return null;
|
|
1137
1239
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
if (validated)
|
|
1165
|
-
return validated;
|
|
1240
|
+
function mcpToolProfileStatus(value) {
|
|
1241
|
+
const profile = activeToolProfile();
|
|
1242
|
+
const visibleToolNames = toolsForProfile(profile).map((tool) => tool.name);
|
|
1243
|
+
const payload = recordValue(value);
|
|
1244
|
+
const cachedOrStale = payload?.cached === true
|
|
1245
|
+
|| payload?.stale === true
|
|
1246
|
+
|| payload?.status_freshness === 'stale';
|
|
1247
|
+
const backendProjection = cachedOrStale ? null : backendPrimaryToolAvailability(payload);
|
|
1248
|
+
const backendProjectionAvailable = backendProjection?.evidenceState === 'available';
|
|
1249
|
+
return {
|
|
1250
|
+
configured_profile: process.env.MARROW_TOOL_PROFILE ?? 'unset',
|
|
1251
|
+
effective_profile: profile,
|
|
1252
|
+
visible_tool_count: visibleToolNames.length,
|
|
1253
|
+
visible_tool_names: visibleToolNames,
|
|
1254
|
+
local_visibility_grants_entitlement: false,
|
|
1255
|
+
backend_entitlement_projection: {
|
|
1256
|
+
evidence_state: backendProjectionAvailable ? 'available' : 'unavailable',
|
|
1257
|
+
source: cachedOrStale
|
|
1258
|
+
? 'cached_or_stale_status'
|
|
1259
|
+
: backendProjection
|
|
1260
|
+
? 'authenticated_backend'
|
|
1261
|
+
: 'backend_projection_not_provided',
|
|
1262
|
+
authorizes_calls: false,
|
|
1263
|
+
primary_tool_availability: backendProjectionAvailable ? backendProjection.projection : null,
|
|
1264
|
+
},
|
|
1265
|
+
};
|
|
1166
1266
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const cachedOrStale = payload?.cached === true
|
|
1174
|
-
|| payload?.stale === true
|
|
1175
|
-
|| payload?.status_freshness === 'stale';
|
|
1176
|
-
const backendProjection = cachedOrStale ? null : backendPrimaryToolAvailability(payload);
|
|
1177
|
-
const backendProjectionAvailable = backendProjection?.evidenceState === 'available';
|
|
1178
|
-
return {
|
|
1179
|
-
configured_profile: process.env.MARROW_TOOL_PROFILE ?? 'unset',
|
|
1180
|
-
effective_profile: profile,
|
|
1181
|
-
visible_tool_count: visibleToolNames.length,
|
|
1182
|
-
visible_tool_names: visibleToolNames,
|
|
1183
|
-
local_visibility_grants_entitlement: false,
|
|
1184
|
-
backend_entitlement_projection: {
|
|
1185
|
-
evidence_state: backendProjectionAvailable ? 'available' : 'unavailable',
|
|
1186
|
-
source: cachedOrStale
|
|
1187
|
-
? 'cached_or_stale_status'
|
|
1188
|
-
: backendProjection
|
|
1189
|
-
? 'authenticated_backend'
|
|
1190
|
-
: 'backend_projection_not_provided',
|
|
1191
|
-
authorizes_calls: false,
|
|
1192
|
-
primary_tool_availability: backendProjectionAvailable ? backendProjection.projection : null,
|
|
1193
|
-
},
|
|
1194
|
-
};
|
|
1195
|
-
}
|
|
1196
|
-
function withMcpToolProfileStatus(value) {
|
|
1197
|
-
const payload = recordValue(value) || { data: value };
|
|
1198
|
-
return {
|
|
1199
|
-
...payload,
|
|
1200
|
-
mcp_tool_profile: mcpToolProfileStatus(payload),
|
|
1201
|
-
};
|
|
1202
|
-
}
|
|
1203
|
-
// Request handler
|
|
1204
|
-
async function handleRequest(req) {
|
|
1205
|
-
const { id, method, params } = req;
|
|
1206
|
-
const responseStartedAt = performance.now();
|
|
1207
|
-
// [FIX #15] Enforce initialize-first per MCP spec
|
|
1208
|
-
if (!initialized && method !== 'initialize') {
|
|
1209
|
-
error(id, -32002, 'Server not initialized. Send initialize first.');
|
|
1210
|
-
return;
|
|
1267
|
+
function withMcpToolProfileStatus(value) {
|
|
1268
|
+
const payload = recordValue(value) || { data: value };
|
|
1269
|
+
return {
|
|
1270
|
+
...payload,
|
|
1271
|
+
mcp_tool_profile: mcpToolProfileStatus(payload),
|
|
1272
|
+
};
|
|
1211
1273
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
capabilities: { tools: {}, prompts: {} },
|
|
1220
|
-
serverInfo: { name: 'marrow', version: hook_contract_2.MCP_ADAPTER_VERSION },
|
|
1221
|
-
...(AUTO_ENROLL ? {
|
|
1222
|
-
instructions: `Use marrow_agent_runtime before consequential actions and follow its gate, proof and approval contract. Reuse runtime.decision_id when completion_contract identifies a created decision; keep runtime.runtime_authorization.id separate as gate_receipt_id. Call marrow_think only when decision creation is required. Close outcomes with marrow_commit; only committed:true confirms closure. ${activeToolProfile() === 'primary' ? 'The default primary profile exposes 17 tools. Use marrow_agent_status for status and marrow_fleet_lessons for prior lessons.' : 'This explicitly selected core/full profile also provides marrow_auto, marrow_status and marrow_ask. Resume pending auto only after retry_after_ms with the same operation_id and unchanged scope, outcome, proof and receipts.'} An advisory risk_gate.enforced:false is not live enforcement; review_required or block still does not grant permission. Infrastructure failures are not policy denials; continue only low-risk reversible work from returned outage-safe guidance and require a fresh gate for high-risk work. ${(0, host_capability_1.hostCapabilityInstructions)(hostCapability)}`,
|
|
1223
|
-
} : {}),
|
|
1224
|
-
_meta: { host_capability: hostCapability },
|
|
1225
|
-
});
|
|
1226
|
-
// Auto-enroll: emit enrollment notification on connection
|
|
1227
|
-
if (AUTO_ENROLL) {
|
|
1228
|
-
send({
|
|
1229
|
-
jsonrpc: '2.0',
|
|
1230
|
-
method: 'notifications/message',
|
|
1231
|
-
params: {
|
|
1232
|
-
level: 'info',
|
|
1233
|
-
logger: 'marrow',
|
|
1234
|
-
data: {
|
|
1235
|
-
type: 'auto_enroll',
|
|
1236
|
-
message: `Marrow operating contract available. MCP tools are on demand. ${hostCapability.exact_next_action}`,
|
|
1237
|
-
agentId: FLEET_AGENT_ID,
|
|
1238
|
-
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1239
|
-
host_capability: hostCapability,
|
|
1240
|
-
},
|
|
1241
|
-
},
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1274
|
+
// Request handler
|
|
1275
|
+
async function handleRequest(req) {
|
|
1276
|
+
const { id, method, params } = req;
|
|
1277
|
+
const responseStartedAt = performance.now();
|
|
1278
|
+
// [FIX #15] Enforce initialize-first per MCP spec
|
|
1279
|
+
if (!initialized && method !== 'initialize') {
|
|
1280
|
+
error(id, -32002, 'Server not initialized. Send initialize first.');
|
|
1244
1281
|
return;
|
|
1245
1282
|
}
|
|
1246
|
-
|
|
1247
|
-
if (
|
|
1283
|
+
try {
|
|
1284
|
+
if (method === 'initialize') {
|
|
1285
|
+
initialized = true;
|
|
1286
|
+
const hostCapability = mcpHostCapability();
|
|
1287
|
+
const protocolVersion = noteClientCapabilities(params);
|
|
1248
1288
|
success(id, {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
],
|
|
1289
|
+
protocolVersion,
|
|
1290
|
+
capabilities: { tools: {}, prompts: {} },
|
|
1291
|
+
serverInfo: { name: 'marrow', version: hook_contract_2.MCP_ADAPTER_VERSION },
|
|
1292
|
+
...(AUTO_ENROLL ? {
|
|
1293
|
+
instructions: `Use marrow_agent_runtime before consequential actions and follow its gate, proof and approval contract. Reuse runtime.decision_id when completion_contract identifies a created decision; keep runtime.runtime_authorization.id separate as gate_receipt_id. Call marrow_think only when decision creation is required. Close outcomes with marrow_commit; only committed:true confirms closure. ${activeToolProfile() === 'primary' ? 'The default primary profile exposes 17 tools. Use marrow_agent_status for status and marrow_fleet_lessons for prior lessons.' : 'This explicitly selected core/full profile also provides marrow_auto, marrow_status and marrow_ask. Resume pending auto only after retry_after_ms with the same operation_id and unchanged scope, outcome, proof and receipts.'} An advisory risk_gate.enforced:false is not live enforcement; review_required or block still does not grant permission. Infrastructure failures are not policy denials; continue only low-risk reversible work from returned outage-safe guidance and require a fresh gate for high-risk work. ${(0, host_capability_1.hostCapabilityInstructions)(hostCapability)}`,
|
|
1294
|
+
} : {}),
|
|
1295
|
+
_meta: { host_capability: hostCapability },
|
|
1257
1296
|
});
|
|
1297
|
+
// Auto-enroll: emit enrollment notification on connection
|
|
1298
|
+
if (AUTO_ENROLL) {
|
|
1299
|
+
send({
|
|
1300
|
+
jsonrpc: '2.0',
|
|
1301
|
+
method: 'notifications/message',
|
|
1302
|
+
params: {
|
|
1303
|
+
level: 'info',
|
|
1304
|
+
logger: 'marrow',
|
|
1305
|
+
data: {
|
|
1306
|
+
type: 'auto_enroll',
|
|
1307
|
+
message: `Marrow operating contract available. MCP tools are on demand. ${hostCapability.exact_next_action}`,
|
|
1308
|
+
agentId: FLEET_AGENT_ID,
|
|
1309
|
+
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1310
|
+
host_capability: hostCapability,
|
|
1311
|
+
},
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
return;
|
|
1258
1316
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1317
|
+
if (method === 'prompts/list') {
|
|
1318
|
+
if (AUTO_ENROLL) {
|
|
1319
|
+
success(id, {
|
|
1320
|
+
prompts: [
|
|
1321
|
+
{
|
|
1322
|
+
name: 'marrow-always-on',
|
|
1323
|
+
description: 'Marrow control and proof contract. MCP tools are on demand; host activity is client-self-reported and coverage remains unverified without independent authority.',
|
|
1324
|
+
arguments: [],
|
|
1325
|
+
_meta: { host_capability: mcpHostCapability() },
|
|
1326
|
+
},
|
|
1327
|
+
],
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
success(id, { prompts: [] });
|
|
1332
|
+
}
|
|
1268
1333
|
return;
|
|
1269
1334
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1335
|
+
if (method === 'prompts/get') {
|
|
1336
|
+
const promptName = params?.name;
|
|
1337
|
+
if (promptName !== 'marrow-always-on' || !AUTO_ENROLL) {
|
|
1338
|
+
error(id, -32602, 'Unknown prompt');
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
const hostCapability = mcpHostCapability();
|
|
1342
|
+
success(id, {
|
|
1343
|
+
description: 'Marrow control and proof contract — MCP tools are on demand, host activity is client-self-reported, and coverage is unverified without independent authority',
|
|
1344
|
+
_meta: { host_capability: hostCapability },
|
|
1345
|
+
messages: [
|
|
1346
|
+
{
|
|
1347
|
+
role: 'user',
|
|
1348
|
+
content: {
|
|
1349
|
+
type: 'text',
|
|
1350
|
+
text: `You have Marrow — the agent control and proof layer around this workflow.
|
|
1280
1351
|
|
|
1281
1352
|
## Capability-qualified operating contract
|
|
1282
1353
|
|
|
@@ -1307,995 +1378,996 @@ Use marrow_ask for authorized history questions such as:
|
|
|
1307
1378
|
- "what worked last time we published?"
|
|
1308
1379
|
|
|
1309
1380
|
Marrow is not a replacement agent or a standalone memory app. Context and prior lessons support the control loop; policy before action, proof after action, accountable outcomes, and fleet improvement are the product.`,
|
|
1381
|
+
},
|
|
1310
1382
|
},
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
});
|
|
1314
|
-
return;
|
|
1315
|
-
}
|
|
1316
|
-
if (method === 'tools/list') {
|
|
1317
|
-
success(id, { tools: advertisedTools() });
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
if (method === 'tools/call') {
|
|
1321
|
-
const toolName = params?.name;
|
|
1322
|
-
const args = (params?.arguments || {});
|
|
1323
|
-
if (!toolAllowedByActiveProfile(toolName)) {
|
|
1324
|
-
const repair = toolName && tool_catalog_1.PRIMARY_TOOL_NAMES.has(toolName)
|
|
1325
|
-
? 'Unset MARROW_TOOL_PROFILE or set MARROW_TOOL_PROFILE=primary, then restart MCP.'
|
|
1326
|
-
: 'Set MARROW_TOOL_PROFILE=full, then restart MCP for the complete advanced/legacy catalog.';
|
|
1327
|
-
error(id, -32601, `Tool is not available in the active Marrow tool profile. ${repair}`);
|
|
1383
|
+
],
|
|
1384
|
+
});
|
|
1328
1385
|
return;
|
|
1329
1386
|
}
|
|
1330
|
-
if (
|
|
1331
|
-
|
|
1332
|
-
const wantAutoWarn = args.autoWarn ?? true;
|
|
1333
|
-
const taskType = args.taskType;
|
|
1334
|
-
const result = await withControlDeadline((signal) => (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType, autoWarn: wantAutoWarn }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: isHighRiskAction(`Orient before ${taskType || 'general'} work`, taskType), toolName: 'marrow_orient' });
|
|
1335
|
-
if (AUTO_ENROLL && orientCallCount === 1) {
|
|
1336
|
-
const enrollmentText = `\n\n**Marrow control and proof active**\n\n` +
|
|
1337
|
-
`Marrow applies relevant policy and prior lessons before consequential actions, then records evidence and the real outcome afterward.\n\n` +
|
|
1338
|
-
`1. Respect allow, warn, review_required, and block decisions before acting.\n` +
|
|
1339
|
-
`2. Use marrow_agent_runtime when a risky action needs an explicit gate.\n` +
|
|
1340
|
-
`3. Close meaningful work with marrow_commit; a tool exit alone is not outcome proof.\n` +
|
|
1341
|
-
`4. Use marrow_decision_trace to explain the prior failure, lesson, gate, proof, workflow, and outcome path.\n\n` +
|
|
1342
|
-
`${(0, host_capability_1.hostCapabilityInstructions)(mcpHostCapability())}\n\nLeave unknown outcomes pending instead of inventing success.\n`;
|
|
1343
|
-
const orientText = JSON.stringify(result, null, 2);
|
|
1344
|
-
success(id, {
|
|
1345
|
-
content: [{ type: 'text', text: enrollmentText + orientText }],
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
else {
|
|
1349
|
-
success(id, {
|
|
1350
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1387
|
+
if (method === 'tools/list') {
|
|
1388
|
+
success(id, { tools: advertisedTools() });
|
|
1353
1389
|
return;
|
|
1354
1390
|
}
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
1357
|
-
const
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
previous_outcome: args.previous_outcome,
|
|
1365
|
-
checkLoop: args.checkLoop ?? true,
|
|
1366
|
-
source_kind: args.source_kind,
|
|
1367
|
-
human_directed: args.human_directed,
|
|
1368
|
-
instruction_ref: args.instruction_ref,
|
|
1369
|
-
source_meta: args.source_meta,
|
|
1370
|
-
}, SESSION_ID, FLEET_AGENT_ID, undefined, { idempotencyKey: args.idempotency_key, requestHash: args.request_hash });
|
|
1371
|
-
// Refresh orient warnings every 5th think call
|
|
1372
|
-
thinkCallCount++;
|
|
1373
|
-
if (thinkCallCount % 5 === 0) {
|
|
1374
|
-
refreshOrientWarnings();
|
|
1375
|
-
}
|
|
1376
|
-
// Inject cached orient warnings into intelligence.insights
|
|
1377
|
-
if (cachedOrientWarnings.length > 0) {
|
|
1378
|
-
const existingInsights = result.intelligence?.insights || [];
|
|
1379
|
-
result.intelligence.insights = [
|
|
1380
|
-
...cachedOrientWarnings.map((w) => ({
|
|
1381
|
-
type: 'failure_pattern',
|
|
1382
|
-
summary: w.message,
|
|
1383
|
-
action: `Review past ${w.type} failures before proceeding`,
|
|
1384
|
-
severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
|
|
1385
|
-
count: 0,
|
|
1386
|
-
})),
|
|
1387
|
-
...existingInsights,
|
|
1388
|
-
];
|
|
1391
|
+
if (method === 'tools/call') {
|
|
1392
|
+
const toolName = params?.name;
|
|
1393
|
+
const args = (params?.arguments || {});
|
|
1394
|
+
if (!toolAllowedByActiveProfile(toolName)) {
|
|
1395
|
+
const repair = toolName && tool_catalog_1.PRIMARY_TOOL_NAMES.has(toolName)
|
|
1396
|
+
? 'Unset MARROW_TOOL_PROFILE or set MARROW_TOOL_PROFILE=primary, then restart MCP.'
|
|
1397
|
+
: 'Set MARROW_TOOL_PROFILE=full, then restart MCP for the complete advanced/legacy catalog.';
|
|
1398
|
+
error(id, -32601, `Tool is not available in the active Marrow tool profile. ${repair}`);
|
|
1399
|
+
return;
|
|
1389
1400
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
hostSessionId: SESSION_ID || 'mcp-server',
|
|
1415
|
-
};
|
|
1416
|
-
commitSessionId = (0, host_approval_1.holdSessionForReceipt)(holdContext, args.gate_receipt_id) || SESSION_ID;
|
|
1417
|
-
await (0, host_approval_1.deliverQueuedForReceipt)(holdContext, args.gate_receipt_id).catch(() => undefined);
|
|
1401
|
+
if (toolName === 'marrow_orient') {
|
|
1402
|
+
orientCallCount++;
|
|
1403
|
+
const wantAutoWarn = args.autoWarn ?? true;
|
|
1404
|
+
const taskType = args.taskType;
|
|
1405
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType, autoWarn: wantAutoWarn }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: isHighRiskAction(`Orient before ${taskType || 'general'} work`, taskType), toolName: 'marrow_orient' });
|
|
1406
|
+
if (AUTO_ENROLL && orientCallCount === 1) {
|
|
1407
|
+
const enrollmentText = `\n\n**Marrow control and proof active**\n\n` +
|
|
1408
|
+
`Marrow applies relevant policy and prior lessons before consequential actions, then records evidence and the real outcome afterward.\n\n` +
|
|
1409
|
+
`1. Respect allow, warn, review_required, and block decisions before acting.\n` +
|
|
1410
|
+
`2. Use marrow_agent_runtime when a risky action needs an explicit gate.\n` +
|
|
1411
|
+
`3. Close meaningful work with marrow_commit; a tool exit alone is not outcome proof.\n` +
|
|
1412
|
+
`4. Use marrow_decision_trace to explain the prior failure, lesson, gate, proof, workflow, and outcome path.\n\n` +
|
|
1413
|
+
`${(0, host_capability_1.hostCapabilityInstructions)(mcpHostCapability())}\n\nLeave unknown outcomes pending instead of inventing success.\n`;
|
|
1414
|
+
const orientText = JSON.stringify(result, null, 2);
|
|
1415
|
+
success(id, {
|
|
1416
|
+
content: [{ type: 'text', text: enrollmentText + orientText }],
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
else {
|
|
1420
|
+
success(id, {
|
|
1421
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
return;
|
|
1418
1425
|
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
result = await
|
|
1423
|
-
|
|
1424
|
-
success: commitSuccess,
|
|
1425
|
-
outcome,
|
|
1426
|
-
caused_by: args.caused_by,
|
|
1427
|
-
proof: args.proof,
|
|
1428
|
-
gate_receipt_id: args.gate_receipt_id,
|
|
1429
|
-
arbitration_receipt_id: args.arbitration_receipt_id,
|
|
1430
|
-
owner_approval_receipt_id: args.owner_approval_receipt_id,
|
|
1431
|
-
action: args.action,
|
|
1432
|
-
target: args.target,
|
|
1426
|
+
if (toolName === 'marrow_think') {
|
|
1427
|
+
// [FIX #9] Validate required param
|
|
1428
|
+
const action = requireString(args, 'action');
|
|
1429
|
+
const result = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1430
|
+
action,
|
|
1433
1431
|
type: args.type,
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1432
|
+
context: args.context,
|
|
1433
|
+
previous_decision_id: args.previous_decision_id,
|
|
1434
|
+
previous_success: args.previous_success,
|
|
1435
|
+
previous_outcome: args.previous_outcome,
|
|
1436
|
+
checkLoop: args.checkLoop ?? true,
|
|
1437
|
+
source_kind: args.source_kind,
|
|
1438
|
+
human_directed: args.human_directed,
|
|
1439
|
+
instruction_ref: args.instruction_ref,
|
|
1440
|
+
source_meta: args.source_meta,
|
|
1441
|
+
}, SESSION_ID, FLEET_AGENT_ID, undefined, { idempotencyKey: args.idempotency_key, requestHash: args.request_hash });
|
|
1442
|
+
// Refresh orient warnings every 5th think call
|
|
1443
|
+
thinkCallCount++;
|
|
1444
|
+
if (thinkCallCount % 5 === 0) {
|
|
1445
|
+
refreshOrientWarnings();
|
|
1446
|
+
}
|
|
1447
|
+
// Inject cached orient warnings into intelligence.insights
|
|
1448
|
+
if (cachedOrientWarnings.length > 0) {
|
|
1449
|
+
const existingInsights = result.intelligence?.insights || [];
|
|
1450
|
+
result.intelligence.insights = [
|
|
1451
|
+
...cachedOrientWarnings.map((w) => ({
|
|
1452
|
+
type: 'failure_pattern',
|
|
1453
|
+
summary: w.message,
|
|
1454
|
+
action: `Review past ${w.type} failures before proceeding`,
|
|
1455
|
+
severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
|
|
1456
|
+
count: 0,
|
|
1457
|
+
})),
|
|
1458
|
+
...existingInsights,
|
|
1459
|
+
];
|
|
1460
|
+
}
|
|
1461
|
+
lastDecisionId = result.decision_id;
|
|
1462
|
+
lastCommitted = false;
|
|
1463
|
+
success(id, {
|
|
1464
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1465
|
+
});
|
|
1466
|
+
return;
|
|
1438
1467
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1468
|
+
if (toolName === 'marrow_commit') {
|
|
1469
|
+
// [FIX #9] Validate required params
|
|
1470
|
+
const decision_id = requireString(args, 'decision_id');
|
|
1471
|
+
const outcome = requireString(args, 'outcome');
|
|
1472
|
+
const commitSuccess = requireBoolean(args, 'success');
|
|
1473
|
+
// A receipt a host hook held: commit in the session it was issued to,
|
|
1474
|
+
// and send the operator's queued answer first so the commit closes
|
|
1475
|
+
// with that approval.
|
|
1476
|
+
let commitSessionId = SESSION_ID;
|
|
1477
|
+
if (typeof args.gate_receipt_id === 'string' && args.gate_receipt_id) {
|
|
1478
|
+
const holdContext = {
|
|
1479
|
+
apiKey: API_KEY,
|
|
1480
|
+
baseUrl: BASE_URL,
|
|
1481
|
+
sessionId: SESSION_ID || 'mcp-server',
|
|
1482
|
+
agentId: FLEET_AGENT_ID,
|
|
1483
|
+
harness: 'mcp-client',
|
|
1484
|
+
host: 'other',
|
|
1485
|
+
hostSessionId: SESSION_ID || 'mcp-server',
|
|
1486
|
+
};
|
|
1487
|
+
commitSessionId = (0, host_approval_1.holdSessionForReceipt)(holdContext, args.gate_receipt_id) || SESSION_ID;
|
|
1488
|
+
await (0, host_approval_1.deliverQueuedForReceipt)(holdContext, args.gate_receipt_id).catch(() => undefined);
|
|
1489
|
+
}
|
|
1490
|
+
const proofKeys = [`decision:${decision_id}`, ...(typeof args.gate_receipt_id === 'string' && args.gate_receipt_id ? [`receipt:${args.gate_receipt_id}`] : [])];
|
|
1491
|
+
let result;
|
|
1492
|
+
try {
|
|
1493
|
+
result = await withControlDeadline((signal) => (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1494
|
+
decision_id,
|
|
1495
|
+
success: commitSuccess,
|
|
1496
|
+
outcome,
|
|
1497
|
+
caused_by: args.caused_by,
|
|
1498
|
+
proof: args.proof,
|
|
1499
|
+
gate_receipt_id: args.gate_receipt_id,
|
|
1500
|
+
arbitration_receipt_id: args.arbitration_receipt_id,
|
|
1501
|
+
owner_approval_receipt_id: args.owner_approval_receipt_id,
|
|
1502
|
+
action: args.action,
|
|
1503
|
+
target: args.target,
|
|
1504
|
+
type: args.type,
|
|
1505
|
+
surfaces: args.surfaces,
|
|
1506
|
+
auto_gate: args.auto_gate,
|
|
1507
|
+
model_usage: args.model_usage,
|
|
1508
|
+
}, commitSessionId, FLEET_AGENT_ID, signal), { highRisk: true, cacheAware: false, toolName: 'marrow_commit' });
|
|
1509
|
+
}
|
|
1510
|
+
catch (err) {
|
|
1511
|
+
if (err instanceof request_reliability_1.MarrowRequestError && err.code === 'proof_required' && err.missingFields.length) {
|
|
1512
|
+
for (const key of proofKeys)
|
|
1513
|
+
commitProofMissing.set(key, [...err.missingFields]);
|
|
1514
|
+
}
|
|
1515
|
+
if (err instanceof request_reliability_1.MarrowRequestError && err.status === 409 && err.backendCode === 'MARROW_OUTCOME_OBSERVATION_CONFLICT') {
|
|
1516
|
+
const fromHold = typeof args.gate_receipt_id === 'string' && args.gate_receipt_id
|
|
1517
|
+
? (0, host_approval_1.proofFieldsForReceipt)({ apiKey: API_KEY, baseUrl: BASE_URL, sessionId: SESSION_ID || 'mcp-server', agentId: FLEET_AGENT_ID, harness: 'mcp-client', host: 'other', hostSessionId: SESSION_ID || 'mcp-server' }, args.gate_receipt_id)
|
|
1518
|
+
: [];
|
|
1519
|
+
toolSuccess(id, proofRetryConflict(proofKeys, args.proof, fromHold), true);
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
throw err;
|
|
1523
|
+
}
|
|
1524
|
+
if (result.committed) {
|
|
1441
1525
|
for (const key of proofKeys)
|
|
1442
|
-
commitProofMissing.
|
|
1526
|
+
commitProofMissing.delete(key);
|
|
1443
1527
|
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1528
|
+
else {
|
|
1529
|
+
// A short close is accepted unverified with the missing proof named ("Retry only after proof includes: a, b.").
|
|
1530
|
+
const unverified = result;
|
|
1531
|
+
const listed = unverified.governance_validation?.code === 'MARROW_PROOF_PACK_INCOMPLETE'
|
|
1532
|
+
? /proof includes: ([A-Za-z0-9_, ]{1,600})\.?\s*$/.exec(String(unverified.exact_next_action || ''))?.[1]
|
|
1533
|
+
: undefined;
|
|
1534
|
+
const missing = listed ? listed.split(',').map((field) => field.trim()).filter(Boolean) : [];
|
|
1535
|
+
if (missing.length)
|
|
1536
|
+
for (const key of proofKeys)
|
|
1537
|
+
commitProofMissing.set(key, missing);
|
|
1450
1538
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1539
|
+
const commitResult = { ...result, narrative: result.narrative ?? null };
|
|
1540
|
+
lastCommitted = result.committed;
|
|
1541
|
+
lastDecisionId = result.committed ? null : decision_id;
|
|
1542
|
+
success(id, {
|
|
1543
|
+
content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
|
|
1544
|
+
});
|
|
1545
|
+
return;
|
|
1456
1546
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const missing = listed ? listed.split(',').map((field) => field.trim()).filter(Boolean) : [];
|
|
1464
|
-
if (missing.length)
|
|
1465
|
-
for (const key of proofKeys)
|
|
1466
|
-
commitProofMissing.set(key, missing);
|
|
1547
|
+
if (toolName === 'marrow_model_usage') {
|
|
1548
|
+
const result = await (0, index_1.marrowModelUsage)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
1549
|
+
success(id, {
|
|
1550
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1551
|
+
});
|
|
1552
|
+
return;
|
|
1467
1553
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
if (toolName === 'marrow_run') {
|
|
1484
|
-
// [FIX #9] Validate required params
|
|
1485
|
-
const description = requireString(args, 'description');
|
|
1486
|
-
const outcome = requireString(args, 'outcome');
|
|
1487
|
-
const measuredSuccess = requireBoolean(args, 'success');
|
|
1488
|
-
// [FIX #16] Handle partial failures — return think result even if commit fails
|
|
1489
|
-
let thinkResult = null;
|
|
1490
|
-
try {
|
|
1491
|
-
await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
1492
|
-
}
|
|
1493
|
-
catch (err) {
|
|
1494
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1495
|
-
process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
|
|
1496
|
-
}
|
|
1497
|
-
thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1498
|
-
action: description,
|
|
1499
|
-
type: args.type || 'general',
|
|
1500
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1501
|
-
let commitResult = null;
|
|
1502
|
-
try {
|
|
1503
|
-
commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1504
|
-
decision_id: thinkResult.decision_id,
|
|
1505
|
-
success: measuredSuccess,
|
|
1506
|
-
outcome,
|
|
1507
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1508
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1509
|
-
: undefined,
|
|
1510
|
-
gate_receipt_id: typeof args.gate_receipt_id === 'string' ? args.gate_receipt_id : undefined,
|
|
1554
|
+
if (toolName === 'marrow_run') {
|
|
1555
|
+
// [FIX #9] Validate required params
|
|
1556
|
+
const description = requireString(args, 'description');
|
|
1557
|
+
const outcome = requireString(args, 'outcome');
|
|
1558
|
+
const measuredSuccess = requireBoolean(args, 'success');
|
|
1559
|
+
// [FIX #16] Handle partial failures — return think result even if commit fails
|
|
1560
|
+
let thinkResult = null;
|
|
1561
|
+
try {
|
|
1562
|
+
await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
1563
|
+
}
|
|
1564
|
+
catch (err) {
|
|
1565
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1566
|
+
process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
|
|
1567
|
+
}
|
|
1568
|
+
thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1511
1569
|
action: description,
|
|
1512
1570
|
type: args.type || 'general',
|
|
1513
1571
|
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1572
|
+
let commitResult = null;
|
|
1573
|
+
try {
|
|
1574
|
+
commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1575
|
+
decision_id: thinkResult.decision_id,
|
|
1576
|
+
success: measuredSuccess,
|
|
1577
|
+
outcome,
|
|
1578
|
+
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1579
|
+
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1580
|
+
: undefined,
|
|
1581
|
+
gate_receipt_id: typeof args.gate_receipt_id === 'string' ? args.gate_receipt_id : undefined,
|
|
1582
|
+
action: description,
|
|
1583
|
+
type: args.type || 'general',
|
|
1584
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1585
|
+
}
|
|
1586
|
+
catch (err) {
|
|
1587
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1588
|
+
process.stderr.write(`[marrow] marrow_run commit failed: ${msg}\n`);
|
|
1589
|
+
success(id, {
|
|
1590
|
+
content: [{
|
|
1591
|
+
type: 'text',
|
|
1592
|
+
text: JSON.stringify({
|
|
1593
|
+
think: thinkResult,
|
|
1594
|
+
commit: null,
|
|
1595
|
+
commit_error: msg,
|
|
1596
|
+
decision_id: thinkResult.decision_id,
|
|
1597
|
+
}, null, 2),
|
|
1598
|
+
}],
|
|
1599
|
+
});
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1518
1602
|
success(id, {
|
|
1519
1603
|
content: [{
|
|
1520
1604
|
type: 'text',
|
|
1521
|
-
text: JSON.stringify({
|
|
1522
|
-
think: thinkResult,
|
|
1523
|
-
commit: null,
|
|
1524
|
-
commit_error: msg,
|
|
1525
|
-
decision_id: thinkResult.decision_id,
|
|
1526
|
-
}, null, 2),
|
|
1605
|
+
text: JSON.stringify({ think: thinkResult, commit: commitResult }, null, 2),
|
|
1527
1606
|
}],
|
|
1528
1607
|
});
|
|
1529
1608
|
return;
|
|
1530
1609
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
: undefined;
|
|
1551
|
-
const suppliedArbitrationReceiptId = typeof args.arbitration_receipt_id === 'string'
|
|
1552
|
-
? args.arbitration_receipt_id
|
|
1553
|
-
: undefined;
|
|
1554
|
-
const suppliedOwnerApprovalReceiptId = typeof args.owner_approval_receipt_id === 'string'
|
|
1555
|
-
? args.owner_approval_receipt_id
|
|
1556
|
-
: undefined;
|
|
1557
|
-
const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
|
|
1558
|
-
action,
|
|
1559
|
-
outcome,
|
|
1560
|
-
success: outcomeSuccess,
|
|
1561
|
-
type,
|
|
1562
|
-
proof: suppliedProof,
|
|
1563
|
-
gate_receipt_id: suppliedRuntimeReceiptId || undefined,
|
|
1564
|
-
arbitration_receipt_id: suppliedArbitrationReceiptId,
|
|
1565
|
-
owner_approval_receipt_id: suppliedOwnerApprovalReceiptId,
|
|
1566
|
-
// Low-risk one-shot capture does not need a policy gate. Consequential
|
|
1567
|
-
// work obtains exactly one canonical runtime authorization inside auto.
|
|
1568
|
-
auto_gate: highRisk,
|
|
1569
|
-
operation_id: typeof args.operation_id === 'string' ? args.operation_id : undefined,
|
|
1570
|
-
...(args.request_owner_link === true ? { request_owner_link: true } : {}),
|
|
1571
|
-
}, SESSION_ID, FLEET_AGENT_ID, 8_000, autoElicitationOptions());
|
|
1572
|
-
let delivered = null;
|
|
1573
|
-
let deliveryFailure = null;
|
|
1574
|
-
let failedHttpAttemptTrace = null;
|
|
1575
|
-
const coreStartedAt = performance.now();
|
|
1576
|
-
try {
|
|
1577
|
-
delivered = await delivery();
|
|
1578
|
-
}
|
|
1579
|
-
catch (err) {
|
|
1580
|
-
deliveryFailure = (0, request_reliability_1.structuredRequestFailure)(err);
|
|
1581
|
-
failedHttpAttemptTrace = (0, index_1.marrowAutoHttpTraceFromError)(err);
|
|
1582
|
-
}
|
|
1583
|
-
const coreDurationMs = Math.floor(performance.now() - coreStartedAt);
|
|
1584
|
-
const runtimeGate = delivered?.runtime_gate || null;
|
|
1585
|
-
if (runtimeGate)
|
|
1586
|
-
storeRuntimeGuidance(runtimeGate);
|
|
1587
|
-
const enqueueStartedAt = performance.now();
|
|
1588
|
-
const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
|
|
1589
|
-
apiKey: API_KEY,
|
|
1590
|
-
baseUrl: BASE_URL,
|
|
1591
|
-
deferDelivery: true,
|
|
1592
|
-
event: {
|
|
1593
|
-
...(delivered?.operation_id ? {
|
|
1594
|
-
// A closed operation has one record; each pending call is its own attempt.
|
|
1595
|
-
event_id: delivered.committed
|
|
1596
|
-
? `auto_closed_${delivered.operation_id}`
|
|
1597
|
-
: `auto_pending_${delivered.operation_id}_${(0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 12)}`,
|
|
1598
|
-
} : {}),
|
|
1599
|
-
event_type: delivered?.committed
|
|
1600
|
-
? 'outcome_committed'
|
|
1601
|
-
: !highRisk && outcomeSuccess === false
|
|
1602
|
-
? 'tool_failed'
|
|
1603
|
-
: !highRisk && outcomeSuccess === true
|
|
1604
|
-
? 'tool_completed'
|
|
1605
|
-
: 'goal_started',
|
|
1606
|
-
harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
|
|
1607
|
-
agent_id: FLEET_AGENT_ID,
|
|
1608
|
-
session_id: SESSION_ID,
|
|
1609
|
-
decision_id: delivered?.decision_id || undefined,
|
|
1610
|
+
if (toolName === 'marrow_auto') {
|
|
1611
|
+
const action = requireString(args, 'action');
|
|
1612
|
+
const outcome = args.outcome;
|
|
1613
|
+
const outcomeSuccess = typeof args.success === 'boolean' ? args.success : undefined;
|
|
1614
|
+
const type = args.type || 'general';
|
|
1615
|
+
const highRisk = isHighRiskAction(action, type);
|
|
1616
|
+
const suppliedProof = args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1617
|
+
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1618
|
+
: undefined;
|
|
1619
|
+
const suppliedRuntimeReceiptId = typeof args.gate_receipt_id === 'string'
|
|
1620
|
+
? args.gate_receipt_id
|
|
1621
|
+
: undefined;
|
|
1622
|
+
const suppliedArbitrationReceiptId = typeof args.arbitration_receipt_id === 'string'
|
|
1623
|
+
? args.arbitration_receipt_id
|
|
1624
|
+
: undefined;
|
|
1625
|
+
const suppliedOwnerApprovalReceiptId = typeof args.owner_approval_receipt_id === 'string'
|
|
1626
|
+
? args.owner_approval_receipt_id
|
|
1627
|
+
: undefined;
|
|
1628
|
+
const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
|
|
1610
1629
|
action,
|
|
1611
|
-
|
|
1612
|
-
success:
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
?
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
: delivered?.phase === 'proof_required'
|
|
1642
|
-
? 'pending_required_proof'
|
|
1643
|
-
: delivered?.phase === 'decision_created' || outcomeSuccess === undefined
|
|
1644
|
-
? 'pending_evidence'
|
|
1645
|
-
: 'delivery_pending',
|
|
1646
|
-
decision_id: delivered?.decision_id || null,
|
|
1647
|
-
operation_id: delivered?.operation_id || (typeof args.operation_id === 'string' ? args.operation_id : null),
|
|
1648
|
-
phase: delivered?.phase || null,
|
|
1649
|
-
resumable: delivered?.resumable || false,
|
|
1650
|
-
retry_after_ms: delivered?.retry_after_ms ?? null,
|
|
1651
|
-
phase_timings_ms: delivered?.phase_timings_ms || null,
|
|
1652
|
-
http_attempt_trace: delivered?.http_attempt_trace || failedHttpAttemptTrace || {
|
|
1653
|
-
attempts: [],
|
|
1654
|
-
dropped_count: 0,
|
|
1655
|
-
},
|
|
1656
|
-
exact_next_action: delivered?.committed && delivered.closure === 'gate_denial' && delivered.exact_next_action
|
|
1657
|
-
? delivered.exact_next_action
|
|
1658
|
-
: delivered?.committed
|
|
1659
|
-
? 'The governed outcome is closed. Reuse this decision_id only for read-only trace inspection.'
|
|
1660
|
-
: delivered?.exact_next_action
|
|
1661
|
-
? delivered.exact_next_action
|
|
1662
|
-
: delivered?.phase === 'owner_approval_required'
|
|
1663
|
-
? 'The account owner must approve one proposal of this arbitration before it runs; Marrow then issues an owner_approval_receipt_id. Do not run any proposal or write an approval yourself. Once Marrow issues the receipt, call marrow_auto once with this same operation_id, arbitration_receipt_id and owner_approval_receipt_id.'
|
|
1664
|
-
: delivered?.phase === 'proof_required'
|
|
1665
|
-
? 'Attach the required measured proof and retry marrow_auto with this same operation_id and unchanged action, context, and surfaces.'
|
|
1666
|
-
: delivered?.resumable
|
|
1667
|
-
? 'Retry marrow_auto with this same operation_id. Do not start a new auto operation.'
|
|
1668
|
-
: 'Close this decision after the real outcome is known.',
|
|
1669
|
-
live_delivery: {
|
|
1670
|
-
accepted: Boolean(delivered?.decision_id),
|
|
1671
|
-
committed: Boolean(delivered?.committed),
|
|
1672
|
-
...(deliveryFailure ? { failure: deliveryFailure } : {}),
|
|
1673
|
-
},
|
|
1674
|
-
host_capability: mcpHostCapability(),
|
|
1675
|
-
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1676
|
-
...(delivered?.approval ? { approval: delivered.approval } : {}),
|
|
1677
|
-
...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
|
|
1678
|
-
};
|
|
1679
|
-
// Measure through response construction; stdout/host consumption occurs
|
|
1680
|
-
// after this snapshot and is not represented as server phase latency.
|
|
1681
|
-
response.response_timings_ms = {
|
|
1682
|
-
core: coreDurationMs,
|
|
1683
|
-
durable_enqueue: enqueueDurationMs,
|
|
1684
|
-
full_response: Math.floor(performance.now() - responseStartedAt),
|
|
1685
|
-
};
|
|
1686
|
-
success(id, {
|
|
1687
|
-
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
1688
|
-
});
|
|
1689
|
-
// The existing bounded owner handles rejection and preserves queued
|
|
1690
|
-
// receipts across exit/restart. Network work never holds this response.
|
|
1691
|
-
void (0, lifecycle_spool_1.nudgeLifecycleSpool)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
1692
|
-
return;
|
|
1693
|
-
}
|
|
1694
|
-
if (toolName === 'marrow_ask') {
|
|
1695
|
-
const query = requireString(args, 'query');
|
|
1696
|
-
const result = await withControlDeadline((signal) => (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID, signal), { toolName: 'marrow_ask' });
|
|
1697
|
-
try {
|
|
1698
|
-
(0, guidance_cache_1.writeGuidanceCache)({
|
|
1630
|
+
outcome,
|
|
1631
|
+
success: outcomeSuccess,
|
|
1632
|
+
type,
|
|
1633
|
+
proof: suppliedProof,
|
|
1634
|
+
gate_receipt_id: suppliedRuntimeReceiptId || undefined,
|
|
1635
|
+
arbitration_receipt_id: suppliedArbitrationReceiptId,
|
|
1636
|
+
owner_approval_receipt_id: suppliedOwnerApprovalReceiptId,
|
|
1637
|
+
// Low-risk one-shot capture does not need a policy gate. Consequential
|
|
1638
|
+
// work obtains exactly one canonical runtime authorization inside auto.
|
|
1639
|
+
auto_gate: highRisk,
|
|
1640
|
+
operation_id: typeof args.operation_id === 'string' ? args.operation_id : undefined,
|
|
1641
|
+
...(args.request_owner_link === true ? { request_owner_link: true } : {}),
|
|
1642
|
+
}, SESSION_ID, FLEET_AGENT_ID, 8_000, autoElicitationOptions());
|
|
1643
|
+
let delivered = null;
|
|
1644
|
+
let deliveryFailure = null;
|
|
1645
|
+
let failedHttpAttemptTrace = null;
|
|
1646
|
+
const coreStartedAt = performance.now();
|
|
1647
|
+
try {
|
|
1648
|
+
delivered = await delivery();
|
|
1649
|
+
}
|
|
1650
|
+
catch (err) {
|
|
1651
|
+
deliveryFailure = (0, request_reliability_1.structuredRequestFailure)(err);
|
|
1652
|
+
failedHttpAttemptTrace = (0, index_1.marrowAutoHttpTraceFromError)(err);
|
|
1653
|
+
}
|
|
1654
|
+
const coreDurationMs = Math.floor(performance.now() - coreStartedAt);
|
|
1655
|
+
const runtimeGate = delivered?.runtime_gate || null;
|
|
1656
|
+
if (runtimeGate)
|
|
1657
|
+
storeRuntimeGuidance(runtimeGate);
|
|
1658
|
+
const enqueueStartedAt = performance.now();
|
|
1659
|
+
const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
|
|
1699
1660
|
apiKey: API_KEY,
|
|
1700
1661
|
baseUrl: BASE_URL,
|
|
1701
|
-
|
|
1702
|
-
|
|
1662
|
+
deferDelivery: true,
|
|
1663
|
+
event: {
|
|
1664
|
+
...(delivered?.operation_id ? {
|
|
1665
|
+
// A closed operation has one record; each pending call is its own attempt.
|
|
1666
|
+
event_id: delivered.committed
|
|
1667
|
+
? `auto_closed_${delivered.operation_id}`
|
|
1668
|
+
: `auto_pending_${delivered.operation_id}_${(0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 12)}`,
|
|
1669
|
+
} : {}),
|
|
1670
|
+
event_type: delivered?.committed
|
|
1671
|
+
? 'outcome_committed'
|
|
1672
|
+
: !highRisk && outcomeSuccess === false
|
|
1673
|
+
? 'tool_failed'
|
|
1674
|
+
: !highRisk && outcomeSuccess === true
|
|
1675
|
+
? 'tool_completed'
|
|
1676
|
+
: 'goal_started',
|
|
1677
|
+
harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
|
|
1678
|
+
agent_id: FLEET_AGENT_ID,
|
|
1679
|
+
session_id: SESSION_ID,
|
|
1680
|
+
decision_id: delivered?.decision_id || undefined,
|
|
1681
|
+
action,
|
|
1682
|
+
outcome_state: delivered?.committed ? 'closed' : 'pending',
|
|
1683
|
+
success: delivered?.committed ? outcomeSuccess : highRisk ? undefined : outcomeSuccess,
|
|
1684
|
+
source: 'client_self_reported',
|
|
1685
|
+
},
|
|
1703
1686
|
});
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1687
|
+
const enqueueDurationMs = Math.floor(performance.now() - enqueueStartedAt);
|
|
1688
|
+
const response = {
|
|
1689
|
+
action,
|
|
1690
|
+
outcome: outcome || 'pending',
|
|
1691
|
+
warnings: cachedOrientWarnings.map(formatWarningActionably),
|
|
1692
|
+
logging: delivered?.committed
|
|
1693
|
+
? 'governed_commit_confirmed'
|
|
1694
|
+
: delivered?.resumable
|
|
1695
|
+
? 'resume_required'
|
|
1696
|
+
: delivered
|
|
1697
|
+
? 'intent_confirmed'
|
|
1698
|
+
: 'durably_queued',
|
|
1699
|
+
receipt,
|
|
1700
|
+
completion_state: delivered?.committed && delivered.closure === 'gate_denial'
|
|
1701
|
+
? 'closed_as_denial'
|
|
1702
|
+
: delivered?.closure === 'already_closed_denial'
|
|
1703
|
+
? 'closed_earlier_as_denial'
|
|
1704
|
+
: delivered?.closure === 'already_closed'
|
|
1705
|
+
? 'closed_earlier'
|
|
1706
|
+
: delivered?.committed
|
|
1707
|
+
? 'closed_with_proof'
|
|
1708
|
+
: delivered?.phase === 'review_required'
|
|
1709
|
+
? 'review_required_terminal'
|
|
1710
|
+
: delivered?.phase === 'owner_approval_required'
|
|
1711
|
+
? 'pending_owner_approval'
|
|
1712
|
+
: delivered?.phase === 'proof_required'
|
|
1713
|
+
? 'pending_required_proof'
|
|
1714
|
+
: delivered?.phase === 'decision_created' || outcomeSuccess === undefined
|
|
1715
|
+
? 'pending_evidence'
|
|
1716
|
+
: 'delivery_pending',
|
|
1717
|
+
decision_id: delivered?.decision_id || null,
|
|
1718
|
+
operation_id: delivered?.operation_id || (typeof args.operation_id === 'string' ? args.operation_id : null),
|
|
1719
|
+
phase: delivered?.phase || null,
|
|
1720
|
+
resumable: delivered?.resumable || false,
|
|
1721
|
+
retry_after_ms: delivered?.retry_after_ms ?? null,
|
|
1722
|
+
phase_timings_ms: delivered?.phase_timings_ms || null,
|
|
1723
|
+
http_attempt_trace: delivered?.http_attempt_trace || failedHttpAttemptTrace || {
|
|
1724
|
+
attempts: [],
|
|
1725
|
+
dropped_count: 0,
|
|
1726
|
+
},
|
|
1727
|
+
exact_next_action: delivered?.committed && delivered.closure === 'gate_denial' && delivered.exact_next_action
|
|
1728
|
+
? delivered.exact_next_action
|
|
1729
|
+
: delivered?.committed
|
|
1730
|
+
? 'The governed outcome is closed. Reuse this decision_id only for read-only trace inspection.'
|
|
1731
|
+
: delivered?.exact_next_action
|
|
1732
|
+
? delivered.exact_next_action
|
|
1733
|
+
: delivered?.phase === 'owner_approval_required'
|
|
1734
|
+
? 'The account owner must approve one proposal of this arbitration before it runs; Marrow then issues an owner_approval_receipt_id. Do not run any proposal or write an approval yourself. Once Marrow issues the receipt, call marrow_auto once with this same operation_id, arbitration_receipt_id and owner_approval_receipt_id.'
|
|
1735
|
+
: delivered?.phase === 'proof_required'
|
|
1736
|
+
? 'Attach the required measured proof and retry marrow_auto with this same operation_id and unchanged action, context, and surfaces.'
|
|
1737
|
+
: delivered?.resumable
|
|
1738
|
+
? 'Retry marrow_auto with this same operation_id. Do not start a new auto operation.'
|
|
1739
|
+
: 'Close this decision after the real outcome is known.',
|
|
1740
|
+
live_delivery: {
|
|
1741
|
+
accepted: Boolean(delivered?.decision_id),
|
|
1742
|
+
committed: Boolean(delivered?.committed),
|
|
1743
|
+
...(deliveryFailure ? { failure: deliveryFailure } : {}),
|
|
1744
|
+
},
|
|
1745
|
+
host_capability: mcpHostCapability(),
|
|
1746
|
+
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1747
|
+
...(delivered?.approval ? { approval: delivered.approval } : {}),
|
|
1748
|
+
...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
|
|
1749
|
+
};
|
|
1750
|
+
// Measure through response construction; stdout/host consumption occurs
|
|
1751
|
+
// after this snapshot and is not represented as server phase latency.
|
|
1752
|
+
response.response_timings_ms = {
|
|
1753
|
+
core: coreDurationMs,
|
|
1754
|
+
durable_enqueue: enqueueDurationMs,
|
|
1755
|
+
full_response: Math.floor(performance.now() - responseStartedAt),
|
|
1756
|
+
};
|
|
1757
|
+
success(id, {
|
|
1758
|
+
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
1759
|
+
});
|
|
1760
|
+
// The existing bounded owner handles rejection and preserves queued
|
|
1761
|
+
// receipts across exit/restart. Network work never holds this response.
|
|
1762
|
+
void (0, lifecycle_spool_1.nudgeLifecycleSpool)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
1720
1763
|
return;
|
|
1721
1764
|
}
|
|
1722
|
-
|
|
1723
|
-
const
|
|
1724
|
-
|
|
1725
|
-
|
|
1765
|
+
if (toolName === 'marrow_ask') {
|
|
1766
|
+
const query = requireString(args, 'query');
|
|
1767
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID, signal), { toolName: 'marrow_ask' });
|
|
1768
|
+
try {
|
|
1769
|
+
(0, guidance_cache_1.writeGuidanceCache)({
|
|
1770
|
+
apiKey: API_KEY,
|
|
1771
|
+
baseUrl: BASE_URL,
|
|
1772
|
+
agentId: FLEET_AGENT_ID,
|
|
1773
|
+
context: `## Marrow answer\n- ${String(result.answer || 'No relevant lesson found.').slice(0, 1200)}`,
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1776
|
+
catch { /* owner-only cache is best effort */ }
|
|
1777
|
+
toolSuccess(id, clientOperationalPayload('marrow_ask', result));
|
|
1778
|
+
return;
|
|
1726
1779
|
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1780
|
+
if (toolName === 'marrow_status') {
|
|
1781
|
+
let cached = null;
|
|
1782
|
+
try {
|
|
1783
|
+
cached = (0, status_cache_1.readStatusCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
1784
|
+
}
|
|
1785
|
+
catch { /* owner-only cache is best effort */ }
|
|
1786
|
+
if (cached?.freshness === 'fresh') {
|
|
1729
1787
|
const startedAt = Date.now();
|
|
1730
|
-
(
|
|
1788
|
+
refreshStatusInBackground();
|
|
1789
|
+
(0, control_path_state_1.recordControlPathSample)('marrow_status', Date.now() - startedAt, true);
|
|
1731
1790
|
toolSuccess(id, clientOperationalPayload('marrow_status', (0, status_cache_1.cachedStatusPayload)(cached)));
|
|
1732
1791
|
return;
|
|
1733
1792
|
}
|
|
1734
|
-
|
|
1793
|
+
try {
|
|
1794
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_status' });
|
|
1795
|
+
storeLastKnownStatus(result, 'status');
|
|
1796
|
+
toolSuccess(id, clientOperationalPayload('marrow_status', result));
|
|
1797
|
+
}
|
|
1798
|
+
catch (error) {
|
|
1799
|
+
if (cached) {
|
|
1800
|
+
const startedAt = Date.now();
|
|
1801
|
+
(0, control_path_state_1.recordControlPathSample)('marrow_status', Date.now() - startedAt, false);
|
|
1802
|
+
toolSuccess(id, clientOperationalPayload('marrow_status', (0, status_cache_1.cachedStatusPayload)(cached)));
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
throw error;
|
|
1806
|
+
}
|
|
1807
|
+
return;
|
|
1735
1808
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1749
|
-
});
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
if (toolName === 'marrow_list_keys') {
|
|
1753
|
-
const result = await (0, index_1.marrowListKeys)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1754
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
if (toolName === 'marrow_get_key') {
|
|
1758
|
-
const keyId = requireString(args, 'id');
|
|
1759
|
-
const result = await (0, index_1.marrowGetKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1760
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1761
|
-
return;
|
|
1762
|
-
}
|
|
1763
|
-
if (toolName === 'marrow_revoke_key') {
|
|
1764
|
-
const keyId = requireString(args, 'id');
|
|
1765
|
-
const result = await (0, index_1.marrowRevokeKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1766
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
if (toolName === 'marrow_rotate_key') {
|
|
1770
|
-
const keyId = requireString(args, 'id');
|
|
1771
|
-
const result = await (0, index_1.marrowRotateKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1772
|
-
success(id, {
|
|
1773
|
-
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1774
|
-
});
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
|
-
// Memory control tools — all use requireString for id validation
|
|
1778
|
-
if (toolName === 'marrow_list_memories') {
|
|
1779
|
-
const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
|
|
1780
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1781
|
-
return;
|
|
1782
|
-
}
|
|
1783
|
-
if (toolName === 'marrow_get_memory') {
|
|
1784
|
-
const memId = requireString(args, 'id');
|
|
1785
|
-
const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
|
|
1786
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1787
|
-
return;
|
|
1788
|
-
}
|
|
1789
|
-
if (toolName === 'marrow_update_memory') {
|
|
1790
|
-
const memId = requireString(args, 'id');
|
|
1791
|
-
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);
|
|
1792
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
if (toolName === 'marrow_delete_memory') {
|
|
1796
|
-
const memId = requireString(args, 'id');
|
|
1797
|
-
const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1798
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1799
|
-
return;
|
|
1800
|
-
}
|
|
1801
|
-
if (toolName === 'marrow_mark_outdated') {
|
|
1802
|
-
const memId = requireString(args, 'id');
|
|
1803
|
-
const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1804
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
if (toolName === 'marrow_supersede_memory') {
|
|
1808
|
-
const memId = requireString(args, 'id');
|
|
1809
|
-
const newText = requireString(args, 'text');
|
|
1810
|
-
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);
|
|
1811
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
if (toolName === 'marrow_share_memory') {
|
|
1815
|
-
const memId = requireString(args, 'id');
|
|
1816
|
-
const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
|
|
1817
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
if (toolName === 'marrow_export_memories') {
|
|
1821
|
-
const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
|
|
1822
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1823
|
-
return;
|
|
1824
|
-
}
|
|
1825
|
-
if (toolName === 'marrow_import_memories') {
|
|
1826
|
-
const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
|
|
1827
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
if (toolName === 'marrow_retrieve_memories') {
|
|
1831
|
-
const query = requireString(args, 'query');
|
|
1832
|
-
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);
|
|
1833
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
1836
|
-
if (toolName === 'marrow_workflow') {
|
|
1837
|
-
const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
|
|
1838
|
-
action: args.action,
|
|
1839
|
-
workflowId: args.workflowId,
|
|
1840
|
-
instanceId: args.instanceId,
|
|
1841
|
-
name: args.name,
|
|
1842
|
-
description: args.description,
|
|
1843
|
-
steps: args.steps,
|
|
1844
|
-
tags: args.tags,
|
|
1845
|
-
agentId: args.agentId,
|
|
1846
|
-
context: args.context,
|
|
1847
|
-
inputs: args.inputs,
|
|
1848
|
-
stepCompleted: args.stepCompleted,
|
|
1849
|
-
outcome: args.outcome,
|
|
1850
|
-
nextAgentId: args.nextAgentId,
|
|
1851
|
-
contextUpdate: args.contextUpdate,
|
|
1852
|
-
status: args.status,
|
|
1853
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1854
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1855
|
-
return;
|
|
1856
|
-
}
|
|
1857
|
-
if (toolName === 'marrow_dashboard') {
|
|
1858
|
-
const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1859
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1860
|
-
return;
|
|
1861
|
-
}
|
|
1862
|
-
if (toolName === 'marrow_digest') {
|
|
1863
|
-
const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
|
|
1864
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1867
|
-
if (toolName === 'marrow_agent_status') {
|
|
1868
|
-
const result = await withControlDeadline(async (signal) => {
|
|
1869
|
-
const [analyticsStatus, context] = await Promise.all([
|
|
1870
|
-
(0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID, signal),
|
|
1871
|
-
(0, index_1.marrowAgentContext)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal)
|
|
1872
|
-
.catch(() => null),
|
|
1873
|
-
]);
|
|
1874
|
-
const contextProjection = recordValue(context)?.primary_tool_availability;
|
|
1875
|
-
return {
|
|
1876
|
-
...analyticsStatus,
|
|
1877
|
-
...(contextProjection === undefined ? {} : { primary_tool_availability: contextProjection }),
|
|
1878
|
-
};
|
|
1879
|
-
}, { cacheAware: false, toolName: 'marrow_agent_status' });
|
|
1880
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus({ ...result, local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)) }), null, 2) }] });
|
|
1881
|
-
return;
|
|
1882
|
-
}
|
|
1883
|
-
if (toolName === 'marrow_runtime_status') {
|
|
1884
|
-
const result = await withControlDeadline((signal) => (0, index_1.marrowRuntimeStatus)(API_KEY, BASE_URL, args.fast !== false, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_runtime_status' });
|
|
1885
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus(result), null, 2) }] });
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
if (toolName === 'marrow_value_report') {
|
|
1889
|
-
const result = await (0, index_1.marrowValueReport)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
|
|
1890
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
if (toolName === 'marrow_decision_brief') {
|
|
1894
|
-
const result = await (0, index_1.marrowDecisionBrief)(API_KEY, BASE_URL, {
|
|
1895
|
-
action: args.action,
|
|
1896
|
-
type: args.type,
|
|
1897
|
-
role: args.role,
|
|
1898
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1899
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1900
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1901
|
-
period: args.period,
|
|
1902
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1903
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1904
|
-
return;
|
|
1905
|
-
}
|
|
1906
|
-
if (toolName === 'marrow_first_value') {
|
|
1907
|
-
const result = await (0, index_1.marrowFirstValue)(API_KEY, BASE_URL, {
|
|
1908
|
-
action: args.action ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
|
|
1909
|
-
type: args.type,
|
|
1910
|
-
role: args.role,
|
|
1911
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1912
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1913
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1914
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1915
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1916
|
-
: undefined,
|
|
1917
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1918
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1919
|
-
: undefined,
|
|
1920
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1921
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
if (toolName === 'marrow_agent_runtime') {
|
|
1925
|
-
const runtimeInput = {
|
|
1926
|
-
action: (0, redact_1.redactSensitiveText)(args.action),
|
|
1927
|
-
type: args.type,
|
|
1928
|
-
role: args.role,
|
|
1929
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1930
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1931
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1932
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1933
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1934
|
-
: undefined,
|
|
1935
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1936
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1937
|
-
: undefined,
|
|
1938
|
-
period: args.period,
|
|
1939
|
-
};
|
|
1940
|
-
const result = await withControlDeadline((signal) => (0, index_1.marrowAgentRuntime)(API_KEY, BASE_URL, runtimeInput, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: isHighRiskAction(runtimeInput.action, runtimeInput.type), toolName: 'marrow_agent_runtime' });
|
|
1941
|
-
storeRuntimeGuidance(result);
|
|
1942
|
-
storeLastKnownStatus(result.status, 'runtime');
|
|
1943
|
-
toolSuccess(id, clientOperationalPayload('marrow_agent_runtime', result));
|
|
1944
|
-
return;
|
|
1945
|
-
}
|
|
1946
|
-
if (toolName === 'marrow_arbitrate') {
|
|
1947
|
-
const result = await (0, index_1.marrowArbitrate)(API_KEY, BASE_URL, {
|
|
1948
|
-
objective: (0, redact_1.redactSensitiveText)(args.objective),
|
|
1949
|
-
owner_intent: typeof args.ownerIntent === 'string' ? (0, redact_1.redactSensitiveText)(args.ownerIntent) : undefined,
|
|
1950
|
-
conflict_type: args.conflictType,
|
|
1951
|
-
proposals: Array.isArray(args.proposals)
|
|
1952
|
-
? args.proposals
|
|
1953
|
-
: [],
|
|
1954
|
-
action: typeof args.action === 'string' ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
|
|
1955
|
-
type: args.type,
|
|
1956
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1957
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1958
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1959
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1960
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1961
|
-
: undefined,
|
|
1962
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1963
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1964
|
-
: undefined,
|
|
1965
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1966
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
if (toolName === 'marrow_coordinate') {
|
|
1970
|
-
const coordinationArgs = { ...args };
|
|
1971
|
-
delete coordinationArgs.agent_id;
|
|
1972
|
-
delete coordinationArgs.source_agent_id;
|
|
1973
|
-
const result = await (0, index_1.marrowCoordinate)(API_KEY, BASE_URL, coordinationArgs, SESSION_ID, FLEET_AGENT_ID);
|
|
1974
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
if (toolName === 'marrow_replay_compare') {
|
|
1978
|
-
const result = await (0, index_1.marrowReplayCompare)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
1979
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1980
|
-
return;
|
|
1981
|
-
}
|
|
1982
|
-
if (toolName === 'marrow_governance_control_plane') {
|
|
1983
|
-
const result = await (0, index_1.marrowGovernanceControlPlane)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1984
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1985
|
-
return;
|
|
1986
|
-
}
|
|
1987
|
-
if (toolName === 'marrow_hermes_integration') {
|
|
1988
|
-
const result = await (0, index_1.marrowHermesIntegration)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1989
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1990
|
-
return;
|
|
1991
|
-
}
|
|
1992
|
-
if (toolName === 'marrow_completion_contracts') {
|
|
1993
|
-
const result = await (0, index_1.marrowCompletionContracts)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1994
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1995
|
-
return;
|
|
1996
|
-
}
|
|
1997
|
-
if (toolName === 'marrow_evaluate_completion_contract') {
|
|
1998
|
-
const input = {
|
|
1999
|
-
action: args.action,
|
|
2000
|
-
workflow_type: args.workflow_type,
|
|
2001
|
-
risk_level: args.risk_level,
|
|
2002
|
-
evidence: args.evidence && typeof args.evidence === 'object' && !Array.isArray(args.evidence)
|
|
2003
|
-
? (0, redact_1.redactSensitiveValue)(args.evidence)
|
|
2004
|
-
: undefined,
|
|
2005
|
-
};
|
|
2006
|
-
const result = await (0, index_1.marrowEvaluateCompletionContract)(API_KEY, BASE_URL, input, SESSION_ID, FLEET_AGENT_ID);
|
|
2007
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
if (toolName === 'marrow_governance_timeline') {
|
|
2011
|
-
const result = await (0, index_1.marrowGovernanceTimeline)(API_KEY, BASE_URL, {
|
|
2012
|
-
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2013
|
-
limit: args.limit,
|
|
2014
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2015
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2016
|
-
return;
|
|
2017
|
-
}
|
|
2018
|
-
if (toolName === 'marrow_decision_trace') {
|
|
2019
|
-
const decisionId = args.decisionId;
|
|
2020
|
-
if (!decisionId) {
|
|
2021
|
-
error(id, -32602, 'decisionId is required');
|
|
1809
|
+
if (toolName === 'marrow_create_key') {
|
|
1810
|
+
const name = requireString(args, 'name');
|
|
1811
|
+
const result = await (0, index_1.marrowCreateKey)(API_KEY, BASE_URL, {
|
|
1812
|
+
name,
|
|
1813
|
+
key_type: args.key_type,
|
|
1814
|
+
scopes: args.scopes,
|
|
1815
|
+
agent_ids: args.agent_ids,
|
|
1816
|
+
expires_at: args.expires_at,
|
|
1817
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1818
|
+
success(id, {
|
|
1819
|
+
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1820
|
+
});
|
|
2022
1821
|
return;
|
|
2023
1822
|
}
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
}
|
|
2028
|
-
if (toolName === 'marrow_buyer_proof') {
|
|
2029
|
-
const result = await (0, index_1.marrowBuyerProof)(API_KEY, BASE_URL, {
|
|
2030
|
-
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2031
|
-
periodDays: args.periodDays,
|
|
2032
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2033
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
if (toolName === 'marrow_mode_recommend') {
|
|
2037
|
-
const result = await (0, index_1.marrowRecommendGovernanceMode)(API_KEY, BASE_URL, {
|
|
2038
|
-
project: args.project && typeof args.project === 'object' && !Array.isArray(args.project)
|
|
2039
|
-
? (0, redact_1.redactSensitiveValue)(args.project)
|
|
2040
|
-
: undefined,
|
|
2041
|
-
workflow: args.workflow && typeof args.workflow === 'object' && !Array.isArray(args.workflow)
|
|
2042
|
-
? (0, redact_1.redactSensitiveValue)(args.workflow)
|
|
2043
|
-
: undefined,
|
|
2044
|
-
agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
|
|
2045
|
-
? (0, redact_1.redactSensitiveValue)(args.agent)
|
|
2046
|
-
: { id: FLEET_AGENT_ID },
|
|
2047
|
-
selected_mode: args.selected_mode,
|
|
2048
|
-
selection_source: args.selection_source,
|
|
2049
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2050
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2051
|
-
return;
|
|
2052
|
-
}
|
|
2053
|
-
if (toolName === 'marrow_policy_profiles') {
|
|
2054
|
-
const result = await (0, index_1.marrowListPolicyProfiles)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2055
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2056
|
-
return;
|
|
2057
|
-
}
|
|
2058
|
-
if (toolName === 'marrow_create_policy_profile') {
|
|
2059
|
-
const result = await (0, index_1.marrowCreatePolicyProfile)(API_KEY, BASE_URL, {
|
|
2060
|
-
name: args.name,
|
|
2061
|
-
description: args.description,
|
|
2062
|
-
rules: Array.isArray(args.rules) ? args.rules : undefined,
|
|
2063
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2064
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2067
|
-
if (toolName === 'marrow_assign_project_policy_profile') {
|
|
2068
|
-
const result = await (0, index_1.marrowAssignProjectPolicyProfile)(API_KEY, BASE_URL, {
|
|
2069
|
-
project_key: args.project_key,
|
|
2070
|
-
profile_id: args.profile_id,
|
|
2071
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2072
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2073
|
-
return;
|
|
2074
|
-
}
|
|
2075
|
-
if (toolName === 'marrow_policy_resolve') {
|
|
2076
|
-
const result = await (0, index_1.marrowResolvePolicy)(API_KEY, BASE_URL, {
|
|
2077
|
-
profile_id: args.profile_id,
|
|
2078
|
-
profile_name: args.profile_name,
|
|
2079
|
-
project: args.project && typeof args.project === 'object' && !Array.isArray(args.project)
|
|
2080
|
-
? (0, redact_1.redactSensitiveValue)(args.project)
|
|
2081
|
-
: undefined,
|
|
2082
|
-
workflow: args.workflow && typeof args.workflow === 'object' && !Array.isArray(args.workflow)
|
|
2083
|
-
? (0, redact_1.redactSensitiveValue)(args.workflow)
|
|
2084
|
-
: undefined,
|
|
2085
|
-
agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
|
|
2086
|
-
? (0, redact_1.redactSensitiveValue)(args.agent)
|
|
2087
|
-
: { id: FLEET_AGENT_ID },
|
|
2088
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2089
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2090
|
-
return;
|
|
2091
|
-
}
|
|
2092
|
-
if (toolName === 'marrow_workflow_gate') {
|
|
2093
|
-
const result = await (0, index_1.marrowWorkflowGate)(API_KEY, BASE_URL, {
|
|
2094
|
-
action: (0, redact_1.redactSensitiveText)(args.action),
|
|
2095
|
-
description: args.description ? (0, redact_1.redactSensitiveText)(args.description) : undefined,
|
|
2096
|
-
risk_tolerance: args.riskTolerance,
|
|
2097
|
-
requires_approval: args.requiresApproval,
|
|
2098
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
2099
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
2100
|
-
: undefined,
|
|
2101
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2102
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2103
|
-
return;
|
|
2104
|
-
}
|
|
2105
|
-
if (toolName === 'marrow_agent_performance') {
|
|
2106
|
-
const result = await (0, index_1.marrowAgentPerformance)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
|
|
2107
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
if (toolName === 'marrow_fleet_lessons') {
|
|
2111
|
-
const result = await (0, index_1.marrowFleetLessons)(API_KEY, BASE_URL, {
|
|
2112
|
-
query: args.query,
|
|
2113
|
-
type: args.type,
|
|
2114
|
-
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2115
|
-
limit: args.limit,
|
|
2116
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2117
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2118
|
-
return;
|
|
2119
|
-
}
|
|
2120
|
-
if (toolName === 'marrow_record_deployment_memory') {
|
|
2121
|
-
const result = await (0, index_1.marrowRecordDeploymentMemory)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2122
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2123
|
-
return;
|
|
2124
|
-
}
|
|
2125
|
-
if (toolName === 'marrow_create_handoff') {
|
|
2126
|
-
const result = await (0, index_1.marrowCreateHandoff)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2127
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2128
|
-
return;
|
|
2129
|
-
}
|
|
2130
|
-
if (toolName === 'marrow_update_handoff') {
|
|
2131
|
-
const handoffId = args.handoffId;
|
|
2132
|
-
if (!handoffId) {
|
|
2133
|
-
error(id, -32602, 'handoffId is required');
|
|
1823
|
+
if (toolName === 'marrow_list_keys') {
|
|
1824
|
+
const result = await (0, index_1.marrowListKeys)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1825
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2134
1826
|
return;
|
|
2135
1827
|
}
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
1828
|
+
if (toolName === 'marrow_get_key') {
|
|
1829
|
+
const keyId = requireString(args, 'id');
|
|
1830
|
+
const result = await (0, index_1.marrowGetKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1831
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1832
|
+
return;
|
|
1833
|
+
}
|
|
1834
|
+
if (toolName === 'marrow_revoke_key') {
|
|
1835
|
+
const keyId = requireString(args, 'id');
|
|
1836
|
+
const result = await (0, index_1.marrowRevokeKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1837
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
if (toolName === 'marrow_rotate_key') {
|
|
1841
|
+
const keyId = requireString(args, 'id');
|
|
1842
|
+
const result = await (0, index_1.marrowRotateKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1843
|
+
success(id, {
|
|
1844
|
+
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1845
|
+
});
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
// Memory control tools — all use requireString for id validation
|
|
1849
|
+
if (toolName === 'marrow_list_memories') {
|
|
1850
|
+
const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
|
|
1851
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (toolName === 'marrow_get_memory') {
|
|
1855
|
+
const memId = requireString(args, 'id');
|
|
1856
|
+
const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
|
|
1857
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
if (toolName === 'marrow_update_memory') {
|
|
1861
|
+
const memId = requireString(args, 'id');
|
|
1862
|
+
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);
|
|
1863
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
if (toolName === 'marrow_delete_memory') {
|
|
1867
|
+
const memId = requireString(args, 'id');
|
|
1868
|
+
const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1869
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
if (toolName === 'marrow_mark_outdated') {
|
|
1873
|
+
const memId = requireString(args, 'id');
|
|
1874
|
+
const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1875
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1876
|
+
return;
|
|
1877
|
+
}
|
|
1878
|
+
if (toolName === 'marrow_supersede_memory') {
|
|
1879
|
+
const memId = requireString(args, 'id');
|
|
1880
|
+
const newText = requireString(args, 'text');
|
|
1881
|
+
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);
|
|
1882
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
if (toolName === 'marrow_share_memory') {
|
|
1886
|
+
const memId = requireString(args, 'id');
|
|
1887
|
+
const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
|
|
1888
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (toolName === 'marrow_export_memories') {
|
|
1892
|
+
const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
|
|
1893
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (toolName === 'marrow_import_memories') {
|
|
1897
|
+
const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
|
|
1898
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (toolName === 'marrow_retrieve_memories') {
|
|
1902
|
+
const query = requireString(args, 'query');
|
|
1903
|
+
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);
|
|
1904
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
if (toolName === 'marrow_workflow') {
|
|
1908
|
+
const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
|
|
1909
|
+
action: args.action,
|
|
1910
|
+
workflowId: args.workflowId,
|
|
1911
|
+
instanceId: args.instanceId,
|
|
1912
|
+
name: args.name,
|
|
1913
|
+
description: args.description,
|
|
1914
|
+
steps: args.steps,
|
|
1915
|
+
tags: args.tags,
|
|
1916
|
+
agentId: args.agentId,
|
|
1917
|
+
context: args.context,
|
|
1918
|
+
inputs: args.inputs,
|
|
1919
|
+
stepCompleted: args.stepCompleted,
|
|
1920
|
+
outcome: args.outcome,
|
|
1921
|
+
nextAgentId: args.nextAgentId,
|
|
1922
|
+
contextUpdate: args.contextUpdate,
|
|
2143
1923
|
status: args.status,
|
|
1924
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1925
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (toolName === 'marrow_dashboard') {
|
|
1929
|
+
const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1930
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1933
|
+
if (toolName === 'marrow_digest') {
|
|
1934
|
+
const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
|
|
1935
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
if (toolName === 'marrow_agent_status') {
|
|
1939
|
+
const result = await withControlDeadline(async (signal) => {
|
|
1940
|
+
const [analyticsStatus, context] = await Promise.all([
|
|
1941
|
+
(0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID, signal),
|
|
1942
|
+
(0, index_1.marrowAgentContext)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal)
|
|
1943
|
+
.catch(() => null),
|
|
1944
|
+
]);
|
|
1945
|
+
const contextProjection = recordValue(context)?.primary_tool_availability;
|
|
1946
|
+
return {
|
|
1947
|
+
...analyticsStatus,
|
|
1948
|
+
...(contextProjection === undefined ? {} : { primary_tool_availability: contextProjection }),
|
|
1949
|
+
};
|
|
1950
|
+
}, { cacheAware: false, toolName: 'marrow_agent_status' });
|
|
1951
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus({ ...result, local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)) }), null, 2) }] });
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (toolName === 'marrow_runtime_status') {
|
|
1955
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowRuntimeStatus)(API_KEY, BASE_URL, args.fast !== false, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_runtime_status' });
|
|
1956
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus(result), null, 2) }] });
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
if (toolName === 'marrow_value_report') {
|
|
1960
|
+
const result = await (0, index_1.marrowValueReport)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
|
|
1961
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
if (toolName === 'marrow_decision_brief') {
|
|
1965
|
+
const result = await (0, index_1.marrowDecisionBrief)(API_KEY, BASE_URL, {
|
|
1966
|
+
action: args.action,
|
|
1967
|
+
type: args.type,
|
|
1968
|
+
role: args.role,
|
|
1969
|
+
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1970
|
+
session_id: args.sessionId || SESSION_ID,
|
|
1971
|
+
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1972
|
+
period: args.period,
|
|
1973
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1974
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
if (toolName === 'marrow_first_value') {
|
|
1978
|
+
const result = await (0, index_1.marrowFirstValue)(API_KEY, BASE_URL, {
|
|
1979
|
+
action: args.action ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
|
|
1980
|
+
type: args.type,
|
|
1981
|
+
role: args.role,
|
|
1982
|
+
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1983
|
+
session_id: args.sessionId || SESSION_ID,
|
|
1984
|
+
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1985
|
+
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1986
|
+
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1987
|
+
: undefined,
|
|
1988
|
+
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1989
|
+
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1990
|
+
: undefined,
|
|
1991
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1992
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1993
|
+
return;
|
|
1994
|
+
}
|
|
1995
|
+
if (toolName === 'marrow_agent_runtime') {
|
|
1996
|
+
const runtimeInput = {
|
|
1997
|
+
action: (0, redact_1.redactSensitiveText)(args.action),
|
|
1998
|
+
type: args.type,
|
|
1999
|
+
role: args.role,
|
|
2000
|
+
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
2001
|
+
session_id: args.sessionId || SESSION_ID,
|
|
2002
|
+
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
2003
|
+
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
2004
|
+
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
2005
|
+
: undefined,
|
|
2006
|
+
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
2007
|
+
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
2008
|
+
: undefined,
|
|
2009
|
+
period: args.period,
|
|
2010
|
+
};
|
|
2011
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowAgentRuntime)(API_KEY, BASE_URL, runtimeInput, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: isHighRiskAction(runtimeInput.action, runtimeInput.type), toolName: 'marrow_agent_runtime' });
|
|
2012
|
+
storeRuntimeGuidance(result);
|
|
2013
|
+
storeLastKnownStatus(result.status, 'runtime');
|
|
2014
|
+
toolSuccess(id, clientOperationalPayload('marrow_agent_runtime', result));
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
if (toolName === 'marrow_arbitrate') {
|
|
2018
|
+
const result = await (0, index_1.marrowArbitrate)(API_KEY, BASE_URL, {
|
|
2019
|
+
objective: (0, redact_1.redactSensitiveText)(args.objective),
|
|
2020
|
+
owner_intent: typeof args.ownerIntent === 'string' ? (0, redact_1.redactSensitiveText)(args.ownerIntent) : undefined,
|
|
2021
|
+
conflict_type: args.conflictType,
|
|
2022
|
+
proposals: Array.isArray(args.proposals)
|
|
2023
|
+
? args.proposals
|
|
2024
|
+
: [],
|
|
2025
|
+
action: typeof args.action === 'string' ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
|
|
2026
|
+
type: args.type,
|
|
2027
|
+
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
2028
|
+
session_id: args.sessionId || SESSION_ID,
|
|
2029
|
+
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
2030
|
+
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
2031
|
+
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
2032
|
+
: undefined,
|
|
2033
|
+
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
2034
|
+
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
2035
|
+
: undefined,
|
|
2036
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2037
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
if (toolName === 'marrow_coordinate') {
|
|
2041
|
+
const coordinationArgs = { ...args };
|
|
2042
|
+
delete coordinationArgs.agent_id;
|
|
2043
|
+
delete coordinationArgs.source_agent_id;
|
|
2044
|
+
const result = await (0, index_1.marrowCoordinate)(API_KEY, BASE_URL, coordinationArgs, SESSION_ID, FLEET_AGENT_ID);
|
|
2045
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if (toolName === 'marrow_replay_compare') {
|
|
2049
|
+
const result = await (0, index_1.marrowReplayCompare)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2050
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
if (toolName === 'marrow_governance_control_plane') {
|
|
2054
|
+
const result = await (0, index_1.marrowGovernanceControlPlane)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2055
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2058
|
+
if (toolName === 'marrow_hermes_integration') {
|
|
2059
|
+
const result = await (0, index_1.marrowHermesIntegration)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2060
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
if (toolName === 'marrow_completion_contracts') {
|
|
2064
|
+
const result = await (0, index_1.marrowCompletionContracts)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2065
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
if (toolName === 'marrow_evaluate_completion_contract') {
|
|
2069
|
+
const input = {
|
|
2070
|
+
action: args.action,
|
|
2071
|
+
workflow_type: args.workflow_type,
|
|
2072
|
+
risk_level: args.risk_level,
|
|
2073
|
+
evidence: args.evidence && typeof args.evidence === 'object' && !Array.isArray(args.evidence)
|
|
2074
|
+
? (0, redact_1.redactSensitiveValue)(args.evidence)
|
|
2075
|
+
: undefined,
|
|
2076
|
+
};
|
|
2077
|
+
const result = await (0, index_1.marrowEvaluateCompletionContract)(API_KEY, BASE_URL, input, SESSION_ID, FLEET_AGENT_ID);
|
|
2078
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
if (toolName === 'marrow_governance_timeline') {
|
|
2082
|
+
const result = await (0, index_1.marrowGovernanceTimeline)(API_KEY, BASE_URL, {
|
|
2144
2083
|
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2145
2084
|
limit: args.limit,
|
|
2146
|
-
}, SESSION_ID, FLEET_AGENT_ID
|
|
2147
|
-
|
|
2085
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2086
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2087
|
+
return;
|
|
2148
2088
|
}
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
state: 'not_entitled',
|
|
2155
|
-
failure_kind: 'entitlement',
|
|
2156
|
-
authorization_state: 'plan_limited',
|
|
2157
|
-
credential_valid: true,
|
|
2158
|
-
feature: 'fleet_handoff_status',
|
|
2159
|
-
current_plan: error.currentPlan,
|
|
2160
|
-
required_plan: 'team',
|
|
2161
|
-
required_feature: error.requiredFeature || 'fleet_learning',
|
|
2162
|
-
service_reachable: true,
|
|
2163
|
-
exact_next_action: 'Use status, ask, runtime, auto, and commit on the current plan. Upgrade to Team before relying on fleet handoff status.',
|
|
2164
|
-
}));
|
|
2089
|
+
if (toolName === 'marrow_decision_trace') {
|
|
2090
|
+
const decisionId = args.decisionId;
|
|
2091
|
+
if (!decisionId) {
|
|
2092
|
+
error(id, -32602, 'decisionId is required');
|
|
2093
|
+
return;
|
|
2165
2094
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2095
|
+
const result = await (0, index_1.marrowDecisionTrace)(API_KEY, BASE_URL, decisionId, SESSION_ID, FLEET_AGENT_ID);
|
|
2096
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
if (toolName === 'marrow_buyer_proof') {
|
|
2100
|
+
const result = await (0, index_1.marrowBuyerProof)(API_KEY, BASE_URL, {
|
|
2101
|
+
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2102
|
+
periodDays: args.periodDays,
|
|
2103
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2104
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
if (toolName === 'marrow_mode_recommend') {
|
|
2108
|
+
const result = await (0, index_1.marrowRecommendGovernanceMode)(API_KEY, BASE_URL, {
|
|
2109
|
+
project: args.project && typeof args.project === 'object' && !Array.isArray(args.project)
|
|
2110
|
+
? (0, redact_1.redactSensitiveValue)(args.project)
|
|
2111
|
+
: undefined,
|
|
2112
|
+
workflow: args.workflow && typeof args.workflow === 'object' && !Array.isArray(args.workflow)
|
|
2113
|
+
? (0, redact_1.redactSensitiveValue)(args.workflow)
|
|
2114
|
+
: undefined,
|
|
2115
|
+
agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
|
|
2116
|
+
? (0, redact_1.redactSensitiveValue)(args.agent)
|
|
2117
|
+
: { id: FLEET_AGENT_ID },
|
|
2118
|
+
selected_mode: args.selected_mode,
|
|
2119
|
+
selection_source: args.selection_source,
|
|
2120
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2121
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
if (toolName === 'marrow_policy_profiles') {
|
|
2125
|
+
const result = await (0, index_1.marrowListPolicyProfiles)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2126
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (toolName === 'marrow_create_policy_profile') {
|
|
2130
|
+
const result = await (0, index_1.marrowCreatePolicyProfile)(API_KEY, BASE_URL, {
|
|
2131
|
+
name: args.name,
|
|
2132
|
+
description: args.description,
|
|
2133
|
+
rules: Array.isArray(args.rules) ? args.rules : undefined,
|
|
2134
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2135
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
if (toolName === 'marrow_assign_project_policy_profile') {
|
|
2139
|
+
const result = await (0, index_1.marrowAssignProjectPolicyProfile)(API_KEY, BASE_URL, {
|
|
2140
|
+
project_key: args.project_key,
|
|
2141
|
+
profile_id: args.profile_id,
|
|
2142
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2143
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
if (toolName === 'marrow_policy_resolve') {
|
|
2147
|
+
const result = await (0, index_1.marrowResolvePolicy)(API_KEY, BASE_URL, {
|
|
2148
|
+
profile_id: args.profile_id,
|
|
2149
|
+
profile_name: args.profile_name,
|
|
2150
|
+
project: args.project && typeof args.project === 'object' && !Array.isArray(args.project)
|
|
2151
|
+
? (0, redact_1.redactSensitiveValue)(args.project)
|
|
2152
|
+
: undefined,
|
|
2153
|
+
workflow: args.workflow && typeof args.workflow === 'object' && !Array.isArray(args.workflow)
|
|
2154
|
+
? (0, redact_1.redactSensitiveValue)(args.workflow)
|
|
2155
|
+
: undefined,
|
|
2156
|
+
agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
|
|
2157
|
+
? (0, redact_1.redactSensitiveValue)(args.agent)
|
|
2158
|
+
: { id: FLEET_AGENT_ID },
|
|
2159
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2160
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
if (toolName === 'marrow_workflow_gate') {
|
|
2164
|
+
const result = await (0, index_1.marrowWorkflowGate)(API_KEY, BASE_URL, {
|
|
2165
|
+
action: (0, redact_1.redactSensitiveText)(args.action),
|
|
2166
|
+
description: args.description ? (0, redact_1.redactSensitiveText)(args.description) : undefined,
|
|
2167
|
+
risk_tolerance: args.riskTolerance,
|
|
2168
|
+
requires_approval: args.requiresApproval,
|
|
2169
|
+
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
2170
|
+
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
2171
|
+
: undefined,
|
|
2172
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2173
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
if (toolName === 'marrow_agent_performance') {
|
|
2177
|
+
const result = await (0, index_1.marrowAgentPerformance)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
|
|
2178
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
if (toolName === 'marrow_fleet_lessons') {
|
|
2182
|
+
const result = await (0, index_1.marrowFleetLessons)(API_KEY, BASE_URL, {
|
|
2183
|
+
query: args.query,
|
|
2184
|
+
type: args.type,
|
|
2185
|
+
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2186
|
+
limit: args.limit,
|
|
2187
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2188
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (toolName === 'marrow_record_deployment_memory') {
|
|
2192
|
+
const result = await (0, index_1.marrowRecordDeploymentMemory)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2193
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
if (toolName === 'marrow_create_handoff') {
|
|
2197
|
+
const result = await (0, index_1.marrowCreateHandoff)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2198
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
if (toolName === 'marrow_update_handoff') {
|
|
2202
|
+
const handoffId = args.handoffId;
|
|
2203
|
+
if (!handoffId) {
|
|
2204
|
+
error(id, -32602, 'handoffId is required');
|
|
2205
|
+
return;
|
|
2168
2206
|
}
|
|
2207
|
+
const result = await (0, index_1.marrowUpdateHandoff)(API_KEY, BASE_URL, handoffId, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2208
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2209
|
+
return;
|
|
2169
2210
|
}
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2211
|
+
if (toolName === 'marrow_handoff_status') {
|
|
2212
|
+
try {
|
|
2213
|
+
const result = await withControlDeadline((signal) => (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
|
|
2214
|
+
status: args.status,
|
|
2215
|
+
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2216
|
+
limit: args.limit,
|
|
2217
|
+
}, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_handoff_status' });
|
|
2218
|
+
toolSuccess(id, clientOperationalPayload('marrow_handoff_status', result));
|
|
2219
|
+
}
|
|
2220
|
+
catch (error) {
|
|
2221
|
+
if (error instanceof request_reliability_1.MarrowRequestError && error.backendCode === 'MARROW_PLAN_UPGRADE_REQUIRED') {
|
|
2222
|
+
toolSuccess(id, clientOperationalPayload('marrow_handoff_status', {
|
|
2223
|
+
ok: true,
|
|
2224
|
+
available: false,
|
|
2225
|
+
state: 'not_entitled',
|
|
2226
|
+
failure_kind: 'entitlement',
|
|
2227
|
+
authorization_state: 'plan_limited',
|
|
2228
|
+
credential_valid: true,
|
|
2229
|
+
feature: 'fleet_handoff_status',
|
|
2230
|
+
current_plan: error.currentPlan,
|
|
2231
|
+
required_plan: 'team',
|
|
2232
|
+
required_feature: error.requiredFeature || 'fleet_learning',
|
|
2233
|
+
service_reachable: true,
|
|
2234
|
+
exact_next_action: 'Use status, ask, runtime, auto, and commit on the current plan. Upgrade to Team before relying on fleet handoff status.',
|
|
2235
|
+
}));
|
|
2236
|
+
}
|
|
2237
|
+
else {
|
|
2238
|
+
throw error;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2181
2241
|
return;
|
|
2182
2242
|
}
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2187
|
-
if (toolName === 'marrow_list_templates') {
|
|
2188
|
-
const result = await (0, index_1.marrowListTemplates)(API_KEY, BASE_URL, {
|
|
2189
|
-
industry: args.industry,
|
|
2190
|
-
category: args.category,
|
|
2191
|
-
limit: args.limit,
|
|
2192
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2193
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2194
|
-
return;
|
|
2195
|
-
}
|
|
2196
|
-
if (toolName === 'marrow_install_template') {
|
|
2197
|
-
const slug = args.slug;
|
|
2198
|
-
if (!slug) {
|
|
2199
|
-
error(id, -32602, 'slug is required');
|
|
2243
|
+
if (toolName === 'marrow_session_end') {
|
|
2244
|
+
const result = await (0, index_1.marrowSessionEnd)(API_KEY, BASE_URL, (0, hook_session_1.sessionEndAutoCommitOpen)(args.autoCommitOpen), SESSION_ID, FLEET_AGENT_ID);
|
|
2245
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2200
2246
|
return;
|
|
2201
2247
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2248
|
+
if (toolName === 'marrow_accept_detected') {
|
|
2249
|
+
const detectedId = args.detectedId;
|
|
2250
|
+
if (!detectedId) {
|
|
2251
|
+
error(id, -32602, 'detectedId is required');
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
const result = await (0, index_1.marrowAcceptDetected)(API_KEY, BASE_URL, detectedId, SESSION_ID, FLEET_AGENT_ID);
|
|
2255
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
if (toolName === 'marrow_list_templates') {
|
|
2259
|
+
const result = await (0, index_1.marrowListTemplates)(API_KEY, BASE_URL, {
|
|
2260
|
+
industry: args.industry,
|
|
2261
|
+
category: args.category,
|
|
2262
|
+
limit: args.limit,
|
|
2263
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2264
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
if (toolName === 'marrow_install_template') {
|
|
2268
|
+
const slug = args.slug;
|
|
2269
|
+
if (!slug) {
|
|
2270
|
+
error(id, -32602, 'slug is required');
|
|
2271
|
+
return;
|
|
2272
|
+
}
|
|
2273
|
+
const result = await (0, index_1.marrowInstallTemplate)(API_KEY, BASE_URL, slug, SESSION_ID, FLEET_AGENT_ID);
|
|
2274
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
error(id, -32601, `Method not found: ${toolName}`);
|
|
2204
2278
|
return;
|
|
2205
2279
|
}
|
|
2206
|
-
error(id, -32601, `Method not found: ${
|
|
2207
|
-
return;
|
|
2280
|
+
error(id, -32601, `Method not found: ${method}`);
|
|
2208
2281
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2282
|
+
catch (err) {
|
|
2283
|
+
if (err instanceof request_reliability_1.MarrowRequestError && method === 'tools/call') {
|
|
2284
|
+
toolSuccess(id, toolFailure(params?.name, err), true);
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2288
|
+
error(id, -32602, (0, redact_1.redactSensitiveText)(message).slice(0, 240));
|
|
2215
2289
|
}
|
|
2216
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2217
|
-
error(id, -32602, (0, redact_1.redactSensitiveText)(message).slice(0, 240));
|
|
2218
2290
|
}
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
autoCommitOnClose().then(() => process.exit(0));
|
|
2227
|
-
}
|
|
2228
|
-
}
|
|
2229
|
-
process.stdin.setEncoding('utf8');
|
|
2230
|
-
process.stdin.on('data', (chunk) => {
|
|
2231
|
-
buffer += chunk;
|
|
2232
|
-
const lines = buffer.split('\n');
|
|
2233
|
-
buffer = lines.pop() || ''; // keep incomplete line in buffer
|
|
2234
|
-
for (const line of lines) {
|
|
2235
|
-
const trimmed = line.trim();
|
|
2236
|
-
if (!trimmed)
|
|
2237
|
-
continue;
|
|
2238
|
-
// [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
|
|
2239
|
-
let msg;
|
|
2240
|
-
try {
|
|
2241
|
-
msg = JSON.parse(trimmed);
|
|
2242
|
-
}
|
|
2243
|
-
catch (parseErr) {
|
|
2244
|
-
process.stderr.write(`[marrow] JSON parse error: ${parseErr}\n`);
|
|
2245
|
-
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
2246
|
-
continue;
|
|
2291
|
+
// MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)
|
|
2292
|
+
let buffer = '';
|
|
2293
|
+
let pendingRequests = 0;
|
|
2294
|
+
let stdinEnded = false;
|
|
2295
|
+
function checkExit() {
|
|
2296
|
+
if (stdinEnded && pendingRequests === 0) {
|
|
2297
|
+
autoCommitOnClose().then(() => process.exit(0));
|
|
2247
2298
|
}
|
|
2248
|
-
// MCP notifications (no id) must be silently ignored per spec
|
|
2249
|
-
if (msg.id === undefined || msg.id === null)
|
|
2250
|
-
continue;
|
|
2251
|
-
// The client's answer to this server's own request (elicitation).
|
|
2252
|
-
if (settleClientResponse(msg))
|
|
2253
|
-
continue;
|
|
2254
|
-
pendingRequests++;
|
|
2255
|
-
handleRequest(msg)
|
|
2256
|
-
.catch((err) => {
|
|
2257
|
-
process.stderr.write(`[marrow] Handler error: ${err}\n`);
|
|
2258
|
-
})
|
|
2259
|
-
.finally(() => {
|
|
2260
|
-
pendingRequests--;
|
|
2261
|
-
checkExit();
|
|
2262
|
-
});
|
|
2263
2299
|
}
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2300
|
+
process.stdin.setEncoding('utf8');
|
|
2301
|
+
process.stdin.on('data', (chunk) => {
|
|
2302
|
+
buffer += chunk;
|
|
2303
|
+
const lines = buffer.split('\n');
|
|
2304
|
+
buffer = lines.pop() || ''; // keep incomplete line in buffer
|
|
2305
|
+
for (const line of lines) {
|
|
2306
|
+
const trimmed = line.trim();
|
|
2307
|
+
if (!trimmed)
|
|
2308
|
+
continue;
|
|
2309
|
+
// [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
|
|
2310
|
+
let msg;
|
|
2311
|
+
try {
|
|
2312
|
+
msg = JSON.parse(trimmed);
|
|
2313
|
+
}
|
|
2314
|
+
catch (parseErr) {
|
|
2315
|
+
process.stderr.write(`[marrow] JSON parse error: ${parseErr}\n`);
|
|
2316
|
+
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
// MCP notifications (no id) must be silently ignored per spec
|
|
2320
|
+
if (msg.id === undefined || msg.id === null)
|
|
2321
|
+
continue;
|
|
2322
|
+
// The client's answer to this server's own request (elicitation).
|
|
2323
|
+
if (settleClientResponse(msg))
|
|
2324
|
+
continue;
|
|
2325
|
+
pendingRequests++;
|
|
2326
|
+
handleRequest(msg)
|
|
2327
|
+
.catch((err) => {
|
|
2328
|
+
process.stderr.write(`[marrow] Handler error: ${err}\n`);
|
|
2329
|
+
})
|
|
2330
|
+
.finally(() => {
|
|
2331
|
+
pendingRequests--;
|
|
2332
|
+
checkExit();
|
|
2333
|
+
});
|
|
2271
2334
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2335
|
+
});
|
|
2336
|
+
process.stdin.on('end', () => {
|
|
2337
|
+
stdinEnded = true;
|
|
2338
|
+
if (buffer.trim()) {
|
|
2339
|
+
let msg;
|
|
2340
|
+
try {
|
|
2341
|
+
msg = JSON.parse(buffer.trim());
|
|
2342
|
+
}
|
|
2343
|
+
catch (err) {
|
|
2344
|
+
process.stderr.write(`[marrow] JSON parse error on remaining buffer: ${err}\n`);
|
|
2345
|
+
checkExit();
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (msg.id === undefined || msg.id === null) {
|
|
2349
|
+
checkExit();
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
pendingRequests++;
|
|
2353
|
+
handleRequest(msg)
|
|
2354
|
+
.catch((err) => {
|
|
2355
|
+
process.stderr.write(`[marrow] Handler error on remaining: ${err}\n`);
|
|
2356
|
+
})
|
|
2357
|
+
.finally(() => {
|
|
2358
|
+
pendingRequests--;
|
|
2359
|
+
checkExit();
|
|
2360
|
+
});
|
|
2276
2361
|
}
|
|
2277
|
-
|
|
2362
|
+
else {
|
|
2278
2363
|
checkExit();
|
|
2279
|
-
return;
|
|
2280
2364
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
pendingRequests--;
|
|
2288
|
-
checkExit();
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
|
-
else {
|
|
2292
|
-
checkExit();
|
|
2293
|
-
}
|
|
2294
|
-
});
|
|
2295
|
-
process.stdin.on('error', (err) => {
|
|
2296
|
-
process.stderr.write(`[marrow] stdin error: ${err}\n`);
|
|
2297
|
-
process.exit(1);
|
|
2298
|
-
});
|
|
2365
|
+
});
|
|
2366
|
+
process.stdin.on('error', (err) => {
|
|
2367
|
+
process.stderr.write(`[marrow] stdin error: ${err}\n`);
|
|
2368
|
+
process.exit(1);
|
|
2369
|
+
});
|
|
2370
|
+
} // Close the keyless-start else
|
|
2299
2371
|
} // Close the if (process.argv[2] !== 'keys') block
|
|
2300
2372
|
}
|
|
2301
2373
|
//# sourceMappingURL=cli.js.map
|