@getmarrow/mcp 3.9.103 → 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 +26 -5
- 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 +1677 -1636
- package/dist/cli.js.map +1 -1
- package/dist/codex-plugin-pins.d.ts.map +1 -1
- package/dist/codex-plugin-pins.js +18 -0
- package/dist/codex-plugin-pins.js.map +1 -1
- 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 +36 -3
- package/dist/hook-contract.js.map +1 -1
- package/dist/hook-pre-action.d.ts +38 -7
- package/dist/hook-pre-action.d.ts.map +1 -1
- package/dist/hook-pre-action.js +254 -21
- 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.map +1 -1
- package/dist/hook.js +5 -3
- 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/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- 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 +2 -0
- package/dist/normalized-action.d.ts.map +1 -1
- package/dist/normalized-action.js +65 -10
- package/dist/normalized-action.js.map +1 -1
- package/dist/own-server-identity.d.ts +22 -3
- package/dist/own-server-identity.d.ts.map +1 -1
- package/dist/own-server-identity.js +98 -27
- package/dist/own-server-identity.js.map +1 -1
- package/dist/own-tool.d.ts +14 -6
- package/dist/own-tool.d.ts.map +1 -1
- package/dist/own-tool.js +18 -7
- package/dist/own-tool.js.map +1 -1
- 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/hook.d.ts +21 -2
- package/dist/seatbelt/hook.d.ts.map +1 -1
- package/dist/seatbelt/hook.js +45 -11
- package/dist/seatbelt/hook.js.map +1 -1
- package/dist/seatbelt/mapper.d.ts.map +1 -1
- package/dist/seatbelt/mapper.js +360 -27
- package/dist/seatbelt/mapper.js.map +1 -1
- package/dist/seatbelt/tables.d.ts +1 -1
- package/dist/seatbelt/tables.d.ts.map +1 -1
- package/dist/seatbelt/tables.js +9 -16
- package/dist/seatbelt/tables.js.map +1 -1
- 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/package.json +2 -2
- package/server.json +2 -2
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,6 +231,27 @@ 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';
|
|
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
|
+
}
|
|
231
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
|
|
|
@@ -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,9 +428,10 @@ 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) => {
|
|
@@ -555,759 +585,769 @@ if (process.argv[2] !== 'keys') {
|
|
|
555
585
|
}
|
|
556
586
|
else {
|
|
557
587
|
const resolvedEnv = (0, env_1.resolveMarrowEnv)();
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
// resolveMarrowEnv already gives MARROW_FLEET_AGENT_ID precedence over MARROW_AGENT_ID.
|
|
567
|
-
// When no identity is configured, omit it and let the API resolve the key's
|
|
568
|
-
// bound agent instead of inventing a process-local identity that changes on restart.
|
|
569
|
-
const FLEET_AGENT_ID = resolvedEnv.agentId || undefined;
|
|
570
|
-
const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
|
|
571
|
-
if (!API_KEY) {
|
|
572
|
-
process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
|
|
573
|
-
process.stderr.write(`${resolvedEnv.exactFix}\n`);
|
|
574
|
-
process.stderr.write(`Get a key: ${GET_A_KEY_URL}. Day-one steps and commands: npx @getmarrow/mcp --help\n`);
|
|
575
|
-
process.exit(1);
|
|
576
|
-
}
|
|
577
|
-
if (ENDPOINT_HOST) {
|
|
578
|
-
process.stderr.write(`[marrow] Using a non-default Marrow endpoint: ${ENDPOINT_HOST} (MARROW_BASE_URL).\n`);
|
|
579
|
-
}
|
|
580
|
-
// [SECURITY #12] Warn if API key is visible in process args
|
|
581
|
-
if (cliArgs.apiKey) {
|
|
582
|
-
process.stderr.write('[marrow] Warning: --key flag exposes API key in process list. Use MARROW_API_KEY env var for production.\n');
|
|
583
|
-
}
|
|
584
|
-
// Auto-orient on startup — cache warnings, inject into EVERY marrow_think response
|
|
585
|
-
let cachedOrientWarnings = [];
|
|
586
|
-
let thinkCallCount = 0;
|
|
587
|
-
let orientCallCount = 0;
|
|
588
|
-
let initialized = false;
|
|
589
|
-
// ---------------------------------------------------------------------------
|
|
590
|
-
// MCP elicitation: where the client can ask its user mid-task, a held
|
|
591
|
-
// marrow_auto action is approved in the client's own dialog (one click). The
|
|
592
|
-
// request goes to the client UI, never to the model; the answer is reported to
|
|
593
|
-
// Marrow as client-attested.
|
|
594
|
-
// ---------------------------------------------------------------------------
|
|
595
|
-
const ELICITATION_PROTOCOL_VERSIONS = ['2025-06-18', '2025-11-25'];
|
|
596
|
-
const ELICITATION_TIMEOUT_MS = 120_000;
|
|
597
|
-
let clientElicitation = false;
|
|
598
|
-
let clientHost = 'other';
|
|
599
|
-
let serverRequestSeq = 0;
|
|
600
|
-
const pendingClientRequests = new Map();
|
|
601
|
-
function noteClientCapabilities(params) {
|
|
602
|
-
const record = params && typeof params === 'object' ? params : {};
|
|
603
|
-
const capabilities = record.capabilities && typeof record.capabilities === 'object' ? record.capabilities : {};
|
|
604
|
-
const requested = typeof record.protocolVersion === 'string' ? record.protocolVersion : '';
|
|
605
|
-
const info = record.clientInfo && typeof record.clientInfo === 'object' ? record.clientInfo : {};
|
|
606
|
-
const name = typeof info.name === 'string' ? info.name.toLowerCase() : '';
|
|
607
|
-
clientHost = /claude/.test(name) ? 'claude-code' : /cursor/.test(name) ? 'cursor' : /hermes/.test(name) ? 'hermes'
|
|
608
|
-
: /codex/.test(name) ? 'codex' : /gemini/.test(name) ? 'gemini' : 'other';
|
|
609
|
-
clientElicitation = Boolean(capabilities.elicitation && typeof capabilities.elicitation === 'object')
|
|
610
|
-
&& ELICITATION_PROTOCOL_VERSIONS.includes(requested);
|
|
611
|
-
// Elicitation needs the protocol version that defines it; otherwise keep the version this server always spoke.
|
|
612
|
-
return clientElicitation ? requested : '2024-11-05';
|
|
613
|
-
}
|
|
614
|
-
function requestClient(method, params, timeoutMs) {
|
|
615
|
-
serverRequestSeq += 1;
|
|
616
|
-
const id = `marrow-server-${serverRequestSeq}`;
|
|
617
|
-
return new Promise((resolve) => {
|
|
618
|
-
const timer = setTimeout(() => {
|
|
619
|
-
pendingClientRequests.delete(id);
|
|
620
|
-
resolve({ error: { code: 'timeout' } });
|
|
621
|
-
}, timeoutMs);
|
|
622
|
-
timer.unref?.();
|
|
623
|
-
pendingClientRequests.set(id, { resolve, timer });
|
|
624
|
-
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),
|
|
625
596
|
});
|
|
626
597
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
+
},
|
|
654
695
|
},
|
|
696
|
+
required: ['decision'],
|
|
655
697
|
},
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
return null;
|
|
666
|
-
const content = result.content && typeof result.content === 'object' ? result.content : {};
|
|
667
|
-
return content.decision === 'approve' ? 'approved' : content.decision === 'decline' ? 'declined' : null;
|
|
668
|
-
}
|
|
669
|
-
function autoElicitationOptions() {
|
|
670
|
-
if (!clientElicitation)
|
|
671
|
-
return undefined;
|
|
672
|
-
const hostSessionId = typeof SESSION_ID === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(SESSION_ID) ? SESSION_ID : `mcp-${process.pid}`;
|
|
673
|
-
return { elicitApproval, elicitHost: clientHost, elicitHostSessionId: hostSessionId };
|
|
674
|
-
}
|
|
675
|
-
function formatWarningActionably(w) {
|
|
676
|
-
const pct = Math.round(w.failureRate * 100);
|
|
677
|
-
return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
|
|
678
|
-
}
|
|
679
|
-
// [FIX #4] Log orient refresh failures instead of silently ignoring
|
|
680
|
-
async function refreshOrientWarnings() {
|
|
681
|
-
try {
|
|
682
|
-
const r = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
683
|
-
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;
|
|
684
708
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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 };
|
|
688
714
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
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() {
|
|
696
721
|
try {
|
|
697
|
-
await (0, index_1.
|
|
698
|
-
|
|
699
|
-
success: false,
|
|
700
|
-
outcome: 'Session ended without explicit commit',
|
|
701
|
-
}, 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;
|
|
702
724
|
}
|
|
703
725
|
catch (err) {
|
|
704
726
|
const msg = err instanceof Error ? err.message : String(err);
|
|
705
|
-
process.stderr.write(`[marrow] Warning:
|
|
727
|
+
process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
|
|
706
728
|
}
|
|
707
729
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
726
|
-
}
|
|
727
|
-
function toolSuccess(id, value, isError = false) {
|
|
728
|
-
success(id, {
|
|
729
|
-
content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
|
|
730
|
-
...(isError ? { isError: true } : {}),
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
/** Proof fields Marrow reported missing per decision or gate receipt, for a plain answer on a retry conflict. */
|
|
734
|
-
const commitProofMissing = new Map();
|
|
735
|
-
function proofFieldsGiven(proof) {
|
|
736
|
-
if (!proof || typeof proof !== 'object' || Array.isArray(proof))
|
|
737
|
-
return [];
|
|
738
|
-
return Object.entries(proof)
|
|
739
|
-
.filter(([, value]) => value !== null && value !== undefined && value !== ''
|
|
740
|
-
&& !(Array.isArray(value) && value.length === 0)
|
|
741
|
-
&& !(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0))
|
|
742
|
-
.map(([key]) => key);
|
|
743
|
-
}
|
|
744
|
-
/**
|
|
745
|
-
* A retry of marrow_commit that added proof, refused as a conflict with the
|
|
746
|
-
* outcome Marrow already holds (409 MARROW_OUTCOME_OBSERVATION_CONFLICT):
|
|
747
|
-
* say plainly which proof fields are still missing.
|
|
748
|
-
*/
|
|
749
|
-
function proofRetryConflict(keys, proof, requiredFromHold) {
|
|
750
|
-
const required = [...new Set([...keys.flatMap((key) => commitProofMissing.get(key) || []), ...requiredFromHold])];
|
|
751
|
-
const given = proofFieldsGiven(proof);
|
|
752
|
-
const stillMissing = required.filter((field) => !given.includes(field));
|
|
753
|
-
return {
|
|
754
|
-
committed: false,
|
|
755
|
-
code: 'MARROW_OUTCOME_OBSERVATION_CONFLICT',
|
|
756
|
-
proof_required: true,
|
|
757
|
-
failure_kind: 'validation',
|
|
758
|
-
still_missing: stillMissing,
|
|
759
|
-
exact_next_action: stillMissing.length
|
|
760
|
-
? `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.`
|
|
761
|
-
: '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.',
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
function toolFailure(toolName, failure) {
|
|
765
|
-
const result = (0, request_reliability_1.structuredRequestFailure)(failure);
|
|
766
|
-
const proofValidation = failure.code === 'proof_required';
|
|
767
|
-
const infrastructureFailure = !proofValidation && !['authentication_required', 'permission_denied'].includes(failure.code);
|
|
768
|
-
const supportsStale = ['marrow_agent_runtime', 'marrow_orient', 'marrow_ask', 'marrow_handoff_status', 'marrow_runtime_status', 'marrow_status'].includes(toolName || '');
|
|
769
|
-
const spool = reportLifecycleSpool({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
770
|
-
result.failure_kind = proofValidation ? 'validation' : infrastructureFailure ? 'infrastructure' : 'authorization';
|
|
771
|
-
result.control_path = (0, control_path_state_1.controlPathStats)(toolName || 'marrow_control');
|
|
772
|
-
result.lifecycle_spool = {
|
|
773
|
-
state: spool.failed > 0 || spool.pending > 0 ? spool.state : 'clear',
|
|
774
|
-
pending: spool.pending,
|
|
775
|
-
failed: spool.failed,
|
|
776
|
-
exact_fix: spool.failed > 0 || spool.pending > 0 ? spool.exact_fix : null,
|
|
777
|
-
drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
|
|
778
|
-
legacy_namespaces: spool.other_namespaces?.count || 0,
|
|
779
|
-
};
|
|
780
|
-
result.host_capability = mcpHostCapability();
|
|
781
|
-
if (proofValidation) {
|
|
782
|
-
result.proof_required = true;
|
|
783
|
-
result.exact_next_action = failure.exactFix;
|
|
784
|
-
}
|
|
785
|
-
if (infrastructureFailure && supportsStale) {
|
|
786
|
-
let cached = null;
|
|
787
|
-
try {
|
|
788
|
-
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
|
+
}
|
|
789
747
|
}
|
|
790
|
-
catch { /* owner-only cache is best effort */ }
|
|
791
|
-
result.stale_brief = cached?.context || [
|
|
792
|
-
'## Marrow control-path outage brief',
|
|
793
|
-
'- This is an infrastructure failure; Marrow returned no policy decision and did not deny the action.',
|
|
794
|
-
'- Preserve local evidence and continue only low-risk, reversible work under the owner\'s existing safeguards.',
|
|
795
|
-
'- Do not perform high-risk work until a fresh Marrow runtime gate returns allow, warn, review_required, or block.',
|
|
796
|
-
].join('\n');
|
|
797
|
-
result.stale_source = cached ? 'last_known_guidance' : 'local_outage_safety';
|
|
798
|
-
result.stale_ms = cached?.stale_ms ?? null;
|
|
799
|
-
result.authorization_state = 'unavailable';
|
|
800
|
-
result.gate_obtained = false;
|
|
801
|
-
result.stale_can_authorize_high_risk = false;
|
|
802
|
-
const retryAfterMs = failure.retryAfterMs;
|
|
803
|
-
result.exact_next_action = cached
|
|
804
|
-
? 'Use this last-known brief for low-risk context only. Obtain a fresh Marrow runtime gate before high-risk work.'
|
|
805
|
-
: retryAfterMs != null
|
|
806
|
-
? `Wait ${retryAfterMs} ms, then retry once. Do not perform high-risk work without a fresh Marrow runtime gate.`
|
|
807
|
-
: failure.exactFix;
|
|
808
748
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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 = {
|
|
834
813
|
state: spool.failed > 0 || spool.pending > 0 ? spool.state : 'clear',
|
|
835
814
|
pending: spool.pending,
|
|
836
815
|
failed: spool.failed,
|
|
837
816
|
exact_fix: spool.failed > 0 || spool.pending > 0 ? spool.exact_fix : null,
|
|
838
817
|
drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
|
|
839
818
|
legacy_namespaces: spool.other_namespaces?.count || 0,
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
: commitBudget ? 8_000 : options.highRisk || runtimeBudget ? 4_500 : 4_000;
|
|
871
|
-
const controller = new AbortController();
|
|
872
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
873
|
-
timer.unref?.();
|
|
874
|
-
const startedAt = Date.now();
|
|
875
|
-
try {
|
|
876
|
-
const result = await operation(controller.signal);
|
|
877
|
-
(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
|
+
}
|
|
878
849
|
return result;
|
|
879
850
|
}
|
|
880
|
-
|
|
881
|
-
(0,
|
|
882
|
-
|
|
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
|
+
});
|
|
883
855
|
}
|
|
884
|
-
|
|
885
|
-
|
|
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
|
+
};
|
|
886
884
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
context: (0, hook_context_1.compactRuntimeContext)(runtime),
|
|
895
|
-
});
|
|
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;
|
|
896
892
|
}
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
apiKey: API_KEY,
|
|
903
|
-
baseUrl: BASE_URL,
|
|
904
|
-
agentId: FLEET_AGENT_ID,
|
|
905
|
-
status,
|
|
906
|
-
source,
|
|
907
|
-
});
|
|
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;
|
|
908
898
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
.
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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 || '')}`);
|
|
902
|
+
}
|
|
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();
|
|
920
915
|
try {
|
|
921
|
-
|
|
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);
|
|
922
926
|
}
|
|
923
|
-
catch { /* ignore */ }
|
|
924
|
-
throw new Error(`API error ${res.status}: ${detail.slice(0, 200)}`);
|
|
925
927
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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 */ }
|
|
929
938
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (params?.agentId)
|
|
942
|
-
qs.set('agent_id', params.agentId);
|
|
943
|
-
const res = await fetch(`${baseUrl}/v1/memories?${qs.toString()}`, {
|
|
944
|
-
headers: {
|
|
945
|
-
Authorization: `Bearer ${apiKey}`,
|
|
946
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
947
|
-
},
|
|
948
|
-
});
|
|
949
|
-
const json = await safeMemoryResponse(res);
|
|
950
|
-
return json.data?.memories || [];
|
|
951
|
-
}
|
|
952
|
-
async function marrowGetMemory(apiKey, baseUrl, id, sessionId) {
|
|
953
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
954
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
955
|
-
headers: {
|
|
956
|
-
Authorization: `Bearer ${apiKey}`,
|
|
957
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
958
|
-
},
|
|
959
|
-
});
|
|
960
|
-
const json = await safeMemoryResponse(res);
|
|
961
|
-
return json.data?.memory || null;
|
|
962
|
-
}
|
|
963
|
-
async function marrowUpdateMemory(apiKey, baseUrl, id, patch, sessionId) {
|
|
964
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
965
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
966
|
-
method: 'PATCH',
|
|
967
|
-
headers: {
|
|
968
|
-
Authorization: `Bearer ${apiKey}`,
|
|
969
|
-
'Content-Type': 'application/json',
|
|
970
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
971
|
-
},
|
|
972
|
-
body: JSON.stringify(patch),
|
|
973
|
-
});
|
|
974
|
-
const json = await safeMemoryResponse(res);
|
|
975
|
-
return json.data?.memory;
|
|
976
|
-
}
|
|
977
|
-
async function marrowDeleteMemory(apiKey, baseUrl, id, meta, sessionId) {
|
|
978
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
979
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}`, {
|
|
980
|
-
method: 'DELETE',
|
|
981
|
-
headers: {
|
|
982
|
-
Authorization: `Bearer ${apiKey}`,
|
|
983
|
-
'Content-Type': 'application/json',
|
|
984
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
985
|
-
},
|
|
986
|
-
body: JSON.stringify(meta || {}),
|
|
987
|
-
});
|
|
988
|
-
const json = await safeMemoryResponse(res);
|
|
989
|
-
return json.data?.memory;
|
|
990
|
-
}
|
|
991
|
-
async function marrowMarkOutdated(apiKey, baseUrl, id, meta, sessionId) {
|
|
992
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
993
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/outdated`, {
|
|
994
|
-
method: 'POST',
|
|
995
|
-
headers: {
|
|
996
|
-
Authorization: `Bearer ${apiKey}`,
|
|
997
|
-
'Content-Type': 'application/json',
|
|
998
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
999
|
-
},
|
|
1000
|
-
body: JSON.stringify(meta || {}),
|
|
1001
|
-
});
|
|
1002
|
-
const json = await safeMemoryResponse(res);
|
|
1003
|
-
return json.data?.memory;
|
|
1004
|
-
}
|
|
1005
|
-
async function marrowSupersedeMemory(apiKey, baseUrl, id, replacement, sessionId) {
|
|
1006
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1007
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/supersede`, {
|
|
1008
|
-
method: 'POST',
|
|
1009
|
-
headers: {
|
|
1010
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1011
|
-
'Content-Type': 'application/json',
|
|
1012
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1013
|
-
},
|
|
1014
|
-
body: JSON.stringify(replacement),
|
|
1015
|
-
});
|
|
1016
|
-
const json = await safeMemoryResponse(res);
|
|
1017
|
-
return json.data;
|
|
1018
|
-
}
|
|
1019
|
-
async function marrowShareMemory(apiKey, baseUrl, id, agentIds, actor, sessionId) {
|
|
1020
|
-
const safeId = (0, index_1.validatePathParam)(id, 'id');
|
|
1021
|
-
const res = await fetch(`${baseUrl}/v1/memories/${safeId}/share`, {
|
|
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({ agent_ids: agentIds, actor }),
|
|
1029
|
-
});
|
|
1030
|
-
const json = await safeMemoryResponse(res);
|
|
1031
|
-
return json.data?.memory;
|
|
1032
|
-
}
|
|
1033
|
-
async function marrowExportMemories(apiKey, baseUrl, params, sessionId) {
|
|
1034
|
-
const qs = new URLSearchParams();
|
|
1035
|
-
if (params?.format)
|
|
1036
|
-
qs.set('format', params.format);
|
|
1037
|
-
if (params?.status)
|
|
1038
|
-
qs.set('status', params.status);
|
|
1039
|
-
if (params?.tags)
|
|
1040
|
-
qs.set('tags', params.tags);
|
|
1041
|
-
const res = await fetch(`${baseUrl}/v1/memories/export?${qs.toString()}`, {
|
|
1042
|
-
headers: {
|
|
1043
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1044
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1045
|
-
},
|
|
1046
|
-
});
|
|
1047
|
-
const json = await safeMemoryResponse(res);
|
|
1048
|
-
return json.data;
|
|
1049
|
-
}
|
|
1050
|
-
async function marrowImportMemories(apiKey, baseUrl, memories, mode, sessionId) {
|
|
1051
|
-
const res = await fetch(`${baseUrl}/v1/memories/import`, {
|
|
1052
|
-
method: 'POST',
|
|
1053
|
-
headers: {
|
|
1054
|
-
Authorization: `Bearer ${apiKey}`,
|
|
1055
|
-
'Content-Type': 'application/json',
|
|
1056
|
-
...(sessionId ? { 'X-Marrow-Session-Id': sessionId } : {}),
|
|
1057
|
-
},
|
|
1058
|
-
body: JSON.stringify({ memories, mode }),
|
|
1059
|
-
});
|
|
1060
|
-
const json = await safeMemoryResponse(res);
|
|
1061
|
-
return json.data;
|
|
1062
|
-
}
|
|
1063
|
-
async function marrowRetrieveMemories(apiKey, baseUrl, query, params, sessionId) {
|
|
1064
|
-
const qs = new URLSearchParams();
|
|
1065
|
-
// The query leaves the process redacted, like every other tool's text.
|
|
1066
|
-
qs.set('q', (0, redact_1.redactSensitiveText)(query));
|
|
1067
|
-
if (params?.limit)
|
|
1068
|
-
qs.set('limit', String(params.limit));
|
|
1069
|
-
if (params?.from)
|
|
1070
|
-
qs.set('from', params.from);
|
|
1071
|
-
if (params?.to)
|
|
1072
|
-
qs.set('to', params.to);
|
|
1073
|
-
if (params?.tags)
|
|
1074
|
-
qs.set('tags', params.tags);
|
|
1075
|
-
if (params?.source)
|
|
1076
|
-
qs.set('source', params.source);
|
|
1077
|
-
if (params?.status)
|
|
1078
|
-
qs.set('status', params.status);
|
|
1079
|
-
if (params?.shared !== undefined)
|
|
1080
|
-
qs.set('shared', String(params.shared));
|
|
1081
|
-
const res = await fetch(`${baseUrl}/v1/memories/retrieve?${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
|
-
function activeToolProfile() {
|
|
1091
|
-
const configured = process.env.MARROW_TOOL_PROFILE;
|
|
1092
|
-
if (configured === undefined || configured === 'primary')
|
|
1093
|
-
return 'primary';
|
|
1094
|
-
if (configured === 'core' || configured === 'full')
|
|
1095
|
-
return configured;
|
|
1096
|
-
throw new Error('Invalid MARROW_TOOL_PROFILE value. Set MARROW_TOOL_PROFILE=primary, core, or full, '
|
|
1097
|
-
+ 'or unset it for primary; then restart MCP.');
|
|
1098
|
-
}
|
|
1099
|
-
function toolsForProfile(profile) {
|
|
1100
|
-
if (profile === 'full')
|
|
1101
|
-
return tool_catalog_1.TOOLS;
|
|
1102
|
-
const visibleNames = profile === 'primary' ? tool_catalog_1.PRIMARY_TOOL_NAMES : tool_catalog_1.CORE_TOOL_NAMES;
|
|
1103
|
-
return tool_catalog_1.TOOLS.filter((tool) => visibleNames.has(tool.name));
|
|
1104
|
-
}
|
|
1105
|
-
function advertisedTools() {
|
|
1106
|
-
return toolsForProfile(activeToolProfile());
|
|
1107
|
-
}
|
|
1108
|
-
function toolAllowedByActiveProfile(toolName) {
|
|
1109
|
-
return Boolean(toolName && toolsForProfile(activeToolProfile()).some((tool) => tool.name === toolName));
|
|
1110
|
-
}
|
|
1111
|
-
function recordValue(value) {
|
|
1112
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1113
|
-
? value
|
|
1114
|
-
: null;
|
|
1115
|
-
}
|
|
1116
|
-
function nonNegativeInteger(value) {
|
|
1117
|
-
return Number.isInteger(value) && Number(value) >= 0;
|
|
1118
|
-
}
|
|
1119
|
-
function validatedPrimaryToolAvailability(value) {
|
|
1120
|
-
const projection = recordValue(value);
|
|
1121
|
-
const evidence = recordValue(projection?.entitlement_evidence);
|
|
1122
|
-
const counts = recordValue(projection?.counts);
|
|
1123
|
-
const tools = projection?.tools;
|
|
1124
|
-
const expectedNames = [...tool_catalog_1.PRIMARY_TOOL_NAMES];
|
|
1125
|
-
if (!projection
|
|
1126
|
-
|| projection.profile !== 'primary'
|
|
1127
|
-
|| (projection.current_plan !== null && typeof projection.current_plan !== 'string')
|
|
1128
|
-
|| typeof projection.owner_management_url !== 'string'
|
|
1129
|
-
|| !evidence
|
|
1130
|
-
|| (evidence.state !== 'available' && evidence.state !== 'unavailable')
|
|
1131
|
-
|| typeof evidence.source !== 'string'
|
|
1132
|
-
|| typeof evidence.authoritative !== 'boolean'
|
|
1133
|
-
|| evidence.authorizing !== false
|
|
1134
|
-
|| !counts
|
|
1135
|
-
|| !nonNegativeInteger(counts.total)
|
|
1136
|
-
|| !nonNegativeInteger(counts.entitled)
|
|
1137
|
-
|| !nonNegativeInteger(counts.upgrade_required)
|
|
1138
|
-
|| !nonNegativeInteger(counts.unavailable)
|
|
1139
|
-
|| counts.total !== expectedNames.length
|
|
1140
|
-
|| counts.entitled + counts.upgrade_required + counts.unavailable !== counts.total
|
|
1141
|
-
|| !Array.isArray(tools)
|
|
1142
|
-
|| tools.length !== expectedNames.length) {
|
|
1143
|
-
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 */ }
|
|
1144
950
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
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;
|
|
1148
1072
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
if (
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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;
|
|
1158
|
+
}
|
|
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) {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
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) {
|
|
1160
1207
|
return null;
|
|
1161
1208
|
}
|
|
1162
|
-
|
|
1209
|
+
return {
|
|
1210
|
+
projection,
|
|
1211
|
+
evidenceState: evidence.state,
|
|
1212
|
+
};
|
|
1163
1213
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
+
}
|
|
1167
1238
|
return null;
|
|
1168
1239
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
if (validated)
|
|
1196
|
-
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
|
+
};
|
|
1197
1266
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
const cachedOrStale = payload?.cached === true
|
|
1205
|
-
|| payload?.stale === true
|
|
1206
|
-
|| payload?.status_freshness === 'stale';
|
|
1207
|
-
const backendProjection = cachedOrStale ? null : backendPrimaryToolAvailability(payload);
|
|
1208
|
-
const backendProjectionAvailable = backendProjection?.evidenceState === 'available';
|
|
1209
|
-
return {
|
|
1210
|
-
configured_profile: process.env.MARROW_TOOL_PROFILE ?? 'unset',
|
|
1211
|
-
effective_profile: profile,
|
|
1212
|
-
visible_tool_count: visibleToolNames.length,
|
|
1213
|
-
visible_tool_names: visibleToolNames,
|
|
1214
|
-
local_visibility_grants_entitlement: false,
|
|
1215
|
-
backend_entitlement_projection: {
|
|
1216
|
-
evidence_state: backendProjectionAvailable ? 'available' : 'unavailable',
|
|
1217
|
-
source: cachedOrStale
|
|
1218
|
-
? 'cached_or_stale_status'
|
|
1219
|
-
: backendProjection
|
|
1220
|
-
? 'authenticated_backend'
|
|
1221
|
-
: 'backend_projection_not_provided',
|
|
1222
|
-
authorizes_calls: false,
|
|
1223
|
-
primary_tool_availability: backendProjectionAvailable ? backendProjection.projection : null,
|
|
1224
|
-
},
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
function withMcpToolProfileStatus(value) {
|
|
1228
|
-
const payload = recordValue(value) || { data: value };
|
|
1229
|
-
return {
|
|
1230
|
-
...payload,
|
|
1231
|
-
mcp_tool_profile: mcpToolProfileStatus(payload),
|
|
1232
|
-
};
|
|
1233
|
-
}
|
|
1234
|
-
// Request handler
|
|
1235
|
-
async function handleRequest(req) {
|
|
1236
|
-
const { id, method, params } = req;
|
|
1237
|
-
const responseStartedAt = performance.now();
|
|
1238
|
-
// [FIX #15] Enforce initialize-first per MCP spec
|
|
1239
|
-
if (!initialized && method !== 'initialize') {
|
|
1240
|
-
error(id, -32002, 'Server not initialized. Send initialize first.');
|
|
1241
|
-
return;
|
|
1267
|
+
function withMcpToolProfileStatus(value) {
|
|
1268
|
+
const payload = recordValue(value) || { data: value };
|
|
1269
|
+
return {
|
|
1270
|
+
...payload,
|
|
1271
|
+
mcp_tool_profile: mcpToolProfileStatus(payload),
|
|
1272
|
+
};
|
|
1242
1273
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
capabilities: { tools: {}, prompts: {} },
|
|
1251
|
-
serverInfo: { name: 'marrow', version: hook_contract_2.MCP_ADAPTER_VERSION },
|
|
1252
|
-
...(AUTO_ENROLL ? {
|
|
1253
|
-
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)}`,
|
|
1254
|
-
} : {}),
|
|
1255
|
-
_meta: { host_capability: hostCapability },
|
|
1256
|
-
});
|
|
1257
|
-
// Auto-enroll: emit enrollment notification on connection
|
|
1258
|
-
if (AUTO_ENROLL) {
|
|
1259
|
-
send({
|
|
1260
|
-
jsonrpc: '2.0',
|
|
1261
|
-
method: 'notifications/message',
|
|
1262
|
-
params: {
|
|
1263
|
-
level: 'info',
|
|
1264
|
-
logger: 'marrow',
|
|
1265
|
-
data: {
|
|
1266
|
-
type: 'auto_enroll',
|
|
1267
|
-
message: `Marrow operating contract available. MCP tools are on demand. ${hostCapability.exact_next_action}`,
|
|
1268
|
-
agentId: FLEET_AGENT_ID,
|
|
1269
|
-
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1270
|
-
host_capability: hostCapability,
|
|
1271
|
-
},
|
|
1272
|
-
},
|
|
1273
|
-
});
|
|
1274
|
-
}
|
|
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.');
|
|
1275
1281
|
return;
|
|
1276
1282
|
}
|
|
1277
|
-
|
|
1278
|
-
if (
|
|
1283
|
+
try {
|
|
1284
|
+
if (method === 'initialize') {
|
|
1285
|
+
initialized = true;
|
|
1286
|
+
const hostCapability = mcpHostCapability();
|
|
1287
|
+
const protocolVersion = noteClientCapabilities(params);
|
|
1279
1288
|
success(id, {
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
],
|
|
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 },
|
|
1288
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;
|
|
1289
1316
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
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
|
+
}
|
|
1299
1333
|
return;
|
|
1300
1334
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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.
|
|
1311
1351
|
|
|
1312
1352
|
## Capability-qualified operating contract
|
|
1313
1353
|
|
|
@@ -1338,995 +1378,996 @@ Use marrow_ask for authorized history questions such as:
|
|
|
1338
1378
|
- "what worked last time we published?"
|
|
1339
1379
|
|
|
1340
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
|
+
},
|
|
1341
1382
|
},
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
});
|
|
1345
|
-
return;
|
|
1346
|
-
}
|
|
1347
|
-
if (method === 'tools/list') {
|
|
1348
|
-
success(id, { tools: advertisedTools() });
|
|
1349
|
-
return;
|
|
1350
|
-
}
|
|
1351
|
-
if (method === 'tools/call') {
|
|
1352
|
-
const toolName = params?.name;
|
|
1353
|
-
const args = (params?.arguments || {});
|
|
1354
|
-
if (!toolAllowedByActiveProfile(toolName)) {
|
|
1355
|
-
const repair = toolName && tool_catalog_1.PRIMARY_TOOL_NAMES.has(toolName)
|
|
1356
|
-
? 'Unset MARROW_TOOL_PROFILE or set MARROW_TOOL_PROFILE=primary, then restart MCP.'
|
|
1357
|
-
: 'Set MARROW_TOOL_PROFILE=full, then restart MCP for the complete advanced/legacy catalog.';
|
|
1358
|
-
error(id, -32601, `Tool is not available in the active Marrow tool profile. ${repair}`);
|
|
1383
|
+
],
|
|
1384
|
+
});
|
|
1359
1385
|
return;
|
|
1360
1386
|
}
|
|
1361
|
-
if (
|
|
1362
|
-
|
|
1363
|
-
const wantAutoWarn = args.autoWarn ?? true;
|
|
1364
|
-
const taskType = args.taskType;
|
|
1365
|
-
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' });
|
|
1366
|
-
if (AUTO_ENROLL && orientCallCount === 1) {
|
|
1367
|
-
const enrollmentText = `\n\n**Marrow control and proof active**\n\n` +
|
|
1368
|
-
`Marrow applies relevant policy and prior lessons before consequential actions, then records evidence and the real outcome afterward.\n\n` +
|
|
1369
|
-
`1. Respect allow, warn, review_required, and block decisions before acting.\n` +
|
|
1370
|
-
`2. Use marrow_agent_runtime when a risky action needs an explicit gate.\n` +
|
|
1371
|
-
`3. Close meaningful work with marrow_commit; a tool exit alone is not outcome proof.\n` +
|
|
1372
|
-
`4. Use marrow_decision_trace to explain the prior failure, lesson, gate, proof, workflow, and outcome path.\n\n` +
|
|
1373
|
-
`${(0, host_capability_1.hostCapabilityInstructions)(mcpHostCapability())}\n\nLeave unknown outcomes pending instead of inventing success.\n`;
|
|
1374
|
-
const orientText = JSON.stringify(result, null, 2);
|
|
1375
|
-
success(id, {
|
|
1376
|
-
content: [{ type: 'text', text: enrollmentText + orientText }],
|
|
1377
|
-
});
|
|
1378
|
-
}
|
|
1379
|
-
else {
|
|
1380
|
-
success(id, {
|
|
1381
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
1382
|
-
});
|
|
1383
|
-
}
|
|
1387
|
+
if (method === 'tools/list') {
|
|
1388
|
+
success(id, { tools: advertisedTools() });
|
|
1384
1389
|
return;
|
|
1385
1390
|
}
|
|
1386
|
-
if (
|
|
1387
|
-
|
|
1388
|
-
const
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
previous_outcome: args.previous_outcome,
|
|
1396
|
-
checkLoop: args.checkLoop ?? true,
|
|
1397
|
-
source_kind: args.source_kind,
|
|
1398
|
-
human_directed: args.human_directed,
|
|
1399
|
-
instruction_ref: args.instruction_ref,
|
|
1400
|
-
source_meta: args.source_meta,
|
|
1401
|
-
}, SESSION_ID, FLEET_AGENT_ID, undefined, { idempotencyKey: args.idempotency_key, requestHash: args.request_hash });
|
|
1402
|
-
// Refresh orient warnings every 5th think call
|
|
1403
|
-
thinkCallCount++;
|
|
1404
|
-
if (thinkCallCount % 5 === 0) {
|
|
1405
|
-
refreshOrientWarnings();
|
|
1406
|
-
}
|
|
1407
|
-
// Inject cached orient warnings into intelligence.insights
|
|
1408
|
-
if (cachedOrientWarnings.length > 0) {
|
|
1409
|
-
const existingInsights = result.intelligence?.insights || [];
|
|
1410
|
-
result.intelligence.insights = [
|
|
1411
|
-
...cachedOrientWarnings.map((w) => ({
|
|
1412
|
-
type: 'failure_pattern',
|
|
1413
|
-
summary: w.message,
|
|
1414
|
-
action: `Review past ${w.type} failures before proceeding`,
|
|
1415
|
-
severity: (w.failureRate > 0.4 ? 'critical' : 'warning'),
|
|
1416
|
-
count: 0,
|
|
1417
|
-
})),
|
|
1418
|
-
...existingInsights,
|
|
1419
|
-
];
|
|
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;
|
|
1420
1400
|
}
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
hostSessionId: SESSION_ID || 'mcp-server',
|
|
1446
|
-
};
|
|
1447
|
-
commitSessionId = (0, host_approval_1.holdSessionForReceipt)(holdContext, args.gate_receipt_id) || SESSION_ID;
|
|
1448
|
-
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;
|
|
1449
1425
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
result = await
|
|
1454
|
-
|
|
1455
|
-
success: commitSuccess,
|
|
1456
|
-
outcome,
|
|
1457
|
-
caused_by: args.caused_by,
|
|
1458
|
-
proof: args.proof,
|
|
1459
|
-
gate_receipt_id: args.gate_receipt_id,
|
|
1460
|
-
arbitration_receipt_id: args.arbitration_receipt_id,
|
|
1461
|
-
owner_approval_receipt_id: args.owner_approval_receipt_id,
|
|
1462
|
-
action: args.action,
|
|
1463
|
-
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,
|
|
1464
1431
|
type: args.type,
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
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;
|
|
1469
1467
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
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) {
|
|
1472
1525
|
for (const key of proofKeys)
|
|
1473
|
-
commitProofMissing.
|
|
1526
|
+
commitProofMissing.delete(key);
|
|
1474
1527
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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);
|
|
1481
1538
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
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;
|
|
1487
1546
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
const missing = listed ? listed.split(',').map((field) => field.trim()).filter(Boolean) : [];
|
|
1495
|
-
if (missing.length)
|
|
1496
|
-
for (const key of proofKeys)
|
|
1497
|
-
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;
|
|
1498
1553
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
if (toolName === 'marrow_run') {
|
|
1515
|
-
// [FIX #9] Validate required params
|
|
1516
|
-
const description = requireString(args, 'description');
|
|
1517
|
-
const outcome = requireString(args, 'outcome');
|
|
1518
|
-
const measuredSuccess = requireBoolean(args, 'success');
|
|
1519
|
-
// [FIX #16] Handle partial failures — return think result even if commit fails
|
|
1520
|
-
let thinkResult = null;
|
|
1521
|
-
try {
|
|
1522
|
-
await (0, index_1.marrowOrient)(API_KEY, BASE_URL, undefined, SESSION_ID, FLEET_AGENT_ID);
|
|
1523
|
-
}
|
|
1524
|
-
catch (err) {
|
|
1525
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1526
|
-
process.stderr.write(`[marrow] marrow_run orient failed (continuing): ${msg}\n`);
|
|
1527
|
-
}
|
|
1528
|
-
thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
|
|
1529
|
-
action: description,
|
|
1530
|
-
type: args.type || 'general',
|
|
1531
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1532
|
-
let commitResult = null;
|
|
1533
|
-
try {
|
|
1534
|
-
commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
1535
|
-
decision_id: thinkResult.decision_id,
|
|
1536
|
-
success: measuredSuccess,
|
|
1537
|
-
outcome,
|
|
1538
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1539
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1540
|
-
: undefined,
|
|
1541
|
-
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, {
|
|
1542
1569
|
action: description,
|
|
1543
1570
|
type: args.type || 'general',
|
|
1544
1571
|
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
+
}
|
|
1549
1602
|
success(id, {
|
|
1550
1603
|
content: [{
|
|
1551
1604
|
type: 'text',
|
|
1552
|
-
text: JSON.stringify({
|
|
1553
|
-
think: thinkResult,
|
|
1554
|
-
commit: null,
|
|
1555
|
-
commit_error: msg,
|
|
1556
|
-
decision_id: thinkResult.decision_id,
|
|
1557
|
-
}, null, 2),
|
|
1605
|
+
text: JSON.stringify({ think: thinkResult, commit: commitResult }, null, 2),
|
|
1558
1606
|
}],
|
|
1559
1607
|
});
|
|
1560
1608
|
return;
|
|
1561
1609
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
: undefined;
|
|
1582
|
-
const suppliedArbitrationReceiptId = typeof args.arbitration_receipt_id === 'string'
|
|
1583
|
-
? args.arbitration_receipt_id
|
|
1584
|
-
: undefined;
|
|
1585
|
-
const suppliedOwnerApprovalReceiptId = typeof args.owner_approval_receipt_id === 'string'
|
|
1586
|
-
? args.owner_approval_receipt_id
|
|
1587
|
-
: undefined;
|
|
1588
|
-
const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
|
|
1589
|
-
action,
|
|
1590
|
-
outcome,
|
|
1591
|
-
success: outcomeSuccess,
|
|
1592
|
-
type,
|
|
1593
|
-
proof: suppliedProof,
|
|
1594
|
-
gate_receipt_id: suppliedRuntimeReceiptId || undefined,
|
|
1595
|
-
arbitration_receipt_id: suppliedArbitrationReceiptId,
|
|
1596
|
-
owner_approval_receipt_id: suppliedOwnerApprovalReceiptId,
|
|
1597
|
-
// Low-risk one-shot capture does not need a policy gate. Consequential
|
|
1598
|
-
// work obtains exactly one canonical runtime authorization inside auto.
|
|
1599
|
-
auto_gate: highRisk,
|
|
1600
|
-
operation_id: typeof args.operation_id === 'string' ? args.operation_id : undefined,
|
|
1601
|
-
...(args.request_owner_link === true ? { request_owner_link: true } : {}),
|
|
1602
|
-
}, SESSION_ID, FLEET_AGENT_ID, 8_000, autoElicitationOptions());
|
|
1603
|
-
let delivered = null;
|
|
1604
|
-
let deliveryFailure = null;
|
|
1605
|
-
let failedHttpAttemptTrace = null;
|
|
1606
|
-
const coreStartedAt = performance.now();
|
|
1607
|
-
try {
|
|
1608
|
-
delivered = await delivery();
|
|
1609
|
-
}
|
|
1610
|
-
catch (err) {
|
|
1611
|
-
deliveryFailure = (0, request_reliability_1.structuredRequestFailure)(err);
|
|
1612
|
-
failedHttpAttemptTrace = (0, index_1.marrowAutoHttpTraceFromError)(err);
|
|
1613
|
-
}
|
|
1614
|
-
const coreDurationMs = Math.floor(performance.now() - coreStartedAt);
|
|
1615
|
-
const runtimeGate = delivered?.runtime_gate || null;
|
|
1616
|
-
if (runtimeGate)
|
|
1617
|
-
storeRuntimeGuidance(runtimeGate);
|
|
1618
|
-
const enqueueStartedAt = performance.now();
|
|
1619
|
-
const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
|
|
1620
|
-
apiKey: API_KEY,
|
|
1621
|
-
baseUrl: BASE_URL,
|
|
1622
|
-
deferDelivery: true,
|
|
1623
|
-
event: {
|
|
1624
|
-
...(delivered?.operation_id ? {
|
|
1625
|
-
// A closed operation has one record; each pending call is its own attempt.
|
|
1626
|
-
event_id: delivered.committed
|
|
1627
|
-
? `auto_closed_${delivered.operation_id}`
|
|
1628
|
-
: `auto_pending_${delivered.operation_id}_${(0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 12)}`,
|
|
1629
|
-
} : {}),
|
|
1630
|
-
event_type: delivered?.committed
|
|
1631
|
-
? 'outcome_committed'
|
|
1632
|
-
: !highRisk && outcomeSuccess === false
|
|
1633
|
-
? 'tool_failed'
|
|
1634
|
-
: !highRisk && outcomeSuccess === true
|
|
1635
|
-
? 'tool_completed'
|
|
1636
|
-
: 'goal_started',
|
|
1637
|
-
harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
|
|
1638
|
-
agent_id: FLEET_AGENT_ID,
|
|
1639
|
-
session_id: SESSION_ID,
|
|
1640
|
-
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, {
|
|
1641
1629
|
action,
|
|
1642
|
-
|
|
1643
|
-
success:
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
?
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
: delivered?.phase === 'proof_required'
|
|
1673
|
-
? 'pending_required_proof'
|
|
1674
|
-
: delivered?.phase === 'decision_created' || outcomeSuccess === undefined
|
|
1675
|
-
? 'pending_evidence'
|
|
1676
|
-
: 'delivery_pending',
|
|
1677
|
-
decision_id: delivered?.decision_id || null,
|
|
1678
|
-
operation_id: delivered?.operation_id || (typeof args.operation_id === 'string' ? args.operation_id : null),
|
|
1679
|
-
phase: delivered?.phase || null,
|
|
1680
|
-
resumable: delivered?.resumable || false,
|
|
1681
|
-
retry_after_ms: delivered?.retry_after_ms ?? null,
|
|
1682
|
-
phase_timings_ms: delivered?.phase_timings_ms || null,
|
|
1683
|
-
http_attempt_trace: delivered?.http_attempt_trace || failedHttpAttemptTrace || {
|
|
1684
|
-
attempts: [],
|
|
1685
|
-
dropped_count: 0,
|
|
1686
|
-
},
|
|
1687
|
-
exact_next_action: delivered?.committed && delivered.closure === 'gate_denial' && delivered.exact_next_action
|
|
1688
|
-
? delivered.exact_next_action
|
|
1689
|
-
: delivered?.committed
|
|
1690
|
-
? 'The governed outcome is closed. Reuse this decision_id only for read-only trace inspection.'
|
|
1691
|
-
: delivered?.exact_next_action
|
|
1692
|
-
? delivered.exact_next_action
|
|
1693
|
-
: delivered?.phase === 'owner_approval_required'
|
|
1694
|
-
? '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.'
|
|
1695
|
-
: delivered?.phase === 'proof_required'
|
|
1696
|
-
? 'Attach the required measured proof and retry marrow_auto with this same operation_id and unchanged action, context, and surfaces.'
|
|
1697
|
-
: delivered?.resumable
|
|
1698
|
-
? 'Retry marrow_auto with this same operation_id. Do not start a new auto operation.'
|
|
1699
|
-
: 'Close this decision after the real outcome is known.',
|
|
1700
|
-
live_delivery: {
|
|
1701
|
-
accepted: Boolean(delivered?.decision_id),
|
|
1702
|
-
committed: Boolean(delivered?.committed),
|
|
1703
|
-
...(deliveryFailure ? { failure: deliveryFailure } : {}),
|
|
1704
|
-
},
|
|
1705
|
-
host_capability: mcpHostCapability(),
|
|
1706
|
-
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
1707
|
-
...(delivered?.approval ? { approval: delivered.approval } : {}),
|
|
1708
|
-
...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
|
|
1709
|
-
};
|
|
1710
|
-
// Measure through response construction; stdout/host consumption occurs
|
|
1711
|
-
// after this snapshot and is not represented as server phase latency.
|
|
1712
|
-
response.response_timings_ms = {
|
|
1713
|
-
core: coreDurationMs,
|
|
1714
|
-
durable_enqueue: enqueueDurationMs,
|
|
1715
|
-
full_response: Math.floor(performance.now() - responseStartedAt),
|
|
1716
|
-
};
|
|
1717
|
-
success(id, {
|
|
1718
|
-
content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
|
|
1719
|
-
});
|
|
1720
|
-
// The existing bounded owner handles rejection and preserves queued
|
|
1721
|
-
// receipts across exit/restart. Network work never holds this response.
|
|
1722
|
-
void (0, lifecycle_spool_1.nudgeLifecycleSpool)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
|
|
1723
|
-
return;
|
|
1724
|
-
}
|
|
1725
|
-
if (toolName === 'marrow_ask') {
|
|
1726
|
-
const query = requireString(args, 'query');
|
|
1727
|
-
const result = await withControlDeadline((signal) => (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID, signal), { toolName: 'marrow_ask' });
|
|
1728
|
-
try {
|
|
1729
|
-
(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)({
|
|
1730
1660
|
apiKey: API_KEY,
|
|
1731
1661
|
baseUrl: BASE_URL,
|
|
1732
|
-
|
|
1733
|
-
|
|
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
|
+
},
|
|
1734
1686
|
});
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
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 });
|
|
1751
1763
|
return;
|
|
1752
1764
|
}
|
|
1753
|
-
|
|
1754
|
-
const
|
|
1755
|
-
|
|
1756
|
-
|
|
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;
|
|
1757
1779
|
}
|
|
1758
|
-
|
|
1759
|
-
|
|
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') {
|
|
1760
1787
|
const startedAt = Date.now();
|
|
1761
|
-
(
|
|
1788
|
+
refreshStatusInBackground();
|
|
1789
|
+
(0, control_path_state_1.recordControlPathSample)('marrow_status', Date.now() - startedAt, true);
|
|
1762
1790
|
toolSuccess(id, clientOperationalPayload('marrow_status', (0, status_cache_1.cachedStatusPayload)(cached)));
|
|
1763
1791
|
return;
|
|
1764
1792
|
}
|
|
1765
|
-
|
|
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;
|
|
1766
1808
|
}
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1780
|
-
});
|
|
1781
|
-
return;
|
|
1782
|
-
}
|
|
1783
|
-
if (toolName === 'marrow_list_keys') {
|
|
1784
|
-
const result = await (0, index_1.marrowListKeys)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1785
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
if (toolName === 'marrow_get_key') {
|
|
1789
|
-
const keyId = requireString(args, 'id');
|
|
1790
|
-
const result = await (0, index_1.marrowGetKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1791
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1792
|
-
return;
|
|
1793
|
-
}
|
|
1794
|
-
if (toolName === 'marrow_revoke_key') {
|
|
1795
|
-
const keyId = requireString(args, 'id');
|
|
1796
|
-
const result = await (0, index_1.marrowRevokeKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1797
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1798
|
-
return;
|
|
1799
|
-
}
|
|
1800
|
-
if (toolName === 'marrow_rotate_key') {
|
|
1801
|
-
const keyId = requireString(args, 'id');
|
|
1802
|
-
const result = await (0, index_1.marrowRotateKey)(API_KEY, BASE_URL, keyId, SESSION_ID, FLEET_AGENT_ID);
|
|
1803
|
-
success(id, {
|
|
1804
|
-
content: [{ type: 'text', text: JSON.stringify({ ...result, warning: formatKeyMaterialWarning() }, null, 2) }],
|
|
1805
|
-
});
|
|
1806
|
-
return;
|
|
1807
|
-
}
|
|
1808
|
-
// Memory control tools — all use requireString for id validation
|
|
1809
|
-
if (toolName === 'marrow_list_memories') {
|
|
1810
|
-
const result = await marrowListMemories(API_KEY, BASE_URL, { status: args.status, query: args.query, limit: args.limit, agentId: args.agentId }, SESSION_ID);
|
|
1811
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
if (toolName === 'marrow_get_memory') {
|
|
1815
|
-
const memId = requireString(args, 'id');
|
|
1816
|
-
const result = await marrowGetMemory(API_KEY, BASE_URL, memId, SESSION_ID);
|
|
1817
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
if (toolName === 'marrow_update_memory') {
|
|
1821
|
-
const memId = requireString(args, 'id');
|
|
1822
|
-
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);
|
|
1823
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1824
|
-
return;
|
|
1825
|
-
}
|
|
1826
|
-
if (toolName === 'marrow_delete_memory') {
|
|
1827
|
-
const memId = requireString(args, 'id');
|
|
1828
|
-
const result = await marrowDeleteMemory(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1829
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1830
|
-
return;
|
|
1831
|
-
}
|
|
1832
|
-
if (toolName === 'marrow_mark_outdated') {
|
|
1833
|
-
const memId = requireString(args, 'id');
|
|
1834
|
-
const result = await marrowMarkOutdated(API_KEY, BASE_URL, memId, { actor: args.actor, note: args.note }, SESSION_ID);
|
|
1835
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
if (toolName === 'marrow_supersede_memory') {
|
|
1839
|
-
const memId = requireString(args, 'id');
|
|
1840
|
-
const newText = requireString(args, 'text');
|
|
1841
|
-
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);
|
|
1842
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1843
|
-
return;
|
|
1844
|
-
}
|
|
1845
|
-
if (toolName === 'marrow_share_memory') {
|
|
1846
|
-
const memId = requireString(args, 'id');
|
|
1847
|
-
const result = await marrowShareMemory(API_KEY, BASE_URL, memId, args.agentIds || [], args.actor, SESSION_ID);
|
|
1848
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1849
|
-
return;
|
|
1850
|
-
}
|
|
1851
|
-
if (toolName === 'marrow_export_memories') {
|
|
1852
|
-
const result = await marrowExportMemories(API_KEY, BASE_URL, { format: args.format, status: args.status, tags: args.tags }, SESSION_ID);
|
|
1853
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1854
|
-
return;
|
|
1855
|
-
}
|
|
1856
|
-
if (toolName === 'marrow_import_memories') {
|
|
1857
|
-
const result = await marrowImportMemories(API_KEY, BASE_URL, args.memories || [], args.mode || 'merge', SESSION_ID);
|
|
1858
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1859
|
-
return;
|
|
1860
|
-
}
|
|
1861
|
-
if (toolName === 'marrow_retrieve_memories') {
|
|
1862
|
-
const query = requireString(args, 'query');
|
|
1863
|
-
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);
|
|
1864
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1865
|
-
return;
|
|
1866
|
-
}
|
|
1867
|
-
if (toolName === 'marrow_workflow') {
|
|
1868
|
-
const result = await (0, index_1.marrowWorkflow)(API_KEY, BASE_URL, {
|
|
1869
|
-
action: args.action,
|
|
1870
|
-
workflowId: args.workflowId,
|
|
1871
|
-
instanceId: args.instanceId,
|
|
1872
|
-
name: args.name,
|
|
1873
|
-
description: args.description,
|
|
1874
|
-
steps: args.steps,
|
|
1875
|
-
tags: args.tags,
|
|
1876
|
-
agentId: args.agentId,
|
|
1877
|
-
context: args.context,
|
|
1878
|
-
inputs: args.inputs,
|
|
1879
|
-
stepCompleted: args.stepCompleted,
|
|
1880
|
-
outcome: args.outcome,
|
|
1881
|
-
nextAgentId: args.nextAgentId,
|
|
1882
|
-
contextUpdate: args.contextUpdate,
|
|
1883
|
-
status: args.status,
|
|
1884
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1885
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
if (toolName === 'marrow_dashboard') {
|
|
1889
|
-
const result = await (0, index_1.marrowDashboard)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
1890
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
if (toolName === 'marrow_digest') {
|
|
1894
|
-
const result = await (0, index_1.marrowDigest)(API_KEY, BASE_URL, args.period || '7d', SESSION_ID, FLEET_AGENT_ID);
|
|
1895
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
if (toolName === 'marrow_agent_status') {
|
|
1899
|
-
const result = await withControlDeadline(async (signal) => {
|
|
1900
|
-
const [analyticsStatus, context] = await Promise.all([
|
|
1901
|
-
(0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID, signal),
|
|
1902
|
-
(0, index_1.marrowAgentContext)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal)
|
|
1903
|
-
.catch(() => null),
|
|
1904
|
-
]);
|
|
1905
|
-
const contextProjection = recordValue(context)?.primary_tool_availability;
|
|
1906
|
-
return {
|
|
1907
|
-
...analyticsStatus,
|
|
1908
|
-
...(contextProjection === undefined ? {} : { primary_tool_availability: contextProjection }),
|
|
1909
|
-
};
|
|
1910
|
-
}, { cacheAware: false, toolName: 'marrow_agent_status' });
|
|
1911
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus({ ...result, local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)) }), null, 2) }] });
|
|
1912
|
-
return;
|
|
1913
|
-
}
|
|
1914
|
-
if (toolName === 'marrow_runtime_status') {
|
|
1915
|
-
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' });
|
|
1916
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(withMcpToolProfileStatus(result), null, 2) }] });
|
|
1917
|
-
return;
|
|
1918
|
-
}
|
|
1919
|
-
if (toolName === 'marrow_value_report') {
|
|
1920
|
-
const result = await (0, index_1.marrowValueReport)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
|
|
1921
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1922
|
-
return;
|
|
1923
|
-
}
|
|
1924
|
-
if (toolName === 'marrow_decision_brief') {
|
|
1925
|
-
const result = await (0, index_1.marrowDecisionBrief)(API_KEY, BASE_URL, {
|
|
1926
|
-
action: 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
|
-
period: args.period,
|
|
1933
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1934
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1935
|
-
return;
|
|
1936
|
-
}
|
|
1937
|
-
if (toolName === 'marrow_first_value') {
|
|
1938
|
-
const result = await (0, index_1.marrowFirstValue)(API_KEY, BASE_URL, {
|
|
1939
|
-
action: args.action ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
|
|
1940
|
-
type: args.type,
|
|
1941
|
-
role: args.role,
|
|
1942
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1943
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1944
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1945
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1946
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1947
|
-
: undefined,
|
|
1948
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1949
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1950
|
-
: undefined,
|
|
1951
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1952
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1953
|
-
return;
|
|
1954
|
-
}
|
|
1955
|
-
if (toolName === 'marrow_agent_runtime') {
|
|
1956
|
-
const runtimeInput = {
|
|
1957
|
-
action: (0, redact_1.redactSensitiveText)(args.action),
|
|
1958
|
-
type: args.type,
|
|
1959
|
-
role: args.role,
|
|
1960
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1961
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1962
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1963
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1964
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1965
|
-
: undefined,
|
|
1966
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1967
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1968
|
-
: undefined,
|
|
1969
|
-
period: args.period,
|
|
1970
|
-
};
|
|
1971
|
-
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' });
|
|
1972
|
-
storeRuntimeGuidance(result);
|
|
1973
|
-
storeLastKnownStatus(result.status, 'runtime');
|
|
1974
|
-
toolSuccess(id, clientOperationalPayload('marrow_agent_runtime', result));
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
if (toolName === 'marrow_arbitrate') {
|
|
1978
|
-
const result = await (0, index_1.marrowArbitrate)(API_KEY, BASE_URL, {
|
|
1979
|
-
objective: (0, redact_1.redactSensitiveText)(args.objective),
|
|
1980
|
-
owner_intent: typeof args.ownerIntent === 'string' ? (0, redact_1.redactSensitiveText)(args.ownerIntent) : undefined,
|
|
1981
|
-
conflict_type: args.conflictType,
|
|
1982
|
-
proposals: Array.isArray(args.proposals)
|
|
1983
|
-
? args.proposals
|
|
1984
|
-
: [],
|
|
1985
|
-
action: typeof args.action === 'string' ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
|
|
1986
|
-
type: args.type,
|
|
1987
|
-
agent_id: args.agentId || FLEET_AGENT_ID,
|
|
1988
|
-
session_id: args.sessionId || SESSION_ID,
|
|
1989
|
-
surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
|
|
1990
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
1991
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
1992
|
-
: undefined,
|
|
1993
|
-
proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
|
|
1994
|
-
? (0, redact_1.redactSensitiveValue)(args.proof)
|
|
1995
|
-
: undefined,
|
|
1996
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
1997
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
if (toolName === 'marrow_coordinate') {
|
|
2001
|
-
const coordinationArgs = { ...args };
|
|
2002
|
-
delete coordinationArgs.agent_id;
|
|
2003
|
-
delete coordinationArgs.source_agent_id;
|
|
2004
|
-
const result = await (0, index_1.marrowCoordinate)(API_KEY, BASE_URL, coordinationArgs, SESSION_ID, FLEET_AGENT_ID);
|
|
2005
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2006
|
-
return;
|
|
2007
|
-
}
|
|
2008
|
-
if (toolName === 'marrow_replay_compare') {
|
|
2009
|
-
const result = await (0, index_1.marrowReplayCompare)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2010
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2011
|
-
return;
|
|
2012
|
-
}
|
|
2013
|
-
if (toolName === 'marrow_governance_control_plane') {
|
|
2014
|
-
const result = await (0, index_1.marrowGovernanceControlPlane)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2015
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2016
|
-
return;
|
|
2017
|
-
}
|
|
2018
|
-
if (toolName === 'marrow_hermes_integration') {
|
|
2019
|
-
const result = await (0, index_1.marrowHermesIntegration)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2020
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
if (toolName === 'marrow_completion_contracts') {
|
|
2024
|
-
const result = await (0, index_1.marrowCompletionContracts)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2025
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2026
|
-
return;
|
|
2027
|
-
}
|
|
2028
|
-
if (toolName === 'marrow_evaluate_completion_contract') {
|
|
2029
|
-
const input = {
|
|
2030
|
-
action: args.action,
|
|
2031
|
-
workflow_type: args.workflow_type,
|
|
2032
|
-
risk_level: args.risk_level,
|
|
2033
|
-
evidence: args.evidence && typeof args.evidence === 'object' && !Array.isArray(args.evidence)
|
|
2034
|
-
? (0, redact_1.redactSensitiveValue)(args.evidence)
|
|
2035
|
-
: undefined,
|
|
2036
|
-
};
|
|
2037
|
-
const result = await (0, index_1.marrowEvaluateCompletionContract)(API_KEY, BASE_URL, input, SESSION_ID, FLEET_AGENT_ID);
|
|
2038
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2039
|
-
return;
|
|
2040
|
-
}
|
|
2041
|
-
if (toolName === 'marrow_governance_timeline') {
|
|
2042
|
-
const result = await (0, index_1.marrowGovernanceTimeline)(API_KEY, BASE_URL, {
|
|
2043
|
-
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2044
|
-
limit: args.limit,
|
|
2045
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2046
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2047
|
-
return;
|
|
2048
|
-
}
|
|
2049
|
-
if (toolName === 'marrow_decision_trace') {
|
|
2050
|
-
const decisionId = args.decisionId;
|
|
2051
|
-
if (!decisionId) {
|
|
2052
|
-
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
|
+
});
|
|
2053
1821
|
return;
|
|
2054
1822
|
}
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
}
|
|
2059
|
-
if (toolName === 'marrow_buyer_proof') {
|
|
2060
|
-
const result = await (0, index_1.marrowBuyerProof)(API_KEY, BASE_URL, {
|
|
2061
|
-
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2062
|
-
periodDays: args.periodDays,
|
|
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_mode_recommend') {
|
|
2068
|
-
const result = await (0, index_1.marrowRecommendGovernanceMode)(API_KEY, BASE_URL, {
|
|
2069
|
-
project: args.project && typeof args.project === 'object' && !Array.isArray(args.project)
|
|
2070
|
-
? (0, redact_1.redactSensitiveValue)(args.project)
|
|
2071
|
-
: undefined,
|
|
2072
|
-
workflow: args.workflow && typeof args.workflow === 'object' && !Array.isArray(args.workflow)
|
|
2073
|
-
? (0, redact_1.redactSensitiveValue)(args.workflow)
|
|
2074
|
-
: undefined,
|
|
2075
|
-
agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
|
|
2076
|
-
? (0, redact_1.redactSensitiveValue)(args.agent)
|
|
2077
|
-
: { id: FLEET_AGENT_ID },
|
|
2078
|
-
selected_mode: args.selected_mode,
|
|
2079
|
-
selection_source: args.selection_source,
|
|
2080
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2081
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2082
|
-
return;
|
|
2083
|
-
}
|
|
2084
|
-
if (toolName === 'marrow_policy_profiles') {
|
|
2085
|
-
const result = await (0, index_1.marrowListPolicyProfiles)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
|
|
2086
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2087
|
-
return;
|
|
2088
|
-
}
|
|
2089
|
-
if (toolName === 'marrow_create_policy_profile') {
|
|
2090
|
-
const result = await (0, index_1.marrowCreatePolicyProfile)(API_KEY, BASE_URL, {
|
|
2091
|
-
name: args.name,
|
|
2092
|
-
description: args.description,
|
|
2093
|
-
rules: Array.isArray(args.rules) ? args.rules : undefined,
|
|
2094
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2095
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2096
|
-
return;
|
|
2097
|
-
}
|
|
2098
|
-
if (toolName === 'marrow_assign_project_policy_profile') {
|
|
2099
|
-
const result = await (0, index_1.marrowAssignProjectPolicyProfile)(API_KEY, BASE_URL, {
|
|
2100
|
-
project_key: args.project_key,
|
|
2101
|
-
profile_id: args.profile_id,
|
|
2102
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2103
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2104
|
-
return;
|
|
2105
|
-
}
|
|
2106
|
-
if (toolName === 'marrow_policy_resolve') {
|
|
2107
|
-
const result = await (0, index_1.marrowResolvePolicy)(API_KEY, BASE_URL, {
|
|
2108
|
-
profile_id: args.profile_id,
|
|
2109
|
-
profile_name: args.profile_name,
|
|
2110
|
-
project: args.project && typeof args.project === 'object' && !Array.isArray(args.project)
|
|
2111
|
-
? (0, redact_1.redactSensitiveValue)(args.project)
|
|
2112
|
-
: undefined,
|
|
2113
|
-
workflow: args.workflow && typeof args.workflow === 'object' && !Array.isArray(args.workflow)
|
|
2114
|
-
? (0, redact_1.redactSensitiveValue)(args.workflow)
|
|
2115
|
-
: undefined,
|
|
2116
|
-
agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
|
|
2117
|
-
? (0, redact_1.redactSensitiveValue)(args.agent)
|
|
2118
|
-
: { id: FLEET_AGENT_ID },
|
|
2119
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2120
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2121
|
-
return;
|
|
2122
|
-
}
|
|
2123
|
-
if (toolName === 'marrow_workflow_gate') {
|
|
2124
|
-
const result = await (0, index_1.marrowWorkflowGate)(API_KEY, BASE_URL, {
|
|
2125
|
-
action: (0, redact_1.redactSensitiveText)(args.action),
|
|
2126
|
-
description: args.description ? (0, redact_1.redactSensitiveText)(args.description) : undefined,
|
|
2127
|
-
risk_tolerance: args.riskTolerance,
|
|
2128
|
-
requires_approval: args.requiresApproval,
|
|
2129
|
-
context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
|
|
2130
|
-
? (0, redact_1.redactSensitiveValue)(args.context)
|
|
2131
|
-
: undefined,
|
|
2132
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2133
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2134
|
-
return;
|
|
2135
|
-
}
|
|
2136
|
-
if (toolName === 'marrow_agent_performance') {
|
|
2137
|
-
const result = await (0, index_1.marrowAgentPerformance)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
|
|
2138
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2139
|
-
return;
|
|
2140
|
-
}
|
|
2141
|
-
if (toolName === 'marrow_fleet_lessons') {
|
|
2142
|
-
const result = await (0, index_1.marrowFleetLessons)(API_KEY, BASE_URL, {
|
|
2143
|
-
query: args.query,
|
|
2144
|
-
type: args.type,
|
|
2145
|
-
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2146
|
-
limit: args.limit,
|
|
2147
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2148
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2149
|
-
return;
|
|
2150
|
-
}
|
|
2151
|
-
if (toolName === 'marrow_record_deployment_memory') {
|
|
2152
|
-
const result = await (0, index_1.marrowRecordDeploymentMemory)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2153
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
if (toolName === 'marrow_create_handoff') {
|
|
2157
|
-
const result = await (0, index_1.marrowCreateHandoff)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
|
|
2158
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2159
|
-
return;
|
|
2160
|
-
}
|
|
2161
|
-
if (toolName === 'marrow_update_handoff') {
|
|
2162
|
-
const handoffId = args.handoffId;
|
|
2163
|
-
if (!handoffId) {
|
|
2164
|
-
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) }] });
|
|
2165
1826
|
return;
|
|
2166
1827
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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,
|
|
2174
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, {
|
|
2175
2083
|
agentId: args.agentId || FLEET_AGENT_ID,
|
|
2176
2084
|
limit: args.limit,
|
|
2177
|
-
}, SESSION_ID, FLEET_AGENT_ID
|
|
2178
|
-
|
|
2085
|
+
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2086
|
+
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2087
|
+
return;
|
|
2179
2088
|
}
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
state: 'not_entitled',
|
|
2186
|
-
failure_kind: 'entitlement',
|
|
2187
|
-
authorization_state: 'plan_limited',
|
|
2188
|
-
credential_valid: true,
|
|
2189
|
-
feature: 'fleet_handoff_status',
|
|
2190
|
-
current_plan: error.currentPlan,
|
|
2191
|
-
required_plan: 'team',
|
|
2192
|
-
required_feature: error.requiredFeature || 'fleet_learning',
|
|
2193
|
-
service_reachable: true,
|
|
2194
|
-
exact_next_action: 'Use status, ask, runtime, auto, and commit on the current plan. Upgrade to Team before relying on fleet handoff status.',
|
|
2195
|
-
}));
|
|
2089
|
+
if (toolName === 'marrow_decision_trace') {
|
|
2090
|
+
const decisionId = args.decisionId;
|
|
2091
|
+
if (!decisionId) {
|
|
2092
|
+
error(id, -32602, 'decisionId is required');
|
|
2093
|
+
return;
|
|
2196
2094
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
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;
|
|
2199
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;
|
|
2200
2210
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
+
}
|
|
2212
2241
|
return;
|
|
2213
2242
|
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
}
|
|
2218
|
-
if (toolName === 'marrow_list_templates') {
|
|
2219
|
-
const result = await (0, index_1.marrowListTemplates)(API_KEY, BASE_URL, {
|
|
2220
|
-
industry: args.industry,
|
|
2221
|
-
category: args.category,
|
|
2222
|
-
limit: args.limit,
|
|
2223
|
-
}, SESSION_ID, FLEET_AGENT_ID);
|
|
2224
|
-
success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
if (toolName === 'marrow_install_template') {
|
|
2228
|
-
const slug = args.slug;
|
|
2229
|
-
if (!slug) {
|
|
2230
|
-
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) }] });
|
|
2231
2246
|
return;
|
|
2232
2247
|
}
|
|
2233
|
-
|
|
2234
|
-
|
|
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}`);
|
|
2235
2278
|
return;
|
|
2236
2279
|
}
|
|
2237
|
-
error(id, -32601, `Method not found: ${
|
|
2238
|
-
return;
|
|
2280
|
+
error(id, -32601, `Method not found: ${method}`);
|
|
2239
2281
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
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));
|
|
2246
2289
|
}
|
|
2247
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2248
|
-
error(id, -32602, (0, redact_1.redactSensitiveText)(message).slice(0, 240));
|
|
2249
2290
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
autoCommitOnClose().then(() => process.exit(0));
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
process.stdin.setEncoding('utf8');
|
|
2261
|
-
process.stdin.on('data', (chunk) => {
|
|
2262
|
-
buffer += chunk;
|
|
2263
|
-
const lines = buffer.split('\n');
|
|
2264
|
-
buffer = lines.pop() || ''; // keep incomplete line in buffer
|
|
2265
|
-
for (const line of lines) {
|
|
2266
|
-
const trimmed = line.trim();
|
|
2267
|
-
if (!trimmed)
|
|
2268
|
-
continue;
|
|
2269
|
-
// [FIX #1] Wrap JSON.parse in try-catch to prevent crash on malformed input
|
|
2270
|
-
let msg;
|
|
2271
|
-
try {
|
|
2272
|
-
msg = JSON.parse(trimmed);
|
|
2273
|
-
}
|
|
2274
|
-
catch (parseErr) {
|
|
2275
|
-
process.stderr.write(`[marrow] JSON parse error: ${parseErr}\n`);
|
|
2276
|
-
send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } });
|
|
2277
|
-
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));
|
|
2278
2298
|
}
|
|
2279
|
-
// MCP notifications (no id) must be silently ignored per spec
|
|
2280
|
-
if (msg.id === undefined || msg.id === null)
|
|
2281
|
-
continue;
|
|
2282
|
-
// The client's answer to this server's own request (elicitation).
|
|
2283
|
-
if (settleClientResponse(msg))
|
|
2284
|
-
continue;
|
|
2285
|
-
pendingRequests++;
|
|
2286
|
-
handleRequest(msg)
|
|
2287
|
-
.catch((err) => {
|
|
2288
|
-
process.stderr.write(`[marrow] Handler error: ${err}\n`);
|
|
2289
|
-
})
|
|
2290
|
-
.finally(() => {
|
|
2291
|
-
pendingRequests--;
|
|
2292
|
-
checkExit();
|
|
2293
|
-
});
|
|
2294
2299
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
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
|
+
});
|
|
2302
2334
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
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
|
+
});
|
|
2307
2361
|
}
|
|
2308
|
-
|
|
2362
|
+
else {
|
|
2309
2363
|
checkExit();
|
|
2310
|
-
return;
|
|
2311
2364
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
pendingRequests--;
|
|
2319
|
-
checkExit();
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
else {
|
|
2323
|
-
checkExit();
|
|
2324
|
-
}
|
|
2325
|
-
});
|
|
2326
|
-
process.stdin.on('error', (err) => {
|
|
2327
|
-
process.stderr.write(`[marrow] stdin error: ${err}\n`);
|
|
2328
|
-
process.exit(1);
|
|
2329
|
-
});
|
|
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
|
|
2330
2371
|
} // Close the if (process.argv[2] !== 'keys') block
|
|
2331
2372
|
}
|
|
2332
2373
|
//# sourceMappingURL=cli.js.map
|