@getmarrow/mcp 3.9.55 → 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.' },
@@ -1174,6 +1249,75 @@ if (process.argv[2] !== 'keys') {
1174
1249
  required: ['objective', 'proposals'],
1175
1250
  },
1176
1251
  },
1252
+ {
1253
+ name: 'marrow_coordinate',
1254
+ description: 'Coordinate tenant agents without sharing transcripts. Acquire or release a bounded resource lease, ' +
1255
+ 'list current leases, or create/list compact evidence-backed child proof packets for a parent agent.',
1256
+ inputSchema: {
1257
+ type: 'object',
1258
+ properties: {
1259
+ action: {
1260
+ type: 'string',
1261
+ enum: ['list_leases', 'acquire_lease', 'release_lease', 'list_proof_packets', 'create_proof_packet'],
1262
+ },
1263
+ resource_type: { type: 'string', enum: ['file', 'directory', 'service', 'workflow', 'deployment', 'custom'] },
1264
+ resource: { type: 'string', maxLength: 160, description: 'Bounded tenant-visible resource label. Do not send secrets.' },
1265
+ workflow_id: { type: 'string' },
1266
+ ttl_seconds: { type: 'number', minimum: 30, maximum: 3600 },
1267
+ lease_id: { type: 'string' },
1268
+ lease_token: { type: 'string', description: 'One-time lease capability returned by acquire_lease.' },
1269
+ status: { type: 'string', enum: ['active', 'released', 'expired', 'incomplete', 'complete', 'failed'] },
1270
+ limit: { type: 'number', minimum: 1, maximum: 100 },
1271
+ decision_id: { type: 'string' },
1272
+ proof_pack_id: { type: 'string' },
1273
+ summary: { type: 'string', maxLength: 280, description: 'Compact result summary; no raw transcript.' },
1274
+ evidence_refs: {
1275
+ type: 'array', maxItems: 20, items: { type: 'string' },
1276
+ description: 'Opaque durable evidence identifiers only.',
1277
+ },
1278
+ },
1279
+ required: ['action'],
1280
+ },
1281
+ },
1282
+ {
1283
+ name: 'marrow_replay_compare',
1284
+ description: 'Compare two already-recorded outcomes for the same tenant task using durable proof. ' +
1285
+ 'This does not run a model or replay customer content; it returns complete or insufficient_evidence.',
1286
+ inputSchema: {
1287
+ type: 'object',
1288
+ properties: {
1289
+ comparison_id: { type: 'string', description: 'Fetch a prior replay comparison by id.' },
1290
+ source_decision_id: { type: 'string', description: 'Original task decision used to bind the comparison.' },
1291
+ workspace_binding_id: { type: 'string', description: 'Optional privacy-safe workspace binding from runtime.' },
1292
+ constraints: {
1293
+ type: 'object',
1294
+ additionalProperties: false,
1295
+ maxProperties: 7,
1296
+ description: 'Allowlisted comparison labels only; prompts, code, transcripts, credentials, and nested content are rejected.',
1297
+ properties: {
1298
+ environment: { type: 'string', maxLength: 80 },
1299
+ tests: { type: 'string', maxLength: 80 },
1300
+ policy_profile_id: { type: 'string', maxLength: 80 },
1301
+ workflow_type: { type: 'string', maxLength: 80 },
1302
+ task_type: { type: 'string', maxLength: 80 },
1303
+ required_proof: { type: 'boolean' },
1304
+ same_workspace: { type: 'boolean' },
1305
+ },
1306
+ },
1307
+ baseline: {
1308
+ type: 'object',
1309
+ properties: { label: { type: 'string' }, decision_id: { type: 'string' } },
1310
+ required: ['decision_id'],
1311
+ },
1312
+ candidate: {
1313
+ type: 'object',
1314
+ properties: { label: { type: 'string' }, decision_id: { type: 'string' } },
1315
+ required: ['decision_id'],
1316
+ },
1317
+ },
1318
+ required: [],
1319
+ },
1320
+ },
1177
1321
  {
1178
1322
  name: 'marrow_governance_control_plane',
1179
1323
  description: 'Return Marrow control-plane proof: governance, runtime gates, proof packs, fleet intelligence, supported harnesses, and exact next action.',
@@ -1209,7 +1353,7 @@ if (process.argv[2] !== 'keys') {
1209
1353
  inputSchema: {
1210
1354
  type: 'object',
1211
1355
  properties: {
1212
- 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.' },
1213
1357
  limit: { type: 'number', description: 'Max events to return, default 25, max 100.' },
1214
1358
  },
1215
1359
  required: [],
@@ -1232,7 +1376,7 @@ if (process.argv[2] !== 'keys') {
1232
1376
  inputSchema: {
1233
1377
  type: 'object',
1234
1378
  properties: {
1235
- 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.' },
1236
1380
  periodDays: { type: 'number', description: 'Lookback period in days, default 30, max 90.' },
1237
1381
  },
1238
1382
  required: [],
@@ -1321,7 +1465,7 @@ if (process.argv[2] !== 'keys') {
1321
1465
  type: 'object',
1322
1466
  properties: {
1323
1467
  period: { type: 'string', description: 'Time period: 7d (default), 14d, 30d, or day count up to 90.' },
1324
- 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.' },
1325
1469
  },
1326
1470
  required: [],
1327
1471
  },
@@ -1454,6 +1598,24 @@ if (process.argv[2] !== 'keys') {
1454
1598
  },
1455
1599
  },
1456
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
+ }
1457
1619
  // Request handler
1458
1620
  async function handleRequest(req) {
1459
1621
  const { id, method, params } = req;
@@ -1468,7 +1630,7 @@ if (process.argv[2] !== 'keys') {
1468
1630
  success(id, {
1469
1631
  protocolVersion: '2024-11-05',
1470
1632
  capabilities: { tools: {}, prompts: {} },
1471
- serverInfo: { name: 'marrow', version: '3.9.55' },
1633
+ serverInfo: { name: 'marrow', version: hook_contract_1.MCP_ADAPTER_VERSION },
1472
1634
  });
1473
1635
  // Auto-enroll: emit enrollment notification on connection
1474
1636
  if (AUTO_ENROLL) {
@@ -1481,7 +1643,8 @@ if (process.argv[2] !== 'keys') {
1481
1643
  data: {
1482
1644
  type: 'auto_enroll',
1483
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.',
1484
- agentId: AGENT_ID || 'auto',
1646
+ agentId: FLEET_AGENT_ID,
1647
+ client_update: (0, request_reliability_1.localClientUpdate)(),
1485
1648
  },
1486
1649
  },
1487
1650
  });
@@ -1554,29 +1717,21 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1554
1717
  return;
1555
1718
  }
1556
1719
  if (method === 'tools/list') {
1557
- success(id, { tools: TOOLS });
1720
+ success(id, { tools: advertisedTools() });
1558
1721
  return;
1559
1722
  }
1560
1723
  if (method === 'tools/call') {
1561
1724
  const toolName = params?.name;
1562
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
+ }
1563
1730
  if (toolName === 'marrow_orient') {
1564
1731
  orientCallCount++;
1565
- let result;
1566
1732
  const wantAutoWarn = args.autoWarn ?? true;
1567
- try {
1568
- result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: wantAutoWarn }, SESSION_ID, FLEET_AGENT_ID);
1569
- }
1570
- catch (e) {
1571
- // autoWarn endpoint may not be deployed yet — fall back to legacy orient
1572
- if (wantAutoWarn) {
1573
- process.stderr.write(`[marrow] autoWarn orient not available, falling back to legacy\n`);
1574
- result = await (0, index_1.marrowOrient)(API_KEY, BASE_URL, { taskType: args.taskType, autoWarn: false }, SESSION_ID, FLEET_AGENT_ID);
1575
- }
1576
- else {
1577
- throw e;
1578
- }
1579
- }
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) });
1580
1735
  if (AUTO_ENROLL && orientCallCount === 1) {
1581
1736
  const enrollmentText = `\n\n**Marrow control and proof active**\n\n` +
1582
1737
  `Marrow applies relevant policy and prior lessons before consequential actions, then records evidence and the real outcome afterward.\n\n` +
@@ -1680,6 +1835,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1680
1835
  // [FIX #9] Validate required params
1681
1836
  const description = requireString(args, 'description');
1682
1837
  const outcome = requireString(args, 'outcome');
1838
+ const measuredSuccess = requireBoolean(args, 'success');
1683
1839
  // [FIX #16] Handle partial failures — return think result even if commit fails
1684
1840
  let thinkResult = null;
1685
1841
  try {
@@ -1697,18 +1853,12 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1697
1853
  try {
1698
1854
  commitResult = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1699
1855
  decision_id: thinkResult.decision_id,
1700
- success: args.success ?? true,
1856
+ success: measuredSuccess,
1701
1857
  outcome,
1702
- proof: {
1703
- summary: description,
1704
- checks: ['marrow_run completed'],
1705
- outcome,
1706
- blockers: (args.success ?? true) ? 'none' : 'see outcome',
1707
- commits_prs_shas: 'not applicable',
1708
- rollback_target: 'not applicable',
1709
- handoff_result_file: 'not applicable',
1710
- deployment_and_smoke: 'not applicable',
1711
- },
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,
1712
1862
  action: description,
1713
1863
  type: args.type || 'general',
1714
1864
  }, SESSION_ID, FLEET_AGENT_ID);
@@ -1738,50 +1888,90 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1738
1888
  return;
1739
1889
  }
