deadbyte-mcp 0.15.18 → 0.15.20

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 (57) hide show
  1. package/MANIFEST.SHA256 +56 -34
  2. package/PROVENANCE.json +13 -7
  3. package/README.txt +7 -7
  4. package/RELEASE.json +30 -27
  5. package/SBOM.cdx.json +3 -3
  6. package/TEST-MATRIX.json +27 -7
  7. package/controller/deadbyte-controller.ps1 +56 -1
  8. package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +247 -44
  9. package/docs/COMMANDER_REFERENCE_0_2_51.json +46 -0
  10. package/docs/R47-PROVENANCE.json +11 -0
  11. package/docs/R47-RELEASE.json +58 -0
  12. package/docs/plans/2026-09-27-desktop-commander-drop-in-parity.md +92 -0
  13. package/package.json +4 -2
  14. package/proof/commander-closure-20260927/CONTINUATION.json +43 -0
  15. package/proof/commander-closure-20260927/RESULT.json +30 -0
  16. package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +30 -0
  17. package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +13 -0
  18. package/scripts/deploy-r48.mjs +86 -0
  19. package/scripts/full-regression-r48.mjs +28 -0
  20. package/src/coding-search.mjs +65 -7
  21. package/src/coding-task-runtime.mjs +1 -1
  22. package/src/commander-compat-contracts.mjs +10 -0
  23. package/src/commander-core-bridge.mjs +2 -2
  24. package/src/commander-extended-bridge-r47.mjs +116 -12
  25. package/src/commander-extended-runtime.mjs +33 -6
  26. package/src/commander-external-process-r48.mjs +125 -0
  27. package/src/commander-observability.mjs +66 -16
  28. package/src/commander-office-reader-r48.mjs +548 -0
  29. package/src/commander-pdf-r48.mjs +448 -0
  30. package/src/commander-ui-resources.mjs +26 -0
  31. package/src/commander-url-reader-r48.mjs +201 -0
  32. package/src/commander-wire-adapter-r47.mjs +595 -101
  33. package/src/deadbyte-cli.mjs +73 -8
  34. package/src/mcp-server.mjs +9 -2
  35. package/src/observation-output-contracts.mjs +2 -1
  36. package/src/observation-runtime.mjs +4 -2
  37. package/src/version.mjs +1 -1
  38. package/test/coding-runtime.test.mjs +7 -0
  39. package/test/coding-search-r36.test.mjs +71 -0
  40. package/test/commander-core-bridge-r47.test.mjs +32 -3
  41. package/test/commander-exhaustive-surface-r47.test.mjs +5 -1
  42. package/test/commander-extended-runtime-r47.test.mjs +8 -0
  43. package/test/commander-extended-wire-r48.test.mjs +98 -0
  44. package/test/commander-external-process-r48.test.mjs +76 -0
  45. package/test/commander-office-reader-r48.test.mjs +152 -0
  46. package/test/commander-pdf-r48.test.mjs +103 -0
  47. package/test/commander-protocol-r47.test.mjs +23 -1
  48. package/test/commander-reference-contract-r48.test.mjs +26 -0
  49. package/test/commander-schema-differential-r48.test.mjs +88 -0
  50. package/test/commander-url-reader-r48.test.mjs +68 -0
  51. package/test/commander-wire-contract-r47.test.mjs +461 -41
  52. package/test/controller-r212.test.mjs +23 -2
  53. package/test/observation-runtime.test.mjs +4 -3
  54. package/test/prompts-resources.test.mjs +1 -1
  55. package/test/r47-release-contract.test.mjs +9 -8
  56. package/test/r48-release-contract.test.mjs +36 -0
  57. package/test/remote-session-cli.test.mjs +34 -1
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { closeSync, openSync } from 'node:fs';
2
3
  import { lstat, mkdir, open, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
3
4
  import { spawn, spawnSync } from 'node:child_process';
4
5
  import { createHash, randomBytes } from 'node:crypto';
@@ -10,6 +11,7 @@ import { defaultPublicKeyPath } from './trust.mjs';
10
11
  import { verifyAuditChain } from './audit-verify.mjs';
11
12
  import { openRemoteResultJournal, parseRemoteResultCursor } from './remote-result-journal.mjs';
12
13
  import { DEADBYTE_VERSION } from './version.mjs';
14
+ import { parseActivePointer } from './release-slot.mjs';
13
15
  import { printDoctor } from './doctor-r45.mjs';
14
16
  import { createLiveTimelineTracker, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
15
17
  import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
@@ -60,14 +62,62 @@ async function remoteContext(){
60
62
  const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
61
63
  return {root,launcher,powershell};
62
64
  }
63
- function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false}={}){
65
+ function boundedSingleLine(value,maxChars=2048){
66
+ const line=String(value??'').replace(/[\r\n\t]+/g,' ').replace(/\s{2,}/g,' ').trim();
67
+ return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
68
+ }
69
+ async function readActiveRuntimeBinding(root){
70
+ const file=path.join(root,'active-release.json');
71
+ try{return parseActivePointer(await readFile(file,'utf8'));}
72
+ catch(error){if(error?.code==='ENOENT') return null;throw new Error(`active runtime pointer invalid: ${boundedSingleLine(error?.message||error,512)}`);}
73
+ }
74
+ async function reportVersionCoherence(root,{displayMode='ascii'}={}){
75
+ const active=await readActiveRuntimeBinding(root);
76
+ if(!active||active.version===DEADBYTE_VERSION) return active;
77
+ writeRemote(process.stderr,`${remoteTag('warn',displayMode)} VERSION SKEW: CLI package ${DEADBYTE_VERSION}; active runtime ${active.version} (${active.release_id})\n`,displayMode);
78
+ return active;
79
+ }
80
+ async function readControllerStartFailure(root,command,{maxChars=2048}={}){
81
+ const file=path.join(root,'runtime','controller-start-failure.json');
82
+ let raw;
83
+ try{raw=await readFile(file,'utf8');}catch(error){if(error?.code==='ENOENT') return null;throw error;}
84
+ let value;
85
+ try{value=JSON.parse(String(raw).replace(/^\uFEFF/,''));}catch{return null;}
86
+ if(value?.schema!=='deadbyte.controller-start-failure.v1'||String(value?.command||'')!==command||typeof value?.message!=='string') return null;
87
+ const message=boundedSingleLine(value.message,maxChars);
88
+ return message?{file,message}:null;
89
+ }
90
+ async function readTailBounded(file,maxBytes=4096){
91
+ try{
92
+ const info=await stat(file);
93
+ const length=Math.min(info.size,maxBytes);
94
+ if(length===0) return '';
95
+ const buffer=Buffer.alloc(length);
96
+ const handle=await open(file,'r');
97
+ try{await handle.read(buffer,0,length,info.size-length);}finally{await handle.close();}
98
+ return boundedSingleLine(buffer.toString('utf8'),maxBytes);
99
+ }catch(error){if(error?.code==='ENOENT') return '';throw error;}
100
+ }
101
+ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null}={}){
64
102
  if(capture&&discardOutput) throw new Error('invokeController cannot capture and discard output together');
65
- const options={
66
- stdio:discardOutput?['ignore','ignore','ignore']:(capture?['ignore','pipe','pipe']:'inherit'),
67
- encoding:capture?'utf8':undefined,windowsHide:false,shell:false
68
- };
69
- if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
70
- const result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
103
+ if(capture&&redirectOutput) throw new Error('invokeController cannot capture and redirect output together');
104
+ if(discardOutput&&redirectOutput) throw new Error('invokeController cannot discard and redirect output together');
105
+ let stdoutFd=null,stderrFd=null,result;
106
+ try{
107
+ let stdio;
108
+ if(discardOutput) stdio=['ignore','ignore','ignore'];
109
+ else if(redirectOutput){
110
+ stdoutFd=openSync(redirectOutput.stdout_path,'w');
111
+ stderrFd=openSync(redirectOutput.stderr_path,'w');
112
+ stdio=['ignore',stdoutFd,stderrFd];
113
+ }else stdio=capture?['ignore','pipe','pipe']:'inherit';
114
+ const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false};
115
+ if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
116
+ result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
117
+ }finally{
118
+ if(stdoutFd!==null) closeSync(stdoutFd);
119
+ if(stderrFd!==null) closeSync(stderrFd);
120
+ }
71
121
  if(result.error){
72
122
  if(result.error?.code==='ETIMEDOUT') throw new Error(`remote controller '${command}' timed out after ${timeoutMs}ms`);
73
123
  throw result.error;
@@ -295,6 +345,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false}={}){
295
345
  const display=prepareRemoteConsole();
296
346
  const displayMode=display.mode;
297
347
  const ctx=await remoteContext();
348
+ await reportVersionCoherence(ctx.root,{displayMode});
298
349
  if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
299
350
  if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
300
351
  await recoverRemoteSession(ctx,{displayMode});
@@ -324,7 +375,20 @@ async function runRemoteSession({resumeCursor=null,verbose=false}={}){
324
375
  }else{
325
376
  const recovery=preflight.status===0&&controllerRuntimeDegraded(preflight.stdout);
326
377
  process.stdout.write(recovery?'[RECOVERY] Runtime DEGRADED; restarting verified owned components...\n':'[BOOT] Starting bridge/runtime...\n');
327
- invokeController(ctx,recovery?'restart':'start',{discardOutput:true,timeoutMs:REMOTE_START_TIMEOUT_MS});
378
+ const startCommand=recovery?'restart':'start';
379
+ const controllerLogDir=path.join(ctx.root,'runtime','logs');
380
+ await mkdir(controllerLogDir,{recursive:true});
381
+ const launchOutput={
382
+ stdout_path:path.join(controllerLogDir,`controller-${startCommand}.stdout.log`),
383
+ stderr_path:path.join(controllerLogDir,`controller-${startCommand}.stderr.log`)
384
+ };
385
+ const launch=invokeController(ctx,startCommand,{allowFailure:true,redirectOutput:launchOutput,timeoutMs:REMOTE_START_TIMEOUT_MS});
386
+ if(launch.status!==0){
387
+ const evidence=await readControllerStartFailure(ctx.root,startCommand);
388
+ const fallback=evidence?'':await readTailBounded(launchOutput.stderr_path,2048);
389
+ const detail=evidence?`; controller-start-failure: ${evidence.message}`:(fallback?`; controller-stderr: ${fallback}`:'');
390
+ throw new Error(`remote controller '${startCommand}' failed exit=${launch.status}${detail}`);
391
+ }
328
392
  const started=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
329
393
  if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error(`remote controller ${recovery?'restart':'start'} returned without a verified ONLINE runtime`);
330
394
  startText=started.stdout||'';
@@ -371,6 +435,7 @@ async function runRemote(args){
371
435
  if(filtered.length>1) throw new Error('remote accepts one controller command or --resume <cursor>');
372
436
  const command=filtered[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
373
437
  const ctx=await remoteContext();
438
+ await reportVersionCoherence(ctx.root,{displayMode:'ascii'});
374
439
  const result=invokeController(ctx,command,{allowFailure:true});
375
440
  process.exitCode=result.status;
376
441
  }
@@ -51,11 +51,13 @@ import { registerAutonomousTools } from './autonomous-mcp-tools.mjs';
51
51
  import { createToolAudit, installToolAudit } from './tool-audit.mjs';
52
52
  import { createAuditContextProvider } from './audit-context.mjs';
53
53
  import { registerOperatorSurface } from './operator-surface.mjs';
54
+ import { registerCommanderUiResources } from './commander-ui-resources.mjs';
54
55
  import { createOperationHistory } from './operation-history.mjs';
55
56
  import { createCommanderObservability } from './commander-observability.mjs';
56
- import { registerCommanderExtendedBridge } from './commander-extended-bridge-r47.mjs';
57
+ import { registerCommanderExtendedBridge, registerCommanderHiddenUiDispatch } from './commander-extended-bridge-r47.mjs';
57
58
  import { createCommanderExtendedRuntime } from './commander-extended-runtime.mjs';
58
59
  import { registerCommanderCoreBridge } from './commander-core-bridge.mjs';
60
+ import { createCommanderExternalProcessRuntime } from './commander-external-process-r48.mjs';
59
61
  import { registerCommanderRemoteBridge } from './commander-remote-bridge-r47.mjs';
60
62
  import { createCommanderRemoteRelayR47 } from './commander-remote-relay-r47.mjs';
61
63
  import { createRemoteDevicePlaneR44 } from './remote-device-r44.mjs';
@@ -246,6 +248,9 @@ const skillRoot=path.join(runtimeStateRoot,'skills');
246
248
  await mkdir(skillRoot,{recursive:true});
247
249
  const skillRuntime=await createSkillPlane({root:skillRoot,verifyKeyPath});
248
250
  const commanderExtendedRuntime=createCommanderExtendedRuntime({stateRoot:runtimeStateRoot});
251
+ const commanderExternalProcessRuntime=observationRuntime?createCommanderExternalProcessRuntime({
252
+ observationRuntime,hostPolicy,hostExecInlineFn:hostExecInline
253
+ }):null;
249
254
  const commanderHistory=createOperationHistory({maxEvents:1024,maxBytes:1024*1024});
250
255
  const commanderObservability=createCommanderObservability({history:commanderHistory});
251
256
  const remoteRelayEndpoint=process.env.DEADBYTE_REMOTE_RELAY_ENDPOINT||process.env.DEADBYTE_REMOTE_ENDPOINT||'';
@@ -604,9 +609,11 @@ serveStdio(() => {
604
609
  }
605
610
 
606
611
  if (surfaceProfile === 'full') {
607
- registerCommanderCoreBridge(toolServer,{machineFsRuntime,processRuntime,searchRuntime:codingRuntime?.search??null,extendedRuntime:commanderExtendedRuntime,includeMarkdownEditor:true});
612
+ registerCommanderCoreBridge(toolServer,{machineFsRuntime,processRuntime,searchRuntime:codingRuntime?.search??null,extendedRuntime:commanderExtendedRuntime,externalProcessRuntime:commanderExternalProcessRuntime,includeMarkdownEditor:true});
608
613
  registerCommanderExtendedBridge(toolServer,{extendedRuntime:commanderExtendedRuntime,observability:commanderObservability,skillPlane:skillRuntime});
609
614
  registerCommanderRemoteBridge(toolServer,{remoteDevicePlane});
615
+ registerCommanderHiddenUiDispatch(server);
616
+ registerCommanderUiResources(server);
610
617
  }
611
618
 
612
619
  return server;
@@ -8,7 +8,8 @@ const evidenceShape={request_sha256:hex64,result_head_sha256:hex64,receipt_id:re
8
8
  const overflowSchema=z.strictObject({occurred:z.boolean(),reason:z.enum(['result_cap','byte_cap','time_limit','producer_failure']).nullable(),emitted:z.number().int().nonnegative(),dropped:z.union([z.number().int().nonnegative(),z.literal('unknown')]),resume_supported:z.boolean()});
9
9
  const resultSchema=z.strictObject({result_id:hex64,path:z.string().min(1).max(512),file_sha256:hex64,line:z.number().int().min(1),column:z.number().int().min(1),preview:z.string().max(512)});
10
10
  const processSchema=z.strictObject({pid:z.number().int().positive(),parent_pid:z.number().int().nonnegative(),name:z.string().max(260),
11
- ownership:z.enum(['deadbyte-runtime','deadbyte-session','external']),executable_path:z.string().max(2048).nullable(),command_line:z.string().max(4096).nullable()});
11
+ ownership:z.enum(['deadbyte-runtime','deadbyte-session','external']),creation_time_utc:z.string().min(1).nullable(),
12
+ executable_path:z.string().max(2048).nullable(),command_line:z.string().max(4096).nullable()});
12
13
 
13
14
  function statusContract(shape,successRequired=[]){
14
15
  return z.strictObject({status:z.enum(['ok','failed']),error:z.string().optional(),...shape}).superRefine((value,ctx)=>{
@@ -91,12 +91,13 @@ export async function verifyObservationReceiptFile({policy,verifyKeyPath,receipt
91
91
  function defaultProcessSnapshotProvider(){
92
92
  if(process.platform!=='win32') return [];
93
93
  const exe=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
94
- const command="Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,Name,ExecutablePath,CommandLine | ConvertTo-Json -Compress";
94
+ const command="$ErrorActionPreference='Stop'; Get-CimInstance Win32_Process | ForEach-Object { $created=$null; try { if ($_.CreationDate) { $created=([DateTime]$_.CreationDate).ToUniversalTime().ToString('o') } } catch {}; [pscustomobject]@{ ProcessId=$_.ProcessId; ParentProcessId=$_.ParentProcessId; Name=$_.Name; CreationTimeUtc=$created; ExecutablePath=$_.ExecutablePath; CommandLine=$_.CommandLine } } | ConvertTo-Json -Compress";
95
95
  const result=spawnSync(exe,['-NoLogo','-NoProfile','-Command',command],{encoding:'utf8',windowsHide:true,timeout:15000,maxBuffer:8*1024*1024});
96
96
  if(result.status!==0) throw new Error(`process observation failed: ${result.stderr||result.stdout}`);
97
97
  const text=(result.stdout??'').trim(); if(!text) return [];
98
98
  const parsed=JSON.parse(text); const rows=Array.isArray(parsed)?parsed:[parsed];
99
99
  return rows.map(item=>({pid:Number(item.ProcessId),parent_pid:Number(item.ParentProcessId),name:String(item.Name??''),
100
+ creation_time_utc:typeof item.CreationTimeUtc==='string'&&Number.isFinite(Date.parse(item.CreationTimeUtc))?new Date(item.CreationTimeUtc).toISOString():null,
100
101
  executable_path:typeof item.ExecutablePath==='string'?item.ExecutablePath:null,
101
102
  command_line:typeof item.CommandLine==='string'?item.CommandLine:null}));
102
103
  }
@@ -287,7 +288,8 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
287
288
  const processes=rows.filter(item=>Number.isInteger(item.pid)&&item.pid>0).map(item=>{
288
289
  const ownership=sessionPids.has(item.pid)?'deadbyte-session':runtimePids.has(item.pid)?'deadbyte-runtime':'external';
289
290
  if(ownership==='external'&&!includeExternal) return null;
290
- return {pid:item.pid,parent_pid:Number.isInteger(item.parent_pid)?item.parent_pid:0,name:String(item.name??'').slice(0,260),ownership,
291
+ const creationTime=typeof item.creation_time_utc==='string'&&Number.isFinite(Date.parse(item.creation_time_utc))?new Date(item.creation_time_utc).toISOString():null;
292
+ return {pid:item.pid,parent_pid:Number.isInteger(item.parent_pid)?item.parent_pid:0,name:String(item.name??'').slice(0,260),ownership,creation_time_utc:creationTime,
291
293
  executable_path:ownership==='external'?null:(typeof item.executable_path==='string'?item.executable_path.slice(0,2048):null),
292
294
  command_line:ownership==='external'?null:(typeof item.command_line==='string'?item.command_line.slice(0,4096):null)};
293
295
  }).filter(Boolean).sort((a,b)=>a.pid-b.pid||a.name.localeCompare(b.name));
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.18';
1
+ export const DEADBYTE_VERSION = '0.15.20';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -158,6 +158,13 @@ test('durable coding task chains edit command benchmark and verifies task receip
158
158
  assert.equal(page.stream_id, submitted.task_id);
159
159
  assert.equal(page.events.length, 3);
160
160
  assert.equal(page.events[0].seq, 4);
161
+ assert.equal(page.events[0].schema, 'deadbyte.coding-task-event.v1');
162
+ assert.equal(page.events[0].task_id, submitted.task_id);
163
+ assert.match(page.events[0].created_at_utc, /^\d{4}-\d{2}-\d{2}T/);
164
+ assert.match(page.events[0].prev_sha256, /^[0-9a-f]{64}$/);
165
+ assert.equal(page.events[0].attestation.algorithm, 'ed25519');
166
+ assert.match(page.events[0].attestation.key_id, /^[0-9a-f]{64}$/);
167
+ assert.equal(typeof page.events[0].attestation.signature_base64, 'string');
161
168
  assert.equal(page.events[2].seq, 6);
162
169
  const verified = await f.tasks.verify({ taskId: submitted.task_id });
163
170
  assert.equal(verified.ok, true);
@@ -29,6 +29,77 @@ async function fixture(){
29
29
  return {temp,state,search,get reads(){return reads;},cleanup:()=>rm(temp,{recursive:true,force:true})};
30
30
  }
31
31
 
32
+ test('Commander hidden and file-pattern flags affect native search and cursor identity',async()=>{
33
+ const f=await fixture();
34
+ try{
35
+ f.state.set('src/.hidden.txt',Buffer.from('foo'));
36
+ f.state.set('.cache/item.txt',Buffer.from('foo'));
37
+ const query={rootId:'core',target:'content',mode:'literal',pattern:'foo',maxResults:20,pageSize:20};
38
+ const visible=await f.search({...query,includeHidden:false,filePattern:'*.txt'});
39
+ assert.ok(visible.matches.length>0);
40
+ assert.ok(visible.matches.every(x=>!x.path.split('/').some(p=>p.startsWith('.'))&&x.path.endsWith('.txt')));
41
+ const hidden=await f.search({...query,includeHidden:true,filePattern:'*.txt'});
42
+ assert.ok(hidden.matches.some(x=>x.path==='src/.hidden.txt'));
43
+ const js=await f.search({...query,includeHidden:true,filePattern:'*.js'});
44
+ assert.equal(js.matches.length,0);
45
+ assert.notEqual(visible.query_sha256,hidden.query_sha256);
46
+ }finally{await f.cleanup();}
47
+ });
48
+
49
+ test('native search abort prevents further reads after an in-flight file returns',async()=>{
50
+ const controller=new AbortController();let reads=0;
51
+ const search=createCodingSearch({
52
+ tree:async()=>({entries:[{path:'a.txt',type:'file',bytes:1},{path:'b.txt',type:'file',bytes:1}]}),
53
+ fileRead:async()=>{reads++;controller.abort();return {content:Buffer.from('x').toString('base64'),bytes:1};},
54
+ cursorCodec:{encode:async()=>'',decode:async()=>({})}
55
+ });
56
+ await assert.rejects(()=>search({rootId:'core',pattern:'x',signal:controller.signal}),/cancelled/);
57
+ assert.equal(reads,1);
58
+ await assert.rejects(()=>search({rootId:'core',pattern:'x',signal:controller.signal}),/cancelled/);
59
+ assert.equal(reads,1);
60
+ });
61
+
62
+ test('Commander file search treats glob and exact filename as filenames, not content regex',async()=>{
63
+ const f=await fixture();
64
+ try{
65
+ f.state.set('src/not-a.txt.backup',Buffer.from('x'));
66
+ const glob=await f.search({rootId:'core',target:'path',mode:'literal',pathGlob:true,pattern:'*.js',maxResults:20});
67
+ assert.deepEqual(glob.matches.map(x=>x.path),['src/foo-file.js']);
68
+ const exact=await f.search({rootId:'core',target:'path',mode:'literal',pathGlob:true,pattern:'a.txt',maxResults:20});
69
+ assert.deepEqual(exact.matches.map(x=>x.path),['src/a.txt']);
70
+ assert.equal(f.reads,0);
71
+ }finally{await f.cleanup();}
72
+ });
73
+
74
+ test('Commander context lines are ordered, deduplicated and query-bound',async()=>{
75
+ const f=await fixture();
76
+ try{
77
+ f.state.clear();f.state.set('a.txt',Buffer.from('before\r\nfoo foo\r\nafter\r\n'));
78
+ const q={rootId:'core',pattern:'foo',target:'content',mode:'literal',maxResults:20};
79
+ const context=await f.search({...q,contextLines:1});
80
+ assert.deepEqual(context.matches.map(x=>[x.line,x.preview]),[[1,'before'],[2,'foo'],[3,'after']]);
81
+ const zero=await f.search({...q,contextLines:0});
82
+ assert.deepEqual(zero.matches.map(x=>[x.line,x.preview]),[[2,'foo']]);
83
+ assert.notEqual(context.query_sha256,zero.query_sha256);
84
+ }finally{await f.cleanup();}
85
+ });
86
+
87
+ test('Commander exact filename early termination returns the first deterministic match unless disabled',async()=>{
88
+ const f=await fixture();
89
+ try{
90
+ f.state.clear();
91
+ f.state.set('a/target.txt',Buffer.from('a'));
92
+ f.state.set('b/target.txt',Buffer.from('b'));
93
+ f.state.set('c/other.txt',Buffer.from('c'));
94
+ const q={rootId:'core',target:'path',mode:'literal',pathGlob:true,pattern:'target.txt',maxResults:20};
95
+ const early=await f.search({...q,earlyTermination:true});
96
+ assert.deepEqual(early.matches.map(x=>x.path),['a/target.txt']);
97
+ const exhaustive=await f.search({...q,earlyTermination:false});
98
+ assert.deepEqual(exhaustive.matches.map(x=>x.path),['a/target.txt','b/target.txt']);
99
+ assert.notEqual(early.query_sha256,exhaustive.query_sha256);
100
+ }finally{await f.cleanup();}
101
+ });
102
+
32
103
  test('R36 regex search returns multiple matches per file with deterministic signed paging',async()=>{
33
104
  const f=await fixture();
34
105
  try{
@@ -2,6 +2,19 @@ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { registerCommanderCoreBridge } from '../src/commander-core-bridge.mjs';
4
4
 
5
+ test('search adapter drains bounded native pages instead of silently losing later matches',async()=>{
6
+ const tools=new Map(), cursors=[];
7
+ const searchRuntime=async query=>{cursors.push(query.cursor);return query.cursor===null
8
+ ?{matches:[{path:'a.txt'}],next_cursor:'page-2'}
9
+ :{matches:[{path:'b.txt'}],next_cursor:null};};
10
+ registerCommanderCoreBridge({registerTool(name,config,handler){tools.set(name,{config,handler});}},{searchRuntime});
11
+ const started=await tools.get('start_search').handler({path:'.',pattern:'.',searchType:'files',maxResults:10});
12
+ let page;
13
+ for(let i=0;i<20;i++){page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:0,length:10});if(page.isComplete)break;await new Promise(r=>setTimeout(r,1));}
14
+ assert.deepEqual(cursors,[null,'page-2']);
15
+ assert.deepEqual(page.results.map(x=>x.file),['a.txt','b.txt']);
16
+ });
17
+
5
18
  test('bridge registers all aliases',()=>{
6
19
  const names=[]; registerCommanderCoreBridge({registerTool(name){names.push(name);}},{}); assert.equal(names.length,20);
7
20
  assert.ok(names.includes('read_file')); assert.ok(names.includes('start_search'));
@@ -20,12 +33,28 @@ test('search adapter provides Commander progressive session semantics',async()=>
20
33
  throw new Error('search session did not complete');
21
34
  });
22
35
 
23
- test('search adapter supports tail pagination and cancellation',async()=>{
24
- const tools=new Map(); let resolveSearch; const searchRuntime=()=>new Promise(r=>{resolveSearch=r;});
36
+ test('search adapter cancellation aborts native work and rejects late results',async()=>{
37
+ const tools=new Map(); let resolveSearch,signal; const searchRuntime=query=>{signal=query.signal;return new Promise(r=>{resolveSearch=r;});};
25
38
  registerCommanderCoreBridge({registerTool(name,config,handler){tools.set(name,{config,handler});}},{searchRuntime});
26
39
  const started=await tools.get('start_search').handler({path:'.',pattern:'x',searchType:'files'});
27
40
  assert.equal(await tools.get('stop_search').handler({sessionId:started.sessionId}),true);
41
+ assert.equal(signal.aborted,true);
42
+ assert.equal(await tools.get('stop_search').handler({sessionId:started.sessionId}),false);
28
43
  resolveSearch({matches:[{path:'z.txt'}],truncated:false});
29
44
  let page; for(let i=0;i<20;i++){page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:-1,length:100}); if(page.isComplete) break; await new Promise(r=>setTimeout(r,1));}
30
- assert.equal(page.isComplete,true); assert.equal(page.results.length,1);
45
+ assert.equal(page.isComplete,true); assert.equal(page.results.length,0);
46
+ });
47
+
48
+ test('search adapter timeout aborts native work, completes cleanly, and rejects late results',async()=>{
49
+ const tools=new Map(); let resolveSearch,signal; const searchRuntime=query=>{signal=query.signal;return new Promise(r=>{resolveSearch=r;});};
50
+ registerCommanderCoreBridge({registerTool(name,config,handler){tools.set(name,{config,handler});}},{searchRuntime});
51
+ const started=await tools.get('start_search').handler({path:'.',pattern:'needle',searchType:'content',timeout_ms:5});
52
+ await new Promise(r=>setTimeout(r,20));
53
+ assert.equal(signal.aborted,true);
54
+ let page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:0,length:100});
55
+ assert.equal(page.isComplete,true); assert.equal(page.isError,false); assert.equal(page.results.length,0);
56
+ resolveSearch({matches:[{path:'late.txt',line:1,preview:'needle'}],truncated:false});
57
+ await new Promise(r=>setTimeout(r,5));
58
+ page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:0,length:100});
59
+ assert.equal(page.results.length,0);
31
60
  });
@@ -14,7 +14,11 @@ test('Commander exhaustive surface is internally consistent and natively impleme
14
14
  assert.equal(new Set(catalog.surface.map(x=>x.name)).size,40);
15
15
  assert.ok(catalog.surface.every(x=>x.status==='implemented'));
16
16
  assert.ok(catalog.surface.every(x=>typeof x.native==='string'&&x.native.length>0));
17
- assert.equal(sha(Buffer.from(JSON.stringify(catalog.reference))),sha(Buffer.from(JSON.stringify({package:'@wonderwhy-er/desktop-commander',version:'0.2.51',license:'MIT'}))));
17
+ assert.equal(catalog.reference.package,'@wonderwhy-er/desktop-commander');
18
+ assert.equal(catalog.reference.version,'0.2.51');
19
+ assert.equal(catalog.reference.license,'MIT');
20
+ assert.equal(catalog.reference.server_sha256,'dfc303bb7dd2e8d01debac50fc43bb8644d00089bffdf2887e149497cff8b305');
21
+ assert.equal(catalog.reference.schemas_sha256,'f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140');
18
22
  });
19
23
 
20
24
  test('Commander extended native source has no Commander dependency',async()=>{
@@ -9,12 +9,20 @@ test('Commander extended config is hash-preconditioned and bounded',async()=>{
9
9
  const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
10
10
  const rt=createCommanderExtendedRuntime({stateRoot:root});
11
11
  const initial=await rt.getConfig();
12
+ for(const key of ['blockedCommands','defaultShell','allowedDirectories','fileReadLineLimit','fileWriteLineLimit','telemetryEnabled','currentClient','clientHistory','version','systemInfo'])assert.ok(Object.hasOwn(initial.config,key),`missing compatibility config key ${key}`);
13
+ assert.deepEqual(initial.config.allowedDirectories,[]);
14
+ assert.equal(initial.config.telemetryEnabled,false);
12
15
  const committed=await rt.setConfigValue({key:'theme',value:'classic',expected_sha256:initial.config_sha256});
13
16
  assert.equal(committed.status,'committed');
14
17
  await assert.rejects(()=>rt.setConfigValue({key:'theme',value:'modern',expected_sha256:initial.config_sha256}),/precondition/);
15
18
  assert.equal((await rt.getConfig({key:'theme'})).config.theme,'classic');
16
19
  });
17
20
 
21
+ test('Commander prompt runtime accepts reference-valid long IDs within bounded native storage',()=>{
22
+ const id='x'.repeat(256),rt=createCommanderExtendedRuntime({stateRoot:'x',prompts:[{id,title:'Long',prompt:'bounded'}]});
23
+ assert.equal(rt.getPrompt(id).prompt.id,id);
24
+ });
25
+
18
26
  test('Commander extended feedback is bounded and durably recorded',async()=>{
19
27
  const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
20
28
  const rt=createCommanderExtendedRuntime({stateRoot:root});
@@ -0,0 +1,98 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { createCommanderExtendedRuntime } from '../src/commander-extended-runtime.mjs';
7
+ import { registerCommanderExtendedBridge } from '../src/commander-extended-bridge-r47.mjs';
8
+ import { createOperationHistory } from '../src/operation-history.mjs';
9
+ import { createCommanderObservability } from '../src/commander-observability.mjs';
10
+
11
+ function fakeServer(){
12
+ const tools=new Map();
13
+ return {
14
+ registerTool(name,definition,handler){tools.set(name,{definition,handler});},
15
+ tools
16
+ };
17
+ }
18
+ function shape(schema){return schema?._def?.shape??schema?.shape??{};}
19
+
20
+ test('R48 extended bridge exposes Desktop Commander 0.2.51 argument shapes',async()=>{
21
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r48-extended-schema-'));
22
+ try{
23
+ const runtime=createCommanderExtendedRuntime({stateRoot:root,prompts:[{id:'onb2_01',title:'Explore',prompt:'Inspect the workspace'}]});
24
+ const history=createOperationHistory();
25
+ const observability=createCommanderObservability({history});
26
+ const server=fakeServer();
27
+ registerCommanderExtendedBridge(server,{extendedRuntime:runtime,observability});
28
+ const getConfig=shape(server.tools.get('get_config').definition.inputSchema);
29
+ const setConfig=shape(server.tools.get('set_config_value').definition.inputSchema);
30
+ const getPrompts=shape(server.tools.get('get_prompts').definition.inputSchema);
31
+ const recent=shape(server.tools.get('get_recent_tool_calls').definition.inputSchema);
32
+ const feedback=shape(server.tools.get('give_feedback_to_desktop_commander').definition.inputSchema);
33
+ assert.ok(getConfig.origin); assert.equal(getConfig.key,undefined);
34
+ assert.ok(setConfig.key); assert.ok(setConfig.value); assert.ok(setConfig.origin); assert.equal(setConfig.expected_sha256,undefined);
35
+ assert.ok(getPrompts.action); assert.ok(getPrompts.promptId);
36
+ assert.ok(recent.maxResults); assert.ok(recent.toolName); assert.ok(recent.since);
37
+ assert.deepEqual(Object.keys(feedback),[]);
38
+ }finally{await rm(root,{recursive:true,force:true});}
39
+ });
40
+
41
+ test('R48 set_config_value performs internal CAS while preserving reference wire',async()=>{
42
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r48-extended-cas-'));
43
+ try{
44
+ const runtime=createCommanderExtendedRuntime({stateRoot:root});
45
+ const server=fakeServer();
46
+ registerCommanderExtendedBridge(server,{extendedRuntime:runtime,observability:null});
47
+ const set=await server.tools.get('set_config_value').handler({key:'defaultShell',value:'powershell.exe'});
48
+ assert.equal(set.isError,undefined,JSON.stringify(set));
49
+ assert.match(set.content[0].text,/Successfully set defaultShell/);
50
+ const get=await server.tools.get('get_config').handler({});
51
+ assert.equal(get.structuredContent.config.defaultShell,'powershell.exe');
52
+ }finally{await rm(root,{recursive:true,force:true});}
53
+ });
54
+
55
+ test('R48 prompts, history filters, and feedback accept reference calls',async()=>{
56
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r48-extended-behavior-'));
57
+ try{
58
+ const runtime=createCommanderExtendedRuntime({stateRoot:root,prompts:[{id:'onb2_01',title:'Explore',prompt:'Inspect the workspace'}]});
59
+ const history=createOperationHistory({clockMs:()=>Date.parse('2026-09-27T00:00:00.000Z')});
60
+ history.append('tool.call',{tool:'read_file'});
61
+ history.append('tool.result',{tool:'read_file'});
62
+ history.append('tool.call',{tool:'start_process'});
63
+ const observability=createCommanderObservability({history});
64
+ const server=fakeServer();
65
+ registerCommanderExtendedBridge(server,{extendedRuntime:runtime,observability});
66
+ const prompt=await server.tools.get('get_prompts').handler({action:'get_prompt',promptId:'onb2_01'});
67
+ assert.equal(prompt.isError,undefined,JSON.stringify(prompt));
68
+ assert.match(prompt.content[0].text,/Inspect the workspace/);
69
+ const recent=await server.tools.get('get_recent_tool_calls').handler({maxResults:10,toolName:'read_file',since:'2026-09-26T00:00:00.000Z'});
70
+ assert.equal(recent.isError,undefined,JSON.stringify(recent));
71
+ assert.ok(recent.structuredContent.events.length>=2);
72
+ assert.ok(recent.structuredContent.events.every(e=>e.payload?.tool==='read_file'));
73
+ const feedback=await server.tools.get('give_feedback_to_desktop_commander').handler({});
74
+ assert.equal(feedback.isError,undefined,JSON.stringify(feedback));
75
+ assert.match(feedback.content[0].text,/feedback/i);
76
+ }finally{await rm(root,{recursive:true,force:true});}
77
+ });
78
+
79
+
80
+ test('R48 extended schema matches reference promptId and ISO datetime semantics exactly',async()=>{
81
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r48-extended-schema-exact-'));
82
+ try{
83
+ const runtime=createCommanderExtendedRuntime({stateRoot:root,prompts:[{id:'x'.repeat(256),title:'Long',prompt:'Long id is legal on reference wire'}]});
84
+ const history=createOperationHistory();
85
+ const observability=createCommanderObservability({history});
86
+ const server=fakeServer();
87
+ registerCommanderExtendedBridge(server,{extendedRuntime:runtime,observability});
88
+ const promptSchema=server.tools.get('get_prompts').definition.inputSchema;
89
+ const recentSchema=server.tools.get('get_recent_tool_calls').definition.inputSchema;
90
+ assert.doesNotThrow(()=>promptSchema.parse({action:'get_prompt',promptId:'x'.repeat(256)}));
91
+ assert.doesNotThrow(()=>recentSchema.parse({since:'2026-09-27T00:00:00.000Z'}));
92
+ assert.doesNotThrow(()=>recentSchema.parse({maxResults:1.5}));
93
+ history.append('tool.call',{tool:'read_file'});history.append('tool.result',{tool:'read_file'});
94
+ const fractional=await server.tools.get('get_recent_tool_calls').handler(recentSchema.parse({maxResults:1.5}));
95
+ assert.equal(fractional.isError,undefined);assert.equal(fractional.structuredContent.events.length,1);
96
+ assert.throws(()=>recentSchema.parse({since:'not-a-datetime'}));
97
+ }finally{await rm(root,{recursive:true,force:true});}
98
+ });
@@ -0,0 +1,76 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createCommanderExternalProcessRuntime } from '../src/commander-external-process-r48.mjs';
4
+
5
+ function observationSequence(pages){
6
+ let call=0;
7
+ return {
8
+ calls:()=>call,
9
+ async processList(args){
10
+ const page=pages[Math.min(call,pages.length-1)];
11
+ call++;
12
+ return typeof page==='function'?page(args):structuredClone(page);
13
+ }
14
+ };
15
+ }
16
+
17
+ test('R48 external process list exhausts one signed observation snapshot through cursors',async()=>{
18
+ const obs=observationSequence([
19
+ {status:'ok',snapshot_id:'a'.repeat(64),observed_at_utc:'2026-09-27T00:00:00.000Z',
20
+ processes:[{pid:10,parent_pid:1,name:'a.exe',ownership:'external',creation_time_utc:'2026-09-27T00:00:01.000Z',executable_path:null,command_line:null}],
21
+ next_cursor:'cursor-1',receipt_id:'1'.repeat(24),receipt_sha256:'2'.repeat(64)},
22
+ {status:'ok',snapshot_id:'a'.repeat(64),observed_at_utc:'2026-09-27T00:00:00.000Z',
23
+ processes:[{pid:20,parent_pid:1,name:'b.exe',ownership:'external',creation_time_utc:'2026-09-27T00:00:02.000Z',executable_path:null,command_line:null}],
24
+ next_cursor:null,receipt_id:'1'.repeat(24),receipt_sha256:'2'.repeat(64)}
25
+ ]);
26
+ const rt=createCommanderExternalProcessRuntime({observationRuntime:obs,hostPolicy:null,hostExecInlineFn:null});
27
+ const listed=await rt.list();
28
+ assert.deepEqual(listed.processes.map(x=>x.pid),[10,20]);
29
+ assert.equal(listed.snapshot_id,'a'.repeat(64));
30
+ assert.equal(obs.calls(),2);
31
+ });
32
+
33
+ test('R48 external kill binds PID creation time and name then proves the same identity disappeared',async()=>{
34
+ const before={status:'ok',snapshot_id:'a'.repeat(64),observed_at_utc:'2026-09-27T00:00:00.000Z',
35
+ processes:[{pid:222,parent_pid:1,name:"external.exe",ownership:'external',creation_time_utc:'2026-09-27T00:00:02.000Z',executable_path:null,command_line:null}],
36
+ next_cursor:null,receipt_id:'1'.repeat(24),receipt_sha256:'2'.repeat(64)};
37
+ const after={...before,snapshot_id:'b'.repeat(64),processes:[],receipt_id:'3'.repeat(24),receipt_sha256:'4'.repeat(64)};
38
+ const obs=observationSequence([before,after]);
39
+ const hostCalls=[];
40
+ const hostPolicy={commands:{powershell_inline:{kind:'inline',root_ids:['core']}},roots:{core:{}}};
41
+ const rt=createCommanderExternalProcessRuntime({
42
+ observationRuntime:obs,hostPolicy,
43
+ hostExecInlineFn:async(policy,args)=>{hostCalls.push({policy,args});return {exit_code:0,timed_out:false,stdout:'TERMINATED\n',stderr:''};}
44
+ });
45
+ const result=await rt.terminate({pid:222});
46
+ assert.equal(result.status,'terminated');
47
+ assert.equal(result.pid,222);
48
+ assert.equal(result.creation_time_utc,'2026-09-27T00:00:02.000Z');
49
+ assert.equal(hostCalls.length,1);
50
+ const command=hostCalls[0].args.command;
51
+ assert.match(command,/\$pidExpected=222;/);
52
+ assert.match(command,/2026-09-27T00:00:02\.000Z/);
53
+ assert.match(command,/Stop-Process -Id \$pidExpected -Force/);
54
+ assert.doesNotMatch(command,/external\.exe/,'process name must not be interpolated raw');
55
+ });
56
+
57
+ test('R48 external kill fails closed on missing identity and on post-effect same identity',async()=>{
58
+ const noIdentity={status:'ok',snapshot_id:'a'.repeat(64),observed_at_utc:'2026-09-27T00:00:00.000Z',
59
+ processes:[{pid:222,parent_pid:1,name:'external.exe',ownership:'external',creation_time_utc:null,executable_path:null,command_line:null}],
60
+ next_cursor:null,receipt_id:'1'.repeat(24),receipt_sha256:'2'.repeat(64)};
61
+ const hostPolicy={commands:{powershell_inline:{kind:'inline',root_ids:['core']}},roots:{core:{}}};
62
+ let hostCount=0;
63
+ const noIdentityRuntime=createCommanderExternalProcessRuntime({
64
+ observationRuntime:observationSequence([noIdentity]),hostPolicy,
65
+ hostExecInlineFn:async()=>{hostCount++;return {exit_code:0,timed_out:false,stdout:'',stderr:''};}
66
+ });
67
+ await assert.rejects(noIdentityRuntime.terminate({pid:222}),/creation time identity unavailable/);
68
+ assert.equal(hostCount,0);
69
+
70
+ const stable={...noIdentity,processes:[{...noIdentity.processes[0],creation_time_utc:'2026-09-27T00:00:02.000Z'}]};
71
+ const stableRuntime=createCommanderExternalProcessRuntime({
72
+ observationRuntime:observationSequence([stable,stable]),hostPolicy,
73
+ hostExecInlineFn:async()=>({exit_code:0,timed_out:false,stdout:'TERMINATED',stderr:''})
74
+ });
75
+ await assert.rejects(stableRuntime.terminate({pid:222}),/same process identity is still alive/);
76
+ });