deadbyte-mcp 0.15.18 → 0.15.19
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 +54 -32
- package/PROVENANCE.json +13 -7
- package/README.txt +7 -7
- package/RELEASE.json +25 -6
- package/SBOM.cdx.json +3 -3
- package/TEST-MATRIX.json +26 -7
- package/controller/deadbyte-controller.ps1 +56 -1
- package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +247 -44
- package/docs/COMMANDER_REFERENCE_0_2_51.json +46 -0
- package/docs/R47-PROVENANCE.json +11 -0
- package/docs/R47-RELEASE.json +58 -0
- package/docs/plans/2026-09-27-desktop-commander-drop-in-parity.md +92 -0
- package/package.json +4 -2
- package/proof/commander-closure-20260927/CONTINUATION.json +43 -0
- package/proof/commander-closure-20260927/RESULT.json +30 -0
- package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +30 -0
- package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +13 -0
- package/scripts/deploy-r48.mjs +86 -0
- package/scripts/full-regression-r48.mjs +28 -0
- package/src/coding-search.mjs +65 -7
- package/src/commander-compat-contracts.mjs +10 -0
- package/src/commander-core-bridge.mjs +2 -2
- package/src/commander-extended-bridge-r47.mjs +116 -12
- package/src/commander-extended-runtime.mjs +33 -6
- package/src/commander-external-process-r48.mjs +125 -0
- package/src/commander-observability.mjs +66 -16
- package/src/commander-office-reader-r48.mjs +548 -0
- package/src/commander-pdf-r48.mjs +448 -0
- package/src/commander-ui-resources.mjs +26 -0
- package/src/commander-url-reader-r48.mjs +201 -0
- package/src/commander-wire-adapter-r47.mjs +595 -101
- package/src/deadbyte-cli.mjs +73 -8
- package/src/mcp-server.mjs +9 -2
- package/src/observation-output-contracts.mjs +2 -1
- package/src/observation-runtime.mjs +4 -2
- package/src/version.mjs +1 -1
- package/test/coding-search-r36.test.mjs +71 -0
- package/test/commander-core-bridge-r47.test.mjs +32 -3
- package/test/commander-exhaustive-surface-r47.test.mjs +5 -1
- package/test/commander-extended-runtime-r47.test.mjs +8 -0
- package/test/commander-extended-wire-r48.test.mjs +98 -0
- package/test/commander-external-process-r48.test.mjs +76 -0
- package/test/commander-office-reader-r48.test.mjs +152 -0
- package/test/commander-pdf-r48.test.mjs +103 -0
- package/test/commander-protocol-r47.test.mjs +23 -1
- package/test/commander-reference-contract-r48.test.mjs +26 -0
- package/test/commander-schema-differential-r48.test.mjs +88 -0
- package/test/commander-url-reader-r48.test.mjs +68 -0
- package/test/commander-wire-contract-r47.test.mjs +461 -41
- package/test/controller-r212.test.mjs +23 -2
- package/test/observation-runtime.test.mjs +4 -3
- package/test/prompts-resources.test.mjs +1 -1
- package/test/r47-release-contract.test.mjs +9 -8
- package/test/r48-release-contract.test.mjs +36 -0
- package/test/remote-session-cli.test.mjs +34 -1
|
@@ -1,59 +1,85 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { readCommanderUrl } from './commander-url-reader-r48.mjs';
|
|
5
|
+
import { readDocxBytes, createDocxBytes, editDocxBytes, readXlsxBytes, editXlsxBytes, createXlsxBytes, appendXlsxBytes } from './commander-office-reader-r48.mjs';
|
|
6
|
+
import { editDeadbytePdf, parseDeadbytePdf, readPdfText } from './commander-pdf-r48.mjs';
|
|
4
7
|
|
|
5
8
|
const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
9
|
+
const RDC_PROCESS_READ_CHUNK=256*1024;
|
|
10
|
+
const RDC_PROCESS_SNAPSHOT_MAX=16*1024*1024;
|
|
11
|
+
|
|
12
|
+
function splitLineRanges(bytes){
|
|
13
|
+
const ranges=[];
|
|
14
|
+
let start=0;
|
|
15
|
+
for(let i=0;i<bytes.length;i++){
|
|
16
|
+
const b=bytes[i];
|
|
17
|
+
if(b!==0x0a&&b!==0x0d) continue;
|
|
18
|
+
let next=i+1;
|
|
19
|
+
if(b===0x0d&&next<bytes.length&&bytes[next]===0x0a) next++;
|
|
20
|
+
ranges.push({start,end:i,next});
|
|
21
|
+
start=next;i=next-1;
|
|
22
|
+
}
|
|
23
|
+
if(start<bytes.length) ranges.push({start,end:bytes.length,next:bytes.length});
|
|
24
|
+
return ranges;
|
|
25
|
+
}
|
|
26
|
+
function decodeLines(bytes,ranges){return ranges.map(r=>bytes.subarray(r.start,r.end).toString('utf8'));}
|
|
6
27
|
|
|
7
28
|
const anyRecord = z.record(z.string(), z.unknown());
|
|
8
|
-
const empty = z.
|
|
29
|
+
const empty = z.object({});
|
|
9
30
|
const origin = z.enum(['ui','llm']).optional();
|
|
10
31
|
|
|
11
32
|
const RDC = Object.freeze({
|
|
12
|
-
read_file: z.
|
|
33
|
+
read_file: z.object({
|
|
13
34
|
path:z.string(), isUrl:z.boolean().optional().default(false),
|
|
14
35
|
offset:z.number().optional().default(0), length:z.number().optional().default(1000),
|
|
15
36
|
sheet:z.string().optional(), range:z.string().optional(), options:anyRecord.optional(),
|
|
16
37
|
origin
|
|
17
38
|
}),
|
|
18
|
-
read_multiple_files:z.
|
|
19
|
-
write_file:z.
|
|
39
|
+
read_multiple_files:z.object({paths:z.array(z.string())}),
|
|
40
|
+
write_file:z.object({
|
|
20
41
|
path:z.string(), content:z.string(),
|
|
21
42
|
mode:z.enum(['rewrite','append']).default('rewrite'), origin
|
|
22
43
|
}),
|
|
23
|
-
create_directory:z.
|
|
24
|
-
list_directory:z.
|
|
25
|
-
move_file:z.
|
|
26
|
-
get_file_info:z.
|
|
44
|
+
create_directory:z.object({path:z.string()}),
|
|
45
|
+
list_directory:z.object({path:z.string(),depth:z.number().optional().default(2),origin}),
|
|
46
|
+
move_file:z.object({source:z.string(),destination:z.string()}),
|
|
47
|
+
get_file_info:z.object({path:z.string()}),
|
|
27
48
|
edit_block:z.object({
|
|
28
49
|
file_path:z.string(), old_string:z.string().optional(), new_string:z.string().optional(),
|
|
29
50
|
expected_replacements:z.number().optional().default(1), range:z.string().optional(),
|
|
30
51
|
content:z.unknown().optional(), options:anyRecord.optional(), origin
|
|
31
|
-
}),
|
|
32
|
-
start_process:z.
|
|
33
|
-
read_process_output:z.
|
|
34
|
-
interact_with_process:z.
|
|
35
|
-
force_terminate:z.
|
|
52
|
+
}).refine(data=>((data.old_string!==undefined&&data.old_string!==''&&data.new_string!==undefined)||(data.range!==undefined&&data.range!==''&&data.content!==undefined&&data.content!=='')),{message:'Must provide either (old_string + new_string) or (range + content)'}),
|
|
53
|
+
start_process:z.object({command:z.string(),timeout_ms:z.number(),shell:z.string().optional(),verbose_timing:z.boolean().optional(),origin}),
|
|
54
|
+
read_process_output:z.object({pid:z.number(),timeout_ms:z.number().optional(),offset:z.number().optional(),length:z.number().optional(),verbose_timing:z.boolean().optional()}),
|
|
55
|
+
interact_with_process:z.object({pid:z.number(),input:z.string(),timeout_ms:z.number().optional(),wait_for_prompt:z.boolean().optional(),verbose_timing:z.boolean().optional()}),
|
|
56
|
+
force_terminate:z.object({pid:z.number()}),
|
|
36
57
|
list_sessions:empty,
|
|
37
58
|
list_processes:empty,
|
|
38
|
-
kill_process:z.
|
|
39
|
-
start_search:z.
|
|
59
|
+
kill_process:z.object({pid:z.number()}),
|
|
60
|
+
start_search:z.object({
|
|
40
61
|
path:z.string(),pattern:z.string(),searchType:z.enum(['files','content']).default('files'),
|
|
41
62
|
filePattern:z.string().optional(),ignoreCase:z.boolean().optional().default(true),
|
|
42
63
|
maxResults:z.number().optional(),includeHidden:z.boolean().optional().default(false),
|
|
43
64
|
contextLines:z.number().optional().default(5),timeout_ms:z.number().optional(),
|
|
44
65
|
earlyTermination:z.boolean().optional(),literalSearch:z.boolean().optional().default(false),origin
|
|
45
66
|
}),
|
|
46
|
-
get_more_search_results:z.
|
|
47
|
-
stop_search:z.
|
|
67
|
+
get_more_search_results:z.object({sessionId:z.string(),offset:z.number().optional().default(0),length:z.number().optional().default(100)}),
|
|
68
|
+
stop_search:z.object({sessionId:z.string()}),
|
|
48
69
|
list_searches:empty,
|
|
49
|
-
write_pdf:z.
|
|
70
|
+
write_pdf:z.object({
|
|
50
71
|
path:z.string(),
|
|
51
|
-
content:z.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
content:z.preprocess(value=>{
|
|
73
|
+
if(typeof value==='string'&&value.trim().startsWith('[')){
|
|
74
|
+
try{return JSON.parse(value);}catch{}
|
|
75
|
+
}
|
|
76
|
+
return value;
|
|
77
|
+
},z.union([z.string(),z.array(z.union([
|
|
78
|
+
z.object({type:z.literal('insert'),pageIndex:z.number(),markdown:z.string().optional(),sourcePdfPath:z.string().optional(),pdfOptions:anyRecord.optional()}),
|
|
79
|
+
z.object({type:z.literal('delete'),pageIndexes:z.array(z.number())})
|
|
80
|
+
]))])),
|
|
55
81
|
outputPath:z.string().optional(),
|
|
56
|
-
options:anyRecord.optional()
|
|
82
|
+
options:anyRecord.optional()
|
|
57
83
|
})
|
|
58
84
|
});
|
|
59
85
|
|
|
@@ -114,77 +140,204 @@ function richFormat(pathname){
|
|
|
114
140
|
}[ext]??null;
|
|
115
141
|
}
|
|
116
142
|
|
|
117
|
-
export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime=null,searchRuntime=null,extendedRuntime=null}={}){
|
|
143
|
+
export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime=null,searchRuntime=null,extendedRuntime=null,externalProcessRuntime=null,urlReader=readCommanderUrl}={}){
|
|
118
144
|
const searchSessions=new Map();
|
|
119
145
|
|
|
120
146
|
async function readFile(args){
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
147
|
+
const requestedOffset=args.offset??0;
|
|
148
|
+
const requestedLength=args.length??1000;
|
|
149
|
+
if(!Number.isInteger(requestedOffset)) throw new Error('offset must be an integer');
|
|
150
|
+
if(!Number.isInteger(requestedLength)||requestedLength<0) throw new Error('length must be a non-negative integer');
|
|
151
|
+
const ignoredOptions=args.options&&typeof args.options==='object'&&!Array.isArray(args.options)?Object.keys(args.options).sort():[];
|
|
152
|
+
const optionMeta=ignoredOptions.length?{ignoredOptions}:{};
|
|
153
|
+
if(args.isUrl){
|
|
154
|
+
if(args.sheet!==undefined||args.range!==undefined) throw new Error('sheet/range options are not valid for URL reads');
|
|
155
|
+
const remote=await urlReader(args.path);
|
|
156
|
+
if(remote.isPdf===true){
|
|
157
|
+
const pdf=readPdfText(Buffer.from(remote.content,'base64'));
|
|
158
|
+
const content=pdf.pages.map((page,index)=>`## Page ${index+1}\n${page}`).join('\n\n');
|
|
159
|
+
return text(content,false,{
|
|
160
|
+
path:args.path,content,mimeType:'text/markdown',isImage:false,isPdf:true,
|
|
161
|
+
pageOffset:0,pageCount:pdf.page_count,totalPages:pdf.page_count,profile:pdf.profile,partial:pdf.partial===true,warnings:pdf.warnings??[],
|
|
162
|
+
fileSha256:remote.sha256,url:remote.url,...optionMeta
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return text(remote.content,false,{
|
|
166
|
+
path:args.path,content:remote.content,mimeType:remote.mimeType,
|
|
167
|
+
isImage:remote.isImage===true,isPdf:false,
|
|
168
|
+
lineOffset:0,lineCount:remote.isImage?0:remote.content.replaceAll('\r\n','\n').replaceAll('\r','\n').split('\n').length,
|
|
169
|
+
fileSha256:remote.sha256,url:remote.url,...optionMeta
|
|
170
|
+
});
|
|
128
171
|
}
|
|
129
|
-
if(!
|
|
130
|
-
if(!Number.isInteger(args.length??1000)||args.length<0) throw new Error('length must be a non-negative integer');
|
|
172
|
+
if(!machineFsRuntime) throw new Error('machine filesystem runtime is disabled');
|
|
131
173
|
const target=resolveRdcPath(machineFsRuntime,args.path);
|
|
132
|
-
const start=args.offset??0;
|
|
133
|
-
const length=args.length??1000;
|
|
134
|
-
const raw=await machineFsRuntime.read({rootId:target.rootId,relativePath:target.relativePath,offset:0,maxBytes:Math.min(4*1024*1024,machineFsRuntime.policy.max_read_bytes??4*1024*1024),hashFile:false});
|
|
135
|
-
const fileSha256=raw.file_sha256??sha256Bytes(Buffer.from(raw.data_base64,'base64'));
|
|
136
174
|
const ext=path.extname(args.path).toLowerCase();
|
|
175
|
+
const richDocument=ext==='.docx'||ext==='.xlsx'||ext==='.xlsm'||ext==='.pdf';
|
|
137
176
|
const imageMime={'.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.gif':'image/gif','.webp':'image/webp'}[ext];
|
|
177
|
+
if((args.sheet!==undefined||args.range!==undefined)&&!(ext==='.xlsx'||ext==='.xlsm')) {
|
|
178
|
+
throw new Error('sheet/range reads are valid only for native XLSX/XLSM reads');
|
|
179
|
+
}
|
|
180
|
+
if(requestedOffset<0&&!richDocument&&!imageMime&&ext!=='.xls'){
|
|
181
|
+
const policyMax=Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??4*1024*1024);
|
|
182
|
+
const tail=await machineFsRuntime.read({
|
|
183
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:-policyMax,maxBytes:policyMax,hashFile:false
|
|
184
|
+
});
|
|
185
|
+
let decoded=Buffer.from(tail.data_base64,'base64').toString('utf8').replaceAll('\r\n','\n').replaceAll('\r','\n');
|
|
186
|
+
if((tail.offset??0)>0){
|
|
187
|
+
const firstBreak=decoded.indexOf('\n');
|
|
188
|
+
if(firstBreak<0) throw new Error('text tail window contains no complete line');
|
|
189
|
+
decoded=decoded.slice(firstBreak+1);
|
|
190
|
+
}
|
|
191
|
+
let all=decoded.split('\n');
|
|
192
|
+
if(all.length&&all.at(-1)==='') all.pop();
|
|
193
|
+
const wanted=Math.abs(requestedOffset);
|
|
194
|
+
if((tail.offset??0)>0&&all.length<wanted) throw new Error(`text tail window does not contain ${wanted} complete lines`);
|
|
195
|
+
const selected=all.slice(Math.max(0,all.length-wanted));
|
|
196
|
+
const content=selected.join('\n');
|
|
197
|
+
return text(content,false,{
|
|
198
|
+
path:args.path,content,mimeType:'text/plain',isImage:false,isPdf:false,
|
|
199
|
+
lineOffset:Math.max(0,all.length-selected.length),lineCount:selected.length,
|
|
200
|
+
fileSha256:tail.file_sha256??null,tailByteOffset:tail.offset??null,fileSize:tail.file_size??null,...optionMeta
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const maxRead=richDocument?64*1024*1024:4*1024*1024;
|
|
204
|
+
const raw=await machineFsRuntime.read({
|
|
205
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:0,
|
|
206
|
+
maxBytes:Math.min(maxRead,machineFsRuntime.policy.max_read_bytes??maxRead),
|
|
207
|
+
hashFile:richDocument
|
|
208
|
+
});
|
|
209
|
+
if(richDocument&&raw.eof===false) throw new Error('document exceeds bounded native read limit');
|
|
210
|
+
if(!richDocument&&raw.eof===false) throw new Error('text file exceeds bounded compatibility read limit');
|
|
211
|
+
const bytes=Buffer.from(raw.data_base64,'base64');
|
|
212
|
+
const fileSha256=raw.file_sha256??sha256Bytes(bytes);
|
|
213
|
+
|
|
214
|
+
if(ext==='.docx'){
|
|
215
|
+
const doc=readDocxBytes(bytes,{path:args.path,offset:requestedOffset,length:requestedLength});
|
|
216
|
+
return text(doc.content,false,{path:args.path,content:doc.content,mimeType:doc.mimeType,isImage:false,isPdf:false,fileSha256,...doc.metadata,outline:doc.outline,...optionMeta});
|
|
217
|
+
}
|
|
218
|
+
if(ext==='.xlsx'||ext==='.xlsm'){
|
|
219
|
+
const book=readXlsxBytes(bytes,{path:args.path,sheet:args.sheet,range:args.range,offset:requestedOffset,length:requestedLength});
|
|
220
|
+
return text(book.content,false,{path:args.path,content:book.content,mimeType:book.mimeType,isImage:false,isPdf:false,fileSha256,...book.metadata,sheetName:book.sheetName,data:book.data,totalRows:book.totalRows,returnedRows:book.returnedRows,...optionMeta});
|
|
221
|
+
}
|
|
222
|
+
if(ext==='.pdf'){
|
|
223
|
+
const pdf=readPdfText(bytes);
|
|
224
|
+
const start=requestedOffset<0?Math.max(0,pdf.page_count+requestedOffset):Math.min(requestedOffset,pdf.page_count);
|
|
225
|
+
const count=requestedOffset<0?pdf.page_count-start:requestedLength;
|
|
226
|
+
const selected=pdf.pages.slice(start,start+count);
|
|
227
|
+
const content=selected.map((page,index)=>`## Page ${start+index+1}\n${page}`).join('\n\n');
|
|
228
|
+
return text(content,false,{
|
|
229
|
+
path:args.path,content,mimeType:'text/markdown',isImage:false,isPdf:true,fileSha256,
|
|
230
|
+
pageOffset:start,pageCount:selected.length,totalPages:pdf.page_count,profile:pdf.profile,partial:pdf.partial===true,warnings:pdf.warnings??[],...optionMeta
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
138
234
|
if(imageMime) {
|
|
139
235
|
const content=raw.data_base64;
|
|
140
|
-
return text(content,false,{path:args.path,content,mimeType:imageMime,isImage:true,isPdf:false,fileSha256});
|
|
236
|
+
return text(content,false,{path:args.path,content,mimeType:imageMime,isImage:true,isPdf:false,fileSha256,...optionMeta});
|
|
141
237
|
}
|
|
142
|
-
if(
|
|
143
|
-
throw new Error(
|
|
238
|
+
if(ext==='.xls') {
|
|
239
|
+
throw new Error('format .xls requires a native BIFF adapter; request rejected rather than returned as plain text');
|
|
144
240
|
}
|
|
145
241
|
const all=linesFrom(raw.data_base64);
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
242
|
+
const start=requestedOffset<0?Math.max(0,all.length+requestedOffset):Math.min(requestedOffset,all.length);
|
|
243
|
+
const end=requestedOffset<0?all.length:Math.min(all.length,start+requestedLength);
|
|
244
|
+
const selected=all.slice(start,end);
|
|
245
|
+
const content=selected.join('\n')+(requestedOffset>=0&&end<all.length?'\n':'');
|
|
246
|
+
return text(content,false,{path:args.path,content,mimeType:'text/plain',isImage:false,isPdf:false,lineOffset:start,lineCount:selected.length,fileSha256,...optionMeta});
|
|
149
247
|
}
|
|
150
248
|
|
|
151
249
|
async function readMultipleFiles(args){
|
|
152
|
-
if(!machineFsRuntime) throw new Error('machine filesystem runtime is disabled');
|
|
153
250
|
const results=[];
|
|
154
251
|
for(const pathname of args.paths){
|
|
155
252
|
try{
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
253
|
+
const result=await readFile({path:pathname,isUrl:false,offset:0,length:1000});
|
|
254
|
+
if(result?.isError) throw new Error(result?.structuredContent?.error??result?.content?.[0]?.text??'read failed');
|
|
255
|
+
const item=result?.structuredContent??{};
|
|
256
|
+
results.push({
|
|
257
|
+
path:pathname,
|
|
258
|
+
content:String(item.content??result?.content?.[0]?.text??''),
|
|
259
|
+
mimeType:item.mimeType??'text/plain',
|
|
260
|
+
isImage:item.isImage===true,
|
|
261
|
+
isPdf:item.isPdf===true,
|
|
262
|
+
fileSha256:item.fileSha256??null,
|
|
263
|
+
...(item.isPdf?{payload:{
|
|
264
|
+
metadata:{totalPages:item.totalPages??item.pageCount??0,profile:item.profile??null},
|
|
265
|
+
pages:item.content?String(item.content).split(/\n\n(?=## Page \d+\n)/):[]
|
|
266
|
+
}}:{})
|
|
267
|
+
});
|
|
169
268
|
}catch(error){
|
|
170
269
|
results.push({path:pathname,error:error instanceof Error?error.message:String(error)});
|
|
171
270
|
}
|
|
172
271
|
}
|
|
173
|
-
return text(results.map(x=>x.error?`${x.path}: Error: ${x.error}`:`${x.path}
|
|
272
|
+
return text(results.map(x=>x.error?`${x.path}: Error: ${x.error}`:`${x.path}:\n${x.content}`).join('\n\n'),false,{results});
|
|
174
273
|
}
|
|
175
274
|
|
|
176
275
|
async function writeFile(args){
|
|
177
276
|
const target=resolveRdcPath(machineFsRuntime,args.path);
|
|
277
|
+
const ext=path.extname(args.path).toLowerCase();
|
|
278
|
+
const imageExts=new Set(['.png','.jpg','.jpeg','.gif','.webp']);
|
|
279
|
+
if(ext==='.pdf') throw new Error('PDF writes must use write_pdf to preserve document semantics');
|
|
280
|
+
if(ext==='.docx'){
|
|
281
|
+
if(args.mode==='append') throw new Error('DOCX append not supported. Use edit_block to modify existing DOCX files.');
|
|
282
|
+
const generated=createDocxBytes(args.content);
|
|
283
|
+
let expected='absent';
|
|
284
|
+
try{
|
|
285
|
+
const current=await machineFsRuntime.read({rootId:target.rootId,relativePath:target.relativePath,offset:0,maxBytes:Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024),hashFile:true});
|
|
286
|
+
if(current.eof===false) throw new Error('DOCX target exceeds bounded native write limit');
|
|
287
|
+
expected=current.file_sha256??sha256Bytes(Buffer.from(current.data_base64,'base64'));
|
|
288
|
+
}catch(error){if(!/not found|ENOENT/i.test(String(error))) throw error;}
|
|
289
|
+
const result=await machineFsRuntime.write({rootId:target.rootId,relativePath:target.relativePath,content:generated.bytes.toString('base64'),encoding:'base64',expectedSha256:expected});
|
|
290
|
+
return text(`Successfully wrote ${args.path}`,false,{path:args.path,mode:args.mode,status:'committed',format:'docx',bytesSha256:generated.bytes_sha256,...result});
|
|
291
|
+
}
|
|
292
|
+
if(imageExts.has(ext)) throw new Error(`image write_file for ${ext} requires a dedicated native writer`);
|
|
293
|
+
if(ext==='.xls') throw new Error('legacy .xls BIFF write is not supported by a native writer');
|
|
294
|
+
|
|
295
|
+
if(ext==='.xlsx'||ext==='.xlsm'){
|
|
296
|
+
let parsed;
|
|
297
|
+
try{parsed=JSON.parse(args.content);}catch{throw new Error('Invalid XLSX content: expected JSON 2D array or object of sheet arrays');}
|
|
298
|
+
let expected='absent',existing=null;
|
|
299
|
+
try{
|
|
300
|
+
const maxRead=Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024);
|
|
301
|
+
const current=await machineFsRuntime.read({
|
|
302
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:0,maxBytes:maxRead,hashFile:true
|
|
303
|
+
});
|
|
304
|
+
if(current.eof===false) throw new Error('XLSX target exceeds bounded native write limit');
|
|
305
|
+
existing=Buffer.from(current.data_base64,'base64');
|
|
306
|
+
expected=current.file_sha256??sha256Bytes(existing);
|
|
307
|
+
}catch(error){
|
|
308
|
+
if(!/not found|ENOENT/i.test(String(error))) throw error;
|
|
309
|
+
if(args.mode==='append') existing=null;
|
|
310
|
+
}
|
|
311
|
+
const generated=args.mode==='append'&&existing?appendXlsxBytes(existing,parsed):createXlsxBytes(parsed);
|
|
312
|
+
const bytes=generated.bytes??Buffer.from(generated.bytes_base64,'base64');
|
|
313
|
+
const result=await machineFsRuntime.write({
|
|
314
|
+
rootId:target.rootId,relativePath:target.relativePath,
|
|
315
|
+
content:bytes.toString('base64'),encoding:'base64',expectedSha256:expected
|
|
316
|
+
});
|
|
317
|
+
return text(`Successfully wrote ${args.path}`,false,{
|
|
318
|
+
path:args.path,mode:args.mode,status:'committed',format:'xlsx',
|
|
319
|
+
bytesSha256:generated.bytes_sha256??generated.after_sha256??sha256Bytes(bytes),
|
|
320
|
+
appendedRows:generated.appended_rows??0,sheetCount:generated.sheet_count??null,...result
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
178
324
|
let expected='absent';
|
|
179
325
|
let content=args.content;
|
|
180
326
|
try{
|
|
181
|
-
const current=await machineFsRuntime.read({
|
|
182
|
-
|
|
183
|
-
|
|
327
|
+
const current=await machineFsRuntime.read({
|
|
328
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:0,
|
|
329
|
+
maxBytes:Math.min(4*1024*1024,machineFsRuntime.policy.max_read_bytes??4*1024*1024),hashFile:true
|
|
330
|
+
});
|
|
331
|
+
if(current.eof===false) throw new Error('text write target exceeds bounded compatibility limit');
|
|
332
|
+
const currentBytes=Buffer.from(current.data_base64,'base64');
|
|
333
|
+
expected=current.file_sha256??sha256Bytes(currentBytes);
|
|
334
|
+
if(args.mode==='append') content=currentBytes.toString('utf8')+args.content;
|
|
184
335
|
}catch(error){
|
|
185
|
-
if(args.mode==='append'
|
|
336
|
+
if(args.mode==='append'||!/not found|ENOENT/i.test(String(error))) throw error;
|
|
186
337
|
}
|
|
187
|
-
const result=await machineFsRuntime.write({
|
|
338
|
+
const result=await machineFsRuntime.write({
|
|
339
|
+
rootId:target.rootId,relativePath:target.relativePath,content,encoding:'utf8',expectedSha256:expected
|
|
340
|
+
});
|
|
188
341
|
return text(`Successfully wrote ${args.path}`,false,{path:args.path,mode:args.mode,status:'committed',...result});
|
|
189
342
|
}
|
|
190
343
|
|
|
@@ -196,9 +349,40 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
196
349
|
|
|
197
350
|
async function listDirectory(args){
|
|
198
351
|
const target=resolveRdcPath(machineFsRuntime,args.path);
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
352
|
+
const depth=args.depth??2;
|
|
353
|
+
if(!Number.isInteger(depth)||depth<1||depth>10) throw new Error('list_directory depth must be an integer in [1, 10]');
|
|
354
|
+
const maxEntries=4096,all=[];
|
|
355
|
+
let truncated=false;
|
|
356
|
+
let frontier=[{relativePath:target.relativePath,level:1}];
|
|
357
|
+
for(let level=1;level<=depth&&frontier.length&&!truncated;level++){
|
|
358
|
+
const next=[];
|
|
359
|
+
for(const node of frontier){
|
|
360
|
+
let cursor=null;
|
|
361
|
+
const seen=new Set();
|
|
362
|
+
do{
|
|
363
|
+
const page=await machineFsRuntime.list({
|
|
364
|
+
rootId:target.rootId,relativePath:node.relativePath,cursor,limit:1000,hashFiles:false
|
|
365
|
+
});
|
|
366
|
+
for(const entry of page.entries??[]){
|
|
367
|
+
if(all.length>=maxEntries){truncated=true;break;}
|
|
368
|
+
all.push(entry);
|
|
369
|
+
const kind=entry.kind??entry.type;
|
|
370
|
+
if(kind==='directory'&&level<depth) next.push({relativePath:entry.path,level:level+1});
|
|
371
|
+
}
|
|
372
|
+
if(truncated) break;
|
|
373
|
+
const nextCursor=page.next_cursor??null;
|
|
374
|
+
if(nextCursor!==null){
|
|
375
|
+
if(seen.has(nextCursor)) throw new Error('list_directory cursor loop detected');
|
|
376
|
+
seen.add(nextCursor);
|
|
377
|
+
}
|
|
378
|
+
cursor=nextCursor;
|
|
379
|
+
}while(cursor!==null);
|
|
380
|
+
if(truncated) break;
|
|
381
|
+
}
|
|
382
|
+
frontier=next;
|
|
383
|
+
}
|
|
384
|
+
const rows=all.map(e=>`${(e.kind??e.type)==='directory'?'[DIR]':'[FILE]'} ${e.path}`);
|
|
385
|
+
return text(rows.join('\n'),false,{path:args.path,depth,entries:all,truncated,entryCount:all.length});
|
|
202
386
|
}
|
|
203
387
|
|
|
204
388
|
async function moveFile(args){
|
|
@@ -216,24 +400,99 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
216
400
|
}
|
|
217
401
|
|
|
218
402
|
async function editBlock(args){
|
|
219
|
-
if(args.range!==undefined) throw new Error('structured range edits require a native DEADBYTE document adapter');
|
|
220
|
-
if(args.old_string===undefined||args.new_string===undefined) throw new Error('Must provide either old_string + new_string');
|
|
221
403
|
const target=resolveRdcPath(machineFsRuntime,args.file_path);
|
|
404
|
+
const ext=path.extname(args.file_path).toLowerCase();
|
|
405
|
+
if(ext==='.docx'){
|
|
406
|
+
if(args.range!==undefined) throw new Error('DOCX edit_block uses old_string/new_string XML replacement, not range mode');
|
|
407
|
+
if(args.old_string===undefined||args.new_string===undefined) throw new Error('DOCX edit requires old_string + new_string');
|
|
408
|
+
const maxRead=Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024);
|
|
409
|
+
const raw=await machineFsRuntime.read({
|
|
410
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:0,maxBytes:maxRead,hashFile:true
|
|
411
|
+
});
|
|
412
|
+
if(raw.eof===false) throw new Error('DOCX document exceeds bounded native edit limit');
|
|
413
|
+
const before=Buffer.from(raw.data_base64,'base64');
|
|
414
|
+
const beforeSha=raw.file_sha256??sha256Bytes(before);
|
|
415
|
+
const edited=editDocxBytes(before,{
|
|
416
|
+
oldString:args.old_string,newString:args.new_string,expectedReplacements:args.expected_replacements
|
|
417
|
+
});
|
|
418
|
+
const result=await machineFsRuntime.write({
|
|
419
|
+
rootId:target.rootId,relativePath:target.relativePath,
|
|
420
|
+
content:edited.bytes_base64,encoding:'base64',expectedSha256:beforeSha
|
|
421
|
+
});
|
|
422
|
+
return text(`Successfully edited ${args.file_path}`,false,{
|
|
423
|
+
path:args.file_path,status:'committed',format:'docx',replacements:edited.replacements,
|
|
424
|
+
beforeSha256:edited.before_sha256,afterSha256:edited.after_sha256,...result
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
if(args.range!==undefined){
|
|
428
|
+
const ext=path.extname(args.file_path).toLowerCase();
|
|
429
|
+
if(ext!=='.xlsx'&&ext!=='.xlsm') throw new Error('structured range edits are supported only for XLSX/XLSM');
|
|
430
|
+
if(args.content===undefined) throw new Error('range edit requires content');
|
|
431
|
+
const maxRead=Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024);
|
|
432
|
+
const raw=await machineFsRuntime.read({
|
|
433
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:0,maxBytes:maxRead,hashFile:true
|
|
434
|
+
});
|
|
435
|
+
if(raw.eof===false) throw new Error('XLSX document exceeds bounded native edit limit');
|
|
436
|
+
const before=Buffer.from(raw.data_base64,'base64');
|
|
437
|
+
const beforeSha=raw.file_sha256??sha256Bytes(before);
|
|
438
|
+
const edited=editXlsxBytes(before,{range:args.range,content:args.content});
|
|
439
|
+
const result=await machineFsRuntime.write({
|
|
440
|
+
rootId:target.rootId,relativePath:target.relativePath,
|
|
441
|
+
content:edited.bytes_base64,encoding:'base64',expectedSha256:beforeSha
|
|
442
|
+
});
|
|
443
|
+
const ignoredOptions=args.options&&typeof args.options==='object'&&!Array.isArray(args.options)?Object.keys(args.options).sort():[];
|
|
444
|
+
return text(`Successfully edited ${args.file_path} range ${args.range}`,false,{
|
|
445
|
+
path:args.file_path,status:'committed',range:args.range,sheet:edited.sheet,
|
|
446
|
+
updatedCells:edited.updated_cells,beforeSha256:edited.before_sha256,afterSha256:edited.after_sha256,
|
|
447
|
+
...(ignoredOptions.length?{ignoredOptions}:{}),...result
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if(args.old_string===undefined||args.new_string===undefined) throw new Error('Must provide either old_string + new_string');
|
|
222
451
|
const raw=await machineFsRuntime.read({rootId:target.rootId,relativePath:target.relativePath,offset:0,maxBytes:Math.min(4*1024*1024,machineFsRuntime.policy.max_read_bytes??4*1024*1024),hashFile:false});
|
|
223
|
-
|
|
224
|
-
const
|
|
452
|
+
if(raw.eof===false) throw new Error('text edit target exceeds bounded compatibility edit limit');
|
|
453
|
+
const before=Buffer.from(raw.data_base64,'base64');
|
|
454
|
+
const fileSha256=raw.file_sha256??sha256Bytes(before);
|
|
455
|
+
const current=before.toString('utf8');
|
|
225
456
|
let count=0,index=0;
|
|
226
457
|
while((index=current.indexOf(args.old_string,index))>=0){count++;index+=Math.max(1,args.old_string.length);}
|
|
227
458
|
if(count!==args.expected_replacements) throw new Error(`expected ${args.expected_replacements} replacements, found ${count}`);
|
|
228
459
|
const next=current.split(args.old_string).join(args.new_string);
|
|
229
|
-
const result=await machineFsRuntime.write({rootId:target.rootId,relativePath:target.relativePath,content:next,encoding:'utf8',expectedSha256:
|
|
460
|
+
const result=await machineFsRuntime.write({rootId:target.rootId,relativePath:target.relativePath,content:next,encoding:'utf8',expectedSha256:fileSha256});
|
|
230
461
|
return text(`Successfully edited ${args.file_path}`,false,{path:args.file_path,status:'committed',replacements:count,...result});
|
|
231
462
|
}
|
|
232
463
|
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return
|
|
464
|
+
function shellKey(value){
|
|
465
|
+
if(typeof value!=='string'||!value.trim()) return null;
|
|
466
|
+
const base=path.win32.basename(value.trim()).toLowerCase();
|
|
467
|
+
return base.endsWith('.exe')?base.slice(0,-4):base;
|
|
468
|
+
}
|
|
469
|
+
function profileForCommand(shell=null){
|
|
470
|
+
const forced=process.env.DEADBYTE_COMMANDER_COMPAT_PROFILE;
|
|
471
|
+
if(forced) return forced;
|
|
472
|
+
const profiles=processRuntime?.policy?.profiles??{};
|
|
473
|
+
const requested=shellKey(shell);
|
|
474
|
+
if(requested){
|
|
475
|
+
const matches=Object.entries(profiles).filter(([,profile])=>shellKey(profile?.executable)===requested);
|
|
476
|
+
const raw=matches.find(([,profile])=>profile?.allow_raw_input===true);
|
|
477
|
+
if(raw) return raw[0];
|
|
478
|
+
if(matches.length===1) return matches[0][0];
|
|
479
|
+
if(matches.length>1) throw new Error(`RDC shell '${shell}' maps to multiple process profiles; set DEADBYTE_COMMANDER_COMPAT_PROFILE`);
|
|
480
|
+
throw new Error(`RDC shell '${shell}' is not represented by an approved DEADBYTE process profile`);
|
|
481
|
+
}
|
|
482
|
+
if(profiles.powershell_repl?.allow_raw_input===true) return 'powershell_repl';
|
|
483
|
+
const raw=Object.entries(profiles).filter(([,profile])=>profile?.allow_raw_input===true);
|
|
484
|
+
if(raw.length===1) return raw[0][0];
|
|
485
|
+
throw new Error('RDC start_process requires an approved raw-input process profile or DEADBYTE_COMMANDER_COMPAT_PROFILE');
|
|
486
|
+
}
|
|
487
|
+
function assertRequestedShell(profileId,shell){
|
|
488
|
+
if(shell===undefined) return;
|
|
489
|
+
const profile=processRuntime?.policy?.profiles?.[profileId];
|
|
490
|
+
if(profile){
|
|
491
|
+
if(shellKey(profile.executable)!==shellKey(shell)) throw new Error(`RDC shell '${shell}' does not match approved profile '${profileId}'`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const configured=process.env.DEADBYTE_COMMANDER_COMPAT_SHELL;
|
|
495
|
+
if(configured!==shell) throw new Error('RDC shell does not match the explicitly configured compatibility shell');
|
|
237
496
|
}
|
|
238
497
|
function defaultProcessRoot(){
|
|
239
498
|
const id=process.env.DEADBYTE_COMMANDER_COMPAT_ROOT;
|
|
@@ -243,14 +502,114 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
243
502
|
throw new Error('RDC start_process requires DEADBYTE_COMMANDER_COMPAT_ROOT when multiple process roots exist');
|
|
244
503
|
}
|
|
245
504
|
const pidMap=new Map();
|
|
505
|
+
const processCompatState=new Map();
|
|
506
|
+
|
|
507
|
+
function boundedLineLength(value){
|
|
508
|
+
const n=value??1000;
|
|
509
|
+
if(!Number.isInteger(n)||n<1||n>100000) throw new Error('RDC process length must be an integer in [1,100000]');
|
|
510
|
+
return n;
|
|
511
|
+
}
|
|
512
|
+
function boundedWait(value,fallback){
|
|
513
|
+
const n=value??fallback;
|
|
514
|
+
if(!Number.isFinite(n)||n<0||n>300000) throw new Error('RDC process timeout_ms must be in [0,300000]');
|
|
515
|
+
return Math.floor(n);
|
|
516
|
+
}
|
|
517
|
+
async function processStreamSnapshot(sessionId){
|
|
518
|
+
const probe=await processRuntime.read({sessionId,offset:0,maxBytes:1,waitMs:0});
|
|
519
|
+
const earliest=Number.isInteger(probe.earliest_offset)?probe.earliest_offset:probe.offset;
|
|
520
|
+
const streamHead=Number.isInteger(probe.stream_head)?probe.stream_head:probe.next_offset;
|
|
521
|
+
if(!Number.isInteger(earliest)||!Number.isInteger(streamHead)||earliest<0||streamHead<earliest) throw new Error('native process stream identity invalid');
|
|
522
|
+
if(streamHead-earliest>RDC_PROCESS_SNAPSHOT_MAX) throw new Error(`RDC retained process output exceeds ${RDC_PROCESS_SNAPSHOT_MAX} byte compatibility bound`);
|
|
523
|
+
const chunks=[];
|
|
524
|
+
let position=earliest;
|
|
525
|
+
while(position<streamHead){
|
|
526
|
+
const page=await processRuntime.read({
|
|
527
|
+
sessionId,offset:position,maxBytes:Math.min(RDC_PROCESS_READ_CHUNK,streamHead-position),waitMs:0
|
|
528
|
+
});
|
|
529
|
+
if(page.offset!==position) throw new Error('native process output moved during compatibility snapshot');
|
|
530
|
+
const data=Buffer.from(page.data_base64??'','base64');
|
|
531
|
+
if(data.length<1) throw new Error('native process output snapshot made no forward progress');
|
|
532
|
+
chunks.push(data);
|
|
533
|
+
const next=page.next_offset;
|
|
534
|
+
if(!Number.isInteger(next)||next<=position||next>streamHead) throw new Error('native process output cursor invalid');
|
|
535
|
+
position=next;
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
bytes:Buffer.concat(chunks),
|
|
539
|
+
earliest_offset:earliest,
|
|
540
|
+
stream_head:streamHead,
|
|
541
|
+
state:probe.state,
|
|
542
|
+
exit_code:probe.exit_code
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
async function waitForStreamAdvance(sessionId,baseline,timeoutMs){
|
|
546
|
+
let remaining=timeoutMs;
|
|
547
|
+
while(remaining>0){
|
|
548
|
+
const slice=Math.min(30000,remaining);
|
|
549
|
+
const observed=await processRuntime.read({sessionId,offset:baseline,maxBytes:1,waitMs:slice});
|
|
550
|
+
if((Number.isInteger(observed.stream_head)&&observed.stream_head>baseline)||observed.state!=='running') return observed;
|
|
551
|
+
remaining-=slice;
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
async function readProcessRange(sessionId,start,end){
|
|
556
|
+
if(!Number.isInteger(start)||!Number.isInteger(end)||start<0||end<start) throw new Error('native process range invalid');
|
|
557
|
+
if(end-start>RDC_PROCESS_SNAPSHOT_MAX) throw new Error(`RDC process response exceeds ${RDC_PROCESS_SNAPSHOT_MAX} byte compatibility bound`);
|
|
558
|
+
const chunks=[];
|
|
559
|
+
let position=start;
|
|
560
|
+
while(position<end){
|
|
561
|
+
const page=await processRuntime.read({sessionId,offset:position,maxBytes:Math.min(RDC_PROCESS_READ_CHUNK,end-position),waitMs:0});
|
|
562
|
+
if(page.offset!==position) throw new Error('native process response moved during compatibility read');
|
|
563
|
+
const data=Buffer.from(page.data_base64??'','base64');
|
|
564
|
+
if(data.length<1) throw new Error('native process response made no forward progress');
|
|
565
|
+
chunks.push(data);
|
|
566
|
+
const next=page.next_offset;
|
|
567
|
+
if(!Number.isInteger(next)||next<=position||next>end) throw new Error('native process response cursor invalid');
|
|
568
|
+
position=next;
|
|
569
|
+
}
|
|
570
|
+
return Buffer.concat(chunks);
|
|
571
|
+
}
|
|
572
|
+
function lineIndexAtByte(ranges,relativeByte){
|
|
573
|
+
let index=0;
|
|
574
|
+
while(index<ranges.length&&ranges[index].next<=relativeByte) index++;
|
|
575
|
+
return index;
|
|
576
|
+
}
|
|
577
|
+
function processOutputText({readFrom,readCount,totalLines,remaining,lines,state,exitCode,truncated}){
|
|
578
|
+
let header=readFrom===0
|
|
579
|
+
?`[Reading ${readCount} lines (total: ${totalLines} lines)]`
|
|
580
|
+
:`[Reading ${readCount} lines from line ${readFrom} (total: ${totalLines} lines, ${remaining} remaining)]`;
|
|
581
|
+
if(truncated) header+='\n[WARNING: earliest retained process output was evicted; line numbers refer to retained output only]';
|
|
582
|
+
const body=lines.length?lines.join('\n'):'(No output in requested range)';
|
|
583
|
+
const terminal=state==='running'?'':`\nProcess ${state}${exitCode===null||exitCode===undefined?'':` with exit code ${exitCode}`}`;
|
|
584
|
+
return `${header}\n\n${body}${terminal}`;
|
|
585
|
+
}
|
|
246
586
|
|
|
247
587
|
async function startProcess(args){
|
|
248
|
-
if(
|
|
249
|
-
const profileId=profileForCommand(),rootId=defaultProcessRoot();
|
|
250
|
-
|
|
588
|
+
if(!processRuntime) throw new Error('process runtime is disabled');
|
|
589
|
+
const profileId=profileForCommand(args.shell??null),rootId=defaultProcessRoot();
|
|
590
|
+
assertRequestedShell(profileId,args.shell);
|
|
591
|
+
const profile=processRuntime?.policy?.profiles?.[profileId]??null;
|
|
592
|
+
const rawInput=profile?.allow_raw_input===true;
|
|
593
|
+
const native=await processRuntime.start({
|
|
594
|
+
profileId,rootId,cwd:'.',
|
|
595
|
+
args:rawInput?[]:(args.command.length===0?[]:[args.command]),
|
|
596
|
+
timeoutMs:args.timeout_ms
|
|
597
|
+
});
|
|
598
|
+
try{
|
|
599
|
+
if(rawInput&&args.command.length>0){
|
|
600
|
+
await processRuntime.write({sessionId:native.session_id,input:args.command,appendNewline:true});
|
|
601
|
+
}
|
|
602
|
+
}catch(error){
|
|
603
|
+
await processRuntime.terminate({sessionId:native.session_id}).catch(()=>{});
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
251
606
|
const compatPid=native.child_pid??0;
|
|
252
|
-
if(!Number.isInteger(compatPid)||compatPid<=0)
|
|
607
|
+
if(!Number.isInteger(compatPid)||compatPid<=0) {
|
|
608
|
+
await processRuntime.terminate({sessionId:native.session_id}).catch(()=>{});
|
|
609
|
+
throw new Error('native process did not expose a usable child PID');
|
|
610
|
+
}
|
|
253
611
|
pidMap.set(compatPid,native.session_id);
|
|
612
|
+
processCompatState.set(compatPid,{last_read_byte:0});
|
|
254
613
|
return text(`Process started with PID ${compatPid} (DEADBYTE session ${native.session_id})\\n`,false,{pid:compatPid,session_id:native.session_id});
|
|
255
614
|
}
|
|
256
615
|
function sessionForPid(pid){
|
|
@@ -260,60 +619,193 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
260
619
|
}
|
|
261
620
|
async function readProcessOutput(args){
|
|
262
621
|
const sessionId=sessionForPid(args.pid);
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
|
|
622
|
+
const state=processCompatState.get(args.pid)??{last_read_byte:0};
|
|
623
|
+
const requestedOffset=args.offset??0;
|
|
624
|
+
if(!Number.isInteger(requestedOffset)||requestedOffset<-(1<<30)||requestedOffset>(1<<30)) throw new Error('RDC process offset invalid');
|
|
625
|
+
const length=boundedLineLength(args.length);
|
|
626
|
+
const waitMs=boundedWait(args.timeout_ms,5000);
|
|
627
|
+
let snapshot=await processStreamSnapshot(sessionId);
|
|
628
|
+
let retainedRanges=splitLineRanges(snapshot.bytes);
|
|
629
|
+
if(requestedOffset===0){
|
|
630
|
+
let startByte=Math.max(state.last_read_byte,snapshot.earliest_offset);
|
|
631
|
+
if(startByte>=snapshot.stream_head&&snapshot.state==='running'&&waitMs>0){
|
|
632
|
+
await waitForStreamAdvance(sessionId,snapshot.stream_head,waitMs);
|
|
633
|
+
snapshot=await processStreamSnapshot(sessionId);
|
|
634
|
+
retainedRanges=splitLineRanges(snapshot.bytes);
|
|
635
|
+
startByte=Math.max(state.last_read_byte,snapshot.earliest_offset);
|
|
636
|
+
}
|
|
637
|
+
const wasTruncated=state.last_read_byte<snapshot.earliest_offset;
|
|
638
|
+
const relativeStart=Math.max(0,startByte-snapshot.earliest_offset);
|
|
639
|
+
const unread=snapshot.bytes.subarray(relativeStart);
|
|
640
|
+
const unreadRanges=splitLineRanges(unread);
|
|
641
|
+
const selected=unreadRanges.slice(0,length);
|
|
642
|
+
const lines=decodeLines(unread,selected);
|
|
643
|
+
const readFrom=lineIndexAtByte(retainedRanges,relativeStart);
|
|
644
|
+
const readCount=selected.length;
|
|
645
|
+
const remaining=Math.max(0,unreadRanges.length-readCount);
|
|
646
|
+
const consumed=selected.length?selected.at(-1).next:0;
|
|
647
|
+
state.last_read_byte=startByte+consumed;
|
|
648
|
+
processCompatState.set(args.pid,state);
|
|
649
|
+
return text(processOutputText({
|
|
650
|
+
readFrom,readCount,totalLines:retainedRanges.length,remaining,lines,
|
|
651
|
+
state:snapshot.state,exitCode:snapshot.exit_code,truncated:wasTruncated
|
|
652
|
+
}),false,{
|
|
653
|
+
pid:args.pid,session_id:sessionId,read_from:readFrom,read_count:readCount,total_lines:retainedRanges.length,
|
|
654
|
+
remaining,next_line:readFrom+readCount,stream_head:snapshot.stream_head,state:snapshot.state,exit_code:snapshot.exit_code
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
const totalLines=retainedRanges.length;
|
|
658
|
+
const readFrom=requestedOffset<0?Math.max(0,totalLines+requestedOffset):Math.min(requestedOffset,totalLines);
|
|
659
|
+
const selected=retainedRanges.slice(readFrom,readFrom+length);
|
|
660
|
+
const lines=decodeLines(snapshot.bytes,selected);
|
|
661
|
+
const readCount=selected.length,remaining=Math.max(0,totalLines-(readFrom+readCount));
|
|
662
|
+
return text(processOutputText({
|
|
663
|
+
readFrom,readCount,totalLines,remaining,lines,state:snapshot.state,exitCode:snapshot.exit_code,truncated:snapshot.earliest_offset>0
|
|
664
|
+
}),false,{
|
|
665
|
+
pid:args.pid,session_id:sessionId,read_from:readFrom,read_count:readCount,total_lines:totalLines,
|
|
666
|
+
remaining,next_line:readFrom+readCount,stream_head:snapshot.stream_head,state:snapshot.state,exit_code:snapshot.exit_code
|
|
667
|
+
});
|
|
266
668
|
}
|
|
267
669
|
async function interactProcess(args){
|
|
268
670
|
const sessionId=sessionForPid(args.pid);
|
|
269
|
-
const
|
|
270
|
-
|
|
671
|
+
const before=await processStreamSnapshot(sessionId);
|
|
672
|
+
const written=await processRuntime.write({sessionId,input:args.input,appendNewline:true});
|
|
673
|
+
const waitForPrompt=args.wait_for_prompt??true;
|
|
674
|
+
if(!waitForPrompt){
|
|
675
|
+
return text(`Input sent to process ${args.pid}. Use read_process_output to get the response.`,false,{
|
|
676
|
+
pid:args.pid,session_id:sessionId,wait_for_prompt:false,...written
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
const waitMs=boundedWait(args.timeout_ms,8000);
|
|
680
|
+
let observed=await processRuntime.read({sessionId,offset:before.stream_head,maxBytes:1,waitMs:0});
|
|
681
|
+
if((observed.stream_head??before.stream_head)<=before.stream_head&&observed.state==='running'&&waitMs>0){
|
|
682
|
+
observed=await waitForStreamAdvance(sessionId,before.stream_head,waitMs)??observed;
|
|
683
|
+
}
|
|
684
|
+
const streamHead=Number.isInteger(observed.stream_head)?observed.stream_head:before.stream_head;
|
|
685
|
+
const earliest=Number.isInteger(observed.earliest_offset)?observed.earliest_offset:observed.offset;
|
|
686
|
+
const start=Math.max(before.stream_head,earliest);
|
|
687
|
+
const responseBytes=streamHead>start?await readProcessRange(sessionId,start,streamHead):Buffer.alloc(0);
|
|
688
|
+
const response=responseBytes.toString('utf8');
|
|
689
|
+
const output=response.length?response:'(No output produced)';
|
|
690
|
+
return text(`Input executed in process ${args.pid}:\n\n${output}`,false,{
|
|
691
|
+
pid:args.pid,session_id:sessionId,wait_for_prompt:true,response_from_offset:start,
|
|
692
|
+
stream_head:streamHead,state:observed.state,exit_code:observed.exit_code,...written
|
|
693
|
+
});
|
|
271
694
|
}
|
|
272
695
|
async function terminateProcess(args){
|
|
273
696
|
const sessionId=sessionForPid(args.pid);
|
|
274
697
|
const native=await processRuntime.terminate({sessionId});
|
|
275
698
|
pidMap.delete(args.pid);
|
|
699
|
+
processCompatState.delete(args.pid);
|
|
276
700
|
return text(`Successfully terminated process ${args.pid}`,false,{pid:args.pid,session_id:sessionId,...native});
|
|
277
701
|
}
|
|
278
702
|
async function listSessions(){
|
|
279
703
|
const native=await processRuntime.list();
|
|
280
704
|
return text(native.sessions.map(s=>`Session ${s.session_id}: state=${s.state}, profile=${s.profile_id}, root=${s.root_id}`).join('\\n'),false,{sessions:native.sessions});
|
|
281
705
|
}
|
|
282
|
-
async function listProcesses(){
|
|
283
|
-
|
|
706
|
+
async function listProcesses(){
|
|
707
|
+
if(!externalProcessRuntime) return listSessions();
|
|
708
|
+
const native=await externalProcessRuntime.list();
|
|
709
|
+
const lines=native.processes.map(p=>`PID: ${p.pid}, Command: ${p.name}, CPU: n/a, Memory: n/a`);
|
|
710
|
+
return text(lines.join('\n'),false,{...native});
|
|
711
|
+
}
|
|
712
|
+
async function killProcess(args){
|
|
713
|
+
if(pidMap.has(args.pid)) return terminateProcess(args);
|
|
714
|
+
if(!externalProcessRuntime) throw new Error(`unknown RDC pid ${args.pid}`);
|
|
715
|
+
const native=await externalProcessRuntime.terminate({pid:args.pid});
|
|
716
|
+
return text(`Successfully terminated process ${args.pid}`,false,native);
|
|
717
|
+
}
|
|
284
718
|
|
|
285
719
|
async function writePdf(args){
|
|
286
|
-
if(!
|
|
287
|
-
|
|
288
|
-
|
|
720
|
+
if(!machineFsRuntime) throw new Error('write_pdf compatibility requires the machine filesystem runtime');
|
|
721
|
+
async function readPdfBytes(compatPath,label){
|
|
722
|
+
const resolved=resolveRdcPath(machineFsRuntime,compatPath);
|
|
723
|
+
const maxRead=Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024);
|
|
724
|
+
const raw=await machineFsRuntime.read({rootId:resolved.rootId,relativePath:resolved.relativePath,offset:0,maxBytes:maxRead,hashFile:true});
|
|
725
|
+
if(raw.eof===false) throw new Error(`${label} exceeds bounded native PDF read limit`);
|
|
726
|
+
return {resolved,raw,bytes:Buffer.from(raw.data_base64,'base64')};
|
|
727
|
+
}
|
|
728
|
+
let pdf;
|
|
729
|
+
let ignoredPdfOptions=[];
|
|
730
|
+
if(Array.isArray(args.content)){
|
|
731
|
+
ignoredPdfOptions=args.content.flatMap((op,index)=>
|
|
732
|
+
op?.type==='insert'&&op.pdfOptions!==undefined&&Object.keys(op.pdfOptions??{}).length>0?[index]:[]
|
|
733
|
+
);
|
|
734
|
+
const base=await readPdfBytes(args.path,'base PDF');
|
|
735
|
+
pdf=await editDeadbytePdf(base.bytes,args.content,{
|
|
736
|
+
sourceResolver:async sourcePdfPath=>(await readPdfBytes(sourcePdfPath,'source PDF')).bytes
|
|
737
|
+
});
|
|
738
|
+
}else{
|
|
739
|
+
if(!extendedRuntime) throw new Error('write_pdf string mode requires the DEADBYTE document runtime');
|
|
740
|
+
pdf=extendedRuntime.writePdf({text:args.content,metadata:{...args.options??{}}});
|
|
741
|
+
}
|
|
289
742
|
const targetPath=args.outputPath??args.path;
|
|
290
743
|
const target=resolveRdcPath(machineFsRuntime,targetPath);
|
|
291
744
|
let expected='absent';
|
|
292
745
|
try{
|
|
293
|
-
const current=await machineFsRuntime.read({
|
|
294
|
-
|
|
746
|
+
const current=await machineFsRuntime.read({
|
|
747
|
+
rootId:target.rootId,relativePath:target.relativePath,offset:0,
|
|
748
|
+
maxBytes:Math.min(64*1024*1024,machineFsRuntime.policy.max_read_bytes??64*1024*1024),
|
|
749
|
+
hashFile:true
|
|
750
|
+
});
|
|
751
|
+
if(current.eof===false) throw new Error('target PDF exceeds bounded native read limit');
|
|
752
|
+
expected=current.file_sha256??sha256Bytes(Buffer.from(current.data_base64,'base64'));
|
|
295
753
|
}catch(error){
|
|
296
754
|
if(!/not found|ENOENT/i.test(String(error))) throw error;
|
|
297
755
|
}
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
756
|
+
const bytesBase64=pdf.bytes_base64??pdf.bytes?.toString('base64');
|
|
757
|
+
const bytesCount=pdf.bytes?.length??Buffer.from(bytesBase64,'base64').length;
|
|
758
|
+
const digest=pdf.bytes_sha256??sha256Bytes(Buffer.from(bytesBase64,'base64'));
|
|
759
|
+
const result=await machineFsRuntime.write({
|
|
760
|
+
rootId:target.rootId,relativePath:target.relativePath,
|
|
761
|
+
content:bytesBase64,encoding:'base64',expectedSha256:expected
|
|
762
|
+
});
|
|
763
|
+
return text(`Successfully wrote PDF to ${targetPath}${args.outputPath?`\nOriginal file: ${args.path}`:''}`,false,{
|
|
764
|
+
path:args.path,outputPath:args.outputPath??null,status:'committed',mimeType:'application/pdf',
|
|
765
|
+
bytes:bytesCount,bytesSha256:digest,pageCount:pdf.page_count??null,profile:pdf.profile??null,
|
|
766
|
+
...(ignoredPdfOptions.length?{ignoredPdfOptions}:{}),...result
|
|
301
767
|
});
|
|
302
768
|
}
|
|
303
769
|
|
|
304
770
|
async function startSearch(args){
|
|
305
771
|
if(!searchRuntime) throw new Error('dedicated search runtime is disabled');
|
|
306
772
|
const id=`search-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`;
|
|
307
|
-
const
|
|
773
|
+
const exactFilename=args.searchType==='files'&&/\.[a-zA-Z0-9]+$/.test(args.pattern)&&!/[\*\?\[\]\{\}]/.test(args.pattern);
|
|
774
|
+
const timeoutMs=args.timeout_ms??(exactFilename?1500:null);
|
|
775
|
+
const state={id,created:Date.now(),args,isComplete:false,isError:false,results:[],error:null,timedOut:false,abortController:new AbortController()};
|
|
308
776
|
searchSessions.set(id,state);
|
|
777
|
+
let timeoutHandle=null;
|
|
778
|
+
if(timeoutMs){
|
|
779
|
+
timeoutHandle=setTimeout(()=>{
|
|
780
|
+
if(state.isComplete) return;
|
|
781
|
+
state.timedOut=true;
|
|
782
|
+
state.abortController.abort();
|
|
783
|
+
state.isComplete=true;
|
|
784
|
+
},Math.max(0,timeoutMs));
|
|
785
|
+
}
|
|
309
786
|
state.promise=(async()=>{
|
|
310
787
|
try{
|
|
311
788
|
const target=machineFsRuntime?resolveRdcPath(machineFsRuntime,args.path):{rootId:'core',relativePath:args.path};
|
|
312
|
-
const query={rootId:target.rootId,path:target.relativePath,pattern:args.pattern,target:args.searchType==='content'?'content':'path',mode:args.literalSearch?'literal':'regex',caseSensitive:!args.ignoreCase,maxResults:Math.min(Math.max(Math.trunc(args.maxResults??500),1),5000),maxFileBytes:262144,maxScanBytes:64*1024*1024,pageSize:500,cursor:null,filePattern:args.filePattern,includeHidden:args.includeHidden,contextLines:args.contextLines,earlyTermination:args.earlyTermination};
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
789
|
+
const query={rootId:target.rootId,path:target.relativePath,pattern:args.pattern,target:args.searchType==='content'?'content':'path',mode:args.literalSearch?'literal':'regex',caseSensitive:!args.ignoreCase,maxResults:Math.min(Math.max(Math.trunc(args.maxResults??500),1),5000),maxFileBytes:262144,maxScanBytes:64*1024*1024,pageSize:500,cursor:null,filePattern:args.filePattern,includeHidden:args.includeHidden,contextLines:args.contextLines,earlyTermination:args.searchType==='files'?args.earlyTermination!==false:false};
|
|
790
|
+
const seenCursors=new Set();
|
|
791
|
+
if(args.searchType!=='content'){
|
|
792
|
+
query.mode='literal';
|
|
793
|
+
query.pathGlob=true;
|
|
794
|
+
}
|
|
795
|
+
do{
|
|
796
|
+
const r=await searchRuntime({...query,signal:state.abortController.signal});
|
|
797
|
+
if(state.cancelled||state.timedOut) break;
|
|
798
|
+
const remaining=query.maxResults-state.results.length;
|
|
799
|
+
state.results.push(...(r.matches??[]).slice(0,remaining).map(x=>({file:x.path,line:x.line,match:x.preview,type:args.searchType==='content'?'content':'file'})));
|
|
800
|
+
query.cursor=r.next_cursor??null;
|
|
801
|
+
if(query.cursor!==null){
|
|
802
|
+
if(seenCursors.has(query.cursor)) throw new Error('search runtime repeated a paging cursor');
|
|
803
|
+
seenCursors.add(query.cursor);
|
|
804
|
+
}
|
|
805
|
+
}while(query.cursor!==null&&state.results.length<query.maxResults);
|
|
806
|
+
if(!state.cancelled&&!state.timedOut) state.isComplete=true;
|
|
807
|
+
}catch(error){if(!state.cancelled&&!state.timedOut){state.isError=true;state.error=String(error?.message??error);}state.isComplete=true;}
|
|
808
|
+
finally{if(timeoutHandle!==null) clearTimeout(timeoutHandle);}
|
|
317
809
|
})();
|
|
318
810
|
return {sessionId:id,isComplete:false,isError:false,results:[],totalResults:0};
|
|
319
811
|
}
|
|
@@ -328,8 +820,10 @@ export function createCommanderWireAdapter({machineFsRuntime=null,processRuntime
|
|
|
328
820
|
}
|
|
329
821
|
async function stopSearch(args){
|
|
330
822
|
const s=searchSessions.get(args.sessionId);
|
|
331
|
-
if(!s) return false;
|
|
823
|
+
if(!s||s.isComplete) return false;
|
|
332
824
|
s.cancelled=true;
|
|
825
|
+
s.abortController.abort();
|
|
826
|
+
s.isComplete=true;
|
|
333
827
|
return true;
|
|
334
828
|
}
|
|
335
829
|
async function listSearches(){
|