1740
1890
  if (toolName === 'marrow_auto') {
1741
- // [FIX #9] Validate required param
1742
1891
  const action = requireString(args, 'action');
1743
1892
  const outcome = args.outcome;
1744
- const outcomeSuccess = args.success ?? true;
1893
+ const outcomeSuccess = typeof args.success === 'boolean' ? args.success : undefined;
1745
1894
  const type = args.type || 'general';
1746
- // [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
+ });
1747
1929
  const response = {
1748
1930
  action,
1749
1931
  outcome: outcome || 'pending',
1750
1932
  warnings: cachedOrientWarnings.map(formatWarningActionably),
1751
- 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
+ } : {}),
1752
1941
  };
1753
- // Fire-and-forget the actual API calls
1754
- (async () => {
1755
- try {
1756
- if (!outcome) {
1757
- await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1758
- }
1759
- else {
1760
- const thinkResult = await (0, index_1.marrowThink)(API_KEY, BASE_URL, { action, type }, SESSION_ID, FLEET_AGENT_ID);
1761
- await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1762
- decision_id: thinkResult.decision_id,
1763
- success: outcomeSuccess,
1764
- outcome,
1765
- proof: {
1766
- summary: action,
1767
- checks: ['marrow_auto completed'],
1768
- outcome,
1769
- blockers: outcomeSuccess ? 'none' : 'see outcome',
1770
- commits_prs_shas: 'not applicable',
1771
- rollback_target: 'not applicable',
1772
- handoff_result_file: 'not applicable',
1773
- deployment_and_smoke: 'not applicable',
1774
- },
1775
- action,
1776
- type,
1777
- }, SESSION_ID, FLEET_AGENT_ID);
1778
- }
1779
- }
1780
- catch (err) {
1781
- const msg = err instanceof Error ? err.message : String(err);
1782
- 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
+ });
1783
1961
  }
