@getmarrow/install 0.1.62 → 0.1.64
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 +9 -1
- package/package.json +1 -1
- package/src/installer.js +75 -7
package/README.md
CHANGED
|
@@ -101,7 +101,15 @@ npx @getmarrow/install controller stop
|
|
|
101
101
|
|
|
102
102
|
Persistent controller lifecycle is currently Linux-only. On macOS or Windows, activation still writes supported configuration and verifies one server-side install self-test without certifying that hooks continuously ran; run `npx @getmarrow/install sidecar` under an owner-managed service and pass `--no-controller`. The controller does not silently upgrade packages, change governance policy, rotate credentials, or modify unrelated project configuration.
|
|
103
103
|
|
|
104
|
-
## What's New in v0.1.
|
|
104
|
+
## What's New in v0.1.64
|
|
105
|
+
|
|
106
|
+
v0.1.64 pins MCP `3.9.96` (source `031c944936271fd6e8768ad2619ee0430b68e2c7`, packed integrity `sha512-kuBpuWaWAvusS+FbXXFw1fOEGja4T8ahj/vkmXTd/CWjz0t6teT6g9c1HfJhAK6KLzesZtcvUdZLn6pBErKP9Q==`) while keeping SDK `3.7.64` unchanged. The embedded MCP pin requires this installer patch to deliver the native pre-action hook permit fix: protected actions are no longer denied after an allowing runtime gate, and unprotected actions stop at the gate without creating a decision or permit. Policy decisions, proof requirements and fail-closed behavior are unchanged. `activate` and `doctor --self-test` now resend the identical self-test decision and commit with a stable `Idempotency-Key` after a transient 429/502/503/504 or a durable pending acknowledgement (at most three attempts, about one second apart), instead of failing with "self-test did not return decision_id"; client errors still fail immediately, and a final failure names the last state. Restart the owning harness, review hook trust and run `doctor --self-test` after updating. Package publication and registry integrity verification must precede claiming these candidate pins are publicly available.
|
|
107
|
+
|
|
108
|
+
### Previous release: v0.1.63
|
|
109
|
+
|
|
110
|
+
v0.1.63 pins MCP `3.9.95` (source `967b17735b26534b7dc0536ec93c481a5dc07297`, packed integrity `sha512-Sw8RIyxHkjxllwwh+9za5ItoM9X0NwN4R8+Qdd7nvOuU6qAm9LotIC09GOJDim3o9jc9m8yTqxKciroRTS12EQ==`) while keeping SDK `3.7.64` unchanged. The embedded MCP pin requires this installer patch to deliver the stdin usage-loss correction and safe bounded native Codex capture. Supported transcript schema is Codex `0.157.1`; capture covers only the latest proven model-call delta. Unknown versions, missing model/turn or billing metadata, unsafe paths and unproven subagent bindings remain incomplete or unpriced. Capture does not prove a baseline, complete overhead or savings. Restart the owning harness and run `doctor --self-test` after updating. Package publication and registry integrity verification must precede claiming these candidate pins are publicly available.
|
|
111
|
+
|
|
112
|
+
### Previous release: v0.1.62
|
|
105
113
|
|
|
106
114
|
v0.1.62 delivers the model-cost capture corrections in MCP `3.9.94` (source `9456bc63de4cc92f16726d996695516be9395f26`, packed integrity `sha512-gEQejUWkcuKd1p930TLsWCSMM4rjmr/Wzz0++qa69B6hj/e64lMdT9xO+jZP8+yl4WLKgcfo0raHANllUe96/A==`) and SDK `3.7.64` (source `40b68dee609e9351fa6c79aee629fc88869a3b4f`, packed integrity `sha512-8qJj/8ouHEz1NnZkmujtFxUm/fWldqR/rHv62/sqabaxT0H90xCCHxodLOkV0SVxDscAtnahioZakqkeGNUwyA==`). Published installer0.1.61 pins older bytes and cannot deliver these fixes. Only observed usage with sufficient host, model, token and pricing evidence becomes calculated cost; unobservable host usage, unsupported streams and mixed cache TTL writes stay incomplete. No baseline or net savings is invented. Update once, restart the owning harness and run `doctor --self-test` to verify actual active versions.
|
|
107
115
|
|
package/package.json
CHANGED
package/src/installer.js
CHANGED
|
@@ -11,9 +11,9 @@ const { evidence: localControlEvidence } = require('./control-state');
|
|
|
11
11
|
const DEFAULT_BASE_URL = 'https://api.getmarrow.ai';
|
|
12
12
|
const MARROW_BLOCK_START = '<!-- marrow:passive-start -->';
|
|
13
13
|
const MARROW_BLOCK_END = '<!-- marrow:passive-end -->';
|
|
14
|
-
const MCP_ADAPTER_VERSION = '3.9.
|
|
15
|
-
const MCP_ADAPTER_SOURCE_SHA = '
|
|
16
|
-
const MCP_ADAPTER_INTEGRITY = 'sha512-
|
|
14
|
+
const MCP_ADAPTER_VERSION = '3.9.96';
|
|
15
|
+
const MCP_ADAPTER_SOURCE_SHA = '031c944936271fd6e8768ad2619ee0430b68e2c7';
|
|
16
|
+
const MCP_ADAPTER_INTEGRITY = 'sha512-kuBpuWaWAvusS+FbXXFw1fOEGja4T8ahj/vkmXTd/CWjz0t6teT6g9c1HfJhAK6KLzesZtcvUdZLn6pBErKP9Q==';
|
|
17
17
|
const SDK_ADAPTER_VERSION = '3.7.64';
|
|
18
18
|
const SDK_ADAPTER_INTEGRITY = 'sha512-8qJj/8ouHEz1NnZkmujtFxUm/fWldqR/rHv62/sqabaxT0H90xCCHxodLOkV0SVxDscAtnahioZakqkeGNUwyA==';
|
|
19
19
|
const SDK_ADAPTER_TARBALL = `https://registry.npmjs.org/@getmarrow/sdk/-/sdk-${SDK_ADAPTER_VERSION}.tgz`;
|
|
@@ -2684,6 +2684,60 @@ async function requestJson(url, options) {
|
|
|
2684
2684
|
return json.data || json;
|
|
2685
2685
|
}
|
|
2686
2686
|
|
|
2687
|
+
const SELF_TEST_WRITE_ATTEMPTS = 3;
|
|
2688
|
+
const SELF_TEST_RETRY_DELAY_MS = 1_000;
|
|
2689
|
+
const SELF_TEST_MAX_RETRY_DELAY_MS = 2_000;
|
|
2690
|
+
const SELF_TEST_TRANSIENT_STATUSES = new Set([429, 502, 503, 504]);
|
|
2691
|
+
|
|
2692
|
+
function selfTestRetryDelayMs(response, data, override) {
|
|
2693
|
+
if (Number.isFinite(override) && override >= 0) return override;
|
|
2694
|
+
const hinted = Number(data?.retry_after_ms);
|
|
2695
|
+
const header = Number(response?.headers?.get?.('retry-after')) * 1000;
|
|
2696
|
+
const delay = hinted > 0 ? hinted : header > 0 ? header : SELF_TEST_RETRY_DELAY_MS;
|
|
2697
|
+
return Math.min(delay, SELF_TEST_MAX_RETRY_DELAY_MS);
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
// A first write can meet a transient 5xx, or a durable "pending" acknowledgement
|
|
2701
|
+
// that has no decision id yet. Marrow's contract is to resend the identical request
|
|
2702
|
+
// with the same Idempotency-Key, so a retry can never create a second record.
|
|
2703
|
+
async function selfTestWrite(url, options, { idempotencyKey, complete, retryDelayMs }) {
|
|
2704
|
+
let lastState = 'no response';
|
|
2705
|
+
for (let attempt = 1; attempt <= SELF_TEST_WRITE_ATTEMPTS; attempt += 1) {
|
|
2706
|
+
const res = await fetch(url, { ...options, headers: { ...options.headers, 'idempotency-key': idempotencyKey } });
|
|
2707
|
+
const text = await res.text();
|
|
2708
|
+
let json = {};
|
|
2709
|
+
try {
|
|
2710
|
+
json = text ? JSON.parse(text) : {};
|
|
2711
|
+
} catch {
|
|
2712
|
+
json = { raw: text.slice(0, 500) };
|
|
2713
|
+
}
|
|
2714
|
+
const data = json.data || json;
|
|
2715
|
+
if (res.ok) {
|
|
2716
|
+
const result = complete(data);
|
|
2717
|
+
if (result !== undefined) return result;
|
|
2718
|
+
const pending = data?.retryable === true && data?.committed === false && data?.idempotency_key === idempotencyKey;
|
|
2719
|
+
if (!pending) return undefined;
|
|
2720
|
+
lastState = 'pending';
|
|
2721
|
+
} else if (SELF_TEST_TRANSIENT_STATUSES.has(res.status)) {
|
|
2722
|
+
const reason = String(json.error || json.message || '').slice(0, 200);
|
|
2723
|
+
lastState = reason ? `HTTP ${res.status}: ${reason}` : `HTTP ${res.status}`;
|
|
2724
|
+
} else {
|
|
2725
|
+
throw new Error(String(json.error || json.message || `HTTP ${res.status}`));
|
|
2726
|
+
}
|
|
2727
|
+
if (attempt < SELF_TEST_WRITE_ATTEMPTS) {
|
|
2728
|
+
await new Promise((resolve) => setTimeout(resolve, selfTestRetryDelayMs(res, data, retryDelayMs)));
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
return { failed: lastState };
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
function selfTestDecisionId(data) {
|
|
2735
|
+
const decisionId = data?.decision_id || data?.decisionId;
|
|
2736
|
+
if (typeof decisionId !== 'string' || !decisionId) return undefined;
|
|
2737
|
+
const pending = data.retryable === true && data.committed === false;
|
|
2738
|
+
return !pending || data.decision_state === 'created' ? decisionId : undefined;
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2687
2741
|
function runLoopGuardSelfTest(options = {}) {
|
|
2688
2742
|
const target = executableMcpTarget(options);
|
|
2689
2743
|
const isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), 'marrow-install-loop-guard-'));
|
|
@@ -2782,7 +2836,8 @@ async function runSelfTest(options) {
|
|
|
2782
2836
|
if (options.agentId) headers['x-marrow-agent-id'] = options.agentId;
|
|
2783
2837
|
|
|
2784
2838
|
const baseUrl = options.baseUrl.replace(/\/+$/, '');
|
|
2785
|
-
const
|
|
2839
|
+
const selfTestKey = `install-self-test:${crypto.randomUUID()}`;
|
|
2840
|
+
const think = await selfTestWrite(`${baseUrl}/v1/agent/think`, {
|
|
2786
2841
|
method: 'POST',
|
|
2787
2842
|
headers,
|
|
2788
2843
|
body: JSON.stringify({
|
|
@@ -2794,12 +2849,18 @@ async function runSelfTest(options) {
|
|
|
2794
2849
|
user_intent: 'operate',
|
|
2795
2850
|
},
|
|
2796
2851
|
}),
|
|
2852
|
+
}, {
|
|
2853
|
+
idempotencyKey: `${selfTestKey}:think`,
|
|
2854
|
+
complete: (data) => selfTestDecisionId(data),
|
|
2855
|
+
retryDelayMs: options.selfTestRetryDelayMs,
|
|
2797
2856
|
});
|
|
2798
2857
|
|
|
2799
|
-
const decisionId = think
|
|
2800
|
-
if (!decisionId)
|
|
2858
|
+
const decisionId = typeof think === 'string' ? think : undefined;
|
|
2859
|
+
if (!decisionId) {
|
|
2860
|
+
throw new Error(`self-test did not return decision_id${think?.failed ? ` after ${SELF_TEST_WRITE_ATTEMPTS} attempts (last: ${think.failed})` : ''}`);
|
|
2861
|
+
}
|
|
2801
2862
|
|
|
2802
|
-
await
|
|
2863
|
+
const commit = await selfTestWrite(`${baseUrl}/v1/agent/commit`, {
|
|
2803
2864
|
method: 'POST',
|
|
2804
2865
|
headers,
|
|
2805
2866
|
body: JSON.stringify({
|
|
@@ -2807,7 +2868,14 @@ async function runSelfTest(options) {
|
|
|
2807
2868
|
success: true,
|
|
2808
2869
|
outcome: 'Marrow passive installer self-test completed successfully',
|
|
2809
2870
|
}),
|
|
2871
|
+
}, {
|
|
2872
|
+
idempotencyKey: `${selfTestKey}:commit`,
|
|
2873
|
+
complete: (data) => (data?.retryable === true && data?.committed === false ? undefined : data),
|
|
2874
|
+
retryDelayMs: options.selfTestRetryDelayMs,
|
|
2810
2875
|
});
|
|
2876
|
+
if (commit?.failed) {
|
|
2877
|
+
throw new Error(`self-test commit did not complete after ${SELF_TEST_WRITE_ATTEMPTS} attempts (last: ${commit.failed})`);
|
|
2878
|
+
}
|
|
2811
2879
|
|
|
2812
2880
|
const status = await requestJson(`${baseUrl}/v1/agent/status`, { headers });
|
|
2813
2881
|
const context = await requestJson(`${baseUrl}/v1/agent/context`, { headers })
|