deadbyte-mcp 0.15.23 → 0.15.24
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/MANIFEST.SHA256 +34 -28
- package/PROVENANCE.json +11 -10
- package/README.txt +7 -7
- package/RELEASE.json +19 -38
- package/TEST-MATRIX.json +32 -96
- package/package.json +2 -2
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +111 -0
- package/scripts/commander-real-performance-benchmark-r47.mjs +12 -3
- package/src/atomic-mutation.mjs +12 -4
- package/src/coding-mcp-tools.mjs +15 -1
- package/src/coding-output-contracts.mjs +9 -3
- package/src/commander-office-reader-r48.mjs +36 -3
- package/src/commander-pdf-r48.mjs +36 -1
- package/src/commander-wire-adapter-r47.mjs +56 -2
- package/src/host-plane.mjs +10 -31
- package/src/machine-fs-mcp-tools.mjs +7 -1
- package/src/machine-fs-output-contracts.mjs +6 -0
- package/src/mcp-server.mjs +14 -2
- package/src/output-contracts.mjs +6 -4
- package/src/stdio-protocol-guard.mjs +71 -0
- package/src/version.mjs +1 -1
- package/test/atomic-mutation.test.mjs +61 -0
- package/test/coding-mcp-unknown-effect.test.mjs +42 -0
- package/test/commander-biff-reference-r48.test.mjs +38 -0
- package/test/commander-core-bridge-r47.test.mjs +19 -0
- package/test/commander-office-reader-r48.test.mjs +18 -2
- package/test/commander-pdf-r48.test.mjs +35 -0
- package/test/commander-protocol-adversarial-r48.test.mjs +166 -0
- package/test/commander-real-parity-r47.test.mjs +37 -3
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/commander-wire-contract-r47.test.mjs +32 -0
- package/test/machine-fs-mcp-tools.test.mjs +12 -0
- package/test/output-schema.test.mjs +20 -1
- package/test/r48-release-contract.test.mjs +27 -27
- package/test/tunnel-stdio-compat.test.mjs +3 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "deadbyte.commander-differential-checkpoint.v1",
|
|
3
|
+
"status": "INCOMPLETE",
|
|
4
|
+
"observed_at_utc": "2026-09-28T15:19:36.8198652Z",
|
|
5
|
+
"source": {
|
|
6
|
+
"requested_path": "D:\\Projects\\deadbyte-mcp-r36-dev",
|
|
7
|
+
"requested_path_version": "0.12.0",
|
|
8
|
+
"actual_successor_workspace": "D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-r42-agentic-dev",
|
|
9
|
+
"version": "0.15.24",
|
|
10
|
+
"git_head": "f9037d4847a7204ce4e1f808758ee3f0e9e7a738",
|
|
11
|
+
"path_drift_classification": "requested source path is a historical snapshot and cannot produce a successor of active 0.15.23"
|
|
12
|
+
},
|
|
13
|
+
"reference": {
|
|
14
|
+
"path": "D:\\Projects\\desktop-commander-stage-20260922\\desktop-commander",
|
|
15
|
+
"package": "@wonderwhy-er/desktop-commander",
|
|
16
|
+
"version": "0.2.51",
|
|
17
|
+
"server_sha256": "dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305",
|
|
18
|
+
"schemas_sha256": "f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140"
|
|
19
|
+
},
|
|
20
|
+
"live": {
|
|
21
|
+
"release_id": "v0.15.23-c97e3d21e792420e",
|
|
22
|
+
"release_manifest_sha256": "c97e3d21e792420ea90dada3c1360cd56e4c1f221daa86596d611b0d1ac351ec",
|
|
23
|
+
"generation_manifest_sha256": "4d2d3a4dc103576c39171c52db3436366e62d8b872bcafa4c55f2adb6d3a1126",
|
|
24
|
+
"bridge_pid": 2804,
|
|
25
|
+
"cloud_pid": 13704,
|
|
26
|
+
"state": "ONLINE",
|
|
27
|
+
"authority": "Process/Coding/Autonomous/Desktop/Host ARMED standing-grant"
|
|
28
|
+
},
|
|
29
|
+
"findings": [
|
|
30
|
+
{
|
|
31
|
+
"id": "reference-biff8",
|
|
32
|
+
"classification": "REFERENCE_DEFECT",
|
|
33
|
+
"red": "real OLE/BIFF8 fixture is rejected by the reference ExcelJS xlsx reader",
|
|
34
|
+
"green": "independent BIFF8 fixture test proves reference read rejection and OOXML bytes emitted under .xls",
|
|
35
|
+
"fixture_sha256": "096168376d0abf282546749fdf6765309f895d12fe81d45c52a2c54bb6c0d139"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "incremental-pdf-revisions",
|
|
39
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
40
|
+
"red": "valid independent-oracle-readable incremental PDF failed with PDF duplicate object 3 0",
|
|
41
|
+
"green": "latest revision accepted only when both revisions are bound by a validated classic xref Prev chain; PDF suite 10/10 PASS"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "powershell-command-byte-preservation",
|
|
45
|
+
"classification": "OBSERVED_PASS",
|
|
46
|
+
"green": "real policy-bound PowerShell REPL preserved variables, quotes, pipe and Unicode and returned more than 1 MiB through bounded chunk reads"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "atomic-mutation-post-effect-observation",
|
|
50
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
51
|
+
"red": "machine filesystem, coding, and Host public boundaries collapsed a post-rename observation failure into a generic denied/failed response without uncertainty identity",
|
|
52
|
+
"green": "shared atomic mutation marks FAILED_AFTER_UNKNOWN_EFFECT with unknown side-effect state and expected-after SHA-256; machine filesystem, coding, and Host structured outputs preserve it"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "read-file-directory-and-binary-semantics",
|
|
56
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
57
|
+
"red": "reference returns a bounded recursive listing for read_file(directory) and explicit binary guidance, while DEADBYTE attempted a file read or lossy UTF-8 decoding",
|
|
58
|
+
"green": "compatibility read_file uses policy-bound stat plus paged listing for directories and deterministic binary classification without replacement-character data loss"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "docx-outline-and-content-search",
|
|
62
|
+
"classification": "REAL_DEADBYTE_DEFECT_FIXED_IN_SOURCE",
|
|
63
|
+
"red": "reference exposes drawing markers, header/footer text, and literal DOCX part search while DEADBYTE omitted those document parts and searched ZIP bytes",
|
|
64
|
+
"green": "native bounded DOCX parsing now exposes drawing/header/footer evidence and searches document/header/footer w:t parts through deterministic compatibility sessions"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"focused_evidence": [
|
|
68
|
+
{"scope": "schema-search", "tests": 21, "pass": 21, "fail": 0, "skip": 0},
|
|
69
|
+
{"scope": "documents-wire-url", "tests": 49, "pass": 49, "fail": 0, "skip": 0},
|
|
70
|
+
{"scope": "biff-reference", "tests": 1, "pass": 1, "fail": 0, "skip": 0},
|
|
71
|
+
{"scope": "process-adjacent", "tests": 55, "pass": 55, "fail": 0, "skip": 0},
|
|
72
|
+
{"scope": "mcp-protocol", "tests": 9, "pass": 9, "fail": 0, "skip": 0},
|
|
73
|
+
{"scope": "mcp-protocol-adversarial", "tests": 3, "pass": 3, "fail": 0, "skip": 0},
|
|
74
|
+
{"scope": "atomic-mutation-public-boundaries", "tests": 15, "pass": 14, "fail": 0, "skip": 1},
|
|
75
|
+
{"scope": "full-regression-r48", "files": 211, "batches": 8, "fail": 0, "task_receipt_sha256": "7711039e4807f6c2af02bbda897d0032b067e0a6631f789fd3bf801258160d3f"},
|
|
76
|
+
{"scope": "npm-audit-production", "vulnerabilities": 0},
|
|
77
|
+
{"scope": "real-performance-sample", "iterations_per_workload": 50, "reference_byte_pins_verified": true, "status": "HISTORICAL_ONLY_FRESH_RUN_BLOCKED_REFERENCE_RUNTIME_DEPS",
|
|
78
|
+
"results": {"read_file": {"reference_ops_per_sec": 55, "deadbyte_ops_per_sec": 1058}, "list_directory": {"reference_ops_per_sec": 3818, "deadbyte_ops_per_sec": 1959}, "get_file_info": {"reference_ops_per_sec": 1805, "deadbyte_ops_per_sec": 2231}, "read_multiple_files": {"reference_ops_per_sec": 30, "deadbyte_ops_per_sec": 562}}}
|
|
79
|
+
,{"scope": "filesystem-document-search-adjacent", "tests": 54, "pass": 54, "fail": 0, "skip": 0}
|
|
80
|
+
],
|
|
81
|
+
"changed_file_sha256": {
|
|
82
|
+
"src/commander-pdf-r48.mjs": "23cda18e86daa4163003fb5a80e0069bc19541d454f32a9a38b1d901ee39f26b",
|
|
83
|
+
"src/coding-search.mjs": "7f15b59bc240d896beddc2057354f8c72f296fe189db30c200d2c84a67d65773",
|
|
84
|
+
"src/commander-wire-adapter-r47.mjs": "2ac8421b3dcdd52431001ac16ee1be8cc54a5483d6aab9700b118172ce0d44bd",
|
|
85
|
+
"src/commander-office-reader-r48.mjs": "7379825118da8fae16d63fe7c4d89b0ddb03e7649f3754adae58a4480f8b317f",
|
|
86
|
+
"test/commander-pdf-r48.test.mjs": "bfc2e6d7e1aa5332c1c81687001cdb5f923a45c5ce64925097fe11fd850e9214",
|
|
87
|
+
"test/commander-biff-reference-r48.test.mjs": "8e8b20604062a0398214e159729b10bedff334cb44bddfa4b4046b1ae396520f",
|
|
88
|
+
"test/commander-schema-differential-r48.test.mjs": "fb82a21e8752ccb82cbd6b631f3366bfe5af9a4c5a433db5a128c0a1fe703c66",
|
|
89
|
+
"test/commander-real-parity-r47.test.mjs": "cad9a9fc42c50e450a3b019e9ed066e7313631015179be7edbbdd094fea00a81"
|
|
90
|
+
,"test/commander-wire-contract-r47.test.mjs": "d8e884e241c705b12797f911fb967a4a33b1ec6f3e15b433735882b3de4a34e5"
|
|
91
|
+
,"test/commander-office-reader-r48.test.mjs": "abf1eb0977a064101420a0e908a4c2adbf9d606e9873433709910efafa2b4453"
|
|
92
|
+
,"test/commander-core-bridge-r47.test.mjs": "16092ed097420b9da99573c1e84f62d011fcb5db069242d70ffd1649c704fd35"
|
|
93
|
+
,"src/atomic-mutation.mjs": "622ee1d3c34c50a6343788cc7312e47cb43a87bdace78fb37ca7f51d453b19ad"
|
|
94
|
+
,"src/machine-fs-mcp-tools.mjs": "5413fea537f9d72f73e89137b4c750530fbca41598b16bd9ede0e1c272111488"
|
|
95
|
+
,"src/machine-fs-output-contracts.mjs": "d94e4478b7e465fa559065e508f65b9ef3eff815a8f2d7b2481bf2a0b05224c9"
|
|
96
|
+
,"src/coding-mcp-tools.mjs": "c4e07aa6818cd989c43ce8d027dc7d467e05c8e2fe96b5548eea291285d3bbb1"
|
|
97
|
+
,"src/coding-output-contracts.mjs": "059b4bef2ae6a6c2d7a706c463bea6e49d364faeb619372a429ba36c53004ab7"
|
|
98
|
+
,"src/host-plane.mjs": "0ff88bce7ee34b22f48a1494efb16586b68c74bd46c608116eb9d9daa65fdc43"
|
|
99
|
+
,"src/output-contracts.mjs": "6cbe72d14af097dde78e989bc7b28b48b9cb8990c989d0d40783633f3a7be465"
|
|
100
|
+
,"src/mcp-server.mjs": "16e2250e20710b358b304a0bc41bb416ecb9a586c50d5933f6a4eb2230569b21"
|
|
101
|
+
,"test/atomic-mutation.test.mjs": "98de1fc43092c27e0fa85f391607494b11e6cc0d53eaf47b6db1ab457c6d6a4b"
|
|
102
|
+
,"test/coding-mcp-unknown-effect.test.mjs": "f37e260c2def4c48434b3f251a4cb756a1852843784c08cf6c39dba9045dfb69"
|
|
103
|
+
,"scripts/commander-real-performance-benchmark-r47.mjs": "d7806aa408dc7ca74f05feec535ad6cc545f8c61892c51f0fe388bb13d975290"
|
|
104
|
+
},
|
|
105
|
+
"release_created": true,
|
|
106
|
+
"candidate_version": "0.15.24",
|
|
107
|
+
"publish_attempt": {"status":"BLOCKED_NPM_AUTH","npm_whoami":"E401 Unauthorized","registry_maintainer":"deadbytes <termay10000@gmail.com>","publish_error":"E404 package not found or no permission"},
|
|
108
|
+
"published": false,
|
|
109
|
+
"activated": false,
|
|
110
|
+
"remaining_scope": "restore npm authentication, registry round-trip, successor activation, exact 0.15.23 rollback, exact successor restore and independent final closure"
|
|
111
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
|
-
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { mkdtemp, writeFile, readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
@@ -9,7 +9,16 @@ import { loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
|
9
9
|
import { createMachineFsRuntime } from '../src/machine-fs.mjs';
|
|
10
10
|
|
|
11
11
|
const N=50;
|
|
12
|
-
const
|
|
12
|
+
const referenceRoot=path.resolve(process.env.DEADBYTE_COMMANDER_REFERENCE_RUNTIME??'D:/Projects/desktop-commander-stage-20260922/desktop-commander');
|
|
13
|
+
const expectedReferenceHashes={
|
|
14
|
+
'dist/server.js':'dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305',
|
|
15
|
+
'dist/tools/schemas.js':'f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140'
|
|
16
|
+
};
|
|
17
|
+
for(const [relative,expected] of Object.entries(expectedReferenceHashes)){
|
|
18
|
+
const actual=createHash('sha256').update(await readFile(path.join(referenceRoot,relative))).digest('hex');
|
|
19
|
+
if(actual!==expected)throw new Error(`reference byte pin mismatch: ${relative}`);
|
|
20
|
+
}
|
|
21
|
+
const commanderFs=await import(pathToFileURL(path.join(referenceRoot,'dist','tools','filesystem.js')).href);
|
|
13
22
|
const { readFileFromDisk, readMultipleFiles, listDirectory, getFileInfo }=commanderFs;
|
|
14
23
|
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
15
24
|
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-bench-'));
|
|
@@ -37,5 +46,5 @@ rows.push(await t('Commander get_file_info',()=>getFileInfo(commanderFile)));
|
|
|
37
46
|
rows.push(await t('DEADBYTE get_file_info',()=>db.stat({rootId:'bench',relativePath:'a.txt',hash:true})));
|
|
38
47
|
rows.push(await t('Commander read_multiple_files',()=>readMultipleFiles([path.join(commanderDir,'a.txt'),path.join(commanderDir,'b.txt')])));
|
|
39
48
|
rows.push(await t('DEADBYTE read_multiple_files', async()=>{ await db.read({rootId:'bench',relativePath:'a.txt',offset:0,maxBytes:4096}); await db.read({rootId:'bench',relativePath:'b.txt',offset:0,maxBytes:4096}); }));
|
|
40
|
-
console.log(JSON.stringify({schema:'deadbyte.commander-real-performance-benchmark.v1',reference:'@wonderwhy-er/desktop-commander
|
|
49
|
+
console.log(JSON.stringify({schema:'deadbyte.commander-real-performance-benchmark.v1',reference:'@wonderwhy-er/desktop-commander@0.2.51',reference_hashes:expectedReferenceHashes,iterations:N,rows},null,2));
|
|
41
50
|
await rm(root,{recursive:true,force:true});
|
package/src/atomic-mutation.mjs
CHANGED
|
@@ -74,11 +74,19 @@ export async function atomicMutation({
|
|
|
74
74
|
throw error;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
const after = await readState(target);
|
|
78
77
|
const expectedAfter = sha256Bytes(bytes);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
try {
|
|
79
|
+
const after = await readState(target);
|
|
80
|
+
if (!after.exists || after.sha256 !== expectedAfter) throw new Error('atomic mutation post-commit hash mismatch');
|
|
81
|
+
if (verifyPostcondition) await verifyPostcondition({ before, after, expected_after_sha256: expectedAfter });
|
|
82
|
+
return { before, after, expected_after_sha256: expectedAfter };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
85
|
+
failure.outcome = 'FAILED_AFTER_UNKNOWN_EFFECT';
|
|
86
|
+
failure.side_effect_state = 'unknown';
|
|
87
|
+
failure.expected_after_sha256 = expectedAfter;
|
|
88
|
+
throw failure;
|
|
89
|
+
}
|
|
82
90
|
} finally {
|
|
83
91
|
await releaseLock();
|
|
84
92
|
}
|
package/src/coding-mcp-tools.mjs
CHANGED
|
@@ -46,7 +46,21 @@ const taskStep = z.union([
|
|
|
46
46
|
]);
|
|
47
47
|
|
|
48
48
|
function result(schema, value, isError = false) { return codingStructuredResult(schema, value, isError); }
|
|
49
|
-
function fail(schema, error) {
|
|
49
|
+
function fail(schema, error) {
|
|
50
|
+
const base = { status: 'failed', error: String(error) };
|
|
51
|
+
if (error?.outcome === 'FAILED_AFTER_UNKNOWN_EFFECT'
|
|
52
|
+
&& error?.side_effect_state === 'unknown'
|
|
53
|
+
&& HEX64.test(error?.expected_after_sha256 ?? '')) {
|
|
54
|
+
const enriched = {
|
|
55
|
+
...base,
|
|
56
|
+
outcome: error.outcome,
|
|
57
|
+
side_effect_state: error.side_effect_state,
|
|
58
|
+
expected_after_sha256: error.expected_after_sha256
|
|
59
|
+
};
|
|
60
|
+
if (schema.safeParse(enriched).success) return result(schema, enriched, true);
|
|
61
|
+
}
|
|
62
|
+
return result(schema, base, true);
|
|
63
|
+
}
|
|
50
64
|
export function registerCodingTools(server, { codingRuntime, taskRuntime, semanticRuntime }) {
|
|
51
65
|
if (!codingRuntime || !taskRuntime || !semanticRuntime) throw new Error('coding tool registration requires runtimes');
|
|
52
66
|
|
|
@@ -168,7 +168,9 @@ export const codingSymbolReplaceOutputSchema = statusContract({
|
|
|
168
168
|
shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.optional(),
|
|
169
169
|
sha256: hex64.optional(), bytes: z.number().int().min(0).optional(), replacements: z.literal(1).optional(),
|
|
170
170
|
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional(),
|
|
171
|
-
symbol_id: hex64.optional(), symbol_name: z.string().min(1).max(256).optional(), engine: z.string().min(1).max(64).optional()
|
|
171
|
+
symbol_id: hex64.optional(), symbol_name: z.string().min(1).max(256).optional(), engine: z.string().min(1).max(64).optional(),
|
|
172
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(), side_effect_state: z.literal('unknown').optional(),
|
|
173
|
+
expected_after_sha256: hex64.optional() },
|
|
172
174
|
successStatuses: ['committed'], successRequired: ['root_id','path','previous_sha256','sha256','bytes','replacements','receipt_id','receipt_sha256','request_sha256','symbol_id','symbol_name','engine']
|
|
173
175
|
});
|
|
174
176
|
|
|
@@ -189,14 +191,18 @@ export const codingFileWriteOutputSchema = statusContract({
|
|
|
189
191
|
statuses: ['committed', 'failed'],
|
|
190
192
|
shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.nullable().optional(),
|
|
191
193
|
sha256: hex64.optional(), bytes: z.number().int().min(0).optional(), created: z.boolean().optional(), receipt_id: receiptId.optional(),
|
|
192
|
-
receipt_sha256: hex64.optional(), request_sha256: hex64.optional()
|
|
194
|
+
receipt_sha256: hex64.optional(), request_sha256: hex64.optional(),
|
|
195
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(), side_effect_state: z.literal('unknown').optional(),
|
|
196
|
+
expected_after_sha256: hex64.optional() },
|
|
193
197
|
successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'created', 'receipt_id', 'receipt_sha256', 'request_sha256']
|
|
194
198
|
});
|
|
195
199
|
export const codingTextReplaceOutputSchema = statusContract({
|
|
196
200
|
statuses: ['committed', 'failed'],
|
|
197
201
|
shape: { root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(), previous_sha256: hex64.optional(),
|
|
198
202
|
sha256: hex64.optional(), bytes: z.number().int().min(0).optional(), replacements: z.number().int().min(1).max(64).optional(),
|
|
199
|
-
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional()
|
|
203
|
+
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional(),
|
|
204
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(), side_effect_state: z.literal('unknown').optional(),
|
|
205
|
+
expected_after_sha256: hex64.optional() },
|
|
200
206
|
successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'replacements', 'receipt_id', 'receipt_sha256', 'request_sha256']
|
|
201
207
|
});
|
|
202
208
|
export const codingCommandRunOutputSchema = statusContract({
|
|
@@ -140,7 +140,7 @@ function prettyXml(xml){
|
|
|
140
140
|
}
|
|
141
141
|
return lines.join('\n');
|
|
142
142
|
}
|
|
143
|
-
function docxOutline(xml){
|
|
143
|
+
function docxOutline(xml,files){
|
|
144
144
|
const body=xml.match(/<w:body\b[^>]*>([\s\S]*?)<\/w:body>/)?.[1];
|
|
145
145
|
if(body===undefined) return '[No w:body found in document.xml]';
|
|
146
146
|
const elements=[...body.matchAll(/<w:(p|tbl)\b[\s\S]*?<\/w:\1>/g)].map(m=>({kind:m[1],xml:m[0]}));
|
|
@@ -152,6 +152,8 @@ function docxOutline(xml){
|
|
|
152
152
|
let line=`[${i}] w:p`;
|
|
153
153
|
if(style) line+=` style="${xmlDecode(style)}"`;
|
|
154
154
|
const value=wordText(item.xml);
|
|
155
|
+
const hasDrawing=item.xml.includes('<w:drawing')||item.xml.includes('<mc:AlternateContent');
|
|
156
|
+
if(hasDrawing) line+=value?' (+ drawing/image)':` [drawing/image, ${(Buffer.byteLength(item.xml)/1024).toFixed(1)}KB]`;
|
|
155
157
|
if(value) line+='\n '+value;
|
|
156
158
|
lines.push(line);
|
|
157
159
|
}else{
|
|
@@ -163,7 +165,15 @@ function docxOutline(xml){
|
|
|
163
165
|
}
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
|
-
|
|
168
|
+
const headersFooters=[];
|
|
169
|
+
for(const [name,bytes] of [...files.entries()].sort(([a],[b])=>a.localeCompare(b))){
|
|
170
|
+
if(!/^word\/(?:header|footer)[^/]*\.xml$/i.test(name)) continue;
|
|
171
|
+
try{
|
|
172
|
+
const value=wordText(decodeUtf8(bytes,name));
|
|
173
|
+
headersFooters.push(`${name.slice('word/'.length)}: ${value?`"${value.length>100?value.slice(0,100)+'...':value}"`:'(no text content)'}`);
|
|
174
|
+
}catch{/* malformed optional part is omitted; document.xml remains authoritative */}
|
|
175
|
+
}
|
|
176
|
+
return lines.join('\n')+(headersFooters.length?`\n\nHeaders/Footers:\n${headersFooters.join('\n')}`:'');
|
|
167
177
|
}
|
|
168
178
|
|
|
169
179
|
export function readDocxBytes(input,{path:displayPath='document.docx',offset=0,length=1000}={}){
|
|
@@ -180,11 +190,34 @@ export function readDocxBytes(input,{path:displayPath='document.docx',offset=0,l
|
|
|
180
190
|
return {content:`[DOCX XML: lines ${start}-${Math.max(start,start+sliced.length-1)} of ${totalLines} (${remaining} remaining)]\n${sliced.join('\n')}`,
|
|
181
191
|
mimeType:'application/xml',metadata:{isDocx:true,lineCount:totalLines},outline:false};
|
|
182
192
|
}
|
|
183
|
-
const outline=docxOutline(xml);
|
|
193
|
+
const outline=docxOutline(xml,files);
|
|
184
194
|
const rawSizeKB=(Buffer.byteLength(xml)/1024).toFixed(1);
|
|
185
195
|
return {content:`${outline}\n\nRaw XML: ${totalLines} lines, ${rawSizeKB}KB.`,mimeType:'text/plain',metadata:{isDocx:true,lineCount:totalLines},outline:true};
|
|
186
196
|
}
|
|
187
197
|
|
|
198
|
+
export function searchDocxTextBytes(input,{pattern,ignoreCase=true,maxResults=500}={}){
|
|
199
|
+
if(typeof pattern!=='string'||pattern.length<1||pattern.length>4096) throw new Error('DOCX search pattern invalid');
|
|
200
|
+
if(!Number.isInteger(maxResults)||maxResults<1||maxResults>5000) throw new Error('DOCX search maxResults invalid');
|
|
201
|
+
const files=unzipOffice(input),needle=ignoreCase?pattern.toLowerCase():pattern,results=[];
|
|
202
|
+
const names=[...files.keys()].filter(name=>name==='word/document.xml'||/^word\/(?:header|footer)[^/]*\.xml$/i.test(name)).sort((a,b)=>{
|
|
203
|
+
if(a==='word/document.xml')return -1;if(b==='word/document.xml')return 1;return a.localeCompare(b);
|
|
204
|
+
});
|
|
205
|
+
for(const name of names){
|
|
206
|
+
let xml;
|
|
207
|
+
try{xml=decodeUtf8(files.get(name),name);}catch{continue;}
|
|
208
|
+
let line=0;
|
|
209
|
+
for(const match of xml.matchAll(/<w:t(?:\s[^>]*)?>([^<]*)<\/w:t>/g)){
|
|
210
|
+
const value=xmlDecode(match[1]??'');if(!value.trim())continue;line++;
|
|
211
|
+
const haystack=ignoreCase?value.toLowerCase():value,index=haystack.indexOf(needle);
|
|
212
|
+
if(index<0)continue;
|
|
213
|
+
const start=Math.max(0,index-50),end=Math.min(value.length,index+needle.length+50);
|
|
214
|
+
results.push({part:name,line,match:`${start>0?'...':''}${value.slice(start,end)}${end<value.length?'...':''}`});
|
|
215
|
+
if(results.length>=maxResults)return results;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return results;
|
|
219
|
+
}
|
|
220
|
+
|
|
188
221
|
|
|
189
222
|
export function createDocxBytes(content){
|
|
190
223
|
if(typeof content!=='string') throw new Error('DOCX content must be string');
|
|
@@ -110,6 +110,37 @@ export function parseDeadbytePdf(input){
|
|
|
110
110
|
const MAX_GENERIC_OBJECTS=10000;
|
|
111
111
|
const MAX_GENERIC_STREAM_BYTES=16*1024*1024;
|
|
112
112
|
|
|
113
|
+
function classicIncrementalObjectOffsets(text){
|
|
114
|
+
const starts=[...text.matchAll(/startxref\s+(\d+)\s+%%EOF/g)];
|
|
115
|
+
if(starts.length<2)return new Map();
|
|
116
|
+
const offsets=new Map(),visited=new Set();
|
|
117
|
+
let cursor=Number(starts.at(-1)[1]);
|
|
118
|
+
while(Number.isSafeInteger(cursor)&&cursor>=0&&!visited.has(cursor)){
|
|
119
|
+
visited.add(cursor);
|
|
120
|
+
if(text.slice(cursor,cursor+4)!=='xref')return new Map();
|
|
121
|
+
const trailerAt=text.indexOf('trailer',cursor+4);
|
|
122
|
+
if(trailerAt<0)return new Map();
|
|
123
|
+
const lines=text.slice(cursor+4,trailerAt).trim().split(/\r?\n/);
|
|
124
|
+
for(let i=0;i<lines.length;){
|
|
125
|
+
const header=lines[i++].trim().match(/^(\d+)\s+(\d+)$/);if(!header)return new Map();
|
|
126
|
+
const first=Number(header[1]),count=Number(header[2]);
|
|
127
|
+
if(!Number.isSafeInteger(first)||!Number.isSafeInteger(count)||count<0||count>MAX_GENERIC_OBJECTS)return new Map();
|
|
128
|
+
for(let n=0;n<count;n++){
|
|
129
|
+
const entry=lines[i++]?.trim().match(/^(\d{10})\s+(\d{5})\s+([nf])$/);if(!entry)return new Map();
|
|
130
|
+
if(entry[3]==='n'){
|
|
131
|
+
const key=`${first+n} ${Number(entry[2])}`;
|
|
132
|
+
const list=offsets.get(key)??[];list.push(Number(entry[1]));offsets.set(key,list);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const trailerEnd=text.indexOf('startxref',trailerAt+7);if(trailerEnd<0)return new Map();
|
|
137
|
+
const prev=text.slice(trailerAt+7,trailerEnd).match(/\/Prev\s+(\d+)/)?.[1];
|
|
138
|
+
if(prev===undefined)break;
|
|
139
|
+
const next=Number(prev);if(!Number.isSafeInteger(next)||next>=cursor)return new Map();cursor=next;
|
|
140
|
+
}
|
|
141
|
+
return offsets;
|
|
142
|
+
}
|
|
143
|
+
|
|
113
144
|
function decodePdfTextBytes(bytes){
|
|
114
145
|
if(bytes.length>=2&&bytes[0]===0xfe&&bytes[1]===0xff){
|
|
115
146
|
let out='';
|
|
@@ -186,6 +217,7 @@ function parseGenericPdfObjects(bytes){
|
|
|
186
217
|
const text=bytes.toString('latin1');
|
|
187
218
|
if(!text.startsWith('%PDF-')) throw new Error('invalid PDF header');
|
|
188
219
|
if(/\/Encrypt\b/.test(text)) throw new Error('encrypted PDF is unsupported');
|
|
220
|
+
const incrementalOffsets=classicIncrementalObjectOffsets(text);
|
|
189
221
|
const objects=new Map(),header=/(\d+)\s+(\d+)\s+obj\b/g;
|
|
190
222
|
let match,count=0;
|
|
191
223
|
while((match=header.exec(text))){
|
|
@@ -193,7 +225,10 @@ function parseGenericPdfObjects(bytes){
|
|
|
193
225
|
const end=text.indexOf('endobj',header.lastIndex);
|
|
194
226
|
if(end<0) throw new Error(`PDF object ${match[1]} ${match[2]} missing endobj`);
|
|
195
227
|
const key=`${match[1]} ${match[2]}`;
|
|
196
|
-
if(objects.has(key))
|
|
228
|
+
if(objects.has(key)){
|
|
229
|
+
const revisions=incrementalOffsets.get(key)??[];
|
|
230
|
+
if(!revisions.includes(objects.get(key).offset)||!revisions.includes(match.index)) throw new Error(`PDF duplicate object ${key}`);
|
|
231
|
+
}
|
|
197
232
|
objects.set(key,{id:Number(match[1]),generation:Number(match[2]),body:text.slice(header.lastIndex,end),offset:match.index});
|
|
198
233
|
header.lastIndex=end+6;
|
|
199
234
|
}
|
|
@@ -2,8 +2,9 @@ import * as z from 'zod/v4';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { readCommanderUrl } from './commander-url-reader-r48.mjs';
|
|
5
|
-
import { readDocxBytes, createDocxBytes, editDocxBytes, readXlsxBytes, editXlsxBytes, createXlsxBytes, appendXlsxBytes } from './commander-office-reader-r48.mjs';
|
|
5
|
+
import { readDocxBytes, searchDocxTextBytes, createDocxBytes, editDocxBytes, readXlsxBytes, editXlsxBytes, createXlsxBytes, appendXlsxBytes } from './commander-office-reader-r48.mjs';
|
|
6
6
|
import { editDeadbytePdf, parseDeadbytePdf, readPdfText } from './commander-pdf-r48.mjs';
|
|
7
|
+
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
7
8
|
|
|
8
9
|
const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
9
10
|
const RDC_PROCESS_READ_CHUNK=256*1024;
|
|
@@ -122,6 +123,19 @@ function absoluteFor(runtime,rootId,relativePath){
|
|
|
122
123
|
function linesFrom(bytes){
|
|
123
124
|
return Buffer.from(bytes,'base64').toString('utf8').replaceAll('\\r\\n','\\n').replaceAll('\\r','\\n').split('\\n');
|
|
124
125
|
}
|
|
126
|
+
function isUnknownBinary(bytes){
|
|
127
|
+
if(bytes.includes(0)) return true;
|
|
128
|
+
try{decodeUtf8Strict(bytes);return false;}catch{return true;}
|
|
129
|
+
}
|
|
130
|
+
function directoryRows(entries,baseRelative){
|
|
131
|
+
const base=String(baseRelative??'.').replaceAll('\\','/').replace(/^\.\//,'').replace(/\/$/,'');
|
|
132
|
+
return entries.map(entry=>{
|
|
133
|
+
const raw=String(entry.path??'').replaceAll('\\','/').replace(/^\.\//,'');
|
|
134
|
+
const relative=base&&base!=='.'&&raw.startsWith(`${base}/`)?raw.slice(base.length+1):raw;
|
|
135
|
+
const display=process.platform==='win32'?relative.replaceAll('/','\\'):relative;
|
|
136
|
+
return `${(entry.kind??entry.type)==='directory'?'[DIR]':'[FILE]'} ${display}`;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
125
139
|
function rdcFileResult(pathname,data){
|
|
126
140
|
return {
|
|
127
141
|
path:pathname,content:data.content,mimeType:data.mime_type,
|
|
@@ -171,6 +185,17 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
171
185
|
}
|
|
172
186
|
if(!machineFsRuntime) throw new Error('machine filesystem runtime is disabled');
|
|
173
187
|
const target=resolveRdcPath(machineFsRuntime,args.path);
|
|
188
|
+
if(typeof machineFsRuntime.stat==='function'){
|
|
189
|
+
const info=await machineFsRuntime.stat({rootId:target.rootId,relativePath:target.relativePath,hash:false});
|
|
190
|
+
if(info?.kind==='directory'){
|
|
191
|
+
const listing=await listDirectory({path:args.path,depth:2});
|
|
192
|
+
const entries=listing.structuredContent?.entries??[];
|
|
193
|
+
const rows=directoryRows(entries,target.relativePath);
|
|
194
|
+
const content=`This is a directory, not a file. Use the list_directory tool instead of read_file for directories.\n\n${rows.join('\n')}`;
|
|
195
|
+
return text(content,false,{path:args.path,content,mimeType:'text/plain',isImage:false,isPdf:false,isDirectory:true,
|
|
196
|
+
entries,truncated:listing.structuredContent?.truncated===true,entryCount:entries.length});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
174
199
|
const ext=path.extname(args.path).toLowerCase();
|
|
175
200
|
const richDocument=ext==='.docx'||ext==='.xlsx'||ext==='.xlsm'||ext==='.pdf';
|
|
176
201
|
const imageMime={'.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.gif':'image/gif','.webp':'image/webp'}[ext];
|
|
@@ -238,6 +263,10 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
238
263
|
if(ext==='.xls') {
|
|
239
264
|
throw new Error('format .xls requires a native BIFF adapter; request rejected rather than returned as plain text');
|
|
240
265
|
}
|
|
266
|
+
if(isUnknownBinary(bytes)){
|
|
267
|
+
const content=`Cannot read binary file as text: ${path.basename(args.path)}\n\nUse start_process + interact_with_process to analyze binary files with appropriate tools (Node.js or Python libraries, command-line utilities, etc.).\n\nThe read_file tool only handles text files, images, and Excel files.`;
|
|
268
|
+
return text(content,false,{path:args.path,content,mimeType:'text/plain',isImage:false,isPdf:false,isBinary:true,fileSha256,...optionMeta});
|
|
269
|
+
}
|
|
241
270
|
const all=linesFrom(raw.data_base64);
|
|
242
271
|
const start=requestedOffset<0?Math.max(0,all.length+requestedOffset):Math.min(requestedOffset,all.length);
|
|
243
272
|
const end=requestedOffset<0?all.length:Math.min(all.length,start+requestedLength);
|
|
@@ -381,7 +410,7 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
381
410
|
}
|
|
382
411
|
frontier=next;
|
|
383
412
|
}
|
|
384
|
-
const rows=all.
|
|
413
|
+
const rows=directoryRows(all,target.relativePath);
|
|
385
414
|
return text(rows.join('\n'),false,{path:args.path,depth,entries:all,truncated,entryCount:all.length});
|
|
386
415
|
}
|
|
387
416
|
|
|
@@ -803,6 +832,31 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
803
832
|
seenCursors.add(query.cursor);
|
|
804
833
|
}
|
|
805
834
|
}while(query.cursor!==null&&state.results.length<query.maxResults);
|
|
835
|
+
const docxSearchEnabled=String(args.filePattern??'').toLowerCase().includes('.docx')||String(args.path).toLowerCase().endsWith('.docx');
|
|
836
|
+
if(args.searchType==='content'&&machineFsRuntime&&docxSearchEnabled&&state.results.length<query.maxResults){
|
|
837
|
+
let docxEntries=[];
|
|
838
|
+
if(typeof machineFsRuntime.stat==='function'){
|
|
839
|
+
const info=await machineFsRuntime.stat({rootId:target.rootId,relativePath:target.relativePath,hash:false});
|
|
840
|
+
if(info?.kind==='file'&&target.relativePath.toLowerCase().endsWith('.docx'))docxEntries=[{path:target.relativePath,kind:'file'}];
|
|
841
|
+
}
|
|
842
|
+
if(docxEntries.length===0){
|
|
843
|
+
const listing=await listDirectory({path:args.path,depth:10});
|
|
844
|
+
docxEntries=(listing.structuredContent?.entries??[]).filter(entry=>(entry.kind??entry.type)==='file'&&String(entry.path).toLowerCase().endsWith('.docx'));
|
|
845
|
+
}
|
|
846
|
+
for(const entry of docxEntries){
|
|
847
|
+
if(state.cancelled||state.timedOut||state.results.length>=query.maxResults)break;
|
|
848
|
+
const raw=await machineFsRuntime.read({rootId:target.rootId,relativePath:entry.path,offset:0,
|
|
849
|
+
maxBytes:Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024),hashFile:true});
|
|
850
|
+
if(raw.eof===false)continue;
|
|
851
|
+
const matches=searchDocxTextBytes(Buffer.from(raw.data_base64,'base64'),{
|
|
852
|
+
pattern:args.pattern,ignoreCase:args.ignoreCase!==false,maxResults:query.maxResults-state.results.length
|
|
853
|
+
});
|
|
854
|
+
for(const match of matches)state.results.push({
|
|
855
|
+
file:absoluteFor(machineFsRuntime,target.rootId,entry.path)+(match.part==='word/document.xml'?'':`:${match.part.slice('word/'.length)}`),
|
|
856
|
+
line:match.line,match:match.match,type:'content'
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
}
|
|
806
860
|
if(!state.cancelled&&!state.timedOut) state.isComplete=true;
|
|
807
861
|
}catch(error){if(!state.cancelled&&!state.timedOut){state.isError=true;state.error=String(error?.message??error);}state.isComplete=true;}
|
|
808
862
|
finally{if(timeoutHandle!==null) clearTimeout(timeoutHandle);}
|
package/src/host-plane.mjs
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import { createHash
|
|
2
|
-
import { constants as fsConstants } from 'node:fs';
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
3
2
|
import {
|
|
4
3
|
lstat,
|
|
5
4
|
mkdir,
|
|
6
|
-
open,
|
|
7
5
|
readFile,
|
|
8
6
|
realpath,
|
|
9
|
-
rename,
|
|
10
|
-
rm,
|
|
11
7
|
stat
|
|
12
8
|
} from 'node:fs/promises';
|
|
13
9
|
import path from 'node:path';
|
|
14
10
|
import { spawn } from 'node:child_process';
|
|
15
11
|
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
16
12
|
import { isInside, validatePortableRelativePath } from './path-policy.mjs';
|
|
13
|
+
import { atomicMutation } from './atomic-mutation.mjs';
|
|
17
14
|
|
|
18
15
|
export const HOST_POLICY_SCHEMA = 'deadbyte.host-policy.v1';
|
|
19
16
|
export const HOST_POLICY_SCHEMA_V2 = 'deadbyte.host-policy.v2';
|
|
@@ -315,32 +312,14 @@ export async function hostFileWrite(policy, { rootId, relativePath, content, enc
|
|
|
315
312
|
const bytes = decodeWriteContent(content, encoding);
|
|
316
313
|
if (bytes.length > policy.max_write_bytes) throw new Error(`host write exceeds ${policy.max_write_bytes} byte limit`);
|
|
317
314
|
|
|
318
|
-
const before = await
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
await handle.writeFile(bytes);
|
|
327
|
-
await handle.sync();
|
|
328
|
-
await handle.close();
|
|
329
|
-
handle = null;
|
|
330
|
-
|
|
331
|
-
const raceCheck = await currentTargetState(lexical);
|
|
332
|
-
checkExpectedState(raceCheck, expectedSha256);
|
|
333
|
-
await authorityCheck();
|
|
334
|
-
await rename(temp, lexical);
|
|
335
|
-
} catch (error) {
|
|
336
|
-
if (handle) await handle.close().catch(() => {});
|
|
337
|
-
await rm(temp, { force: true }).catch(() => {});
|
|
338
|
-
throw error;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
const after = await currentTargetState(lexical);
|
|
342
|
-
const expectedAfter = sha256Bytes(bytes);
|
|
343
|
-
if (!after.exists || after.sha256 !== expectedAfter) throw new Error('host write post-commit hash mismatch');
|
|
315
|
+
const { before, after } = await atomicMutation({
|
|
316
|
+
target: lexical,
|
|
317
|
+
bytes,
|
|
318
|
+
expected: expectedSha256,
|
|
319
|
+
readState: currentTargetState,
|
|
320
|
+
assertExpected: checkExpectedState,
|
|
321
|
+
authorityCheck
|
|
322
|
+
});
|
|
344
323
|
return {
|
|
345
324
|
previous_sha256: before.sha256,
|
|
346
325
|
sha256: after.sha256,
|
|
@@ -10,7 +10,13 @@ const rel = z.string().min(1).max(2048);
|
|
|
10
10
|
const hex64 = z.string().regex(/^[0-9a-f]{64}$/);
|
|
11
11
|
const expectedFile = z.string().regex(/^(?:absent|[0-9a-f]{64})$/);
|
|
12
12
|
const ok = (schema, value) => machineFsStructuredResult(schema, value, false);
|
|
13
|
-
const fail = (schema, error) =>
|
|
13
|
+
const fail = (schema, error) => {
|
|
14
|
+
const unknown=error?.side_effect_state==='unknown'&&error?.outcome==='FAILED_AFTER_UNKNOWN_EFFECT';
|
|
15
|
+
return machineFsStructuredResult(schema, {
|
|
16
|
+
status:unknown?'failed':'denied',error:String(error),
|
|
17
|
+
...(unknown?{outcome:error.outcome,side_effect_state:'unknown',expected_after_sha256:error.expected_after_sha256}: {})
|
|
18
|
+
}, true);
|
|
19
|
+
};
|
|
14
20
|
|
|
15
21
|
export function registerMachineFsTools(server, { runtime }) {
|
|
16
22
|
if (!server || !runtime) throw new Error('machine fs tool registration requires server/runtime');
|
|
@@ -7,6 +7,11 @@ const pathText = z.string().min(1).max(2048);
|
|
|
7
7
|
const statusOk = z.enum(['ok','denied','failed']);
|
|
8
8
|
const statusCommit = z.enum(['committed','denied','failed']);
|
|
9
9
|
const error = z.string().min(1).max(4096).optional();
|
|
10
|
+
const unknownEffect = {
|
|
11
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(),
|
|
12
|
+
side_effect_state: z.literal('unknown').optional(),
|
|
13
|
+
expected_after_sha256: hex64.optional()
|
|
14
|
+
};
|
|
10
15
|
|
|
11
16
|
export const machineFsStatOutputSchema = z.strictObject({
|
|
12
17
|
status: statusOk, error,
|
|
@@ -38,6 +43,7 @@ export const machineFsReadOutputSchema = z.strictObject({
|
|
|
38
43
|
|
|
39
44
|
const commitBase = {
|
|
40
45
|
status: statusCommit, error,
|
|
46
|
+
...unknownEffect,
|
|
41
47
|
root_id: z.string().min(1).max(64).optional(),
|
|
42
48
|
receipt_id: receiptId.optional(), receipt_sha256: hex64.optional(), request_sha256: hex64.optional()
|
|
43
49
|
};
|
package/src/mcp-server.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
7
7
|
import { serveStdio } from '@modelcontextprotocol/server/stdio';
|
|
8
|
+
import { DeadbyteStdioServerTransport } from './stdio-protocol-guard.mjs';
|
|
8
9
|
import * as z from 'zod/v4';
|
|
9
10
|
import { executeProof } from './executor.mjs';
|
|
10
11
|
import { CONTAINED_TRANSFORM_OPERATIONS, executeContainedTransform } from './contained-executor.mjs';
|
|
@@ -529,7 +530,18 @@ serveStdio(() => {
|
|
|
529
530
|
});
|
|
530
531
|
return textResult(hostFileWriteOutputSchema, { status: 'committed', root_id, path: relativePath, ...result });
|
|
531
532
|
} catch (error) {
|
|
532
|
-
|
|
533
|
+
const unknown = error?.outcome === 'FAILED_AFTER_UNKNOWN_EFFECT'
|
|
534
|
+
&& error?.side_effect_state === 'unknown'
|
|
535
|
+
&& /^[0-9a-f]{64}$/.test(error?.expected_after_sha256 ?? '');
|
|
536
|
+
return textResult(hostFileWriteOutputSchema, {
|
|
537
|
+
status: unknown ? 'failed' : 'denied',
|
|
538
|
+
error: String(error),
|
|
539
|
+
...(unknown ? {
|
|
540
|
+
outcome: error.outcome,
|
|
541
|
+
side_effect_state: error.side_effect_state,
|
|
542
|
+
expected_after_sha256: error.expected_after_sha256
|
|
543
|
+
} : {})
|
|
544
|
+
}, true);
|
|
533
545
|
}
|
|
534
546
|
}
|
|
535
547
|
);
|
|
@@ -618,4 +630,4 @@ serveStdio(() => {
|
|
|
618
630
|
}
|
|
619
631
|
|
|
620
632
|
return server;
|
|
621
|
-
}, { legacy: 'reject' });
|
|
633
|
+
}, { legacy: 'reject', transport: new DeadbyteStdioServerTransport() });
|
package/src/output-contracts.mjs
CHANGED
|
@@ -217,13 +217,15 @@ export const hostFileReadOutputSchema = statusContract({
|
|
|
217
217
|
});
|
|
218
218
|
|
|
219
219
|
export const hostFileWriteOutputSchema = statusContract({
|
|
220
|
-
statuses: ['committed', 'denied'],
|
|
220
|
+
statuses: ['committed', 'denied', 'failed'],
|
|
221
221
|
shape: {
|
|
222
222
|
root_id: z.string().min(1).max(64).optional(), path: z.string().min(1).max(512).optional(),
|
|
223
223
|
previous_sha256: hex64.nullable().optional(), sha256: hex64.optional(),
|
|
224
|
-
bytes: z.number().int().min(0).optional(), created: z.boolean().optional()
|
|
224
|
+
bytes: z.number().int().min(0).optional(), created: z.boolean().optional(),
|
|
225
|
+
outcome: z.literal('FAILED_AFTER_UNKNOWN_EFFECT').optional(),
|
|
226
|
+
side_effect_state: z.literal('unknown').optional(), expected_after_sha256: hex64.optional()
|
|
225
227
|
},
|
|
226
|
-
successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'created'], errorStatuses: ['denied']
|
|
228
|
+
successStatuses: ['committed'], successRequired: ['root_id', 'path', 'previous_sha256', 'sha256', 'bytes', 'created'], errorStatuses: ['denied', 'failed']
|
|
227
229
|
});
|
|
228
230
|
|
|
229
231
|
export const hostExecOutputSchema = z.strictObject({
|
|
@@ -265,4 +267,4 @@ export function structuredToolResult(schema, object, isError = false) {
|
|
|
265
267
|
structuredContent: parsed.data,
|
|
266
268
|
content: [{ type: 'text', text: JSON.stringify(parsed.data) }]
|
|
267
269
|
};
|
|
268
|
-
}
|
|
270
|
+
}
|