1784
- })();
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
+ }
1785
1975
  success(id, {
1786
1976
  content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
1787
1977
  });
@@ -1789,17 +1979,25 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1789
1979
  }
1790
1980
  if (toolName === 'marrow_ask') {
1791
1981
  const query = requireString(args, 'query');
1792
- 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 */ }
1793
1992
  success(id, {
1794
1993
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1795
1994
  });
1796
1995
  return;
1797
1996
  }
1798
1997
  if (toolName === 'marrow_status') {
1799
- const result = await (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
1800
- success(id, {
1801
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1802
- });
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)() });
1803
2001
  return;
1804
2002
  }
1805
2003
  if (toolName === 'marrow_create_key') {
@@ -1932,17 +2130,17 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1932
2130
  return;
1933
2131
  }
1934
2132
  if (toolName === 'marrow_agent_status') {
1935
- 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);
1936
2134
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1937
2135
  return;
1938
2136
  }
1939
2137
  if (toolName === 'marrow_runtime_status') {
1940
- 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 });
1941
2139
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1942
2140
  return;
1943
2141
  }
1944
2142
  if (toolName === 'marrow_value_report') {
1945
- 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);
1946
2144
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1947
2145
  return;
1948
2146
  }
@@ -1951,7 +2149,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1951
2149
  action: args.action,
