@getmarrow/mcp 3.9.56 → 3.9.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -17,8 +17,12 @@ const hook_session_1 = require("./hook-session");
17
17
  const hook_pre_action_1 = require("./hook-pre-action");
18
18
  const env_1 = require("./env");
19
19
  const lifecycle_spool_1 = require("./lifecycle-spool");
20
+ const guidance_cache_1 = require("./guidance-cache");
21
+ const request_reliability_1 = require("./request-reliability");
22
+ const hook_contract_1 = require("./hook-contract");
20
23
  const ping_state_1 = require("./ping-state");
21
24
  const redact_1 = require("./redact");
25
+ const runtime_contract_1 = require("./runtime-contract");
22
26
  // Parse CLI args
23
27
  function parseArgs() {
24
28
  const args = process.argv.slice(2);
@@ -355,9 +359,12 @@ if (process.argv[2] !== 'keys') {
355
359
  const rawBaseUrl = resolvedEnv.baseUrl || 'https://api.getmarrow.ai';
356
360
  const BASE_URL = (0, index_1.validateBaseUrl)(rawBaseUrl);
357
361
  const SESSION_ID = resolvedEnv.sessionId || undefined;
358
- const FLEET_AGENT_ID = resolvedEnv.agentId || undefined; // V5: agent UUID for X-Marrow-Agent-Id header
362
+ // One identity must bind headers, bodies, queries, cache, and lifecycle events.
363
+ // resolveMarrowEnv already gives MARROW_FLEET_AGENT_ID precedence over MARROW_AGENT_ID.
364
+ // When no identity is configured, omit it and let the API resolve the key's
365
+ // bound agent instead of inventing a process-local identity that changes on restart.
366
+ const FLEET_AGENT_ID = resolvedEnv.agentId || undefined;
359
367
  const AUTO_ENROLL = process.env.MARROW_AUTO_ENROLL !== 'false'; // on by default
360
- const AGENT_ID = process.env.MARROW_AGENT_ID || `${require('os').hostname()}-${Date.now().toString(36)}`;
361
368
  if (!API_KEY) {
362
369
  process.stderr.write('Error: MARROW_API_KEY environment variable is required\n');
363
370
  process.stderr.write(`${resolvedEnv.exactFix}\n`);
@@ -389,12 +396,6 @@ if (process.argv[2] !== 'keys') {
389
396
  process.stderr.write(`[marrow] Warning: failed to refresh orient warnings: ${msg}\n`);
390
397
  }
391
398
  }
392
- // Initial orient
393
- refreshOrientWarnings().then(() => {
394
- if (cachedOrientWarnings.some((w) => w.failureRate > 0.4)) {
395
- process.stderr.write(`[marrow] ⚠️ High failure rate detected on startup — call marrow_orient for details before acting\n`);
396
- }
397
- });
398
399
  // Auto-commit tracking for session close
399
400
  let lastDecisionId = null;
400
401
  let lastCommitted = false;
@@ -432,6 +433,35 @@ if (process.argv[2] !== 'keys') {
432
433
  function error(id, code, message) {
433
434
  send({ jsonrpc: '2.0', id, error: { code, message } });
434
435
  }
436
+ function toolSuccess(id, value, isError = false) {
437
+ success(id, {
438
+ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }],
439
+ ...(isError ? { isError: true } : {}),
440
+ });
441
+ }
442
+ function toolFailure(toolName, failure) {
443
+ const result = (0, request_reliability_1.structuredRequestFailure)(failure);
444
+ const transient = failure.retryable && !['authentication_required', 'permission_denied'].includes(failure.code);
445
+ const supportsStale = ['marrow_agent_runtime', 'marrow_orient', 'marrow_ask', 'marrow_runtime_status', 'marrow_status'].includes(toolName || '');
446
+ if (transient && supportsStale) {
447
+ let cached = null;
448
+ try {
449
+ cached = (0, guidance_cache_1.readGuidanceCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
450
+ }
451
+ catch { /* owner-only cache is best effort */ }
452
+ if (cached) {
453
+ result.stale_brief = cached.context;
454
+ result.stale_ms = cached.stale_ms;
455
+ }
456
+ result.disposition = 'review_required';
457
+ result.allow = false;
458
+ result.stale_can_authorize_high_risk = false;
459
+ result.exact_next_action = cached
460
+ ? 'Use this last-known brief for low-risk context only. Obtain a fresh Marrow runtime gate before high-risk work.'
461
+ : 'Retry once after retry_after_ms. Do not perform high-risk work without a fresh Marrow runtime gate.';
462
+ }
463
+ return result;
464
+ }
435
465
  // [FIX #9] Runtime validation helper for required string params
436
466
  function requireString(args, name) {
437
467
  const val = args[name];
@@ -440,6 +470,51 @@ if (process.argv[2] !== 'keys') {
440
470
  }
441
471
  return val;
442
472
  }
473
+ function requireBoolean(args, name) {
474
+ const value = args[name];
475
+ if (typeof value !== 'boolean')
476
+ throw new Error(`"${name}" is required and must be boolean`);
477
+ return value;
478
+ }
479
+ 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;
480
+ function isHighRiskAction(action, type) {
481
+ return HIGH_RISK_ACTION.test(`${String(type || '')} ${String(action || '')}`);
482
+ }
483
+ function cachedGuidance() {
484
+ try {
485
+ return (0, guidance_cache_1.readGuidanceCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
486
+ }
487
+ catch {
488
+ return null;
489
+ }
490
+ }
491
+ async function withControlDeadline(operation, options = {}) {
492
+ const hasCache = options.cacheAware !== false && Boolean(cachedGuidance());
493
+ const configuredCanaryMs = Number(process.env.MARROW_REQUEST_TIMEOUT_MS);
494
+ const timeoutMs = process.env.MARROW_CONTROL_PATH_CANARY === '1' && Number.isFinite(configuredCanaryMs)
495
+ ? Math.min(5_000, Math.max(500, Math.floor(configuredCanaryMs)))
496
+ : options.highRisk ? 2_000 : hasCache ? 400 : 1_200;
497
+ const controller = new AbortController();
498
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
499
+ timer.unref?.();
500
+ try {
501
+ return await operation(controller.signal);
502
+ }
503
+ finally {
504
+ clearTimeout(timer);
505
+ }
506
+ }
507
+ function storeRuntimeGuidance(runtime) {
508
+ try {
509
+ (0, guidance_cache_1.writeGuidanceCache)({
510
+ apiKey: API_KEY,
511
+ baseUrl: BASE_URL,
512
+ agentId: FLEET_AGENT_ID,
513
+ context: (0, hook_context_1.compactRuntimeContext)(runtime),
514
+ });
515
+ }
516
+ catch { /* owner-only cache is best effort */ }
517
+ }
443
518
  // [FIX #6 & #7] Safe JSON response helper for memory API functions
444
519
  async function safeMemoryResponse(res) {
445
520
  if (!res.ok) {
@@ -726,8 +801,7 @@ if (process.argv[2] !== 'keys') {
726
801
  },
727
802
  {
728
803
  name: 'marrow_run',
729
- description: 'Zero-ceremony memory logging. Single call handles orient → think → commit automatically. ' +
730
- 'Use this instead of chaining marrow_think + marrow_commit when you want Marrow to just work without managing the loop yourself.',
804
+ description: 'One-call governed outcome capture. Records intent, obtains a gate for risky work, and closes only with an explicit measured result.',
731
805
  inputSchema: {
732
806
  type: 'object',
733
807
  properties: {
@@ -739,27 +813,28 @@ if (process.argv[2] !== 'keys') {
739
813
  enum: ['implementation', 'security', 'architecture', 'process', 'general'],
740
814
  description: 'Type of action (default: general)',
741
815
  },
816
+ proof: { type: 'object', description: 'Measured evidence required to close high-risk work.' },
817
+ gate_receipt_id: { type: 'string', description: 'Fresh receipt returned by marrow_agent_runtime.' },
742
818
  },
743
819
  required: ['description', 'success', 'outcome'],
744
820
  },
745
821
  },
746
822
  {
747
823
  name: 'marrow_auto',
748
- description: 'Zero-friction Marrow logging. One call for any action — Marrow handles everything in the background without blocking. ' +
749
- 'Pass what you are about to do. Optionally pass outcome if already done. ' +
750
- 'Use for ANY action: deploys, file writes, API calls, external sends. ' +
751
- 'If you only have time for one call: pass action + outcome + success together — done in one shot.',
824
+ description: 'Durably capture low-risk activity without blocking. Outcomes remain pending unless success is explicit; risky completion still requires a fresh gate and measured proof.',
752
825
  inputSchema: {
753
826
  type: 'object',
754
827
  properties: {
755
828
  action: { type: 'string', description: 'What you are about to do or just did' },
756
829
  outcome: { type: 'string', description: 'What happened (if already done). Omit to log intent only.' },
757
- success: { type: 'boolean', description: 'Did it succeed (default: true)' },
830
+ success: { type: 'boolean', description: 'Measured result. Omit when the outcome is not yet proven.' },
758
831
  type: {
759
832
  type: 'string',
760
833
  enum: ['implementation', 'security', 'architecture', 'process', 'general'],
761
834
  description: 'Type of action (default: general)',
762
835
  },
836
+ proof: { type: 'object', description: 'Measured completion evidence for gated work.' },
837
+ gate_receipt_id: { type: 'string', description: 'Fresh receipt returned by marrow_agent_runtime.' },
763
838
  },
764
839
  required: ['action'],
765
840
  },
@@ -1022,7 +1097,7 @@ if (process.argv[2] !== 'keys') {
1022
1097
  type: 'object',
1023
1098
  properties: {
1024
1099
  period: { type: 'string', description: 'Time period: 7d (default), 14d, or 30d' },
1025
- agentId: { type: 'string', description: 'Optional agent_id/session_id filter. Defaults to MARROW_AGENT_ID.' },
1100
+ agentId: { type: 'string', description: 'Optional agent_id/session_id filter. Defaults to the canonical fleet-bound agent identity.' },
1026
1101
  },
1027
1102
  required: [],
1028
1103
  },
@@ -1047,7 +1122,7 @@ if (process.argv[2] !== 'keys') {
1047
1122
  type: 'object',
1048
1123
  properties: {
1049
1124
  period: { type: 'string', description: 'Time period: 7d (default), 14d, 30d, or a day count up to 90.' },
1050
- agentId: { type: 'string', description: 'Optional agent_id/session_id filter. Defaults to MARROW_AGENT_ID.' },
1125
+ agentId: { type: 'string', description: 'Optional agent_id/session_id filter. Defaults to the canonical fleet-bound agent identity.' },
1051
1126
  },
1052
1127
  required: [],
1053
1128
  },
@@ -1063,7 +1138,7 @@ if (process.argv[2] !== 'keys') {
1063
1138
  action: { type: 'string', description: 'What the agent is about to do.' },
1064
1139
  type: { type: 'string', description: 'Decision type, e.g. deploy, audit, patch, review.' },
1065
1140
  role: { type: 'string', description: 'Agent role/playbook: deploy, audit, patch, review, or general.' },
1066
- agentId: { type: 'string', description: 'Optional agent_id filter. Defaults to MARROW_AGENT_ID.' },
1141
+ agentId: { type: 'string', description: 'Optional agent_id filter. Defaults to the canonical fleet-bound agent identity.' },
1067
1142
  sessionId: { type: 'string', description: 'Optional session id. Defaults to MARROW_SESSION_ID.' },
1068
1143
  surfaces: {
1069
1144
  type: 'array',
@@ -1085,7 +1160,7 @@ if (process.argv[2] !== 'keys') {
1085
1160
  action: { type: 'string', description: 'Optional action to test. Defaults to a production deploy safety prompt.' },
1086
1161
  type: { type: 'string', description: 'Decision type, e.g. deploy, audit, patch, review.' },
1087
1162
  role: { type: 'string', description: 'Agent role/playbook: deploy, audit, patch, review, or general.' },
1088
- agentId: { type: 'string', description: 'Optional agent_id filter. Defaults to MARROW_AGENT_ID.' },
1163
+ agentId: { type: 'string', description: 'Optional agent_id filter. Defaults to the canonical fleet-bound agent identity.' },
1089
1164
  sessionId: { type: 'string', description: 'Optional session id. Defaults to MARROW_SESSION_ID.' },
1090
1165
  surfaces: { type: 'array', items: { type: 'string' }, description: 'Surfaces to test, e.g. production, deploy, github, npm.' },
1091
1166
  context: { type: 'object', description: 'Optional non-sensitive metadata.' },
@@ -1105,7 +1180,7 @@ if (process.argv[2] !== 'keys') {
1105
1180
  action: { type: 'string', description: 'What the agent is about to do.' },
1106
1181
  type: { type: 'string', description: 'Decision type, e.g. deploy, audit, patch, review.' },
1107
1182
  role: { type: 'string', description: 'Agent role/playbook: deploy, audit, patch, review, or general.' },
1108
- agentId: { type: 'string', description: 'Optional agent_id filter. Defaults to MARROW_AGENT_ID.' },
1183
+ agentId: { type: 'string', description: 'Optional agent_id filter. Defaults to the canonical fleet-bound agent identity.' },
1109
1184
  sessionId: { type: 'string', description: 'Optional session id. Defaults to MARROW_SESSION_ID.' },
1110
1185
  surfaces: {
1111
1186
  type: 'array',
@@ -1165,7 +1240,7 @@ if (process.argv[2] !== 'keys') {
1165
1240
  },
1166
1241
  action: { type: 'string', description: 'Optional runtime action label.' },
1167
1242
  type: { type: 'string', description: 'Optional runtime action type. Defaults to coordination.' },
1168
- agentId: { type: 'string', description: 'Requesting agent id. Defaults to MARROW_AGENT_ID.' },
1243
+ agentId: { type: 'string', description: 'Requesting agent id. Defaults to the canonical fleet-bound agent identity.' },
1169
1244
  sessionId: { type: 'string', description: 'Optional workflow session id.' },
1170
1245
  surfaces: { type: 'array', items: { type: 'string' } },
1171
1246
  context: { type: 'object', description: 'Optional non-sensitive runtime metadata.' },
@@ -1278,7 +1353,7 @@ if (process.argv[2] !== 'keys') {
1278
1353
  inputSchema: {
1279
1354
  type: 'object',
1280
1355
  properties: {
1281
- agentId: { type: 'string', description: 'Optional agent filter. Defaults to MARROW_AGENT_ID.' },
1356
+ agentId: { type: 'string', description: 'Optional agent filter. Defaults to the canonical fleet-bound agent identity.' },
1282
1357
  limit: { type: 'number', description: 'Max events to return, default 25, max 100.' },
1283
1358
  },
1284
1359
  required: [],
@@ -1301,7 +1376,7 @@ if (process.argv[2] !== 'keys') {
1301
1376
  inputSchema: {
1302
1377
  type: 'object',
1303
1378
  properties: {
1304
- agentId: { type: 'string', description: 'Optional agent filter. Defaults to MARROW_AGENT_ID.' },
1379
+ agentId: { type: 'string', description: 'Optional agent filter. Defaults to the canonical fleet-bound agent identity.' },
1305
1380
  periodDays: { type: 'number', description: 'Lookback period in days, default 30, max 90.' },
1306
1381
  },
1307
1382
  required: [],
@@ -1390,7 +1465,7 @@ if (process.argv[2] !== 'keys') {
1390
1465
  type: 'object',
1391
1466
  properties: {
1392
1467
  period: { type: 'string', description: 'Time period: 7d (default), 14d, 30d, or day count up to 90.' },
1393
- agentId: { type: 'string', description: 'Optional agent_id/session_id filter. Defaults to MARROW_AGENT_ID.' },
1468
+ agentId: { type: 'string', description: 'Optional agent_id/session_id filter. Defaults to the canonical fleet-bound agent identity.' },
1394
1469
  },
1395
1470
  required: [],
1396
1471
  },
@@ -1523,6 +1598,24 @@ if (process.argv[2] !== 'keys') {
1523
1598
  },
1524
1599
  },
1525
1600
  ];
1601
+ const CORE_TOOL_NAMES = new Set([
1602
+ 'marrow_agent_runtime',
1603
+ 'marrow_commit',
1604
+ 'marrow_ask',
1605
+ 'marrow_status',
1606
+ 'marrow_auto',
1607
+ 'marrow_handoff_status',
1608
+ ]);
1609
+ function advertisedTools() {
1610
+ return process.env.MARROW_TOOL_PROFILE === 'full'
1611
+ ? TOOLS
1612
+ : TOOLS.filter((tool) => CORE_TOOL_NAMES.has(tool.name));
1613
+ }
1614
+ function toolAllowedByActiveProfile(toolName) {
1615
+ return process.env.MARROW_TOOL_PROFILE === 'full'
1616
+ ? Boolean(toolName && TOOLS.some((tool) => tool.name === toolName))
1617
+ : Boolean(toolName && CORE_TOOL_NAMES.has(toolName));
1618
+ }
1526
1619
  // Request handler
1527
1620
  async function handleRequest(req) {
1528
1621
  const { id, method, params } = req;
@@ -1537,7 +1630,7 @@ if (process.argv[2] !== 'keys') {
1537
1630
  success(id, {
1538
1631
  protocolVersion: '2024-11-05',
1539
1632
  capabilities: { tools: {}, prompts: {} },
1540
- serverInfo: { name: 'marrow', version: '3.9.56' },
1633
+ serverInfo: { name: 'marrow', version: hook_contract_1.MCP_ADAPTER_VERSION },
1541
1634
  });
1542
1635
  // Auto-enroll: emit enrollment notification on connection
1543
1636
  if (AUTO_ENROLL) {
@@ -1550,7 +1643,8 @@ if (process.argv[2] !== 'keys') {
1550
1643
  data: {
1551
1644
  type: 'auto_enroll',
1552
1645
  message: 'Marrow auto-enroll active. Call marrow_orient FIRST, then marrow_think before acting, marrow_commit after. Or use marrow_auto / marrow_run for one-call logging.',
1553
- agentId: AGENT_ID || 'auto',
1646
+ agentId: FLEET_AGENT_ID,
1647
+ client_update: (0, request_reliability_1.localClientUpdate)(),
1554
1648
  },
1555
1649
  },
1556
1650
  });
@@ -1623,29 +1717,21 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1623
1717
  return;
1624
1718
  }
1625
1719
  if (method === 'tools/list') {
1626
- success(id, { tools: TOOLS });
1720
+ success(id, { tools: advertisedTools() });
1627
1721
  return;
1628
1722
  }
1629
1723
  if (method === 'tools/call') {
1630
1724
  const toolName = params?.name;
1631
1725
  const args = (params?.arguments || {});
1726
+ if (!toolAllowedByActiveProfile(toolName)) {
1727
+ error(id, -32601, 'Tool is not available in the active Marrow tool profile. Set MARROW_TOOL_PROFILE=full and restart MCP for advanced operator tools.');
1728
+ return;
1729
+ }
1632
1730
  if (toolName === 'marrow_orient') {
1633
1731
  orientCallCount++;
1634
- let result;
1635
1732
  const wantAutoWarn = args.autoWarn ?? true;
1636
- try {
1637
- result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: wantAutoWarn }, SESSION_ID, FLEET_AGENT_ID);
1638
- }
1639
- catch (e) {
1640
- // autoWarn endpoint may not be deployed yet — fall back to legacy orient
1641
- if (wantAutoWarn) {
1642
- process.stderr.write(`[marrow] autoWarn orient not available, falling back to legacy\n`);
1643
- result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: false }, SESSION_ID, FLEET_AGENT_ID);
1644
- }
1645
- else {
1646
- throw e;
1647
- }
1648
- }
1733
+ const taskType = args.taskType;
1734
+ 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) });
1649
1735
  if (AUTO_ENROLL && orientCallCount === 1) {
1650
1736
  const enrollmentText = `\n\n**Marrow control and proof active**\n\n` +
1651
1737
  `Marrow applies relevant policy and prior lessons before consequential actions, then records evidence and the real outcome afterward.\n\n` +
@@ -1749,6 +1835,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1749
1835
  // [FIX #9] Validate required params
1750
1836
  const description = requireString(args, 'description');
1751
1837
  const outcome = requireString(args, 'outcome');
1838
+ const measuredSuccess = requireBoolean(args, 'success');
1752
1839
  // [FIX #16] Handle partial failures — return think result even if commit fails
1753
1840
  let thinkResult = null;
1754
1841
  try {
@@ -1766,18 +1853,12 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1766
1853
  try {
1767
1854
  commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1768
1855
  decision_id: thinkResult.decision_id,
1769
- success: args.success ?? true,
1856
+ success: measuredSuccess,
1770
1857
  outcome,
1771
- proof: {
1772
- summary: description,
1773
- checks: ['marrow_run completed'],
1774
- outcome,
1775
- blockers: (args.success ?? true) ? 'none' : 'see outcome',
1776
- commits_prs_shas: 'not applicable',
1777
- rollback_target: 'not applicable',
1778
- handoff_result_file: 'not applicable',
1779
- deployment_and_smoke: 'not applicable',
1780
- },
1858
+ proof: args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
1859
+ ? (0, redact_1.redactSensitiveValue)(args.proof)
1860
+ : undefined,
1861
+ gate_receipt_id: typeof args.gate_receipt_id === 'string' ? args.gate_receipt_id : undefined,
1781
1862
  action: description,
1782
1863
  type: args.type || 'general',
1783
1864
  }, SESSION_ID, FLEET_AGENT_ID);
@@ -1807,50 +1888,90 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1807
1888
  return;
1808
1889
  }
1809
1890
  if (toolName === 'marrow_auto') {
1810
- // [FIX #9] Validate required param
1811
1891
  const action = requireString(args, 'action');
1812
1892
  const outcome = args.outcome;
1813
- const outcomeSuccess = args.success ?? true;
1893
+ const outcomeSuccess = typeof args.success === 'boolean' ? args.success : undefined;
1814
1894
  const type = args.type || 'general';
1815
- // [FIX #8] Include pending flag so agent knows logging is deferred
1895
+ const highRisk = isHighRiskAction(action, type);
1896
+ const suppliedProof = args.proof && typeof args.proof === 'object' && !Array.isArray(args.proof)
1897
+ ? (0, redact_1.redactSensitiveValue)(args.proof)
1898
+ : undefined;
1899
+ let runtimeGate = null;
1900
+ if (highRisk) {
1901
+ runtimeGate = await withControlDeadline((signal) => (0, index_1.marrowAgentRuntime)(API_KEY, BASE_URL, {
1902
+ action: (0, redact_1.redactSensitiveText)(action),
1903
+ type,
1904
+ agent_id: FLEET_AGENT_ID,
1905
+ session_id: SESSION_ID,
1906
+ proof: suppliedProof,
1907
+ context: { source: 'mcp_auto_risk_upgrade' },
1908
+ }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: true });
1909
+ storeRuntimeGuidance(runtimeGate);
1910
+ }
1911
+ const proofCanClose = !highRisk || (0, runtime_contract_1.highRiskRuntimeCanClose)(runtimeGate, suppliedProof, args.gate_receipt_id);
1912
+ const acceptedSuccess = proofCanClose ? outcomeSuccess : undefined;
1913
+ const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
1914
+ apiKey: API_KEY,
1915
+ baseUrl: BASE_URL,
1916
+ deferDelivery: true,
1917
+ event: {
1918
+ event_type: !highRisk && acceptedSuccess === false ? 'tool_failed' : !highRisk && acceptedSuccess === true ? 'tool_completed' : 'goal_started',
1919
+ harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
1920
+ agent_id: FLEET_AGENT_ID,
1921
+ session_id: SESSION_ID,
1922
+ action,
1923
+ outcome_state: 'pending',
1924
+ success: highRisk ? undefined : acceptedSuccess,
1925
+ adapter_version: hook_contract_1.MCP_ADAPTER_VERSION,
1926
+ capability_level: 'mcp',
1927
+ },
1928
+ });
1816
1929
  const response = {
1817
1930
  action,
1818
1931
  outcome: outcome || 'pending',
1819
1932
  warnings: cachedOrientWarnings.map(formatWarningActionably),
1820
- logging: 'deferred',
1933
+ logging: 'durably_queued',
1934
+ receipt,
1935
+ completion_state: outcomeSuccess === undefined ? 'pending_evidence' : 'delivery_pending',
1936
+ client_update: (0, request_reliability_1.localClientUpdate)(),
1937
+ ...(runtimeGate ? {
1938
+ runtime_gate: runtimeGate,
1939
+ completion_state: proofCanClose ? 'governed_delivery_pending' : 'pending_required_proof',
1940
+ } : {}),
1821
1941
  };
1822
- // Fire-and-forget the actual API calls
1823
- (async () => {
1824
- try {
1825
- if (!outcome) {
1826
- await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1827
- }
1828
- else {
1829
- const thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1830
- await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1831
- decision_id: thinkResult.decision_id,
1832
- success: outcomeSuccess,
1833
- outcome,
1834
- proof: {
1835
- summary: action,
1836
- checks: ['marrow_auto completed'],
1837
- outcome,
1838
- blockers: outcomeSuccess ? 'none' : 'see outcome',
1839
- commits_prs_shas: 'not applicable',
1840
- rollback_target: 'not applicable',
1841
- handoff_result_file: 'not applicable',
1842
- deployment_and_smoke: 'not applicable',
1843
- },
1844
- action,
1845
- type,
1846
- }, SESSION_ID, FLEET_AGENT_ID);
1847
- }
1848
- }
1849
- catch (err) {
1850
- const msg = err instanceof Error ? err.message : String(err);
1851
- process.stderr.write(`[marrow] marrow_auto background logging failed: ${msg}\n`);
1942
+ const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
1943
+ action,
1944
+ outcome,
1945
+ success: acceptedSuccess,
1946
+ type,
1947
+ proof: suppliedProof,
1948
+ gate_receipt_id: typeof args.gate_receipt_id === 'string'
1949
+ ? args.gate_receipt_id
1950
+ : runtimeGate?.gate_receipt?.id,
1951
+ }, SESSION_ID, FLEET_AGENT_ID, 1_500);
1952
+ if (highRisk && acceptedSuccess !== undefined) {
1953
+ const delivered = await delivery();
1954
+ if (!delivered.committed) {
1955
+ throw new request_reliability_1.MarrowRequestError({
1956
+ code: 'invalid_response',
1957
+ message: 'Marrow did not confirm the governed outcome commit',
1958
+ retryable: true,
1959
+ exactFix: 'Keep the outcome pending and retry marrow_commit with the fresh gate receipt and required proof.',
1960
+ });
1852
1961
  }
1853
- })();
1962
+ response.logging = 'governed_commit_confirmed';
1963
+ response.completion_state = 'closed_with_proof';
1964
+ response.decision_id = delivered.decision_id;
1965
+ }
1966
+ else if (!highRisk) {
1967
+ void delivery().catch((err) => {
1968
+ const failure = (0, request_reliability_1.structuredRequestFailure)(err);
1969
+ const errorCode = failure.error && typeof failure.error === 'object'
1970
+ ? String(failure.error.code || 'request_failed')
1971
+ : 'request_failed';
1972
+ process.stderr.write(`[marrow] marrow_auto queued; live delivery unavailable (${errorCode})\n`);
1973
+ });
1974
+ }
1854
1975
  success(id, {
1855
1976
  content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
1856
1977
  });
@@ -1858,17 +1979,25 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1858
1979
  }
1859
1980
  if (toolName === 'marrow_ask') {
1860
1981
  const query = requireString(args, 'query');
1861
- const result = await (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID);
1982
+ const result = await withControlDeadline((signal) => (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID, signal));
1983
+ try {
1984
+ (0, guidance_cache_1.writeGuidanceCache)({
1985
+ apiKey: API_KEY,
1986
+ baseUrl: BASE_URL,
1987
+ agentId: FLEET_AGENT_ID,
1988
+ context: `## Marrow answer\n- ${String(result.answer || 'No relevant lesson found.').slice(0, 1200)}`,
1989
+ });
1990
+ }
1991
+ catch { /* owner-only cache is best effort */ }
1862
1992
  success(id, {
1863
1993
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1864
1994
  });
1865
1995
  return;
1866
1996
  }
1867
1997
  if (toolName === 'marrow_status') {
1868
- const result = await (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1869
- success(id, {
1870
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1871
- });
1998
+ const result = await withControlDeadline((signal) => (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false });
1999
+ const statusRecord = result;
2000
+ toolSuccess(id, { ...statusRecord, client_update: statusRecord.client_update || (0, request_reliability_1.localClientUpdate)() });
1872
2001
  return;
1873
2002
  }
1874
2003
  if (toolName === 'marrow_create_key') {
@@ -2001,17 +2130,17 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2001
2130
  return;
2002
2131
  }
2003
2132
  if (toolName === 'marrow_agent_status') {
2004
- const result = await (0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2133
+ const result = await (0, index_1.marrowAgentStatus)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2005
2134
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2006
2135
  return;
2007
2136
  }
2008
2137
  if (toolName === 'marrow_runtime_status') {
2009
- const result = await (0, index_1.marrowRuntimeStatus)(API_KEY, BASE_URL, args.fast !== false, SESSION_ID, FLEET_AGENT_ID);
2138
+ const result = await withControlDeadline((signal) => (0, index_1.marrowRuntimeStatus)(API_KEY, BASE_URL, args.fast !== false, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false });
2010
2139
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2011
2140
  return;
2012
2141
  }
2013
2142
  if (toolName === 'marrow_value_report') {
2014
- const result = await (0, index_1.marrowValueReport)(API_KEY, BASE_URL, args.period || '7d', args.agentId || AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2143
+ const result = await (0, index_1.marrowValueReport)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2015
2144
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2016
2145
  return;
2017
2146
  }
@@ -2020,7 +2149,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2020
2149
  action: args.action,
2021
2150
  type: args.type,
2022
2151
  role: args.role,
2023
- agent_id: args.agentId || AGENT_ID,
2152
+ agent_id: args.agentId || FLEET_AGENT_ID,
2024
2153
  session_id: args.sessionId || SESSION_ID,
2025
2154
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
2026
2155
  period: args.period,
@@ -2033,7 +2162,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2033
2162
  action: args.action ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
2034
2163
  type: args.type,
2035
2164
  role: args.role,
2036
- agent_id: args.agentId || AGENT_ID,
2165
+ agent_id: args.agentId || FLEET_AGENT_ID,
2037
2166
  session_id: args.sessionId || SESSION_ID,
2038
2167
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
2039
2168
  context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
@@ -2047,11 +2176,11 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2047
2176
  return;
2048
2177
  }
2049
2178
  if (toolName === 'marrow_agent_runtime') {
2050
- const result = await (0, index_1.marrowAgentRuntime)(API_KEY, BASE_URL, {
2179
+ const runtimeInput = {
2051
2180
  action: (0, redact_1.redactSensitiveText)(args.action),
2052
2181
  type: args.type,
2053
2182
  role: args.role,
2054
- agent_id: args.agentId || AGENT_ID,
2183
+ agent_id: args.agentId || FLEET_AGENT_ID,
2055
2184
  session_id: args.sessionId || SESSION_ID,
2056
2185
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
2057
2186
  context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
@@ -2061,7 +2190,9 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2061
2190
  ? (0, redact_1.redactSensitiveValue)(args.proof)
2062
2191
  : undefined,
2063
2192
  period: args.period,
2064
- }, SESSION_ID, FLEET_AGENT_ID);
2193
+ };
2194
+ 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) });
2195
+ storeRuntimeGuidance(result);
2065
2196
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2066
2197
  return;
2067
2198
  }
@@ -2075,7 +2206,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2075
2206
  : [],
2076
2207
  action: typeof args.action === 'string' ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
2077
2208
  type: args.type,
2078
- agent_id: args.agentId || AGENT_ID,
2209
+ agent_id: args.agentId || FLEET_AGENT_ID,
2079
2210
  session_id: args.sessionId || SESSION_ID,
2080
2211
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
2081
2212
  context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
@@ -2131,7 +2262,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2131
2262
  }
2132
2263
  if (toolName === 'marrow_governance_timeline') {
2133
2264
  const result = await (0, index_1.marrowGovernanceTimeline)(API_KEY, BASE_URL, {
2134
- agentId: args.agentId || AGENT_ID,
2265
+ agentId: args.agentId || FLEET_AGENT_ID,
2135
2266
  limit: args.limit,
2136
2267
  }, SESSION_ID, FLEET_AGENT_ID);
2137
2268
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2149,7 +2280,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2149
2280
  }
2150
2281
  if (toolName === 'marrow_buyer_proof') {
2151
2282
  const result = await (0, index_1.marrowBuyerProof)(API_KEY, BASE_URL, {
2152
- agentId: args.agentId || AGENT_ID,
2283
+ agentId: args.agentId || FLEET_AGENT_ID,
2153
2284
  periodDays: args.periodDays,
2154
2285
  }, SESSION_ID, FLEET_AGENT_ID);
2155
2286
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2165,7 +2296,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2165
2296
  : undefined,
2166
2297
  agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
2167
2298
  ? (0, redact_1.redactSensitiveValue)(args.agent)
2168
- : { id: FLEET_AGENT_ID || AGENT_ID },
2299
+ : { id: FLEET_AGENT_ID },
2169
2300
  selected_mode: args.selected_mode,
2170
2301
  selection_source: args.selection_source,
2171
2302
  }, SESSION_ID, FLEET_AGENT_ID);
