deadbyte-mcp 0.15.14 → 0.15.16

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 (66) hide show
  1. package/MANIFEST.SHA256 +65 -27
  2. package/PROVENANCE.json +6 -6
  3. package/README.txt +6 -6
  4. package/RELEASE.json +58 -53
  5. package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +49 -0
  6. package/docs/COMMANDER_REFERENCE_INVENTORY.json +70 -0
  7. package/docs/R47-FULL-TOOL-CATALOG.json +123 -0
  8. package/package.json +5 -2
  9. package/scripts/commander-hardening-gate-r47.mjs +35 -0
  10. package/scripts/commander-parity-benchmark.mjs +6 -0
  11. package/scripts/commander-real-performance-benchmark-r47.mjs +41 -0
  12. package/scripts/deploy-r47.mjs +67 -8
  13. package/scripts/full-regression-r47.mjs +28 -0
  14. package/scripts/inventory-reference-dist.mjs +17 -0
  15. package/scripts/npm-release-r47.mjs +25 -1
  16. package/scripts/r47-syntax-check.mjs +6 -0
  17. package/scripts/r47-wire-remote-plane.mjs +23 -0
  18. package/src/atomic-mutation.mjs +59 -29
  19. package/src/commander-compat-contracts.mjs +90 -0
  20. package/src/commander-compat-runtime.mjs +91 -0
  21. package/src/commander-core-bridge.mjs +30 -0
  22. package/src/commander-extended-runtime.mjs +74 -0
  23. package/src/commander-observability.mjs +33 -0
  24. package/src/commander-remote-bridge-r47.mjs +41 -0
  25. package/src/commander-remote-relay-r47.mjs +44 -0
  26. package/src/machine-fs.mjs +108 -22
  27. package/src/mcp-server.mjs +94 -0
  28. package/src/observation-output-contracts.mjs +9 -8
  29. package/src/observation-runtime.mjs +47 -17
  30. package/src/operation-history.mjs +95 -0
  31. package/src/operation-lifecycle.mjs +63 -0
  32. package/src/process-output-contracts.mjs +24 -1
  33. package/src/process-readiness.mjs +120 -0
  34. package/src/process-runtime.mjs +178 -6
  35. package/src/release-slot.mjs +296 -293
  36. package/src/runtime-update.mjs +61 -7
  37. package/src/tool-audit.mjs +15 -2
  38. package/src/version.mjs +1 -2
  39. package/test/OBSERVED-COMMANDER-TOOLS.json +1 -0
  40. package/test/agent-runtime.test.mjs +1 -1
  41. package/test/autonomous-runtime.test.mjs +1 -1
  42. package/test/coding-plane.test.mjs +1 -1
  43. package/test/coding-search-r36.test.mjs +14 -0
  44. package/test/commander-compat-runtime.test.mjs +34 -0
  45. package/test/commander-core-bridge-r47.test.mjs +31 -0
  46. package/test/commander-exhaustive-surface-r47.test.mjs +32 -0
  47. package/test/commander-extended-runtime-r47.test.mjs +46 -0
  48. package/test/commander-observability-r47.test.mjs +34 -0
  49. package/test/commander-parity-gate-r47.test.mjs +49 -0
  50. package/test/commander-protocol-r47.test.mjs +30 -0
  51. package/test/commander-real-parity-r47.test.mjs +121 -0
  52. package/test/commander-remote-bridge-r47.test.mjs +39 -0
  53. package/test/commander-remote-plane-r47.test.mjs +25 -0
  54. package/test/commander-torture-r47.test.mjs +146 -0
  55. package/test/full-max-catalog-r46.test.mjs +1 -1
  56. package/test/full-max-catalog-r47.test.mjs +78 -0
  57. package/test/machine-fs-runtime.test.mjs +97 -0
  58. package/test/observation-runtime.test.mjs +28 -0
  59. package/test/operation-history.test.mjs +61 -0
  60. package/test/operation-lifecycle.test.mjs +72 -0
  61. package/test/output-schema.test.mjs +1 -1
  62. package/test/process-readiness.test.mjs +32 -0
  63. package/test/process-runtime.test.mjs +147 -0
  64. package/test/r47-release-contract.test.mjs +37 -0
  65. package/test/release-activation-gate.test.mjs +30 -0
  66. package/test/release-version.test.mjs +1 -1
