@getmarrow/mcp 3.9.58 → 3.9.60

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 (46) hide show
  1. package/README.md +38 -9
  2. package/dist/cli.js +178 -96
  3. package/dist/cli.js.map +1 -1
  4. package/dist/control-path-state.d.ts +14 -0
  5. package/dist/control-path-state.d.ts.map +1 -0
  6. package/dist/control-path-state.js +46 -0
  7. package/dist/control-path-state.js.map +1 -0
  8. package/dist/env.js +1 -1
  9. package/dist/env.js.map +1 -1
  10. package/dist/hook-context.d.ts +1 -1
  11. package/dist/hook-context.d.ts.map +1 -1
  12. package/dist/hook-context.js +3 -3
  13. package/dist/hook-context.js.map +1 -1
  14. package/dist/hook-contract.d.ts +6 -6
  15. package/dist/hook-contract.d.ts.map +1 -1
  16. package/dist/hook-contract.js +6 -6
  17. package/dist/hook-contract.js.map +1 -1
  18. package/dist/hook-session.d.ts +1 -1
  19. package/dist/hook-session.d.ts.map +1 -1
  20. package/dist/hook.d.ts +1 -1
  21. package/dist/hook.d.ts.map +1 -1
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +18 -7
  25. package/dist/index.js.map +1 -1
  26. package/dist/lifecycle-spool.d.ts +9 -0
  27. package/dist/lifecycle-spool.d.ts.map +1 -1
  28. package/dist/lifecycle-spool.js +70 -9
  29. package/dist/lifecycle-spool.js.map +1 -1
  30. package/dist/ping-state.d.ts +1 -0
  31. package/dist/ping-state.d.ts.map +1 -1
  32. package/dist/ping-state.js +7 -0
  33. package/dist/ping-state.js.map +1 -1
  34. package/dist/request-reliability.d.ts +5 -1
  35. package/dist/request-reliability.d.ts.map +1 -1
  36. package/dist/request-reliability.js +44 -21
  37. package/dist/request-reliability.js.map +1 -1
  38. package/dist/runtime-contract.d.ts +2 -1
  39. package/dist/runtime-contract.d.ts.map +1 -1
  40. package/dist/runtime-contract.js +124 -2
  41. package/dist/runtime-contract.js.map +1 -1
  42. package/dist/types.d.ts +23 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/scripts/control-path-canary.cjs +7 -4
  46. package/server.json +2 -2
package/README.md CHANGED
@@ -57,7 +57,7 @@ Verify current claims through the [public evidence manifest](https://getmarrow.a
57
57
  ## Install
58
58
 
59
59
  ```bash
60
- npx @getmarrow/mcp setup
60
+ npx -y --package=@getmarrow/mcp@latest marrow-mcp setup
61
61
  ```
62
62
 
63
63
  Set the key through trusted secret storage:
@@ -75,7 +75,7 @@ Then configure the MCP server:
75
75
  "mcpServers": {
76
76
  "marrow": {
77
77
  "command": "npx",
78
- "args": ["-y", "@getmarrow/mcp"]
78
+ "args": ["-y", "--package=@getmarrow/mcp@latest", "marrow-mcp"]
79
79
  }
80
80
  }
81
81
  }
@@ -96,15 +96,44 @@ npx -y @getmarrow/install@latest activate
96
96
  npx -y @getmarrow/install@latest doctor
97
97
 
98
98
  # Manual MCP-only setup
99
- npx -y @getmarrow/mcp@latest setup
99
+ npx -y --package=@getmarrow/mcp@latest marrow-mcp setup
100
100
 
101
101
  # Verify live read latency, last success, and local backlog