@@ -2206,7 +2337,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2206
2337
  : undefined,
2207
2338
  agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
2208
2339
  ? (0, redact_1.redactSensitiveValue)(args.agent)
2209
- : { id: FLEET_AGENT_ID || AGENT_ID },
2340
+ : { id: FLEET_AGENT_ID },
2210
2341
  }, SESSION_ID, FLEET_AGENT_ID);
2211
2342
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2212
2343
  return;
@@ -2225,7 +2356,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2225
2356
  return;
2226
2357
  }
2227
2358
  if (toolName === 'marrow_agent_performance') {
2228
- const result = await (0, index_1.marrowAgentPerformance)(API_KEY, BASE_URL, args.period || '7d', args.agentId || AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2359
+ const result = await (0, index_1.marrowAgentPerformance)(API_KEY, BASE_URL, args.period || '7d', args.agentId || FLEET_AGENT_ID, SESSION_ID, FLEET_AGENT_ID);
2229
2360
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2230
2361
  return;
2231
2362
  }
@@ -2233,7 +2364,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2233
2364
  const result = await (0, index_1.marrowFleetLessons)(API_KEY, BASE_URL, {
2234
2365
  query: args.query,
2235
2366
  type: args.type,
2236
- agentId: args.agentId || AGENT_ID,
2367
+ agentId: args.agentId || FLEET_AGENT_ID,
2237
2368
  limit: args.limit,
2238
2369
  }, SESSION_ID, FLEET_AGENT_ID);
2239
2370
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2262,7 +2393,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2262
2393
  if (toolName === 'marrow_handoff_status') {
2263
2394
  const result = await (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
2264
2395
  status: args.status,
2265
- agentId: args.agentId || AGENT_ID,
2396
+ agentId: args.agentId || FLEET_AGENT_ID,
2266
2397
  limit: args.limit,
2267
2398
  }, SESSION_ID, FLEET_AGENT_ID);
2268
2399
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2308,8 +2439,12 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2308
2439
  error(id, -32601, `Method not found: ${method}`);
2309
2440
  }
2310
2441
  catch (err) {
2442
+ if (err instanceof request_reliability_1.MarrowRequestError && method === 'tools/call') {
2443
+ toolSuccess(id, toolFailure(params?.name, err), true);
2444
+ return;
2445
+ }
2311
2446
  const message = err instanceof Error ? err.message : String(err);
2312
- error(id, -32000, message);
2447
+ error(id, -32602, (0, redact_1.redactSensitiveText)(message).slice(0, 240));
2313
2448
  }
2314
2449
  }
2315
2450
  // MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)