@@ -0,0 +1,91 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { COMMANDER_COMPAT_TOOLS, DEADBYTE_NATIVE_TOOLS } from './commander-compat-contracts.mjs';
3
+
4
+ export const COMMANDER_COMPAT_RUNTIME_SCHEMA = 'deadbyte.commander-compat-runtime.v2';
5
+ const sha256 = value => createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
6
+ const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
7
+
8
+ const CONTRACTS = Object.freeze({
9
+ contained_transform:{native:'contained.transform',required:['operation','input'],mutation:false},
10
+ create_directory:{native:'machine_fs.mkdir',required:['root_id','path'],mutation:true},
11
+ edit_block:{native:'native_diff',required:['root_id','path','expected_sha256'],mutation:true},
12
+ evidence_verify:{native:'evidence.verify',required:['receipt'],mutation:false},
13
+ file_preview:{native:'commander.file_preview',required:['root_id','path'],mutation:false},
14
+ file_read:{native:'machine_fs.read',required:['root_id','path'],mutation:false},
15
+ force_terminate:{native:'process.terminate',required:['session_id'],mutation:true},
16
+ get_config:{native:'commander.config.get',required:[],mutation:false},
17
+ get_file_info:{native:'machine_fs.stat',required:['root_id','path'],mutation:false},
18
+ get_more_search_results:{native:'machine_search.page',required:['search_id'],mutation:false},
19
+ get_prompts:{native:'commander.prompts',required:[],mutation:false},
20
+ get_recent_tool_calls:{native:'operation_history.recent',required:[],mutation:false},
21
+ get_usage_stats:{native:'operation_history.stats',required:[],mutation:false},
22
+ give_feedback_to_desktop_commander:{native:'commander.feedback',required:['message'],mutation:true},
23
+ host_exec:{native:'host.exec',required:['command_id','root_id','script_path','script_sha256'],mutation:true},
24
+ host_file_read:{native:'host.file_read',required:['path'],mutation:false},
25
+ host_file_write:{native:'host.file_write',required:['path','content','expected_sha256'],mutation:true},
26
+ interact_with_process:{native:'process.input',required:['session_id','input'],mutation:true},
27
+ kill_process:{native:'process.terminate',required:['session_id'],mutation:true},
28
+ list_directory:{native:'machine_fs.list',required:['root_id','path'],mutation:false},
29
+ list_processes:{native:'process.sessions',required:[],mutation:false},
30
+ list_searches:{native:'machine_search.list',required:[],mutation:false},
31
+ list_sessions:{native:'process.sessions',required:[],mutation:false},
32
+ markdown_editor:{native:'native_diff',required:['root_id','path','expected_sha256'],mutation:true},
33
+ move_file:{native:'machine_fs.move',required:['root_id','source_path','destination_path','expected_source_identity'],mutation:true},
34
+ proof_execute:{native:'proof.execute',required:['operation'],mutation:false},
35
+ read_file:{native:'machine_fs.read',required:['root_id','path'],mutation:false},
36
+ read_multiple_files:{native:'machine_fs.read_many',required:['root_id','paths'],mutation:false},
37
+ read_process_output:{native:'process.output',required:['session_id'],mutation:false},
38
+ remote_device:{native:'remote_device',required:['device_id'],mutation:false},
39
+ remote_mcp:{native:'remote_mcp',required:['request_id'],mutation:false},
40
+ resources:{native:'commander.resources',required:[],mutation:false},
41
+ set_config_value:{native:'commander.config.set',required:['key','value','expected_sha256'],mutation:true},
42
+ skill_catalog:{native:'skill.catalog',required:[],mutation:false},
43
+ start_process:{native:'process.start',required:['profile_id','root_id'],mutation:true},
44
+ start_search:{native:'machine_search.start',required:['root_id','path','pattern'],mutation:false},
45
+ stop_search:{native:'machine_search.cancel',required:['search_id'],mutation:false},
46
+ workspace_open:{native:'workspace.open',required:['root_id'],mutation:false},
47
+ write_file:{native:'machine_fs.write',required:['root_id','path','content','expected_sha256'],mutation:true},
48
+ write_pdf:{native:'document.write_pdf',required:['root_id','path','content','expected_sha256'],mutation:true}
49
+ });
50
+
51
+ export function commanderCompatibilityContract(name) {
52
+ if (!COMMANDER_COMPAT_TOOLS.includes(name)) throw new Error(`unsupported Commander capability: ${name}`);
53
+ return CONTRACTS[name] ?? {native:DEADBYTE_NATIVE_TOOLS[name] ?? null,required:[],mutation:false};
54
+ }
55
+
56
+ export function validateCommanderRequest(name,args={}) {
57
+ const c=commanderCompatibilityContract(name);
58
+ if (!args || typeof args!=='object' || Array.isArray(args)) throw new Error('request arguments must be an object');
59
+ const missing=c.required.filter(k=>!own(args,k)||args[k]===undefined||args[k]===null||args[k]==='');
60
+ if(missing.length) throw new Error(`missing required fields: ${missing.join(', ')}`);
61
+ if(c.mutation && !['create_directory','start_process','force_terminate','kill_process','interact_with_process','host_exec'].includes(name) && !('expected_sha256' in args) && !('expected_source_identity' in args)) throw new Error('mutation requires an explicit precondition');
62
+ if((name==='host_exec') && typeof args.script_sha256!=='string') throw new Error('host_exec requires hash-pinned script');
63
+ return Object.freeze({commander_tool:name,native_tool:c.native,mutation:c.mutation,args});
64
+ }
65
+
66
+ export function createCommanderCompatibilityRuntime({invoke,snapshot}) {
67
+ if(typeof invoke!=='function') throw new Error('invoke function required');
68
+ return Object.freeze({
69
+ schema:COMMANDER_COMPAT_RUNTIME_SCHEMA,
70
+ async call(name,args={}) {
71
+ const request=validateCommanderRequest(name,args);
72
+ const before=typeof snapshot==='function'?await snapshot(request):null;
73
+ const result=await invoke(request.native_tool,request.args);
74
+ const after=typeof snapshot==='function'?await snapshot(request):null;
75
+ const resultHash=sha256(result);
76
+ const receipt={schema:COMMANDER_COMPAT_RUNTIME_SCHEMA,commander_tool:name,native_tool:request.native_tool,request_sha256:sha256(request),before_sha256:sha256(before),result_sha256:resultHash,after_sha256:sha256(after),verified:true};
77
+ return {status:'ok',result,receipt};
78
+ }
79
+ });
80
+ }
81
+
82
+ export function assertNoCommanderDependency(sourceText) {
83
+ if(typeof sourceText!=='string') throw new Error('source text must be a string');
84
+ if(/@wonderwhy-er\/desktop-commander|desktop-commander\/dist/.test(sourceText)) throw new Error('native DEADBYTE source must not depend on Commander package');
85
+ return true;
86
+ }
87
+
88
+ export function commanderCompatibilityCoverage() {
89
+ const covered=COMMANDER_COMPAT_TOOLS.filter(name=>CONTRACTS[name]?.native);
90
+ return Object.freeze({schema:COMMANDER_COMPAT_RUNTIME_SCHEMA,covered:covered.length,total:COMMANDER_COMPAT_TOOLS.length,missing:COMMANDER_COMPAT_TOOLS.filter(name=>!CONTRACTS[name]?.native),coverage_sha256:sha256(covered.map(name=>[name,CONTRACTS[name].native]))});
91
+ }
@@ -0,0 +1,30 @@
1
+ import * as z from 'zod/v4';
2
+ const anyArgs=z.record(z.string(),z.unknown());
3
+ const sessions=new Map(); let sessionCounter=0;
4
+ function denied(tool,reason='required native runtime is disabled'){return {status:'denied',tool,error:reason};}
5
+ function register(server,name,schema,handler){server.registerTool(name,{description:'Native Commander-compatible adapter; execution remains inside DEADBYTE policy and evidence boundaries.',inputSchema:schema},handler);}
6
+ function fsOr(runtime,name,fn){return runtime?fn(runtime):denied(name);}
7
+ function processOr(runtime,name,fn){return runtime?fn(runtime):denied(name);}
8
+ function mapResult(item,type){return type==='files'?{file:item.path,type:'file'}:{file:item.path,line:item.line,match:item.preview,type:'content'};}
9
+ function queryArgs(a){return {rootId:a.root_id??'core',path:a.path,pattern:a.pattern,target:a.searchType==='content'?'content':'path',mode:a.literalSearch===true?'literal':'regex',caseSensitive:a.ignoreCase===false,maxResults:Number.isInteger(a.maxResults)?Math.min(Math.max(a.maxResults,1),1000):200,maxFileBytes:262144,maxScanBytes:64*1024*1024,pageSize:500,cursor:null};}
10
+ export function registerCommanderCoreBridge(server,{machineFsRuntime=null,processRuntime=null,searchRuntime=null,remoteDevicePlane=null}={}){
11
+ register(server,'read_file',z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(2048),offset:z.number().int().min(0).default(0),max_bytes:z.number().int().min(1).max(4194304).default(65536),hash_file:z.boolean().default(false),timeout_ms:z.number().int().min(100).max(180000).default(30000)}),a=>fsOr(machineFsRuntime,'read_file',r=>r.read({rootId:a.root_id,relativePath:a.path,offset:a.offset,maxBytes:a.max_bytes,hashFile:a.hash_file,timeoutMs:a.timeout_ms})));
12
+ register(server,'read_multiple_files',z.strictObject({root_id:z.string().min(1).max(64),paths:z.array(z.string().min(1).max(2048)).min(1).max(64),max_bytes:z.number().int().min(1).max(4194304).default(65536),timeout_ms:z.number().int().min(100).max(180000).default(30000)}),a=>fsOr(machineFsRuntime,'read_multiple_files',r=>r.readMany({rootId:a.root_id,relativePaths:a.paths,maxBytes:a.max_bytes,timeoutMs:a.timeout_ms})));
13
+ register(server,'write_file',z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(2048),content:z.string().max(12582912),encoding:z.enum(['utf8','base64']).default('utf8'),expected_sha256:z.string().regex(/^(?:absent|[0-9a-f]{64})$/)}),a=>fsOr(machineFsRuntime,'write_file',r=>r.write({rootId:a.root_id,relativePath:a.path,content:a.content,encoding:a.encoding,expectedSha256:a.expected_sha256})));
14
+ register(server,'create_directory',z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(2048)}),a=>fsOr(machineFsRuntime,'create_directory',r=>r.mkdir({rootId:a.root_id,relativePath:a.path})));
15
+ register(server,'list_directory',z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(2048).default('.'),cursor:z.string().max(4096).nullable().default(null),limit:z.number().int().min(1).max(1000).default(100),hash_files:z.boolean().default(false)}),a=>fsOr(machineFsRuntime,'list_directory',r=>r.list({rootId:a.root_id,relativePath:a.path,cursor:a.cursor,limit:a.limit,hashFiles:a.hash_files})));
16
+ register(server,'move_file',z.strictObject({root_id:z.string().min(1).max(64),source_path:z.string().min(1).max(2048),destination_path:z.string().min(1).max(2048),expected_source_identity:z.string().regex(/^[0-9a-f]{64}$/)}),a=>fsOr(machineFsRuntime,'move_file',r=>r.move({rootId:a.root_id,sourcePath:a.source_path,destinationPath:a.destination_path,expectedSourceIdentity:a.expected_source_identity})));
17
+ register(server,'get_file_info',z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(2048),hash:z.boolean().default(false)}),a=>fsOr(machineFsRuntime,'get_file_info',r=>r.stat({rootId:a.root_id,relativePath:a.path,hash:a.hash})));
18
+ register(server,'edit_block',z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(2048),expected_sha256:z.string().regex(/^[0-9a-f]{64}$/),old_string:z.string().min(1).max(262144),new_string:z.string().max(524288),expected_replacements:z.number().int().min(1).max(64).default(1)}),a=>fsOr(machineFsRuntime,'edit_block',r=>r.editExact({rootId:a.root_id,relativePath:a.path,expectedSha256:a.expected_sha256,oldString:a.old_string,newString:a.new_string,expectedReplacements:a.expected_replacements})));
19
+ register(server,'start_process',anyArgs,a=>processOr(processRuntime,'start_process',r=>r.start(a)));
20
+ register(server,'read_process_output',anyArgs,a=>processOr(processRuntime,'read_process_output',r=>r.read(a)));
21
+ register(server,'interact_with_process',anyArgs,a=>processOr(processRuntime,'interact_with_process',r=>r.action(a)));
22
+ register(server,'force_terminate',anyArgs,a=>processOr(processRuntime,'force_terminate',r=>r.terminate(a)));
23
+ register(server,'list_sessions',z.strictObject({}),()=>processOr(processRuntime,'list_sessions',r=>r.list()));
24
+ register(server,'list_processes',z.strictObject({}),()=>processOr(processRuntime,'list_processes',r=>r.list()));
25
+ register(server,'kill_process',anyArgs,a=>processOr(processRuntime,'kill_process',r=>r.cancel(a)));
26
+ register(server,'start_search',z.strictObject({path:z.string().min(1),pattern:z.string().min(1).max(256),searchType:z.enum(['files','content']).default('files'),filePattern:z.string().optional(),ignoreCase:z.boolean().default(true),maxResults:z.number().int().min(1).max(1000).optional(),includeHidden:z.boolean().default(false),contextLines:z.number().int().min(0).max(50).default(5),timeout_ms:z.number().int().min(100).max(120000).optional(),earlyTermination:z.boolean().optional(),literalSearch:z.boolean().default(false),origin:z.enum(['ui','llm']).optional()}),async a=>{if(!searchRuntime)return denied('start_search','dedicated search runtime is disabled');const id='search-'+(++sessionCounter)+'-'+Date.now().toString(36);const s={id,searchType:a.searchType,pattern:a.pattern,startTime:Date.now(),isComplete:false,isError:false,results:[],totalMatches:0,wasIncomplete:false};sessions.set(id,s);s.promise=(async()=>{try{const r=await searchRuntime(queryArgs(a));s.results=r.matches.map(x=>mapResult(x,a.searchType)).slice(0,a.maxResults??200);s.totalMatches=r.matches.length;s.wasIncomplete=r.truncated===true;s.isComplete=true;}catch(e){s.isError=true;s.error=String(e?.message??e).slice(0,512);s.isComplete=true;}})();return {sessionId:id,isComplete:false,isError:false,results:[],totalResults:0,runtime:0};});
27
+ register(server,'get_more_search_results',z.strictObject({sessionId:z.string().min(1).max(128),offset:z.number().int().default(0),length:z.number().int().min(1).max(1000).default(100)}),async({sessionId,offset,length})=>{const s=sessions.get(sessionId);if(!s)return {status:'error',error:'search session not found'};if(!s.isComplete)return {results:[],returnedCount:0,totalResults:s.results.length,totalMatches:s.totalMatches,isComplete:false,isError:false,hasMoreResults:true,runtime:Date.now()-s.startTime};const start=offset<0?Math.max(0,s.results.length+offset):offset;const end=offset<0?s.results.length:Math.min(s.results.length,start+length);const results=s.results.slice(start,end);return {results,returnedCount:results.length,totalResults:s.results.length,totalMatches:s.totalMatches,isComplete:true,isError:s.isError,error:s.error,hasMoreResults:end<s.results.length,runtime:Date.now()-s.startTime,wasIncomplete:s.wasIncomplete};});
28
+ register(server,'stop_search',z.strictObject({sessionId:z.string().min(1).max(128)}),async({sessionId})=>{const s=sessions.get(sessionId);if(!s)return false;s.cancelled=true;return true;});
29
+ register(server,'list_searches',z.strictObject({}),async()=>[...sessions.values()].map(s=>({id:s.id,searchType:s.searchType,pattern:s.pattern,isComplete:s.isComplete,isError:s.isError,runtime:Date.now()-s.startTime,totalResults:s.results.length})));
30
+ }
@@ -0,0 +1,74 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, writeFile, rename } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { canonicalJson } from './canonical-json.mjs';
5
+
6
+ export const COMMANDER_EXTENDED_SCHEMA='deadbyte.commander-extended.v1';
7
+ const MAX_VALUE=16384,MAX_FEEDBACK=4096,MAX_PREVIEW=65536;
8
+ const sha256=v=>createHash('sha256').update(Buffer.isBuffer(v)?v:Buffer.from(typeof v==='string'?v:canonicalJson(v),'utf8')).digest('hex');
9
+ const keyOk=k=>typeof k==='string'&&/^[A-Za-z0-9._-]{1,128}$/.test(k);
10
+
11
+ function pdfEscape(s){return String(s).replaceAll('\\\\','\\\\\\\\').replaceAll('(','\\\\(').replaceAll(')','\\\\)');}
12
+ function makePdf(text){
13
+ const nl=String.fromCharCode(10),lines=String(text).replaceAll(String.fromCharCode(13),'').split(nl).slice(0,200);
14
+ const stream=['BT','/F1 11 Tf','50 790 14 TL',...lines.map(line=>'('+pdfEscape(line).slice(0,180)+') Tj 0 -14 Td'),'ET'].join(nl);
15
+ const objs=['<< /Type /Catalog /Pages 2 0 R >>','<< /Type /Pages /Kids [3 0 R] /Count 1 >>','<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> /Contents 5 0 R >>','<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>','<< /Length '+Buffer.byteLength(stream)+' >>'+nl+'stream'+nl+stream+nl+'endstream'];
16
+ let out='%PDF-1.4'+nl,offsets=[0];
17
+ for(let i=0;i<objs.length;i++){offsets.push(Buffer.byteLength(out));out+=(i+1)+' 0 obj'+nl+objs[i]+nl+'endobj'+nl;}
18
+ const xref=Buffer.byteLength(out);
19
+ out+='xref'+nl+'0 6'+nl+'0000000000 65535 f '+nl+offsets.slice(1).map(o=>String(o).padStart(10,'0')+' 00000 n ').join(nl)+nl+'trailer'+nl+'<< /Size 6 /Root 1 0 R >>'+nl+'startxref'+nl+xref+nl+'%%EOF'+nl;
20
+ return Buffer.from(out,'utf8');
21
+ }
22
+
23
+ export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=[],resources=[]}={}){
24
+ if(typeof stateRoot!=='string'||!stateRoot)throw new Error('extended runtime stateRoot required');
25
+ const configPath=path.join(stateRoot,'commander-config.json'),feedbackDir=path.join(stateRoot,'feedback');
26
+ let configCache=null;
27
+ async function loadConfig(){
28
+ if(configCache)return configCache;
29
+ try{configCache=JSON.parse(await readFile(configPath,'utf8'));}
30
+ catch(e){if(e?.code!=='ENOENT')throw e;configCache={};}
31
+ return configCache;
32
+ }
33
+ async function getConfig({key=null}={}){
34
+ const cfg=await loadConfig();
35
+ if(key!==null&&!keyOk(key))throw new Error('config key invalid');
36
+ return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',config:key===null?structuredClone(cfg):{[key]:cfg[key]},config_sha256:sha256(cfg)};
37
+ }
38
+ async function setConfigValue({key,value,expected_sha256}){
39
+ if(!keyOk(key))throw new Error('config key invalid');
40
+ if(!['string','number','boolean'].includes(typeof value)&&value!==null)throw new Error('config value must be scalar');
41
+ if(Buffer.byteLength(typeof value==='string'?value:canonicalJson(value))>MAX_VALUE)throw new Error('config value exceeds byte limit');
42
+ const cfg=await loadConfig(),beforeSha=sha256(cfg);
43
+ if(expected_sha256!==beforeSha)throw new Error('config precondition mismatch');
44
+ const next={...cfg,[key]:value};
45
+ await mkdir(stateRoot,{recursive:true});
46
+ const tmp=configPath+'.tmp-'+process.pid+'-'+randomUUID();
47
+ await writeFile(tmp,canonicalJson(next),{encoding:'utf8',flag:'wx',mode:0o600});
48
+ await rename(tmp,configPath); configCache=next;
49
+ return {schema:COMMANDER_EXTENDED_SCHEMA,status:'committed',key,previous_sha256:beforeSha,config_sha256:sha256(next)};
50
+ }
51
+ async function feedback({message,category='general'}){
52
+ if(typeof message!=='string'||!message.trim()||Buffer.byteLength(message)>MAX_FEEDBACK)throw new Error('feedback message invalid');
53
+ if(!/^[A-Za-z0-9._-]{1,64}$/.test(category))throw new Error('feedback category invalid');
54
+ await mkdir(feedbackDir,{recursive:true});
55
+ const id=randomUUID(),record={schema:COMMANDER_EXTENDED_SCHEMA,id,category,message:message.trim(),created_at_utc:new Date().toISOString()};
56
+ await writeFile(path.join(feedbackDir,id+'.json'),canonicalJson(record),{encoding:'utf8',flag:'wx',mode:0o600});
57
+ return {status:'committed',feedback_id:id,feedback_sha256:sha256(record)};
58
+ }
59
+ function getPrompts(){return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',prompts:prompts.map(x=>({...x})).slice(0,128)};}
60
+ function getResources(){return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',resources:resources.map(x=>({...x})).slice(0,128)};}
61
+ async function filePreview({rootId,path:relativePath,offset=0,maxBytes=MAX_PREVIEW,mimeType='text/plain'}){
62
+ if(!Number.isInteger(offset)||offset<0||offset>64*1024*1024)throw new Error('preview offset invalid');
63
+ if(!Number.isInteger(maxBytes)||maxBytes<1||maxBytes>MAX_PREVIEW)throw new Error('preview maxBytes invalid');
64
+ if(typeof rootId!=='string'||typeof relativePath!=='string'||path.isAbsolute(relativePath))throw new Error('preview target invalid');
65
+ const data=await readFile(path.resolve(rootId,relativePath)),slice=data.subarray(offset,offset+maxBytes);
66
+ return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',root_id:rootId,path:relativePath,mime_type:mimeType,offset,bytes:slice.length,total_bytes:data.length,truncated:offset+slice.length<data.length,sha256:sha256(slice),text:mimeType.startsWith('text/')?slice.toString('utf8'):null};
67
+ }
68
+ function writePdf({text,metadata={}}){
69
+ if(typeof text!=='string'||Buffer.byteLength(text)>MAX_PREVIEW)throw new Error('PDF text exceeds byte limit');
70
+ const bytes=makePdf(text);
71
+ return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',mime_type:'application/pdf',bytes_base64:bytes.toString('base64'),bytes_sha256:sha256(bytes),bytes:bytes.length,metadata};
72
+ }
73
+ return Object.freeze({getConfig,setConfigValue,feedback,getPrompts,getResources,filePreview,writePdf,history});
74
+ }
@@ -0,0 +1,33 @@
1
+ import { canonicalJson } from './canonical-json.mjs';
2
+
3
+ const MAX_LIMIT=1000;
4
+ const SCHEMA='deadbyte.commander-observability.v1';
5
+
6
+ function boundLimit(limit){if(!Number.isInteger(limit)||limit<1||limit>MAX_LIMIT)throw new Error('observability limit out of range');return limit;}
7
+
8
+ export function createCommanderObservability({history}={}){
9
+ if(!history||typeof history.read!=='function'||typeof history.verify!=='function'||typeof history.operationId!=='string'||history.operationId.length<1) throw new Error('observability requires operation history');
10
+ function recentToolCalls({afterSequence=0,limit=100}={}){
11
+ boundLimit(limit);
12
+ const page=history.read({afterSequence,limit});
13
+ const events=page.events.filter(e=>e.event_type==='tool.call'||e.event_type==='tool.result'||e.event_type==='tool.error');
14
+ return {schema:SCHEMA,operation_id:page.operation_id,events,next_sequence:page.next_sequence,head_event_hash:page.head_event_hash,earliest_sequence:page.earliest_sequence,overflow:page.overflow};
15
+ }
16
+ function usageStats(){
17
+ const page=history.read({afterSequence:0,limit:MAX_LIMIT});
18
+ const counts={};
19
+ let toolCalls=0,toolResults=0,toolErrors=0;
20
+ for(const event of page.events){
21
+ if(event.event_type==='tool.call') toolCalls++;
22
+ else if(event.event_type==='tool.result') toolResults++;
23
+ else if(event.event_type==='tool.error') toolErrors++;
24
+ else continue;
25
+ const name=event.payload?.tool;
26
+ if(typeof name==='string'&&name.length) counts[name]=(counts[name]??0)+1;
27
+ }
28
+ return {schema:SCHEMA,operation_id:page.operation_id,events_visible:page.events.length,tool_calls:toolCalls,tool_results:toolResults,tool_errors:toolErrors,tool_counts:Object.fromEntries(Object.entries(counts).sort(([a],[b])=>a.localeCompare(b))),head_event_hash:page.head_event_hash,earliest_sequence:page.earliest_sequence,overflow:page.overflow,integrity:history.verify()};
29
+ }
30
+ return Object.freeze({usageStats,recentToolCalls});
31
+ }
32
+
33
+ export const COMMANDER_OBSERVABILITY_SCHEMA=SCHEMA;
@@ -0,0 +1,41 @@
1
+ import * as z from 'zod/v4';
2
+
3
+ const HEX64=/^[0-9a-f]{64}$/;
4
+ const CAP=/^[A-Za-z0-9._-]{1,128}$/;
5
+ function register(server,name,schema,handler){
6
+ server.registerTool(name,{description:'Native remote Commander-compatible bridge backed directly by DEADBYTE remote-device-r44. No Commander runtime is invoked.',inputSchema:schema},handler);
7
+ }
8
+ function requirePlane(plane,name){
9
+ if(!plane) return {status:'unavailable',tool:name,error:'remote-device-r44 plane is not configured'};
10
+ return null;
11
+ }
12
+ export function registerCommanderRemoteBridge(server,{remoteDevicePlane=null}={}){
13
+ register(server,'remote_device',z.strictObject({
14
+ op:z.enum(['pair','status','report_online','probe','dispatch','revoke','rotate_credential','list']),
15
+ device_id:z.string().regex(HEX64).optional(),epoch:z.number().int().min(1).optional(),
16
+ public_key_spki_base64:z.string().min(1).max(4096).optional(),display_name:z.string().min(1).max(128).optional(),
17
+ connection_id:z.string().min(1).max(256).optional(),capability_id:z.string().regex(CAP).optional(),
18
+ payload:z.record(z.string(),z.unknown()).optional(),request_id:z.string().regex(HEX64).optional(),
19
+ encrypted_bundle_base64:z.string().min(1).max(1024*1024).optional(),expected_previous_sha256:z.string().regex(HEX64).nullable().optional()
20
+ }),async a=>{
21
+ const unavailable=requirePlane(remoteDevicePlane,'remote_device');if(unavailable)return unavailable;
22
+ switch(a.op){
23
+ case 'pair': return remoteDevicePlane.pair({publicKeySpkiBase64:a.public_key_spki_base64,displayName:a.display_name});
24
+ case 'status': return remoteDevicePlane.status({deviceId:a.device_id});
25
+ case 'report_online': return remoteDevicePlane.reportOnline({deviceId:a.device_id,epoch:a.epoch,connectionId:a.connection_id});
26
+ case 'probe': return remoteDevicePlane.probe({deviceId:a.device_id,epoch:a.epoch});
27
+ case 'dispatch': return remoteDevicePlane.dispatch({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload??{},requestId:a.request_id});
28
+ case 'revoke': return remoteDevicePlane.revoke({deviceId:a.device_id,epoch:a.epoch});
29
+ case 'rotate_credential': return remoteDevicePlane.rotateCredential({deviceId:a.device_id,epoch:a.epoch,encryptedBundleBase64:a.encrypted_bundle_base64,expectedPreviousSha256:a.expected_previous_sha256??null});
30
+ case 'list': return remoteDevicePlane.list();
31
+ default: throw new Error(`unsupported remote_device operation: ${String(a.op)}`);
32
+ }
33
+ });
34
+ register(server,'remote_mcp',z.strictObject({
35
+ device_id:z.string().regex(HEX64),epoch:z.number().int().min(1),capability_id:z.string().regex(CAP),
36
+ payload:z.record(z.string(),z.unknown()).default({}),request_id:z.string().regex(HEX64)
37
+ }),async a=>{
38
+ const unavailable=requirePlane(remoteDevicePlane,'remote_mcp');if(unavailable)return unavailable;
39
+ return remoteDevicePlane.dispatch({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload,requestId:a.request_id});
40
+ });
41
+ }
@@ -0,0 +1,44 @@
1
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
2
+
3
+ function insist(ok,message){if(!ok)throw new Error(message);}
4
+ function structured(result,label){
5
+ if(result?.isError) throw new Error(`${label} returned MCP error`);
6
+ const value=result?.structuredContent;
7
+ insist(value&&typeof value==='object',`${label} missing structured output`);
8
+ return value;
9
+ }
10
+
11
+ export async function createCommanderRemoteRelayR47({endpoint,protocolVersion='2026-07-28',timeoutMs=15000}={}){
12
+ const url=new URL(String(endpoint??''));
13
+ insist(url.protocol==='https:','remote relay endpoint must use HTTPS');
14
+ insist(Number.isInteger(timeoutMs)&&timeoutMs>=100&&timeoutMs<=120000,'remote relay timeout invalid');
15
+ const client=new Client({name:'deadbyte-r47-remote-relay',version:'0.15.15'},{versionNegotiation:{mode:{pin:protocolVersion}}});
16
+ const transport=new StreamableHTTPClientTransport(url);
17
+ let connected=false;
18
+ let connecting=null;
19
+ async function ensureConnected(){
20
+ if(connected)return;
21
+ if(!connecting){connecting=client.connect(transport).then(()=>{connected=true;}).finally(()=>{connecting=null;});}
22
+ await connecting;
23
+ }
24
+ async function call(name,arguments_,label){await ensureConnected();return structured(await client.callTool({name,arguments:arguments_}),label);}
25
+ async function probe({device_id,epoch,connection_id,nonce}){
26
+ const value=await call('remote_device',{op:'probe',device_id,epoch,connection_id,nonce},'remote readiness probe');
27
+ const result=value.result??value;
28
+ return {device_id,epoch,nonce,ready:result.ready===true||result.reachable===true};
29
+ }
30
+ async function request({device_id,epoch,capability_id,payload,request_id,request_sha256}){
31
+ insist(typeof request_sha256==='string'&&/^[0-9a-f]{64}$/.test(request_sha256),'remote request hash invalid');
32
+ const value=await call('remote_mcp',{device_id,epoch,capability_id,payload,request_id,request_sha256},'remote dispatch');
33
+ const result=value.result??value;
34
+ if(result?.request_id!==undefined) insist(result.request_id===request_id,'remote request identity mismatch');
35
+ if(result?.status==='completed'||result?.state==='completed'){
36
+ insist(typeof result.result_sha256==='string'&&/^[0-9a-f]{64}$/.test(result.result_sha256),'remote terminal result hash invalid');
37
+ return {ack:'accepted',request_id,terminal:{status:'completed',result_sha256:result.result_sha256}};
38
+ }
39
+ if(result?.status==='failed'||result?.state==='failed') return {ack:'accepted',request_id,terminal:{status:'failed',error_code:result.error_code??'remote_failed'}};
40
+ throw new Error('remote dispatch returned non-terminal state');
41
+ }
42
+ async function close(){connected=false;await client.close().catch(()=>{});}
43
+ return Object.freeze({probe,request,close,endpoint:url.toString()});
44
+ }
@@ -33,7 +33,36 @@ async function sha256File(file) {
33
33
  }
34
34
 
35
35
  const machineFsReadWorkerPath = fileURLToPath(new URL('./machine-fs-read-worker.mjs', import.meta.url));
36
+ async function boundedReadInProcess({ file, offset, length, hashFile, timeoutMs }) {
37
+ const handle = await open(file, 'r');
38
+ let timer;
39
+ let settled = false;
40
+ const abort = new Promise((_, reject) => {
41
+ timer = setTimeout(() => {
42
+ settled = true;
43
+ void handle.close().catch(() => {});
44
+ reject(new Error(`machine fs read timed out after ${timeoutMs}ms`));
45
+ }, timeoutMs);
46
+ timer.unref?.();
47
+ });
48
+ try {
49
+ const operation = (async () => {
50
+ const buffer = Buffer.alloc(length);
51
+ const result = length ? await handle.read(buffer, 0, length, offset) : { bytesRead: 0 };
52
+ let fileSha256 = null;
53
+ if (hashFile) fileSha256 = await sha256File(file);
54
+ return { schema: 'deadbyte.machine-fs-read-worker.v1', bytes_read: result.bytesRead,
55
+ data_base64: buffer.subarray(0, result.bytesRead).toString('base64'), file_sha256: fileSha256 };
56
+ })();
57
+ operation.catch(() => {});
58
+ return await Promise.race([operation, abort]);
59
+ } finally {
60
+ if (!settled) { clearTimeout(timer); await handle.close().catch(() => {}); }
61
+ }
62
+ }
63
+
36
64
  async function boundedReadWorker({ file, offset, length, hashFile, timeoutMs, workerPath = machineFsReadWorkerPath }) {
65
+ if (workerPath === machineFsReadWorkerPath) return boundedReadInProcess({ file, offset, length, hashFile, timeoutMs });
37
66
  return await new Promise((resolve, reject) => {
38
67
  const child = spawn(process.execPath, [workerPath, file, String(offset), String(length), hashFile ? '1' : '0'], {
39
68
  windowsHide: true, shell: false, stdio: ['ignore','pipe','pipe']
@@ -103,20 +132,27 @@ function assertNotDenied(root, relativePath) {
103
132
  }
104
133
  async function assertNoReparse(rootPath, target, allowMissingLeaf = false) {
105
134
  const relative = path.relative(rootPath, target);
106
- if (relative === '') return;
135
+ if (relative === '') {
136
+ const info = await lstat(rootPath);
137
+ if (info.isSymbolicLink()) throw new Error('machine fs root reparse point denied');
138
+ return info;
139
+ }
107
140
  if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('machine fs path escapes allowed root');
108
141
  const parts = relative.split(path.sep).filter(Boolean);
109
142
  let cursor = rootPath;
143
+ let targetInfo = null;
110
144
  for (let i = 0; i < parts.length; i += 1) {
111
145
  cursor = path.join(cursor, parts[i]);
112
146
  try {
113
147
  const info = await lstat(cursor);
114
148
  if (info.isSymbolicLink()) throw new Error(`machine fs reparse point denied at '${parts.slice(0, i + 1).join('/')}'`);
149
+ targetInfo = info;
115
150
  } catch (error) {
116
- if (allowMissingLeaf && i === parts.length - 1 && error?.code === 'ENOENT') return;
151
+ if (allowMissingLeaf && i === parts.length - 1 && error?.code === 'ENOENT') return null;
117
152
  throw error;
118
153
  }
119
154
  }
155
+ return targetInfo;
120
156
  }
121
157
 
122
158
  async function resolveExisting(policy, grant, rootId, relativePath, permission, allowDot = false) {
@@ -126,10 +162,10 @@ async function resolveExisting(policy, grant, rootId, relativePath, permission,
126
162
  if (normalized !== '.') assertNotDenied(root, normalized);
127
163
  const lexical = normalized === '.' ? root.real_path : path.resolve(root.real_path, normalized);
128
164
  if (!isInside(root.real_path, lexical) && lexical !== root.real_path) throw new Error('machine fs path escapes allowed root');
129
- await assertNoReparse(root.real_path, lexical, false);
130
- const resolved = await realpath(lexical);
165
+ const targetInfo = await assertNoReparse(root.real_path, lexical, false);
166
+ const resolved = lexical === root.real_path ? lexical : await realpath(lexical);
131
167
  if (!isInside(root.real_path, resolved) && resolved !== root.real_path) throw new Error('machine fs path resolves outside allowed root');
132
- return { root, lexical, resolved, relative: normalized };
168
+ return { root, lexical, resolved, relative: normalized, targetInfo };
133
169
  }
134
170
  async function resolveTarget(policy, grant, rootId, relativePath, permission) {
135
171
  const normalized = normalizeRelative(relativePath, false);
@@ -208,15 +244,41 @@ async function objectIdentity(target, budget = { remaining: MAX_DIRECTORY_IDENTI
208
244
  return { ...core, identity_sha256: sha256Object(core) };
209
245
  }
210
246
 
211
- function decodeCursor(cursor) {
247
+ const LIST_CURSOR_DOMAIN = Buffer.from('DEADBYTE-MACHINE-FS-LIST-CURSOR-V3\\0', 'utf8');
248
+ async function encodeListCursor({ path: relativePath, snapshot_sha256, after_name, signingKeyPath }) {
249
+ const signing = await loadPrivateSigningKey(signingKeyPath);
250
+ const body = { schema: 'deadbyte.machine-fs-list-cursor.v3', path: relativePath, snapshot_sha256, after_name };
251
+ const payload = Buffer.from(canonicalJson(body), 'utf8');
252
+ const signature_base64 = cryptoSign(null, Buffer.concat([LIST_CURSOR_DOMAIN, payload]), signing.privateKey).toString('base64');
253
+ return Buffer.from(canonicalJson({
254
+ ...body,
255
+ attestation: { algorithm: 'ed25519', key_id: signing.keyId, signature_base64 }
256
+ }), 'utf8').toString('base64url');
257
+ }
258
+ async function decodeListCursor(cursor, verifyKeyPath) {
212
259
  if (cursor === null || cursor === undefined) return null;
213
- if (typeof cursor !== 'string' || cursor.length < 1 || cursor.length > 2048) throw new Error('machine fs cursor invalid');
260
+ if (typeof cursor !== 'string' || cursor.length < 1 || cursor.length > 4096) throw new Error('machine fs cursor invalid');
214
261
  let decoded;
215
- try { decoded = Buffer.from(cursor, 'base64url').toString('utf8'); }
262
+ try { decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')); }
216
263
  catch { throw new Error('machine fs cursor invalid'); }
217
- if (!decoded || Buffer.from(decoded, 'utf8').toString('base64url') !== cursor) throw new Error('machine fs cursor invalid');
264
+ if (!decoded || decoded.schema !== 'deadbyte.machine-fs-list-cursor.v3' ||
265
+ typeof decoded.path !== 'string' || !SHA256_RE.test(decoded.snapshot_sha256 ?? '') ||
266
+ typeof decoded.after_name !== 'string' || decoded.after_name.length < 1 ||
267
+ decoded.attestation?.algorithm !== 'ed25519' || typeof decoded.attestation?.key_id !== 'string' ||
268
+ typeof decoded.attestation?.signature_base64 !== 'string') throw new Error('machine fs cursor invalid');
269
+ if (Buffer.from(canonicalJson(decoded), 'utf8').toString('base64url') !== cursor) throw new Error('machine fs cursor invalid');
270
+ const { attestation, ...body } = decoded;
271
+ const verifyKey = await loadPublicVerifyKey(verifyKeyPath);
272
+ if (attestation.key_id !== verifyKey.keyId) throw new Error('machine fs cursor key mismatch');
273
+ const signature = Buffer.from(attestation.signature_base64, 'base64');
274
+ const valid = signature.length === 64 &&
275
+ cryptoVerify(null, Buffer.concat([LIST_CURSOR_DOMAIN, Buffer.from(canonicalJson(body), 'utf8')]), verifyKey.publicKey, signature);
276
+ if (!valid) throw new Error('machine fs cursor signature mismatch');
218
277
  return decoded;
219
278
  }
279
+ function directoryListingSnapshot(children) {
280
+ return sha256Object(children.map(child => ({ name: child.name, kind: child.isSymbolicLink() ? 'reparse' : child.isDirectory() ? 'directory' : child.isFile() ? 'file' : 'other' })));
281
+ }
220
282
 
221
283
  function encodeCursor(name) { return Buffer.from(name, 'utf8').toString('base64url'); }
222
284
 
@@ -286,7 +348,14 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
286
348
  const info = await lstat(found.resolved);
287
349
  const kind = kindOf(info);
288
350
  if (kind === 'reparse') throw new Error('machine fs reparse target denied');
289
- const identity = await objectIdentity(found.resolved);
351
+ let identity;
352
+ if (kind === 'file') {
353
+ const contentSha256 = await sha256File(found.resolved);
354
+ const core = { kind: 'file', bytes: info.size, content_sha256: contentSha256 };
355
+ identity = { ...core, identity_sha256: sha256Object(core) };
356
+ } else {
357
+ identity = await objectIdentity(found.resolved);
358
+ }
290
359
  return {
291
360
  status: 'ok', root_id: rootId, path: found.relative, kind,
292
361
  bytes: kind === 'file' ? info.size : 0,
@@ -300,27 +369,29 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
300
369
  if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT) throw new Error(`machine fs list limit must be in [1, ${MAX_LIST_LIMIT}]`);
301
370
  const grant = await assertCodingArmed(policy, rootId);
302
371
  const found = await resolveExisting(policy, grant, rootId, relativePath, 'read', true);
303
- const info = await lstat(found.resolved);
304
- if (!info.isDirectory()) throw new Error('machine fs list path must be a directory');
305
- const afterName = decodeCursor(cursor);
372
+ const info = found.targetInfo;
373
+ if (!info?.isDirectory()) throw new Error('machine fs list path must be a directory');
374
+ const decodedCursor = await decodeListCursor(cursor, verifyKeyPath);
375
+ if (decodedCursor && decodedCursor.path !== found.relative) throw new Error('machine fs cursor path mismatch');
306
376
  const children = await readdir(found.resolved, { withFileTypes: true });
307
377
  children.sort((a, b) => a.name.localeCompare(b.name));
378
+ const snapshot_sha256 = directoryListingSnapshot(children);
379
+ if (decodedCursor && decodedCursor.snapshot_sha256 !== snapshot_sha256) throw new Error('machine fs cursor snapshot changed');
380
+ const afterName = decodedCursor?.after_name ?? null;
308
381
  const filtered = children.filter(child => afterName === null || child.name.localeCompare(afterName) > 0);
309
382
  const selected = filtered.slice(0, limit);
310
- const entries = [];
311
- for (const child of selected) {
383
+ const entries = await Promise.all(selected.map(async child => {
312
384
  const rel = found.relative === '.' ? child.name : `${found.relative}/${child.name}`;
313
- if (child.isSymbolicLink()) {
314
- entries.push({ path: rel, kind: 'reparse', bytes: 0, mtime_utc: null, sha256: null });
315
- continue;
316
- }
385
+ if (child.isSymbolicLink()) return { path: rel, kind: 'reparse', bytes: 0, mtime_utc: null, sha256: null };
317
386
  const childPath = path.join(found.resolved, child.name);
318
387
  const childInfo = await lstat(childPath);
319
388
  const kind = kindOf(childInfo);
320
389
  const digest = hashFiles && kind === 'file' ? await sha256File(childPath) : null;
321
- entries.push({ path: rel, kind, bytes: kind === 'file' ? childInfo.size : 0, mtime_utc: childInfo.mtime.toISOString(), sha256: digest });
322
- }
323
- const nextCursor = filtered.length > selected.length && selected.length ? encodeCursor(selected.at(-1).name) : null;
390
+ return { path: rel, kind, bytes: kind === 'file' ? childInfo.size : 0, mtime_utc: childInfo.mtime.toISOString(), sha256: digest };
391
+ }));
392
+ const nextCursor = filtered.length > selected.length && selected.length
393
+ ? await encodeListCursor({ path: found.relative, snapshot_sha256, after_name: selected.at(-1).name, signingKeyPath })
394
+ : null;
324
395
  return { status: 'ok', root_id: rootId, path: found.relative, entries, next_cursor: nextCursor };
325
396
  }
326
397
  async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false, timeoutMs = 30000 }) {
@@ -348,6 +419,20 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
348
419
  };
349
420
  }
350
421
 
422
+ async function readMany({ rootId, paths, maxBytes = 65536, timeoutMs = 30000 }) {
423
+ if (!Array.isArray(paths) || paths.length < 1 || paths.length > 64) throw new Error('machine fs read_many paths must contain 1-64 entries');
424
+ if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > policy.max_read_bytes) throw new Error(`machine fs read_many max_bytes must be in [1, ${policy.max_read_bytes}]`);
425
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 180000) throw new Error('machine fs read_many timeout_ms must be in [100, 180000]');
426
+ const unique = new Set();
427
+ for (const relativePath of paths) {
428
+ if (typeof relativePath !== 'string' || relativePath.length < 1 || relativePath.length > 512) throw new Error('machine fs read_many path invalid');
429
+ if (unique.has(relativePath)) throw new Error(`machine fs read_many duplicate path '${relativePath}'`);
430
+ unique.add(relativePath);
431
+ }
432
+ const results = await Promise.all(paths.map(relativePath => readRange({ rootId, relativePath, offset: 0, maxBytes, timeoutMs })));
433
+ return { status: 'ok', root_id: rootId, count: results.length, results };
434
+ }
435
+
351
436
  async function writePath({ rootId, relativePath, content, encoding = 'utf8', expectedSha256, receiptId = null }) {
352
437
  const grant = await assertCodingArmed(policy, rootId);
353
438
  const target = await resolveTarget(policy, grant, rootId, relativePath, 'write');
@@ -551,6 +636,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
551
636
  stat: statPath,
552
637
  list: listPage,
553
638
  read: readRange,
639
+ readMany,
554
640
  write: writePath,
555
641
  editExact,
556
642
  move: movePath,