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.
Files changed (35) hide show
  1. package/MANIFEST.SHA256 +34 -28
  2. package/PROVENANCE.json +11 -10
  3. package/README.txt +7 -7
  4. package/RELEASE.json +19 -38
  5. package/TEST-MATRIX.json +32 -96
  6. package/package.json +2 -2
  7. package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +111 -0
  8. package/scripts/commander-real-performance-benchmark-r47.mjs +12 -3
  9. package/src/atomic-mutation.mjs +12 -4
  10. package/src/coding-mcp-tools.mjs +15 -1
  11. package/src/coding-output-contracts.mjs +9 -3
  12. package/src/commander-office-reader-r48.mjs +36 -3
  13. package/src/commander-pdf-r48.mjs +36 -1
  14. package/src/commander-wire-adapter-r47.mjs +56 -2
  15. package/src/host-plane.mjs +10 -31
  16. package/src/machine-fs-mcp-tools.mjs +7 -1
  17. package/src/machine-fs-output-contracts.mjs +6 -0
  18. package/src/mcp-server.mjs +14 -2
  19. package/src/output-contracts.mjs +6 -4
  20. package/src/stdio-protocol-guard.mjs +71 -0
  21. package/src/version.mjs +1 -1
  22. package/test/atomic-mutation.test.mjs +61 -0
  23. package/test/coding-mcp-unknown-effect.test.mjs +42 -0
  24. package/test/commander-biff-reference-r48.test.mjs +38 -0
  25. package/test/commander-core-bridge-r47.test.mjs +19 -0
  26. package/test/commander-office-reader-r48.test.mjs +18 -2
  27. package/test/commander-pdf-r48.test.mjs +35 -0
  28. package/test/commander-protocol-adversarial-r48.test.mjs +166 -0
  29. package/test/commander-real-parity-r47.test.mjs +37 -3
  30. package/test/commander-schema-differential-r48.test.mjs +1 -1
  31. package/test/commander-wire-contract-r47.test.mjs +32 -0
  32. package/test/machine-fs-mcp-tools.test.mjs +12 -0
  33. package/test/output-schema.test.mjs +20 -1
  34. package/test/r48-release-contract.test.mjs +27 -27
  35. 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 commanderFs=await import(pathToFileURL('D:/Projects/desktop-commander-stage-20260922/desktop-commander/dist/tools/filesystem.js').href);
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 dist local stage',iterations:N,rows},null,2));
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});
@@ -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
- if (!after.exists || after.sha256 !== expectedAfter) throw new Error('atomic mutation post-commit hash mismatch');
80
- if (verifyPostcondition) await verifyPostcondition({ before, after, expected_after_sha256: expectedAfter });
81
- return { before, after, expected_after_sha256: expectedAfter };
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
  }
@@ -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) { return result(schema, { status: 'failed', error: String(error) }, true); }
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
- return lines.join('\n');
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)) throw new Error(`PDF duplicate object ${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.map(e=>`${(e.kind??e.type)==='directory'?'[DIR]':'[FILE]'} ${e.path}`);
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);}
@@ -1,19 +1,16 @@
1
- import { createHash, randomBytes } from 'node:crypto';
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 currentTargetState(lexical);
319
- checkExpectedState(before, expectedSha256);
320
-
321
- const temp = path.join(path.dirname(lexical), `.${path.basename(lexical)}.deadbyte-${process.pid}-${randomBytes(8).toString('hex')}.tmp`);
322
- let handle;
323
- try {
324
- await authorityCheck();
325
- handle = await open(temp, fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_WRONLY, 0o600);
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) => machineFsStructuredResult(schema, { status:'denied', error:String(error) }, true);
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
  };
@@ -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
- return textResult(hostFileWriteOutputSchema, { status: 'denied', error: String(error) }, true);
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() });
@@ -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
+ }