1952
2150
  type: args.type,
1953
2151
  role: args.role,
1954
- agent_id: args.agentId || AGENT_ID,
2152
+ agent_id: args.agentId || FLEET_AGENT_ID,
1955
2153
  session_id: args.sessionId || SESSION_ID,
1956
2154
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
1957
2155
  period: args.period,
@@ -1964,7 +2162,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1964
2162
  action: args.action ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
1965
2163
  type: args.type,
1966
2164
  role: args.role,
1967
- agent_id: args.agentId || AGENT_ID,
2165
+ agent_id: args.agentId || FLEET_AGENT_ID,
1968
2166
  session_id: args.sessionId || SESSION_ID,
1969
2167
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
1970
2168
  context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
@@ -1978,11 +2176,11 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1978
2176
  return;
1979
2177
  }
1980
2178
  if (toolName === 'marrow_agent_runtime') {
1981
- const result = await (0, index_1.marrowAgentRuntime)(API_KEY, BASE_URL, {
2179
+ const runtimeInput = {
1982
2180
  action: (0, redact_1.redactSensitiveText)(args.action),
1983
2181
  type: args.type,
1984
2182
  role: args.role,
1985
- agent_id: args.agentId || AGENT_ID,
2183
+ agent_id: args.agentId || FLEET_AGENT_ID,
1986
2184
  session_id: args.sessionId || SESSION_ID,
1987
2185
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
1988
2186
  context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
@@ -1992,7 +2190,9 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1992
2190
  ? (0, redact_1.redactSensitiveValue)(args.proof)
1993
2191
  : undefined,
1994
2192
  period: args.period,
1995
- }, 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);
1996
2196
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
1997
2197
  return;
1998
2198
  }
@@ -2006,7 +2206,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2006
2206
  : [],
2007
2207
  action: typeof args.action === 'string' ? (0, redact_1.redactSensitiveText)(args.action) : undefined,
2008
2208
  type: args.type,
2009
- agent_id: args.agentId || AGENT_ID,
2209
+ agent_id: args.agentId || FLEET_AGENT_ID,
2010
2210
  session_id: args.sessionId || SESSION_ID,
2011
2211
  surfaces: Array.isArray(args.surfaces) ? args.surfaces : undefined,
2012
2212
  context: args.context && typeof args.context === 'object' && !Array.isArray(args.context)
@@ -2019,6 +2219,19 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2019
2219
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2020
2220
  return;
2021
2221
  }
2222
+ if (toolName === 'marrow_coordinate') {
2223
+ const coordinationArgs = { ...args };
2224
+ delete coordinationArgs.agent_id;
2225
+ delete coordinationArgs.source_agent_id;
2226
+ const result = await (0, index_1.marrowCoordinate)(API_KEY, BASE_URL, coordinationArgs, SESSION_ID, FLEET_AGENT_ID);
2227
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2228
+ return;
2229
+ }
2230
+ if (toolName === 'marrow_replay_compare') {
2231
+ const result = await (0, index_1.marrowReplayCompare)(API_KEY, BASE_URL, args, SESSION_ID, FLEET_AGENT_ID);
2232
+ success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2233
+ return;
2234
+ }
2022
2235
  if (toolName === 'marrow_governance_control_plane') {
2023
2236
  const result = await (0, index_1.marrowGovernanceControlPlane)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID);