102
- npx -y @getmarrow/mcp@latest ping
102
+ npx -y --package=@getmarrow/mcp@latest marrow-mcp ping
103
103
  ```
104
104
 
105
105
  Detection and notification are automatic. After explicit installer activation, the local controller may restore only Marrow-managed hooks/configuration. Package upgrades, owner policy, credentials, and unrelated configuration remain explicit and subject to the operator's normal change policy.
106
106
 
107
- ## What's New in v3.9.58
107
+ ## What's New in v3.9.60
108
+
109
+ v3.9.60 restores the complete control-and-proof loop for ordinary MCP clients:
110
+
111
+ - cached guidance no longer cuts live status, ask, runtime, or handoff reads down to an impossible 500 ms deadline;
112
+ - `marrow_auto` waits for the bounded think-and-commit path and reports the live decision and proof result in-band;
113
+ - `marrow_commit` now shares the same abort and deadline contract as the other control calls;
114
+ - transient retries use a one-second delay so a slow edge path is not immediately hit again;
115
+ - spool status surfaces backlogs under older credential namespaces without replaying them across an unverified tenant boundary;
116
+ - a plan-gated handoff is reported as unavailable for the current plan, not as an API or authentication outage;
117
+ - the release canary runs with the customer's default client deadlines instead of silently overriding them.
118
+
119
+ MCP-only hosts, including Grok today, receive Marrow's control instructions but cannot expose native always-on lifecycle hooks unless the host implements them. Marrow reports that capability boundary explicitly instead of claiming hook coverage it cannot observe.
120
+
121
+ ## Previous: v3.9.59
122
+
123
+ v3.9.59 makes the six-tool control path reliable and honest across ordinary edge and geographic latency:
124
+
125
+ - the default health deadline is 2.5 seconds instead of an unrealistically narrow 400 ms;
126
+ - `MARROW_PING_TIMEOUT_MS` can tune the probe between 500 ms and 5 seconds;
127
+ - authenticated control reads tolerate cold network/TLS paths while cached reads still return quickly;
128
+ - status, ask, runtime, and handoff responses report measured current/p50/p99 latency plus owner-only queue health;
129
+ - a first-session outage returns a clearly labeled local safety brief, and infrastructure failures are never mislabeled as policy denials;
130
+ - timeout errors return a concrete retry delay instead of an unresolved placeholder;
131
+ - Cloudflare edge denials are separated from key-scope or Marrow policy rejections;
132
+ - MCP initialization carries the always-on control/proof instructions even when a client does not request the optional prompt template;
133
+ - update, launch, setup, spool, and ping commands use the unambiguous `npx --package ... marrow-mcp` form;
134
+ - explicit spool drains tolerate slow edge delivery without extending passive hook latency.
135
+
136
+ ## Previous: v3.9.58
108
137
 
109
138
  v3.9.58 makes latency evidence accurate by reusing one initialized MCP process for the complete control-path canary:
110
139
 
@@ -133,7 +162,7 @@ v3.9.56 adds tenant-scoped coordination and evidence-only replay to the existing
133
162
  - transient failures can use an owner-only, account/key/agent-scoped last-known brief for at most one hour, clearly labeled with its age;
134
163
  - 401 and 403 responses never use cached guidance, and cached runtime guidance cannot authorize high-risk work;
135
164
  - `marrow_ask` now maps to the canonical decision brief contract instead of a separate route;
136
- - `npx -y @getmarrow/mcp@latest ping` reports current latency, rolling measured p50/p99, last success, and lifecycle backlog health;
165
+ - `npx -y --package=@getmarrow/mcp@latest marrow-mcp ping` reports current latency, rolling measured p50/p99, last success, and lifecycle backlog health;
137
166
  - `marrow_coordinate` acquires/releases tenant-scoped resource leases and carries compact child proof packets without sharing transcripts;
138
167
  - `marrow_replay_compare` compares already-recorded baseline and candidate outcomes with durable proof and never executes either model;
139
168
  - both new tools preserve agent-bound key scope, reject unsafe path identifiers, and return unavailable or incomplete evidence rather than manufacturing a winner.
@@ -316,15 +345,15 @@ it does not run models, retain prompts, or infer a winner from labels.
316
345
 
317
346
  ## Passive Use
318
347
 
319
- `npx @getmarrow/mcp setup` installs supported prompt, tool-result, and session-stop hooks so the agent can receive before-action context, record meaningful tool outcomes, and keep unfinished closure visible without the owner repeatedly prompting it to use Marrow.
348
+ `npx -y --package=@getmarrow/mcp@latest marrow-mcp setup` installs supported prompt, tool-result, and session-stop hooks so the agent can receive before-action context, record meaningful tool outcomes, and keep unfinished closure visible without the owner repeatedly prompting it to use Marrow.
320
349
 
321
350
  The hooks send compact classifications and lifecycle receipts. They do not need raw prompts, completions, command output, tool output, or credentials. A completed tool or session does not automatically become a successful business outcome; explicit success/failure closure is required.
322
351
 
323
352
  Transient lifecycle receipts use a bounded owner-only spool and are retried with stable event IDs. Operators can inspect and drain it without exposing event content:
324
353
 
325
354
  ```bash
