@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.
Files changed (88) hide show
  1. package/README.md +26 -5
  2. package/dist/approval-reply.d.ts +2 -0
  3. package/dist/approval-reply.d.ts.map +1 -0
  4. package/dist/approval-reply.js +18 -0
  5. package/dist/approval-reply.js.map +1 -0
  6. package/dist/cli.js +1677 -1636
  7. package/dist/cli.js.map +1 -1
  8. package/dist/codex-plugin-pins.d.ts.map +1 -1
  9. package/dist/codex-plugin-pins.js +18 -0
  10. package/dist/codex-plugin-pins.js.map +1 -1
  11. package/dist/env.d.ts +8 -0
  12. package/dist/env.d.ts.map +1 -1
  13. package/dist/env.js +83 -4
  14. package/dist/env.js.map +1 -1
  15. package/dist/hook-context.d.ts.map +1 -1
  16. package/dist/hook-context.js +25 -0
  17. package/dist/hook-context.js.map +1 -1
  18. package/dist/hook-contract.d.ts +4 -3
  19. package/dist/hook-contract.d.ts.map +1 -1
  20. package/dist/hook-contract.js +36 -3
  21. package/dist/hook-contract.js.map +1 -1
  22. package/dist/hook-pre-action.d.ts +38 -7
  23. package/dist/hook-pre-action.d.ts.map +1 -1
  24. package/dist/hook-pre-action.js +254 -21
  25. package/dist/hook-pre-action.js.map +1 -1
  26. package/dist/hook-tool-policy.d.ts +73 -4
  27. package/dist/hook-tool-policy.d.ts.map +1 -1
  28. package/dist/hook-tool-policy.js +287 -18
  29. package/dist/hook-tool-policy.js.map +1 -1
  30. package/dist/hook.d.ts.map +1 -1
  31. package/dist/hook.js +5 -3
  32. package/dist/hook.js.map +1 -1
  33. package/dist/host-approval-state.d.ts +7 -0
  34. package/dist/host-approval-state.d.ts.map +1 -1
  35. package/dist/host-approval-state.js +15 -0
  36. package/dist/host-approval-state.js.map +1 -1
  37. package/dist/host-approval.d.ts +22 -1
  38. package/dist/host-approval.d.ts.map +1 -1
  39. package/dist/host-approval.js +65 -4
  40. package/dist/host-approval.js.map +1 -1
  41. package/dist/index.d.ts +5 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +8 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/keyless-server.d.ts +53 -0
  46. package/dist/keyless-server.d.ts.map +1 -0
  47. package/dist/keyless-server.js +230 -0
  48. package/dist/keyless-server.js.map +1 -0
  49. package/dist/normalized-action.d.ts +2 -0
  50. package/dist/normalized-action.d.ts.map +1 -1
  51. package/dist/normalized-action.js +65 -10
  52. package/dist/normalized-action.js.map +1 -1
  53. package/dist/own-server-identity.d.ts +22 -3
  54. package/dist/own-server-identity.d.ts.map +1 -1
  55. package/dist/own-server-identity.js +98 -27
  56. package/dist/own-server-identity.js.map +1 -1
  57. package/dist/own-tool.d.ts +14 -6
  58. package/dist/own-tool.d.ts.map +1 -1
  59. package/dist/own-tool.js +18 -7
  60. package/dist/own-tool.js.map +1 -1
  61. package/dist/seatbelt/env-dump.d.ts +45 -0
  62. package/dist/seatbelt/env-dump.d.ts.map +1 -0
  63. package/dist/seatbelt/env-dump.js +308 -0
  64. package/dist/seatbelt/env-dump.js.map +1 -0
  65. package/dist/seatbelt/fallback-copy.d.ts +16 -0
  66. package/dist/seatbelt/fallback-copy.d.ts.map +1 -0
  67. package/dist/seatbelt/fallback-copy.js +68 -0
  68. package/dist/seatbelt/fallback-copy.js.map +1 -0
  69. package/dist/seatbelt/hook.d.ts +21 -2
  70. package/dist/seatbelt/hook.d.ts.map +1 -1
  71. package/dist/seatbelt/hook.js +45 -11
  72. package/dist/seatbelt/hook.js.map +1 -1
  73. package/dist/seatbelt/mapper.d.ts.map +1 -1
  74. package/dist/seatbelt/mapper.js +360 -27
  75. package/dist/seatbelt/mapper.js.map +1 -1
  76. package/dist/seatbelt/tables.d.ts +1 -1
  77. package/dist/seatbelt/tables.d.ts.map +1 -1
  78. package/dist/seatbelt/tables.js +9 -16
  79. package/dist/seatbelt/tables.js.map +1 -1
  80. package/dist/secret-paths.d.ts +17 -0
  81. package/dist/secret-paths.d.ts.map +1 -0
  82. package/dist/secret-paths.js +32 -0
  83. package/dist/secret-paths.js.map +1 -0
  84. package/dist/session-loop-guard.d.ts.map +1 -1
  85. package/dist/session-loop-guard.js +15 -5
  86. package/dist/session-loop-guard.js.map +1 -1
  87. package/package.json +2 -2
  88. 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(`Error: MARROW_API_KEY required. ${resolved.exactFix}\n`);
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(`Error: MARROW_API_KEY required. ${resolvedEnv.exactFix}\n`);
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
- const API_KEY = cliArgs.apiKey || resolvedEnv.apiKey || '';
559
- // [SECURITY #3] Validate BASE_URL — require HTTPS to prevent SSRF / credential leakage
560
- const rawBaseUrl = resolvedEnv.baseUrl || 'https://api.getmarrow.ai';
561
- const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
562
- // A non-default endpoint receives every action; name its host wherever status is shown.
563
- const ENDPOINT_HOST = (0, env_1.nonDefaultBaseUrlHost)(BASE_URL);
564
- const SESSION_ID = resolvedEnv.sessionId || undefined;
565
- // One identity must bind headers, bodies, queries, cache, and lifecycle events.
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
- /** A response from the client to one of this server's requests (elicitation). */
628
- function settleClientResponse(message) {
629
- if (typeof message.id !== 'string' || message.method !== undefined)
630
- return false;
631
- const pending = pendingClientRequests.get(message.id);
632
- if (!pending)
633
- return false;
634
- pendingClientRequests.delete(message.id);
635
- clearTimeout(pending.timer);
636
- pending.resolve({ result: message.result, error: message.error });
637
- return true;
638
- }
639
- /** Asks the user in the client's own dialog; approved, declined, or null (cancelled, timed out, unsupported). */
640
- async function elicitApproval(request) {
641
- if (!clientElicitation)
642
- return null;
643
- const answer = await requestClient('elicitation/create', {
644
- message: request.message,
645
- requestedSchema: {
646
- type: 'object',
647
- properties: {
648
- decision: {
649
- type: 'string',
650
- title: 'Your answer',
651
- description: 'Approve only if you authorize this exact action.',
652
- enum: ['approve', 'decline'],
653
- enumNames: ['Approve this action', 'Decline it'],
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
- required: ['decision'],
657
- },
658
- }, ELICITATION_TIMEOUT_MS);
659
- const result = answer.result && typeof answer.result === 'object' ? answer.result : null;
660
- if (!result)
661
- return null;
662
- if (result.action === 'decline')
663
- return 'declined';
664
- if (result.action !== 'accept')
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
- catch (err) {
686
- const msg = err instanceof Error ? err.message : String(err);
687
- process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
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
- // Auto-commit tracking for session close
691
- let lastDecisionId = null;
692
- let lastCommitted = false;
693
- // [FIX #5] Log auto-commit failures instead of silently ignoring; remove broken AbortController
694
- async function autoCommitOnClose() {
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.marrowCommit)(API_KEY, BASE_URL, {
698
- decision_id: lastDecisionId,
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: auto-commit on close failed: ${msg}\n`);
727
+ process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
706
728
  }
707
729
  }
708
- }
709
- // [FIX #10] Handle both SIGTERM and SIGINT for clean shutdown
710
- async function gracefulShutdown() {
711
- const forceExit = setTimeout(() => process.exit(0), 5000);
712
- forceExit.unref();
713
- await autoCommitOnClose();
714
- process.exit(0);
715
- }
716
- process.on('SIGTERM', gracefulShutdown);
717
- process.on('SIGINT', gracefulShutdown);
718
- function send(response) {
719
- process.stdout.write(JSON.stringify(response) + '\n');
720
- }
721
- function success(id, result) {
722
- send({ jsonrpc: '2.0', id, result });
723
- }
724
- function error(id, code, message) {
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
- return result;
810
- }
811
- function mcpHostCapability() {
812
- return (0, host_capability_1.resolveHostCapability)({
813
- hostLabel: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || process.env.MARROW_AGENT_CLIENT,
814
- });
815
- }
816
- function clientOperationalPayload(toolName, value) {
817
- const payload = value && typeof value === 'object' && !Array.isArray(value)
818
- ? value
819
- : { data: value };
820
- const spool = reportLifecycleSpool({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
821
- const habitLoopCopy = (0, habit_loop_copy_1.formatHabitLoopCopy)(payload) || (0, habit_loop_copy_1.formatHabitLoopCopy)(payload.data);
822
- return {
823
- ...payload,
824
- ...(habitLoopCopy ? { habit_loop_copy: habitLoopCopy } : {}),
825
- host_capability: payload.host_capability || mcpHostCapability(),
826
- client_update: payload.client_update
827
- || (payload.data && typeof payload.data === 'object' && !Array.isArray(payload.data)
828
- ? payload.data.client_update
829
- : null)
830
- || (0, request_reliability_1.localClientUpdate)(),
831
- control_path: (0, control_path_state_1.controlPathStats)(toolName),
832
- local_control: (0, control_state_1.localControlEvidence)(Boolean(API_KEY)),
833
- lifecycle_spool: {
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
- mcp_tool_profile: mcpToolProfileStatus(payload),
842
- marrow_endpoint: { host: ENDPOINT_HOST || 'api.getmarrow.ai', non_default: Boolean(ENDPOINT_HOST) },
843
- };
844
- }
845
- // [FIX #9] Runtime validation helper for required string params
846
- function requireString(args, name) {
847
- const val = args[name];
848
- if (typeof val !== 'string' || !val.trim()) {
849
- throw new Error(`"${name}" is required and must be a non-empty string`);
850
- }
851
- return val;
852
- }
853
- function requireBoolean(args, name) {
854
- const value = args[name];
855
- if (typeof value !== 'boolean')
856
- throw new Error(`"${name}" is required and must be boolean`);
857
- return value;
858
- }
859
- 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;
860
- function isHighRiskAction(action, type) {
861
- return HIGH_RISK_ACTION.test(`${String(type || '')} ${String(action || '')}`);
862
- }
863
- async function withControlDeadline(operation, options = {}) {
864
- const toolName = options.toolName || 'marrow_control';
865
- const configuredTimeoutMs = Number(process.env.MARROW_REQUEST_TIMEOUT_MS);
866
- const runtimeBudget = toolName === 'marrow_agent_runtime' || toolName === 'marrow_auto.runtime';
867
- const commitBudget = toolName === 'marrow_commit';
868
- const timeoutMs = Number.isFinite(configuredTimeoutMs)
869
- ? Math.min(10_000, Math.max(150, Math.floor(configuredTimeoutMs)))
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
- catch (error) {
881
- (0, control_path_state_1.recordControlPathSample)(toolName, Date.now() - startedAt, false);
882
- throw error;
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
- finally {
885
- clearTimeout(timer);
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
- function storeRuntimeGuidance(runtime) {
889
- try {
890
- (0, guidance_cache_1.writeGuidanceCache)({
891
- apiKey: API_KEY,
892
- baseUrl: BASE_URL,
893
- agentId: FLEET_AGENT_ID,
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
- catch { /* owner-only cache is best effort */ }
898
- }
899
- function storeLastKnownStatus(status, source) {
900
- try {
901
- (0, status_cache_1.writeStatusCache)({
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
- catch { /* owner-only cache is best effort */ }
910
- }
911
- function refreshStatusInBackground() {
912
- void (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID)
913
- .then((status) => storeLastKnownStatus(status, 'status'))
914
- .catch(() => undefined);
915
- }
916
- // [FIX #6 & #7] Safe JSON response helper for memory API functions
917
- async function safeMemoryResponse(res) {
918
- if (!res.ok) {
919
- let detail = '';
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
- detail = await res.text();
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
- const json = await res.json();
927
- if (json.error) {
928
- throw new Error(json.error);
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
- return json;
931
- }
932
- // Memory API functions — all patched with safeMemoryResponse and validatePathParam
933
- async function marrowListMemories(apiKey, baseUrl, params, sessionId) {
934
- const qs = new URLSearchParams();
935
- if (params?.status)
936
- qs.set('status', params.status);
937
- if (params?.query)
938
- qs.set('query', params.query);
939
- if (params?.limit)
940
- qs.set('limit', String(params.limit));
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
- if ((evidence.state === 'available' && evidence.authoritative !== true)
1146
- || (evidence.state === 'unavailable' && evidence.authoritative !== false)) {
1147
- return null;
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
- const derivedCounts = { entitled: 0, upgrade_required: 0, unavailable: 0 };
1150
- for (let index = 0; index < tools.length; index++) {
1151
- const tool = recordValue(tools[index]);
1152
- const state = tool?.state;
1153
- if (!tool
1154
- || tool.name !== expectedNames[index]
1155
- || (state !== 'entitled' && state !== 'upgrade_required' && state !== 'unavailable')
1156
- || typeof tool.always_available !== 'boolean'
1157
- || (tool.plan_feature !== null && typeof tool.plan_feature !== 'string')
1158
- || (tool.minimum_plan !== null && typeof tool.minimum_plan !== 'string')
1159
- || typeof tool.owner_management_url !== 'string') {
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
- derivedCounts[state]++;
1209
+ return {
1210
+ projection,
1211
+ evidenceState: evidence.state,
1212
+ };
1163
1213
  }
1164
- if (derivedCounts.entitled !== counts.entitled
1165
- || derivedCounts.upgrade_required !== counts.upgrade_required
1166
- || derivedCounts.unavailable !== counts.unavailable) {
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
- return {
1170
- projection,
1171
- evidenceState: evidence.state,
1172
- };
1173
- }
1174
- function backendPrimaryToolAvailability(value) {
1175
- const payload = recordValue(value);
1176
- if (!payload)
1177
- return null;
1178
- const data = recordValue(payload.data);
1179
- const status = recordValue(payload.status);
1180
- const entitlements = recordValue(payload.entitlements);
1181
- const entitlementProjection = recordValue(payload.entitlement_projection);
1182
- const statusEntitlements = recordValue(status?.entitlements);
1183
- const statusProjection = recordValue(status?.entitlement_projection);
1184
- const candidates = [
1185
- payload.primary_tool_availability,
1186
- data?.primary_tool_availability,
1187
- status?.primary_tool_availability,
1188
- entitlements?.primary_tool_availability,
1189
- entitlementProjection?.primary_tool_availability,
1190
- statusEntitlements?.primary_tool_availability,
1191
- statusProjection?.primary_tool_availability,
1192
- ];
1193
- for (const candidate of candidates) {
1194
- const validated = validatedPrimaryToolAvailability(candidate);
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
- return null;
1199
- }
1200
- function mcpToolProfileStatus(value) {
1201
- const profile = activeToolProfile();
1202
- const visibleToolNames = toolsForProfile(profile).map((tool) => tool.name);
1203
- const payload = recordValue(value);
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
- try {
1244
- if (method === 'initialize') {
1245
- initialized = true;
1246
- const hostCapability = mcpHostCapability();
1247
- const protocolVersion = noteClientCapabilities(params);
1248
- success(id, {
1249
- protocolVersion,
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
- if (method === 'prompts/list') {
1278
- if (AUTO_ENROLL) {
1283
+ try {
1284
+ if (method === 'initialize') {
1285
+ initialized = true;
1286
+ const hostCapability = mcpHostCapability();
1287
+ const protocolVersion = noteClientCapabilities(params);
1279
1288
  success(id, {
1280
- prompts: [
1281
- {
1282
- name: 'marrow-always-on',
1283
- description: 'Marrow control and proof contract. MCP tools are on demand; host activity is client-self-reported and coverage remains unverified without independent authority.',
1284
- arguments: [],
1285
- _meta: { host_capability: mcpHostCapability() },
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
- else {
1291
- success(id, { prompts: [] });
1292
- }
1293
- return;
1294
- }
1295
- if (method === 'prompts/get') {
1296
- const promptName = params?.name;
1297
- if (promptName !== 'marrow-always-on' || !AUTO_ENROLL) {
1298
- error(id, -32602, 'Unknown prompt');
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
- const hostCapability = mcpHostCapability();
1302
- success(id, {
1303
- description: 'Marrow control and proof contract — MCP tools are on demand, host activity is client-self-reported, and coverage is unverified without independent authority',
1304
- _meta: { host_capability: hostCapability },
1305
- messages: [
1306
- {
1307
- role: 'user',
1308
- content: {
1309
- type: 'text',
1310
- text: `You have Marrow — the agent control and proof layer around this workflow.
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 (toolName === 'marrow_orient') {
1362
- orientCallCount++;
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 (toolName === 'marrow_think') {
1387
- // [FIX #9] Validate required param
1388
- const action = requireString(args, 'action');
1389
- const result = await (0, index_1.marrowThink)(API_KEY, BASE_URL, {
1390
- action,
1391
- type: args.type,
1392
- context: args.context,
1393
- previous_decision_id: args.previous_decision_id,
1394
- previous_success: args.previous_success,
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
- lastDecisionId = result.decision_id;
1422
- lastCommitted = false;
1423
- success(id, {
1424
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1425
- });
1426
- return;
1427
- }
1428
- if (toolName === 'marrow_commit') {
1429
- // [FIX #9] Validate required params
1430
- const decision_id = requireString(args, 'decision_id');
1431
- const outcome = requireString(args, 'outcome');
1432
- const commitSuccess = requireBoolean(args, 'success');
1433
- // A receipt a host hook held: commit in the session it was issued to,
1434
- // and send the operator's queued answer first so the commit closes
1435
- // with that approval.
1436
- let commitSessionId = SESSION_ID;
1437
- if (typeof args.gate_receipt_id === 'string' && args.gate_receipt_id) {
1438
- const holdContext = {
1439
- apiKey: API_KEY,
1440
- baseUrl: BASE_URL,
1441
- sessionId: SESSION_ID || 'mcp-server',
1442
- agentId: FLEET_AGENT_ID,
1443
- harness: 'mcp-client',
1444
- host: 'other',
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
- const proofKeys = [`decision:${decision_id}`, ...(typeof args.gate_receipt_id === 'string' && args.gate_receipt_id ? [`receipt:${args.gate_receipt_id}`] : [])];
1451
- let result;
1452
- try {
1453
- result = await withControlDeadline((signal) => (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1454
- decision_id,
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
- surfaces: args.surfaces,
1466
- auto_gate: args.auto_gate,
1467
- model_usage: args.model_usage,
1468
- }, commitSessionId, FLEET_AGENT_ID, signal), { highRisk: true, cacheAware: false, toolName: 'marrow_commit' });
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
- catch (err) {
1471
- if (err instanceof request_reliability_1.MarrowRequestError && err.code === 'proof_required' && err.missingFields.length) {
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.set(key, [...err.missingFields]);
1526
+ commitProofMissing.delete(key);
1474
1527
  }
1475
- if (err instanceof request_reliability_1.MarrowRequestError && err.status === 409 && err.backendCode === 'MARROW_OUTCOME_OBSERVATION_CONFLICT') {
1476
- const fromHold = typeof args.gate_receipt_id === 'string' && args.gate_receipt_id
1477
- ? (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)
1478
- : [];
1479
- toolSuccess(id, proofRetryConflict(proofKeys, args.proof, fromHold), true);
1480
- return;
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
- throw err;
1483
- }
1484
- if (result.committed) {
1485
- for (const key of proofKeys)
1486
- commitProofMissing.delete(key);
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
- else {
1489
- // A short close is accepted unverified with the missing proof named ("Retry only after proof includes: a, b.").
1490
- const unverified = result;
1491
- const listed = unverified.governance_validation?.code === 'MARROW_PROOF_PACK_INCOMPLETE'
1492
- ? /proof includes: ([A-Za-z0-9_, ]{1,600})\.?\s*$/.exec(String(unverified.exact_next_action || ''))?.[1]
1493
- : undefined;
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
- const commitResult = { ...result, narrative: result.narrative ?? null };
1500
- lastCommitted = result.committed;
1501
- lastDecisionId = result.committed ? null : decision_id;
1502
- success(id, {
1503
- content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
1504
- });
1505
- return;
1506
- }
1507
- if (toolName === 'marrow_model_usage') {
1508
- const result = await (0, index_1.marrowModelUsage)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
1509
- success(id, {
1510
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1511
- });
1512
- return;
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
- catch (err) {
1547
- const msg = err instanceof Error ? err.message : String(err);
1548
- process.stderr.write(`[marrow] marrow_run commit failed: ${msg}\n`);
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
- success(id, {
1563
- content: [{
1564
- type: 'text',
1565
- text: JSON.stringify({ think: thinkResult, commit: commitResult }, null, 2),
1566
- }],
1567
- });
1568
- return;
1569
- }
1570
- if (toolName === 'marrow_auto') {
1571
- const action = requireString(args, 'action');
1572
- const outcome = args.outcome;
1573
- const outcomeSuccess = typeof args.success === 'boolean' ? args.success : undefined;
1574
- const type = args.type || 'general';
1575
- const highRisk = isHighRiskAction(action, type);
1576
- const suppliedProof = args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
1577
- ? (0, redact_1.redactSensitiveValue)(args.proof)
1578
- : undefined;
1579
- const suppliedRuntimeReceiptId = typeof args.gate_receipt_id === 'string'
1580
- ? args.gate_receipt_id
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
- outcome_state: delivered?.committed ? 'closed' : 'pending',
1643
- success: delivered?.committed ? outcomeSuccess : highRisk ? undefined : outcomeSuccess,
1644
- source: 'client_self_reported',
1645
- },
1646
- });
1647
- const enqueueDurationMs = Math.floor(performance.now() - enqueueStartedAt);
1648
- const response = {
1649
- action,
1650
- outcome: outcome || 'pending',
1651
- warnings: cachedOrientWarnings.map(formatWarningActionably),
1652
- logging: delivered?.committed
1653
- ? 'governed_commit_confirmed'
1654
- : delivered?.resumable
1655
- ? 'resume_required'
1656
- : delivered
1657
- ? 'intent_confirmed'
1658
- : 'durably_queued',
1659
- receipt,
1660
- completion_state: delivered?.committed && delivered.closure === 'gate_denial'
1661
- ? 'closed_as_denial'
1662
- : delivered?.closure === 'already_closed_denial'
1663
- ? 'closed_earlier_as_denial'
1664
- : delivered?.closure === 'already_closed'
1665
- ? 'closed_earlier'
1666
- : delivered?.committed
1667
- ? 'closed_with_proof'
1668
- : delivered?.phase === 'review_required'
1669
- ? 'review_required_terminal'
1670
- : delivered?.phase === 'owner_approval_required'
1671
- ? 'pending_owner_approval'
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
- agentId: FLEET_AGENT_ID,
1733
- context: `## Marrow answer\n- ${String(result.answer || 'No relevant lesson found.').slice(0, 1200)}`,
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
- catch { /* owner-only cache is best effort */ }
1737
- toolSuccess(id, clientOperationalPayload('marrow_ask', result));
1738
- return;
1739
- }
1740
- if (toolName === 'marrow_status') {
1741
- let cached = null;
1742
- try {
1743
- cached = (0, status_cache_1.readStatusCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
1744
- }
1745
- catch { /* owner-only cache is best effort */ }
1746
- if (cached?.freshness === 'fresh') {
1747
- const startedAt = Date.now();
1748
- refreshStatusInBackground();
1749
- (0, control_path_state_1.recordControlPathSample)('marrow_status', Date.now() - startedAt, true);
1750
- toolSuccess(id, clientOperationalPayload('marrow_status', (0, status_cache_1.cachedStatusPayload)(cached)));
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
- try {
1754
- const result = await withControlDeadline((signal) => (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_status' });
1755
- storeLastKnownStatus(result, 'status');
1756
- toolSuccess(id, clientOperationalPayload('marrow_status', result));
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
- catch (error) {
1759
- if (cached) {
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
- (0, control_path_state_1.recordControlPathSample)('marrow_status', Date.now() - startedAt, false);
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
- throw error;
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
- return;
1768
- }
1769
- if (toolName === 'marrow_create_key') {
1770
- const name = requireString(args, 'name');
1771
- const result = await (0, index_1.marrowCreateKey)(API_KEY, BASE_URL, {
1772
- name,
1773
- key_type: args.key_type,
1774
- scopes: args.scopes,
1775
- agent_ids: args.agent_ids,
1776
- expires_at: args.expires_at,
1777
- }, SESSION_ID, FLEET_AGENT_ID);
1778
- success(id, {
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
- const result = await (0, index_1.marrowDecisionTrace)(API_KEY, BASE_URL, decisionId, SESSION_ID, FLEET_AGENT_ID);
2056
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2057
- return;
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
- const result = await (0, index_1.marrowUpdateHandoff)(API_KEY, BASE_URL, handoffId, args, SESSION_ID, FLEET_AGENT_ID);
2168
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2169
- return;
2170
- }
2171
- if (toolName === 'marrow_handoff_status') {
2172
- try {
2173
- const result = await withControlDeadline((signal) => (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
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, signal), { cacheAware: false, toolName: 'marrow_handoff_status' });
2178
- toolSuccess(id, clientOperationalPayload('marrow_handoff_status', result));
2085
+ }, SESSION_ID, FLEET_AGENT_ID);
2086
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2087
+ return;
2179
2088
  }
2180
- catch (error) {
2181
- if (error instanceof request_reliability_1.MarrowRequestError && error.backendCode === 'MARROW_PLAN_UPGRADE_REQUIRED') {
2182
- toolSuccess(id, clientOperationalPayload('marrow_handoff_status', {
2183
- ok: true,
2184
- available: false,
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
- else {
2198
- throw error;
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
- return;
2202
- }
2203
- if (toolName === 'marrow_session_end') {
2204
- const result = await (0, index_1.marrowSessionEnd)(API_KEY, BASE_URL, (0, hook_session_1.sessionEndAutoCommitOpen)(args.autoCommitOpen), SESSION_ID, FLEET_AGENT_ID);
2205
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2206
- return;
2207
- }
2208
- if (toolName === 'marrow_accept_detected') {
2209
- const detectedId = args.detectedId;
2210
- if (!detectedId) {
2211
- error(id, -32602, 'detectedId is required');
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
- const result = await (0, index_1.marrowAcceptDetected)(API_KEY, BASE_URL, detectedId, SESSION_ID, FLEET_AGENT_ID);
2215
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2216
- return;
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
- const result = await (0, index_1.marrowInstallTemplate)(API_KEY, BASE_URL, slug, SESSION_ID, FLEET_AGENT_ID);
2234
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
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: ${toolName}`);
2238
- return;
2280
+ error(id, -32601, `Method not found: ${method}`);
2239
2281
  }
2240
- error(id, -32601, `Method not found: ${method}`);
2241
- }
2242
- catch (err) {
2243
- if (err instanceof request_reliability_1.MarrowRequestError && method === 'tools/call') {
2244
- toolSuccess(id, toolFailure(params?.name, err), true);
2245
- return;
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
- // MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)
2252
- let buffer = '';
2253
- let pendingRequests = 0;
2254
- let stdinEnded = false;
2255
- function checkExit() {
2256
- if (stdinEnded && pendingRequests === 0) {
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
- process.stdin.on('end', () => {
2297
- stdinEnded = true;
2298
- if (buffer.trim()) {
2299
- let msg;
2300
- try {
2301
- msg = JSON.parse(buffer.trim());
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
- catch (err) {
2304
- process.stderr.write(`[marrow] JSON parse error on remaining buffer: ${err}\n`);
2305
- checkExit();
2306
- return;
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
- if (msg.id === undefined || msg.id === null) {
2362
+ else {
2309
2363
  checkExit();
2310
- return;
2311
2364
  }
2312
- pendingRequests++;
2313
- handleRequest(msg)
2314
- .catch((err) => {
2315
- process.stderr.write(`[marrow] Handler error on remaining: ${err}\n`);
2316
- })
2317
- .finally(() => {
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