2024
2237
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2049,7 +2262,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2049
2262
  }
2050
2263
  if (toolName === 'marrow_governance_timeline') {
2051
2264
  const result = await (0, index_1.marrowGovernanceTimeline)(API_KEY, BASE_URL, {
2052
- agentId: args.agentId || AGENT_ID,
2265
+ agentId: args.agentId || FLEET_AGENT_ID,
2053
2266
  limit: args.limit,
2054
2267
  }, SESSION_ID, FLEET_AGENT_ID);
2055
2268
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2067,7 +2280,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2067
2280
  }
2068
2281
  if (toolName === 'marrow_buyer_proof') {
2069
2282
  const result = await (0, index_1.marrowBuyerProof)(API_KEY, BASE_URL, {
2070
- agentId: args.agentId || AGENT_ID,
2283
+ agentId: args.agentId || FLEET_AGENT_ID,
2071
2284
  periodDays: args.periodDays,
2072
2285
  }, SESSION_ID, FLEET_AGENT_ID);
2073
2286
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2083,7 +2296,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2083
2296
  : undefined,
2084
2297
  agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
2085
2298
  ? (0, redact_1.redactSensitiveValue)(args.agent)
2086
- : { id: FLEET_AGENT_ID || AGENT_ID },
2299
+ : { id: FLEET_AGENT_ID },
2087
2300
  selected_mode: args.selected_mode,
2088
2301
  selection_source: args.selection_source,
2089
2302
  }, SESSION_ID, FLEET_AGENT_ID);
@@ -2124,7 +2337,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2124
2337
  : undefined,
2125
2338
  agent: args.agent && typeof args.agent === 'object' && !Array.isArray(args.agent)
2126
2339
  ? (0, redact_1.redactSensitiveValue)(args.agent)
2127
- : { id: FLEET_AGENT_ID || AGENT_ID },
2340
+ : { id: FLEET_AGENT_ID },
2128
2341
  }, SESSION_ID, FLEET_AGENT_ID);
2129
2342
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2130
2343
  return;
@@ -2143,7 +2356,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2143
2356
  return;
2144
2357
  }
2145
2358
  if (toolName === 'marrow_agent_performance') {
2146
- 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);
2147
2360
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2148
2361
  return;
2149
2362
  }
@@ -2151,7 +2364,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2151
2364
  const result = await (0, index_1.marrowFleetLessons)(API_KEY, BASE_URL, {
2152
2365
  query: args.query,
2153
2366
  type: args.type,
2154
- agentId: args.agentId || AGENT_ID,
2367
+ agentId: args.agentId || FLEET_AGENT_ID,
2155
2368
  limit: args.limit,
2156
2369
  }, SESSION_ID, FLEET_AGENT_ID);
2157
2370
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2180,7 +2393,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2180
2393
  if (toolName === 'marrow_handoff_status') {
2181
2394
  const result = await (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
2182
2395
  status: args.status,
2183
- agentId: args.agentId || AGENT_ID,
2396
+ agentId: args.agentId || FLEET_AGENT_ID,
2184
2397
  limit: args.limit,
2185
2398
  }, SESSION_ID, FLEET_AGENT_ID);
2186
2399
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
@@ -2226,8 +2439,12 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2226
2439
  error(id, -32601, `Method not found: ${method}`);
2227
2440
  }
2228
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
+ }
2229
2446
  const message = err instanceof Error ? err.message : String(err);
2230
- error(id, -32000, message);
2447
+ error(id, -32602, (0, redact_1.redactSensitiveText)(message).slice(0, 240));
2231
2448
  }
2232
2449
  }
2233
2450
  // MCP stdio loop — raw stdin, no readline (readline writes prompts to stdout which breaks MCP)