326
- npx @getmarrow/mcp spool-status
327
- npx @getmarrow/mcp drain-spool
355
+ npx -y --package=@getmarrow/mcp@latest marrow-mcp spool-status
356
+ npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool
328
357
  ```
329
358
 
330
359
  The output contains only state, exact pending/failed counts, oldest receipt timestamps, capacity, and an exact fix. Terminal validation/authentication failures and exhausted retries remain explicit durable failures rather than cycling indefinitely.
package/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ const guidance_cache_1 = require("./guidance-cache");
21
21
  const request_reliability_1 = require("./request-reliability");
22
22
  const hook_contract_1 = require("./hook-contract");
23
23
  const ping_state_1 = require("./ping-state");
24
+ const control_path_state_1 = require("./control-path-state");
24
25
  const redact_1 = require("./redact");
25
26
  const runtime_contract_1 = require("./runtime-contract");
26
27
  // Parse CLI args
@@ -73,7 +74,7 @@ async function runPingCommand() {
73
74
  }
74
75
  const baseUrl = (0, index_1.validateBaseUrl)(resolved.baseUrl || 'https://api.getmarrow.ai');
75
76
  const controller = new AbortController();
76
- const timer = setTimeout(() => controller.abort(), 400);
77
+ const timer = setTimeout(() => controller.abort(), (0, ping_state_1.resolvePingTimeoutMs)(process.env.MARROW_PING_TIMEOUT_MS));
77
78
  timer.unref?.();
78
79
  const started = Date.now();
79
80
  try {
@@ -126,12 +127,17 @@ async function runSpoolCommand(drain) {
126
127
  const status = drain
127
128
  ? await (0, lifecycle_spool_1.drainLifecycleSpool)({ apiKey, baseUrl, agentId: resolved.agentId || undefined })
128
129
  : (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey, agentId: resolved.agentId || undefined });
130
+ const namespaceAttention = status.other_namespaces.state === 'attention_required';
129
131
  process.stdout.write(`${JSON.stringify({
130
- ok: status.state !== 'attention_required',
132
+ ok: drain
133
+ ? status.state === 'clear' && !namespaceAttention
134
+ : status.state !== 'attention_required' && !namespaceAttention,
131
135
  lifecycle_spool: status,
132
136
  }, null, 2)}\n`);
133
- if (status.state === 'attention_required')
137
+ if (status.state === 'attention_required' || namespaceAttention)
134
138
  process.exitCode = 2;
139
+ else if (drain && status.state !== 'clear')
140
+ process.exitCode = 1;
135
141
  }
136
142
  // ─── Setup command: inject Marrow instructions into CLAUDE.md ───
137
143
  function runSetup() {
@@ -441,27 +447,76 @@ if (process.argv[2] !== 'keys') {
441
447
  }
442
448
  function toolFailure(toolName, failure) {
443
449
  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) {
450
+ const infrastructureFailure = !['authentication_required', 'permission_denied'].includes(failure.code);
451
+ const supportsStale = ['marrow_agent_runtime', 'marrow_orient', 'marrow_ask', 'marrow_handoff_status', 'marrow_runtime_status', 'marrow_status'].includes(toolName || '');
452
+ const spool = (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey: API_KEY, agentId: FLEET_AGENT_ID });
453
+ result.failure_kind = infrastructureFailure ? 'infrastructure' : 'authorization';
454
+ result.control_path = (0, control_path_state_1.controlPathStats)(toolName || 'marrow_control');
455
+ result.lifecycle_spool = {
456
+ ...spool,
457
+ drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
458
+ };
459
+ result.host_capability = mcpHostCapability();
460
+ if (infrastructureFailure && supportsStale) {
447
461
  let cached = null;
448
462
  try {
449
463
  cached = (0, guidance_cache_1.readGuidanceCache)({ apiKey: API_KEY, baseUrl: BASE_URL, agentId: FLEET_AGENT_ID });
450
464
  }
451
465
  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;
466
+ result.stale_brief = cached?.context || [
467
+ '## Marrow control-path outage brief',
468
+ '- This is an infrastructure failure; Marrow returned no policy decision and did not deny the action.',
469
+ '- Preserve local evidence and continue only low-risk, reversible work under the owner\'s existing safeguards.',
470
+ '- Do not perform high-risk work until a fresh Marrow runtime gate returns allow, warn, review_required, or block.',
471
+ ].join('\n');
472
+ result.stale_source = cached ? 'last_known_guidance' : 'local_outage_safety';
473
+ result.stale_ms = cached?.stale_ms ?? null;
474
+ result.authorization_state = 'unavailable';
475
+ result.gate_obtained = false;
458
476
  result.stale_can_authorize_high_risk = false;
477
+ const retryAfterMs = failure.retryAfterMs;
459
478
  result.exact_next_action = cached
460
479
  ? '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.';
480
+ : retryAfterMs != null
481
+ ? `Wait ${retryAfterMs} ms, then retry once. Do not perform high-risk work without a fresh Marrow runtime gate.`
482
+ : failure.exactFix;
462
483
  }
463
484
  return result;
464
485
  }
486
+ function mcpHostCapability() {
487
+ const raw = String(process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || '').trim().toLowerCase();
488
+ const host = ['grok', 'claude-code', 'codex', 'cursor', 'windsurf', 'gemini', 'kimi', 'qwen', 'deepseek']
489
+ .includes(raw) ? raw : 'mcp-client';
490
+ return {
491
+ transport: 'mcp_stdio',
492
+ host,
493
+ tools_available: true,
494
+ tool_invocation: 'on_demand',
495
+ passive_hooks: {
496
+ provided_by_mcp_transport: false,
497
+ external_host_hook_state: 'unverified',
498
+ observed_by_this_process: false,
499
+ },
500
+ always_on_state: 'host_dependent',
501
+ exact_next_action: 'Use the Marrow MCP tools on demand. Claim passive coverage only when the host separately configures trusted hooks and Marrow receives their lifecycle receipts.',
502
+ };
503
+ }
504
+ function clientOperationalPayload(toolName, value) {
505
+ const payload = value && typeof value === 'object' && !Array.isArray(value)
506
+ ? value
507
+ : { data: value };
508
+ const spool = (0, lifecycle_spool_1.lifecycleSpoolStatus)({ apiKey: API_KEY, agentId: FLEET_AGENT_ID });
509
+ return {
510
+ ...payload,
511
+ host_capability: payload.host_capability || mcpHostCapability(),
512
+ client_update: payload.client_update || (0, request_reliability_1.localClientUpdate)(),
513
+ control_path: (0, control_path_state_1.controlPathStats)(toolName),
514
+ lifecycle_spool: {
515
+ ...spool,
516
+ drain_command: 'npx -y --package=@getmarrow/mcp@latest marrow-mcp drain-spool',
517
+ },
518
+ };
519
+ }
465
520
  // [FIX #9] Runtime validation helper for required string params
466
521
  function requireString(args, name) {
467
522
  const val = args[name];
@@ -480,25 +535,25 @@ if (process.argv[2] !== 'keys') {
480
535
  function isHighRiskAction(action, type) {
481
536
  return HIGH_RISK_ACTION.test(`${String(type || '')} ${String(action || '')}`);
482
537
  }
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
538
  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;
539
+ const toolName = options.toolName || 'marrow_control';
540
+ const configuredTimeoutMs = Number(process.env.MARROW_REQUEST_TIMEOUT_MS);
541
+ const runtimeBudget = toolName === 'marrow_agent_runtime' || toolName === 'marrow_auto.runtime';
542
+ const timeoutMs = Number.isFinite(configuredTimeoutMs)
543
+ ? Math.min(10_000, Math.max(150, Math.floor(configuredTimeoutMs)))
544
+ : options.highRisk || runtimeBudget ? 4_500 : 4_000;
497
545
  const controller = new AbortController();
498
546
  const timer = setTimeout(() => controller.abort(), timeoutMs);
499
547
  timer.unref?.();
548
+ const startedAt = Date.now();
500
549
  try {
501
- return await operation(controller.signal);
550
+ const result = await operation(controller.signal);
551
+ (0, control_path_state_1.recordControlPathSample)(toolName, Date.now() - startedAt, true);
552
+ return result;
553
+ }
554
+ catch (error) {
555
+ (0, control_path_state_1.recordControlPathSample)(toolName, Date.now() - startedAt, false);
556
+ throw error;
502
557
  }
503
558
  finally {
504
559
  clearTimeout(timer);
@@ -1631,6 +1686,9 @@ if (process.argv[2] !== 'keys') {
1631
1686
  protocolVersion: '2024-11-05',
1632
1687
  capabilities: { tools: {}, prompts: {} },
1633
1688
  serverInfo: { name: 'marrow', version: hook_contract_1.MCP_ADAPTER_VERSION },
1689
+ ...(AUTO_ENROLL ? {
1690
+ instructions: 'Use marrow_agent_runtime before consequential actions, obey fresh allow/warn/review_required/block results, use marrow_ask for relevant prior lessons, and close meaningful outcomes with marrow_commit. Infrastructure failures are not policy denials; continue only low-risk reversible work from the returned outage-safe brief, and require a fresh gate for high-risk work. MCP tools are on demand; do not claim passive or prompt-injection coverage unless the host separately configured trusted hooks and Marrow observed their receipts.',
1691
+ } : {}),
1634
1692
  });
1635
1693
  // Auto-enroll: emit enrollment notification on connection
1636
1694
  if (AUTO_ENROLL) {
@@ -1731,7 +1789,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1731
1789
  orientCallCount++;
1732
1790
  const wantAutoWarn = args.autoWarn ?? true;
1733
1791
  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) });
1792
+ 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' });
1735
1793
  if (AUTO_ENROLL && orientCallCount === 1) {
1736
1794
  const enrollmentText = `\n\n**Marrow control and proof active**\n\n` +
1737
1795
  `Marrow applies relevant policy and prior lessons before consequential actions, then records evidence and the real outcome afterward.\n\n` +
@@ -1798,12 +1856,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1798
1856
  // [FIX #9] Validate required params
1799
1857
  const decision_id = requireString(args, 'decision_id');
1800
1858
  const outcome = requireString(args, 'outcome');
1801
- if (typeof args.success !== 'boolean') {
1802
- throw new Error('"success" is required and must be a boolean');
1803
- }
1804
- const result = await (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1859
+ const commitSuccess = requireBoolean(args, 'success');
1860
+ const result = await withControlDeadline((signal) => (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
1805
1861
  decision_id,
1806
- success: args.success,
1862
+ success: commitSuccess,
1807
1863
  outcome,
1808
1864
  caused_by: args.caused_by,
1809
1865
  proof: args.proof,
@@ -1815,10 +1871,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1815
1871
  surfaces: args.surfaces,
1816
1872
  auto_gate: args.auto_gate,
1817
1873
  model_usage: args.model_usage,
1818
- }, SESSION_ID, FLEET_AGENT_ID);
1874
+ }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: true, cacheAware: false, toolName: 'marrow_commit' });
1819
1875
  const commitResult = { ...result, narrative: result.narrative ?? null };
1820
- lastCommitted = true;
1821
- lastDecisionId = null;
1876
+ lastCommitted = result.committed;
1877
+ lastDecisionId = result.committed ? null : decision_id;
1822
1878
  success(id, {
1823
1879
  content: [{ type: 'text', text: JSON.stringify(commitResult, null, 2) }],
1824
1880
  });
@@ -1905,23 +1961,48 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1905
1961
  session_id: SESSION_ID,
1906
1962
  proof: suppliedProof,
1907
1963
  context: { source: 'mcp_auto_risk_upgrade' },
1908
- }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: true });
1964
+ }, SESSION_ID, FLEET_AGENT_ID, signal), { highRisk: true, toolName: 'marrow_auto.runtime' });
1909
1965
  storeRuntimeGuidance(runtimeGate);
1910
1966
  }
1911
1967
  const proofCanClose = !highRisk || (0, runtime_contract_1.highRiskRuntimeCanClose)(runtimeGate, suppliedProof, args.gate_receipt_id);
1912
1968
  const acceptedSuccess = proofCanClose ? outcomeSuccess : undefined;
1969
+ const delivery = () => (0, index_1.marrowAuto)(API_KEY, BASE_URL, {
1970
+ action,
1971
+ outcome,
1972
+ success: acceptedSuccess,
1973
+ type,
1974
+ proof: suppliedProof,
1975
+ gate_receipt_id: typeof args.gate_receipt_id === 'string'
1976
+ ? args.gate_receipt_id
1977
+ : runtimeGate?.gate_receipt?.id,
1978
+ }, SESSION_ID, FLEET_AGENT_ID, 8_000);
1979
+ let delivered = null;
1980
+ let deliveryFailure = null;
1981
+ try {
1982
+ delivered = await delivery();
1983
+ }
1984
+ catch (err) {
1985
+ deliveryFailure = (0, request_reliability_1.structuredRequestFailure)(err);
1986
+ }
1913
1987
  const receipt = await (0, lifecycle_spool_1.recordLifecycleEvent)({
1914
1988
  apiKey: API_KEY,
1915
1989
  baseUrl: BASE_URL,
1916
- deferDelivery: true,
1990
+ deferDelivery: false,
1917
1991
  event: {
1918
- event_type: !highRisk && acceptedSuccess === false ? 'tool_failed' : !highRisk && acceptedSuccess === true ? 'tool_completed' : 'goal_started',
1992
+ event_type: delivered?.committed
1993
+ ? 'outcome_committed'
1994
+ : !highRisk && acceptedSuccess === false
1995
+ ? 'tool_failed'
1996
+ : !highRisk && acceptedSuccess === true
1997
+ ? 'tool_completed'
1998
+ : 'goal_started',
1919
1999
  harness: process.env.MARROW_CLIENT || process.env.MARROW_HARNESS || 'mcp',
1920
2000
  agent_id: FLEET_AGENT_ID,
1921
2001
  session_id: SESSION_ID,
2002
+ decision_id: delivered?.decision_id,
1922
2003
  action,
1923
- outcome_state: 'pending',
1924
- success: highRisk ? undefined : acceptedSuccess,
2004
+ outcome_state: delivered?.committed ? 'closed' : 'pending',
2005
+ success: delivered?.committed ? acceptedSuccess : highRisk ? undefined : acceptedSuccess,
1925
2006
  adapter_version: hook_contract_1.MCP_ADAPTER_VERSION,
1926
2007
  capability_level: 'mcp',
1927
2008
  },
@@ -1930,48 +2011,29 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1930
2011
  action,
1931
2012
  outcome: outcome || 'pending',
1932
2013
  warnings: cachedOrientWarnings.map(formatWarningActionably),
1933
- logging: 'durably_queued',
2014
+ logging: delivered?.committed
2015
+ ? 'governed_commit_confirmed'
2016
+ : delivered
2017
+ ? 'intent_confirmed'
2018
+ : 'durably_queued',
1934
2019
  receipt,
1935
- completion_state: outcomeSuccess === undefined ? 'pending_evidence' : 'delivery_pending',
2020
+ completion_state: delivered?.committed
2021
+ ? 'closed_with_proof'
2022
+ : !proofCanClose
2023
+ ? 'pending_required_proof'
2024
+ : outcomeSuccess === undefined
2025
+ ? 'pending_evidence'
2026
+ : 'delivery_pending',
2027
+ decision_id: delivered?.decision_id || null,
2028
+ live_delivery: {
2029
+ accepted: Boolean(delivered),
2030
+ committed: Boolean(delivered?.committed),
2031
+ ...(deliveryFailure ? { failure: deliveryFailure } : {}),
2032
+ },
2033
+ host_capability: mcpHostCapability(),
1936
2034
  client_update: (0, request_reliability_1.localClientUpdate)(),
1937
- ...(runtimeGate ? {
1938
- runtime_gate: runtimeGate,
1939
- completion_state: proofCanClose ? 'governed_delivery_pending' : 'pending_required_proof',
1940
- } : {}),
2035
+ ...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
1941
2036
  };
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
- });
1961
- }
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
- }
1975
2037
  success(id, {
1976
2038
  content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
1977
2039
  });
@@ -1979,7 +2041,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1979
2041
  }
1980
2042
  if (toolName === 'marrow_ask') {
1981
2043
  const query = requireString(args, 'query');
1982
- const result = await withControlDeadline((signal) => (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID, signal));
2044
+ const result = await withControlDeadline((signal) => (0, index_1.marrowAsk)(API_KEY, BASE_URL, { query }, SESSION_ID, FLEET_AGENT_ID, signal), { toolName: 'marrow_ask' });
1983
2045
  try {
1984
2046
  (0, guidance_cache_1.writeGuidanceCache)({
1985
2047
  apiKey: API_KEY,
@@ -1989,15 +2051,12 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
1989
2051
  });
1990
2052
  }
1991
2053
  catch { /* owner-only cache is best effort */ }
1992
- success(id, {
1993
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1994
- });
2054
+ toolSuccess(id, clientOperationalPayload('marrow_ask', result));
1995
2055
  return;
1996
2056
  }
1997
2057
  if (toolName === 'marrow_status') {
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)() });
2058
+ const result = await withControlDeadline((signal) => (0, index_1.marrowStatus)(API_KEY, BASE_URL, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_status' });
2059
+ toolSuccess(id, clientOperationalPayload('marrow_status', result));
2001
2060
  return;
2002
2061
  }
2003
2062
  if (toolName === 'marrow_create_key') {
@@ -2135,7 +2194,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2135
2194
  return;
2136
2195
  }
2137
2196
  if (toolName === 'marrow_runtime_status') {
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 });
2197
+ 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' });
2139
2198
  success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2140
2199
  return;
2141
2200
  }
@@ -2191,9 +2250,9 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2191
2250
  : undefined,
2192
2251
  period: args.period,
2193
2252
  };
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) });
2253
+ 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' });
2195
2254
  storeRuntimeGuidance(result);
2196
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2255
+ toolSuccess(id, clientOperationalPayload('marrow_agent_runtime', result));
2197
2256
  return;
2198
2257
  }
2199
2258
  if (toolName === 'marrow_arbitrate') {
@@ -2391,12 +2450,35 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
2391
2450
  return;
2392
2451
  }
2393
2452
  if (toolName === 'marrow_handoff_status') {
2394
- const result = await (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
2395
- status: args.status,
2396
- agentId: args.agentId || FLEET_AGENT_ID,
2397
- limit: args.limit,
2398
- }, SESSION_ID, FLEET_AGENT_ID);
2399
- success(id, { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] });
2453
+ try {
2454
+ const result = await withControlDeadline((signal) => (0, index_1.marrowHandoffStatus)(API_KEY, BASE_URL, {
2455
+ status: args.status,
2456
+ agentId: args.agentId || FLEET_AGENT_ID,
2457
+ limit: args.limit,
2458
+ }, SESSION_ID, FLEET_AGENT_ID, signal), { cacheAware: false, toolName: 'marrow_handoff_status' });
2459
+ toolSuccess(id, clientOperationalPayload('marrow_handoff_status', result));
2460
+ }
2461
+ catch (error) {
2462
+ if (error instanceof request_reliability_1.MarrowRequestError && error.backendCode === 'MARROW_PLAN_UPGRADE_REQUIRED') {
2463
+ toolSuccess(id, clientOperationalPayload('marrow_handoff_status', {
2464
+ ok: true,
2465
+ available: false,
2466
+ state: 'not_entitled',
2467
+ failure_kind: 'entitlement',
2468
+ authorization_state: 'plan_limited',
2469
+ credential_valid: true,
2470
+ feature: 'fleet_handoff_status',
2471
+ current_plan: error.currentPlan,
2472
+ required_plan: 'team',
2473
+ required_feature: error.requiredFeature || 'fleet_learning',
2474
+ service_reachable: true,
2475
+ exact_next_action: 'Use status, ask, runtime, auto, and commit on the current plan. Upgrade to Team before relying on fleet handoff status.',
2476
+ }));
2477
+ }
2478
+ else {
2479
+ throw error;
2480
+ }
2481
+ }
2400
2482
  return;
2401
2483
  }
2402
2484
  if (toolName === 'marrow_session_end') {