@getmarrow/mcp 3.9.97 → 3.9.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -338
- package/dist/cli.js +249 -47
- package/dist/cli.js.map +1 -1
- package/dist/hook-context.d.ts.map +1 -1
- package/dist/hook-context.js +135 -9
- package/dist/hook-context.js.map +1 -1
- package/dist/hook-contract.d.ts +26 -5
- package/dist/hook-contract.d.ts.map +1 -1
- package/dist/hook-contract.js +144 -10
- package/dist/hook-contract.js.map +1 -1
- package/dist/hook-pre-action.d.ts +40 -1
- package/dist/hook-pre-action.d.ts.map +1 -1
- package/dist/hook-pre-action.js +429 -35
- package/dist/hook-pre-action.js.map +1 -1
- package/dist/hook-session.d.ts.map +1 -1
- package/dist/hook-session.js +32 -0
- package/dist/hook-session.js.map +1 -1
- package/dist/hook.d.ts +12 -0
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +149 -5
- package/dist/hook.js.map +1 -1
- package/dist/host-approval-state.d.ts +137 -0
- package/dist/host-approval-state.d.ts.map +1 -0
- package/dist/host-approval-state.js +510 -0
- package/dist/host-approval-state.js.map +1 -0
- package/dist/host-approval.d.ts +436 -0
- package/dist/host-approval.d.ts.map +1 -0
- package/dist/host-approval.js +1417 -0
- package/dist/host-approval.js.map +1 -0
- package/dist/host-session.d.ts +82 -0
- package/dist/host-session.d.ts.map +1 -0
- package/dist/host-session.js +570 -0
- package/dist/host-session.js.map +1 -0
- package/dist/index.d.ts +150 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +748 -33
- package/dist/index.js.map +1 -1
- package/dist/lifecycle-spool.d.ts.map +1 -1
- package/dist/lifecycle-spool.js +6 -5
- package/dist/lifecycle-spool.js.map +1 -1
- package/dist/normalized-action.d.ts +120 -0
- package/dist/normalized-action.d.ts.map +1 -0
- package/dist/normalized-action.js +1109 -0
- package/dist/normalized-action.js.map +1 -0
- package/dist/runtime-contract.d.ts +108 -4
- package/dist/runtime-contract.d.ts.map +1 -1
- package/dist/runtime-contract.js +178 -35
- package/dist/runtime-contract.js.map +1 -1
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +21 -25
- package/server.json +15 -4
package/dist/cli.js
CHANGED
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
* MARROW_API_KEY=mrw_abc123 npx @getmarrow/mcp
|
|
11
11
|
*/
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
14
|
const model_usage_1 = require("./model-usage");
|
|
14
15
|
const index_1 = require("./index");
|
|
15
16
|
const control_state_1 = require("./control-state");
|
|
16
17
|
const hook_1 = require("./hook");
|
|
18
|
+
const host_approval_1 = require("./host-approval");
|
|
17
19
|
const hook_contract_1 = require("./hook-contract");
|
|
18
20
|
const hook_context_1 = require("./hook-context");
|
|
19
21
|
const hook_session_1 = require("./hook-session");
|
|
@@ -32,7 +34,6 @@ const status_cache_1 = require("./status-cache");
|
|
|
32
34
|
const habit_loop_copy_1 = require("./habit-loop-copy");
|
|
33
35
|
const host_capability_1 = require("./host-capability");
|
|
34
36
|
const session_loop_guard_1 = require("./session-loop-guard");
|
|
35
|
-
// Parse CLI args
|
|
36
37
|
function parseArgs() {
|
|
37
38
|
const args = process.argv.slice(2);
|
|
38
39
|
const result = {};
|
|
@@ -47,7 +48,10 @@ function parseArgs() {
|
|
|
47
48
|
if (['hook', '--hook', 'claude-hook', 'cline-hook', 'codex-hook', 'cursor-hook', 'gemini-hook', 'grok-hook', 'windsurf-hook'].includes(args[i])) {
|
|
48
49
|
result.hook = true;
|
|
49
50
|
}
|
|
50
|
-
if (['
|
|
51
|
+
if (['permission-request-hook', 'claude-permission-request-hook'].includes(args[i])) {
|
|
52
|
+
result.permissionRequestHook = true;
|
|
53
|
+
}
|
|
54
|
+
if (['context-hook', '--context-hook', 'claude-context-hook', 'codex-context-hook', 'grok-context-hook', 'cursor-context-hook', 'gemini-context-hook'].includes(args[i])) {
|
|
51
55
|
result.contextHook = true;
|
|
52
56
|
}
|
|
53
57
|
if (['pre-action-hook', '--pre-action-hook', 'claude-pre-action-hook', 'cline-pre-action-hook', 'codex-pre-action-hook', 'cursor-pre-action-hook', 'gemini-pre-action-hook', 'grok-pre-action-hook', 'windsurf-pre-action-hook'].includes(args[i])) {
|
|
@@ -306,6 +310,13 @@ ${MARROW_BLOCK_END}`;
|
|
|
306
310
|
else {
|
|
307
311
|
process.stdout.write('PreToolUse hook configuration is present. Activity is client-self-reported and does not certify pre-action control.\n');
|
|
308
312
|
}
|
|
313
|
+
const permissionHookInstall = (0, hook_1.installPermissionRequestHook)(process.cwd());
|
|
314
|
+
if (permissionHookInstall.installed) {
|
|
315
|
+
process.stdout.write('Configured the pass-through PermissionRequest hook and the PostToolBatch hook (both async). They record when Claude Code shows its own permission dialog for a held action and whether the operator declined it; they never answer the dialog. Activity is client-self-reported.\n');
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
process.stdout.write('PermissionRequest and PostToolBatch hook configuration is present. Activity is client-self-reported.\n');
|
|
319
|
+
}
|
|
309
320
|
const sessionHookInstall = (0, hook_session_1.installSessionEndHook)(process.cwd());
|
|
310
321
|
if (sessionHookInstall.installed) {
|
|
311
322
|
process.stdout.write('Configured Stop hook. Activity is client-self-reported and does not certify session-end coverage.\n');
|
|
@@ -430,12 +441,16 @@ if (process.argv[2] !== 'keys') {
|
|
|
430
441
|
else if (cliArgs.backgroundNudge) {
|
|
431
442
|
void runBackgroundNudge();
|
|
432
443
|
}
|
|
444
|
+
else if (cliArgs.permissionRequestHook) {
|
|
445
|
+
// Pass-through: exits 0 with no output, so it never answers the dialog.
|
|
446
|
+
void (0, hook_1.runPermissionRequestHookCommand)().finally(() => process.exit(0));
|
|
447
|
+
}
|
|
433
448
|
else if (cliArgs.contextHook) {
|
|
434
449
|
void (0, hook_context_1.runContextHookCommand)();
|
|
435
450
|
}
|
|
436
451
|
else if (cliArgs.preActionHook) {
|
|
437
452
|
void (0, hook_pre_action_1.runPreActionHookCommand)().catch(() => {
|
|
438
|
-
process.stderr.write('Marrow pre-action
|
|
453
|
+
process.stderr.write('Marrow\'s pre-action check stopped on an internal error, so this action was held. Run `npx -y @getmarrow/install@latest doctor` in this terminal, then retry.\n');
|
|
439
454
|
process.exitCode = 2;
|
|
440
455
|
});
|
|
441
456
|
}
|
|
@@ -486,6 +501,92 @@ if (process.argv[2] !== 'keys') {
|
|
|
486
501
|
let thinkCallCount = 0;
|
|
487
502
|
let orientCallCount = 0;
|
|
488
503
|
let initialized = false;
|
|
504
|
+
// ---------------------------------------------------------------------------
|
|
505
|
+
// MCP elicitation: where the client can ask its user mid-task, a held
|
|
506
|
+
// marrow_auto action is approved in the client's own dialog (one click). The
|
|
507
|
+
// request goes to the client UI, never to the model; the answer is reported to
|
|
508
|
+
// Marrow as client-attested.
|
|
509
|
+
// ---------------------------------------------------------------------------
|
|
510
|
+
const ELICITATION_PROTOCOL_VERSIONS = ['2025-06-18', '2025-11-25'];
|
|
511
|
+
const ELICITATION_TIMEOUT_MS = 120_000;
|
|
512
|
+
let clientElicitation = false;
|
|
513
|
+
let clientHost = 'other';
|
|
514
|
+
let serverRequestSeq = 0;
|
|
515
|
+
const pendingClientRequests = new Map();
|
|
516
|
+
function noteClientCapabilities(params) {
|
|
517
|
+
const record = params && typeof params === 'object' ? params : {};
|
|
518
|
+
const capabilities = record.capabilities && typeof record.capabilities === 'object' ? record.capabilities : {};
|
|
519
|
+
const requested = typeof record.protocolVersion === 'string' ? record.protocolVersion : '';
|
|
520
|
+
const info = record.clientInfo && typeof record.clientInfo === 'object' ? record.clientInfo : {};
|
|
521
|
+
const name = typeof info.name === 'string' ? info.name.toLowerCase() : '';
|
|
522
|
+
clientHost = /claude/.test(name) ? 'claude-code' : /cursor/.test(name) ? 'cursor' : /hermes/.test(name) ? 'hermes'
|
|
523
|
+
: /codex/.test(name) ? 'codex' : /gemini/.test(name) ? 'gemini' : 'other';
|
|
524
|
+
clientElicitation = Boolean(capabilities.elicitation && typeof capabilities.elicitation === 'object')
|
|
525
|
+
&& ELICITATION_PROTOCOL_VERSIONS.includes(requested);
|
|
526
|
+
// Elicitation needs the protocol version that defines it; otherwise keep the version this server always spoke.
|
|
527
|
+
return clientElicitation ? requested : '2024-11-05';
|
|
528
|
+
}
|
|
529
|
+
function requestClient(method, params, timeoutMs) {
|
|
530
|
+
serverRequestSeq += 1;
|
|
531
|
+
const id = `marrow-server-${serverRequestSeq}`;
|
|
532
|
+
return new Promise((resolve) => {
|
|
533
|
+
const timer = setTimeout(() => {
|
|
534
|
+
pendingClientRequests.delete(id);
|
|
535
|
+
resolve({ error: { code: 'timeout' } });
|
|
536
|
+
}, timeoutMs);
|
|
537
|
+
timer.unref?.();
|
|
538
|
+
pendingClientRequests.set(id, { resolve, timer });
|
|
539
|
+
send({ jsonrpc: '2.0', id, method, params });
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
/** A response from the client to one of this server's requests (elicitation). */
|
|
543
|
+
function settleClientResponse(message) {
|
|
544
|
+
if (typeof message.id !== 'string' || message.method !== undefined)
|
|
545
|
+
return false;
|
|
546
|
+
const pending = pendingClientRequests.get(message.id);
|
|
547
|
+
if (!pending)
|
|
548
|
+
return false;
|
|
549
|
+
pendingClientRequests.delete(message.id);
|
|
550
|
+
clearTimeout(pending.timer);
|
|
551
|
+
pending.resolve({ result: message.result, error: message.error });
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
/** Asks the user in the client's own dialog; approved, declined, or null (cancelled, timed out, unsupported). */
|
|
555
|
+
async function elicitApproval(request) {
|
|
556
|
+
if (!clientElicitation)
|
|
557
|
+
return null;
|
|
558
|
+
const answer = await requestClient('elicitation/create', {
|
|
559
|
+
message: request.message,
|
|
560
|
+
requestedSchema: {
|
|
561
|
+
type: 'object',
|
|
562
|
+
properties: {
|
|
563
|
+
decision: {
|
|
564
|
+
type: 'string',
|
|
565
|
+
title: 'Your answer',
|
|
566
|
+
description: 'Approve only if you authorize this exact action.',
|
|
567
|
+
enum: ['approve', 'decline'],
|
|
568
|
+
enumNames: ['Approve this action', 'Decline it'],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
required: ['decision'],
|
|
572
|
+
},
|
|
573
|
+
}, ELICITATION_TIMEOUT_MS);
|
|
574
|
+
const result = answer.result && typeof answer.result === 'object' ? answer.result : null;
|
|
575
|
+
if (!result)
|
|
576
|
+
return null;
|
|
577
|
+
if (result.action === 'decline')
|
|
578
|
+
return 'declined';
|
|
579
|
+
if (result.action !== 'accept')
|
|
580
|
+
return null;
|
|
581
|
+
const content = result.content && typeof result.content === 'object' ? result.content : {};
|
|
582
|
+
return content.decision === 'approve' ? 'approved' : content.decision === 'decline' ? 'declined' : null;
|
|
583
|
+
}
|
|
584
|
+
function autoElicitationOptions() {
|
|
585
|
+
if (!clientElicitation)
|
|
586
|
+
return undefined;
|
|
587
|
+
const hostSessionId = typeof SESSION_ID === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/.test(SESSION_ID) ? SESSION_ID : `mcp-${process.pid}`;
|
|
588
|
+
return { elicitApproval, elicitHost: clientHost, elicitHostSessionId: hostSessionId };
|
|
589
|
+
}
|
|
489
590
|
function formatWarningActionably(w) {
|
|
490
591
|
const pct = Math.round(w.failureRate * 100);
|
|
491
592
|
return `⚠️ ${w.type} has ${pct}% failure rate — check what went wrong last time before proceeding`;
|
|
@@ -544,6 +645,37 @@ if (process.argv[2] !== 'keys') {
|
|
|
544
645
|
...(isError ? { isError: true } : {}),
|
|
545
646
|
});
|
|
546
647
|
}
|
|
648
|
+
/** Proof fields Marrow reported missing per decision or gate receipt, for a plain answer on a retry conflict. */
|
|
649
|
+
const commitProofMissing = new Map();
|
|
650
|
+
function proofFieldsGiven(proof) {
|
|
651
|
+
if (!proof || typeof proof !== 'object' || Array.isArray(proof))
|
|
652
|
+
return [];
|
|
653
|
+
return Object.entries(proof)
|
|
654
|
+
.filter(([, value]) => value !== null && value !== undefined && value !== ''
|
|
655
|
+
&& !(Array.isArray(value) && value.length === 0)
|
|
656
|
+
&& !(typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0))
|
|
657
|
+
.map(([key]) => key);
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* A retry of marrow_commit that added proof, refused as a conflict with the
|
|
661
|
+
* outcome Marrow already holds (409 MARROW_OUTCOME_OBSERVATION_CONFLICT):
|
|
662
|
+
* say plainly which proof fields are still missing.
|
|
663
|
+
*/
|
|
664
|
+
function proofRetryConflict(keys, proof, requiredFromHold) {
|
|
665
|
+
const required = [...new Set([...keys.flatMap((key) => commitProofMissing.get(key) || []), ...requiredFromHold])];
|
|
666
|
+
const given = proofFieldsGiven(proof);
|
|
667
|
+
const stillMissing = required.filter((field) => !given.includes(field));
|
|
668
|
+
return {
|
|
669
|
+
committed: false,
|
|
670
|
+
code: 'MARROW_OUTCOME_OBSERVATION_CONFLICT',
|
|
671
|
+
proof_required: true,
|
|
672
|
+
failure_kind: 'validation',
|
|
673
|
+
still_missing: stillMissing,
|
|
674
|
+
exact_next_action: stillMissing.length
|
|
675
|
+
? `Still missing: ${stillMissing.join(', ')}. Call marrow_commit again with the same decision_id and gate_receipt_id, and a proof that has every field you sent plus these. Fill them from what really happened; do not invent values.`
|
|
676
|
+
: 'Marrow already holds an outcome for this action with different proof. Call marrow_commit again with the same decision_id and gate_receipt_id and the complete proof in one call; do not invent values.',
|
|
677
|
+
};
|
|
678
|
+
}
|
|
547
679
|
function toolFailure(toolName, failure) {
|
|
548
680
|
const result = (0, request_reliability_1.structuredRequestFailure)(failure);
|
|
549
681
|
const proofValidation = failure.code === 'proof_required';
|
|
@@ -931,6 +1063,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
931
1063
|
description: 'Close a recorded action with success/failure, a specific outcome, and required proof. ' +
|
|
932
1064
|
'decision_id comes from marrow_think, marrow_auto, or a runtime that actually created a decision. ' +
|
|
933
1065
|
'Use the gate receipt from marrow_agent_runtime for consequential work. ' +
|
|
1066
|
+
'A held action closes trusted only after the server records its approval (the operator in the host\'s own prompt, or the account owner); never write or claim an approval in proof. ' +
|
|
934
1067
|
'The exact non-authorizing outcome_observation_only runtime correlation may submit an observed_unverified result, but is never sent as receipt evidence and never authorizes action or trusted learning. ' +
|
|
935
1068
|
'Only committed:true closes trusted outcome learning.',
|
|
936
1069
|
inputSchema: {
|
|
@@ -946,7 +1079,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
946
1079
|
},
|
|
947
1080
|
gate_receipt_id: { type: 'string', description: 'Canonical receipt id from marrow_agent_runtime.runtime_authorization.id for risky work.' },
|
|
948
1081
|
arbitration_receipt_id: { type: 'string', description: 'Required for arbitrated work: use marrow_arbitrate.arbitration.receipt_id from the same runtime response.' },
|
|
949
|
-
owner_approval_receipt_id: { type: 'string', description: 'Single-use owner approval receipt issued
|
|
1082
|
+
owner_approval_receipt_id: { type: 'string', description: 'Single-use owner approval receipt that Marrow issued for this hold. Optional for an ordinary held gate receipt (the gate_receipt_id alone finds the recorded approval); required for review_required arbitration. Chat text is not an approval receipt.' },
|
|
950
1083
|
action: { type: 'string', description: 'Optional exact original action for post-action observation lookup. This lookup cannot renew execution authority.' },
|
|
951
1084
|
target: { type: 'string', description: 'Exact original target, if supplied when the decision was created.' },
|
|
952
1085
|
type: { type: 'string', description: 'Exact original action type for observation lookup; defaults to general.' },
|
|
@@ -1007,7 +1140,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
1007
1140
|
},
|
|
1008
1141
|
{
|
|
1009
1142
|
name: 'marrow_auto',
|
|
1010
|
-
description: 'Durably capture activity with bounded core completion. Respect retry_after_ms and reuse operation_id for pending continuation.
|
|
1143
|
+
description: 'Durably capture activity with bounded core completion. Respect retry_after_ms and reuse operation_id for pending continuation. A held action waits for an approval the server records (the answer of the person in the dialog of this client when it supports elicitation; otherwise it waits quietly while you carry on with other work); auto reads its status and resumes on the same gate receipt. Never write or claim an approval yourself. Arbitration requires its server-issued owner approval receipt. Lifecycle receipt queued is separate from server acceptance. Risky completion requires a fresh gate and measured proof.',
|
|
1011
1144
|
inputSchema: {
|
|
1012
1145
|
type: 'object',
|
|
1013
1146
|
properties: {
|
|
@@ -1022,7 +1155,8 @@ if (process.argv[2] !== 'keys') {
|
|
|
1022
1155
|
proof: { type: 'object', description: 'Measured completion evidence for gated work.' },
|
|
1023
1156
|
gate_receipt_id: { type: 'string', description: 'Fresh receipt returned by marrow_agent_runtime.' },
|
|
1024
1157
|
arbitration_receipt_id: { type: 'string', description: 'For arbitrated work, the arbitration receipt returned by the same server-side review flow.' },
|
|
1025
|
-
owner_approval_receipt_id: { type: 'string', description: '
|
|
1158
|
+
owner_approval_receipt_id: { type: 'string', description: 'Single-use owner approval receipt that Marrow issued, for review_required arbitration. An ordinary hold needs none: auto finds the recorded approval from the gate receipt. Chat text is not an approval receipt.' },
|
|
1159
|
+
request_owner_link: { type: 'boolean', description: 'Set true only when the operator asked you to ask the account owner (for example after the owner declined this action). Marrow then sends the owner a one-tap approval link; you never see the link.' },
|
|
1026
1160
|
operation_id: {
|
|
1027
1161
|
type: 'string',
|
|
1028
1162
|
description: 'Opaque 8-80 character resume token. Reuse it only with the original tenant, action, context, and surfaces; proof may be added after proof_required.',
|
|
@@ -1367,6 +1501,7 @@ if (process.argv[2] !== 'keys') {
|
|
|
1367
1501
|
'template suggestion, required proof pack, before-you-act instruction, and exact next action. ' +
|
|
1368
1502
|
'Its runtime_authorization is the authoritative gate receipt; it returns decision_id only when runtime actually creates a decision. ' +
|
|
1369
1503
|
'Reuse its server-created decision_id for outcome closure; use marrow_auto or marrow_think only when the completion contract requires decision creation. ' +
|
|
1504
|
+
'A review_required hold does not permit the action: wait until the server records an approval, then commit with the same gate receipt. ' +
|
|
1370
1505
|
'Use this before meaningful work when you want Marrow to guide the whole action in one call.',
|
|
1371
1506
|
inputSchema: {
|
|
1372
1507
|
type: 'object',
|
|
@@ -2011,8 +2146,9 @@ if (process.argv[2] !== 'keys') {
|
|
|
2011
2146
|
if (method === 'initialize') {
|
|
2012
2147
|
initialized = true;
|
|
2013
2148
|
const hostCapability = mcpHostCapability();
|
|
2149
|
+
const protocolVersion = noteClientCapabilities(params);
|
|
2014
2150
|
success(id, {
|
|
2015
|
-
protocolVersion
|
|
2151
|
+
protocolVersion,
|
|
2016
2152
|
capabilities: { tools: {}, prompts: {} },
|
|
2017
2153
|
serverInfo: { name: 'marrow', version: hook_contract_2.MCP_ADAPTER_VERSION },
|
|
2018
2154
|
...(AUTO_ENROLL ? {
|
|
@@ -2196,22 +2332,72 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2196
2332
|
const decision_id = requireString(args, 'decision_id');
|
|
2197
2333
|
const outcome = requireString(args, 'outcome');
|
|
2198
2334
|
const commitSuccess = requireBoolean(args, 'success');
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2335
|
+
// A receipt a host hook held: commit in the session it was issued to,
|
|
2336
|
+
// and send the operator's queued answer first so the commit closes
|
|
2337
|
+
// with that approval.
|
|
2338
|
+
let commitSessionId = SESSION_ID;
|
|
2339
|
+
if (typeof args.gate_receipt_id === 'string' && args.gate_receipt_id) {
|
|
2340
|
+
const holdContext = {
|
|
2341
|
+
apiKey: API_KEY,
|
|
2342
|
+
baseUrl: BASE_URL,
|
|
2343
|
+
sessionId: SESSION_ID || 'mcp-server',
|
|
2344
|
+
agentId: FLEET_AGENT_ID,
|
|
2345
|
+
harness: 'mcp-client',
|
|
2346
|
+
host: 'other',
|
|
2347
|
+
hostSessionId: SESSION_ID || 'mcp-server',
|
|
2348
|
+
};
|
|
2349
|
+
commitSessionId = (0, host_approval_1.holdSessionForReceipt)(holdContext, args.gate_receipt_id) || SESSION_ID;
|
|
2350
|
+
await (0, host_approval_1.deliverQueuedForReceipt)(holdContext, args.gate_receipt_id).catch(() => undefined);
|
|
2351
|
+
}
|
|
2352
|
+
const proofKeys = [`decision:${decision_id}`, ...(typeof args.gate_receipt_id === 'string' && args.gate_receipt_id ? [`receipt:${args.gate_receipt_id}`] : [])];
|
|
2353
|
+
let result;
|
|
2354
|
+
try {
|
|
2355
|
+
result = await withControlDeadline((signal) => (0, index_1.marrowCommit)(API_KEY, BASE_URL, {
|
|
2356
|
+
decision_id,
|
|
2357
|
+
success: commitSuccess,
|
|
2358
|
+
outcome,
|
|
2359
|
+
caused_by: args.caused_by,
|
|
2360
|
+
proof: args.proof,
|
|
2361
|
+
gate_receipt_id: args.gate_receipt_id,
|
|
2362
|
+
arbitration_receipt_id: args.arbitration_receipt_id,
|
|
2363
|
+
owner_approval_receipt_id: args.owner_approval_receipt_id,
|
|
2364
|
+
action: args.action,
|
|
2365
|
+
target: args.target,
|
|
2366
|
+
type: args.type,
|
|
2367
|
+
surfaces: args.surfaces,
|
|
2368
|
+
auto_gate: args.auto_gate,
|
|
2369
|
+
model_usage: args.model_usage,
|
|
2370
|
+
}, commitSessionId, FLEET_AGENT_ID, signal), { highRisk: true, cacheAware: false, toolName: 'marrow_commit' });
|
|
2371
|
+
}
|
|
2372
|
+
catch (err) {
|
|
2373
|
+
if (err instanceof request_reliability_1.MarrowRequestError && err.code === 'proof_required' && err.missingFields.length) {
|
|
2374
|
+
for (const key of proofKeys)
|
|
2375
|
+
commitProofMissing.set(key, [...err.missingFields]);
|
|
2376
|
+
}
|
|
2377
|
+
if (err instanceof request_reliability_1.MarrowRequestError && err.status === 409 && err.backendCode === 'MARROW_OUTCOME_OBSERVATION_CONFLICT') {
|
|
2378
|
+
const fromHold = typeof args.gate_receipt_id === 'string' && args.gate_receipt_id
|
|
2379
|
+
? (0, host_approval_1.proofFieldsForReceipt)({ apiKey: API_KEY, baseUrl: BASE_URL, sessionId: SESSION_ID || 'mcp-server', agentId: FLEET_AGENT_ID, harness: 'mcp-client', host: 'other', hostSessionId: SESSION_ID || 'mcp-server' }, args.gate_receipt_id)
|
|
2380
|
+
: [];
|
|
2381
|
+
toolSuccess(id, proofRetryConflict(proofKeys, args.proof, fromHold), true);
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
throw err;
|
|
2385
|
+
}
|
|
2386
|
+
if (result.committed) {
|
|
2387
|
+
for (const key of proofKeys)
|
|
2388
|
+
commitProofMissing.delete(key);
|
|
2389
|
+
}
|
|
2390
|
+
else {
|
|
2391
|
+
// A short close is accepted unverified with the missing proof named ("Retry only after proof includes: a, b.").
|
|
2392
|
+
const unverified = result;
|
|
2393
|
+
const listed = unverified.governance_validation?.code === 'MARROW_PROOF_PACK_INCOMPLETE'
|
|
2394
|
+
? /proof includes: ([A-Za-z0-9_, ]{1,600})\.?\s*$/.exec(String(unverified.exact_next_action || ''))?.[1]
|
|
2395
|
+
: undefined;
|
|
2396
|
+
const missing = listed ? listed.split(',').map((field) => field.trim()).filter(Boolean) : [];
|
|
2397
|
+
if (missing.length)
|
|
2398
|
+
for (const key of proofKeys)
|
|
2399
|
+
commitProofMissing.set(key, missing);
|
|
2400
|
+
}
|
|
2215
2401
|
const commitResult = { ...result, narrative: result.narrative ?? null };
|
|
2216
2402
|
lastCommitted = result.committed;
|
|
2217
2403
|
lastDecisionId = result.committed ? null : decision_id;
|
|
@@ -2314,7 +2500,8 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2314
2500
|
// work obtains exactly one canonical runtime authorization inside auto.
|
|
2315
2501
|
auto_gate: highRisk,
|
|
2316
2502
|
operation_id: typeof args.operation_id === 'string' ? args.operation_id : undefined,
|
|
2317
|
-
|
|
2503
|
+
...(args.request_owner_link === true ? { request_owner_link: true } : {}),
|
|
2504
|
+
}, SESSION_ID, FLEET_AGENT_ID, 8_000, autoElicitationOptions());
|
|
2318
2505
|
let delivered = null;
|
|
2319
2506
|
let deliveryFailure = null;
|
|
2320
2507
|
let failedHttpAttemptTrace = null;
|
|
@@ -2337,7 +2524,10 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2337
2524
|
deferDelivery: true,
|
|
2338
2525
|
event: {
|
|
2339
2526
|
...(delivered?.operation_id ? {
|
|
2340
|
-
|
|
2527
|
+
// A closed operation has one record; each pending call is its own attempt.
|
|
2528
|
+
event_id: delivered.committed
|
|
2529
|
+
? `auto_closed_${delivered.operation_id}`
|
|
2530
|
+
: `auto_pending_${delivered.operation_id}_${(0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 12)}`,
|
|
2341
2531
|
} : {}),
|
|
2342
2532
|
event_type: delivered?.committed
|
|
2343
2533
|
? 'outcome_committed'
|
|
@@ -2369,17 +2559,23 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2369
2559
|
? 'intent_confirmed'
|
|
2370
2560
|
: 'durably_queued',
|
|
2371
2561
|
receipt,
|
|
2372
|
-
completion_state: delivered?.committed
|
|
2373
|
-
? '
|
|
2374
|
-
: delivered?.
|
|
2375
|
-
? '
|
|
2376
|
-
: delivered?.
|
|
2377
|
-
? '
|
|
2378
|
-
: delivered?.
|
|
2379
|
-
? '
|
|
2380
|
-
: delivered?.phase === '
|
|
2381
|
-
? '
|
|
2382
|
-
: '
|
|
2562
|
+
completion_state: delivered?.committed && delivered.closure === 'gate_denial'
|
|
2563
|
+
? 'closed_as_denial'
|
|
2564
|
+
: delivered?.closure === 'already_closed_denial'
|
|
2565
|
+
? 'closed_earlier_as_denial'
|
|
2566
|
+
: delivered?.closure === 'already_closed'
|
|
2567
|
+
? 'closed_earlier'
|
|
2568
|
+
: delivered?.committed
|
|
2569
|
+
? 'closed_with_proof'
|
|
2570
|
+
: delivered?.phase === 'review_required'
|
|
2571
|
+
? 'review_required_terminal'
|
|
2572
|
+
: delivered?.phase === 'owner_approval_required'
|
|
2573
|
+
? 'pending_owner_approval'
|
|
2574
|
+
: delivered?.phase === 'proof_required'
|
|
2575
|
+
? 'pending_required_proof'
|
|
2576
|
+
: delivered?.phase === 'decision_created' || outcomeSuccess === undefined
|
|
2577
|
+
? 'pending_evidence'
|
|
2578
|
+
: 'delivery_pending',
|
|
2383
2579
|
decision_id: delivered?.decision_id || null,
|
|
2384
2580
|
operation_id: delivered?.operation_id || (typeof args.operation_id === 'string' ? args.operation_id : null),
|
|
2385
2581
|
phase: delivered?.phase || null,
|
|
@@ -2390,17 +2586,19 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2390
2586
|
attempts: [],
|
|
2391
2587
|
dropped_count: 0,
|
|
2392
2588
|
},
|
|
2393
|
-
exact_next_action: delivered?.committed
|
|
2394
|
-
?
|
|
2395
|
-
: delivered?.
|
|
2396
|
-
?
|
|
2397
|
-
: delivered?.
|
|
2398
|
-
?
|
|
2399
|
-
: delivered?.phase === '
|
|
2400
|
-
? '
|
|
2401
|
-
: delivered?.
|
|
2402
|
-
? '
|
|
2403
|
-
:
|
|
2589
|
+
exact_next_action: delivered?.committed && delivered.closure === 'gate_denial' && delivered.exact_next_action
|
|
2590
|
+
? delivered.exact_next_action
|
|
2591
|
+
: delivered?.committed
|
|
2592
|
+
? 'The governed outcome is closed. Reuse this decision_id only for read-only trace inspection.'
|
|
2593
|
+
: delivered?.exact_next_action
|
|
2594
|
+
? delivered.exact_next_action
|
|
2595
|
+
: delivered?.phase === 'owner_approval_required'
|
|
2596
|
+
? 'The account owner must approve one proposal of this arbitration before it runs; Marrow then issues an owner_approval_receipt_id. Do not run any proposal or write an approval yourself. Once Marrow issues the receipt, call marrow_auto once with this same operation_id, arbitration_receipt_id and owner_approval_receipt_id.'
|
|
2597
|
+
: delivered?.phase === 'proof_required'
|
|
2598
|
+
? 'Attach the required measured proof and retry marrow_auto with this same operation_id and unchanged action, context, and surfaces.'
|
|
2599
|
+
: delivered?.resumable
|
|
2600
|
+
? 'Retry marrow_auto with this same operation_id. Do not start a new auto operation.'
|
|
2601
|
+
: 'Close this decision after the real outcome is known.',
|
|
2404
2602
|
live_delivery: {
|
|
2405
2603
|
accepted: Boolean(delivered?.decision_id),
|
|
2406
2604
|
committed: Boolean(delivered?.committed),
|
|
@@ -2408,6 +2606,7 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2408
2606
|
},
|
|
2409
2607
|
host_capability: mcpHostCapability(),
|
|
2410
2608
|
client_update: (0, request_reliability_1.localClientUpdate)(),
|
|
2609
|
+
...(delivered?.approval ? { approval: delivered.approval } : {}),
|
|
2411
2610
|
...(runtimeGate ? { runtime_gate: runtimeGate } : {}),
|
|
2412
2611
|
};
|
|
2413
2612
|
// Measure through response construction; stdout/host consumption occurs
|
|
@@ -2982,6 +3181,9 @@ Marrow is not a replacement agent or a standalone memory app. Context and prior
|
|
|
2982
3181
|
// MCP notifications (no id) must be silently ignored per spec
|
|
2983
3182
|
if (msg.id === undefined || msg.id === null)
|
|
2984
3183
|
continue;
|
|
3184
|
+
// The client's answer to this server's own request (elicitation).
|
|
3185
|
+
if (settleClientResponse(msg))
|
|
3186
|
+
continue;
|
|
2985
3187
|
pendingRequests++;
|
|
2986
3188
|
handleRequest(msg)
|
|
2987
3189
|
.catch((err) => {
|