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.
- package/MANIFEST.SHA256 +56 -34
- package/PROVENANCE.json +13 -7
- package/README.txt +7 -7
- package/RELEASE.json +30 -27
- package/SBOM.cdx.json +3 -3
- package/TEST-MATRIX.json +27 -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/coding-task-runtime.mjs +1 -1
- 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-runtime.test.mjs +7 -0
- 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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, access } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { loadPowerShellDataFile, runSlotUpdateCli } from '../src/runtime-update.mjs';
|
|
8
|
+
import { parseActivePointer } from '../src/release-slot.mjs';
|
|
9
|
+
|
|
10
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
11
|
+
const state=path.join(os.homedir(),'.deadbyte-mcp');
|
|
12
|
+
const sha=async f=>createHash('sha256').update(await readFile(f)).digest('hex');
|
|
13
|
+
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
14
|
+
|
|
15
|
+
async function waitForActivation({handoffPath,expectedVersion,expectedManifest,timeoutMs=900000,pollMs=1000}){
|
|
16
|
+
const evidenceDir=path.dirname(path.resolve(handoffPath));
|
|
17
|
+
const statePath=path.join(evidenceDir,'state.json');
|
|
18
|
+
const receiptPath=path.join(evidenceDir,'receipt.json');
|
|
19
|
+
const failurePath=path.join(evidenceDir,'failure-receipt.json');
|
|
20
|
+
const deadline=Date.now()+timeoutMs;
|
|
21
|
+
let lastPhase='unknown';
|
|
22
|
+
|
|
23
|
+
while(Date.now()<deadline){
|
|
24
|
+
// Do not poll state.json during activation: the detached supervisor atomically replaces it,
|
|
25
|
+
// and Windows can reject that rename while a read handle is still open. Final receipt/failure
|
|
26
|
+
// files are the synchronization boundary and avoid a cross-process read/write race.
|
|
27
|
+
try{
|
|
28
|
+
const receipt=JSON.parse(await readFile(receiptPath,'utf8'));
|
|
29
|
+
const body=receipt?.body;
|
|
30
|
+
const pointer=body?.active_pointer;
|
|
31
|
+
if(body?.schema!=='deadbyte.r27-slot-update-receipt.v1') throw new Error('activation receipt schema mismatch');
|
|
32
|
+
if(body?.rollback_performed!==false) throw new Error('activation receipt reports rollback');
|
|
33
|
+
if(pointer?.version!==expectedVersion||pointer?.manifest_sha256!==expectedManifest) {
|
|
34
|
+
throw new Error('activation receipt target mismatch');
|
|
35
|
+
}
|
|
36
|
+
parseActivePointer(JSON.stringify(pointer));
|
|
37
|
+
|
|
38
|
+
const activePath=path.join(state,'active-release.json');
|
|
39
|
+
const active=parseActivePointer(await readFile(activePath,'utf8'));
|
|
40
|
+
if(active.version!==expectedVersion||active.manifest_sha256!==expectedManifest) {
|
|
41
|
+
throw new Error('active pointer does not match expected release');
|
|
42
|
+
}
|
|
43
|
+
return {receiptPath,statePath,phase:lastPhase,release_id:active.release_id};
|
|
44
|
+
}catch(error){
|
|
45
|
+
if(error?.code!=='ENOENT') throw error;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try{
|
|
49
|
+
await access(failurePath);
|
|
50
|
+
throw new Error('slot activation failure receipt detected');
|
|
51
|
+
}catch(error){
|
|
52
|
+
if(error?.code!=='ENOENT') throw error;
|
|
53
|
+
}
|
|
54
|
+
await sleep(pollMs);
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`slot activation timed out after ${timeoutMs}ms; last_phase=${lastPhase}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const manifest=await sha(path.join(root,'MANIFEST.SHA256'));
|
|
60
|
+
const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
61
|
+
const cfg=await loadPowerShellDataFile(pointer.config_path);
|
|
62
|
+
const bridgeRoot=path.resolve(cfg.Bridge.WorkingDirectory);
|
|
63
|
+
const bridgeScript=path.join(bridgeRoot,'src','bridge.mjs');
|
|
64
|
+
const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
|
|
65
|
+
const expectedVersion=pkg.version;
|
|
66
|
+
const args=[
|
|
67
|
+
'--stage',root,'--development-root',root,'--expected-version',expectedVersion,'--expected-manifest',manifest,
|
|
68
|
+
'--process-policy',path.join(root,'controller','deadbyte-process-policy.json'),'--process-policy-sha256',await sha(path.join(root,'controller','deadbyte-process-policy.json')),
|
|
69
|
+
'--desktop-policy',path.join(root,'controller','deadbyte-desktop-policy.json'),'--desktop-policy-sha256',await sha(path.join(root,'controller','deadbyte-desktop-policy.json')),
|
|
70
|
+
'--semantic-lsp-config',path.join(root,'controller','deadbyte-semantic-lsp-config.json'),'--semantic-lsp-config-sha256',await sha(path.join(root,'controller','deadbyte-semantic-lsp-config.json')),
|
|
71
|
+
'--bridge-root',bridgeRoot,'--bridge-script-sha256',await sha(bridgeScript),
|
|
72
|
+
'--enable-machine-fs','--enable-observation','--enable-desktop','--enable-autonomous-supervisor','--recover-offline-predecessor'
|
|
73
|
+
];
|
|
74
|
+
const result=await runSlotUpdateCli(args,{stateRoot:state});
|
|
75
|
+
if(!result.handed_off) throw new Error('release activation unexpectedly completed inline; refusing to skip post-activation verification');
|
|
76
|
+
const verification=await waitForActivation({handoffPath:result.handoff_path,expectedVersion,expectedManifest:manifest});
|
|
77
|
+
console.log(JSON.stringify({
|
|
78
|
+
status:'activated',
|
|
79
|
+
manifest_sha256:manifest,
|
|
80
|
+
previous_release:pointer.release_id,
|
|
81
|
+
update_id:result.update_id,
|
|
82
|
+
supervisor_pid:result.supervisor_pid,
|
|
83
|
+
release_id:verification.release_id,
|
|
84
|
+
receipt_path:verification.receiptPath,
|
|
85
|
+
state_path:verification.statePath
|
|
86
|
+
},null,2));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root=path.resolve(here,'..');
|
|
8
|
+
const testDir=path.join(root,'test');
|
|
9
|
+
const excluded=new Set(['full-max-catalog-r44.test.mjs','full-max-catalog-r45.test.mjs','full-max-catalog-r46.test.mjs']);
|
|
10
|
+
const files=(await readdir(testDir,{withFileTypes:true}))
|
|
11
|
+
.filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs')&&!excluded.has(entry.name));
|
|
12
|
+
const weighted=[];
|
|
13
|
+
for(const entry of files) weighted.push({path:path.join('test',entry.name),size:(await stat(path.join(testDir,entry.name))).size});
|
|
14
|
+
weighted.sort((a,b)=>b.size-a.size||a.path.localeCompare(b.path,'en'));
|
|
15
|
+
const batchCount=8;
|
|
16
|
+
const batches=Array.from({length:batchCount},()=>[]);
|
|
17
|
+
weighted.forEach((entry,index)=>batches[index%batchCount].push(entry.path));
|
|
18
|
+
let total=0;
|
|
19
|
+
for(let index=0;index<batchCount;index++){
|
|
20
|
+
const batch=batches[index].sort((a,b)=>a.localeCompare(b,'en'));
|
|
21
|
+
console.log(`R48-BOUNDED-REGRESSION batch=${index+1}/${batchCount} files=${batch.length}`);
|
|
22
|
+
const result=spawnSync(process.execPath,['--test','--test-concurrency=1',...batch],{cwd:root,stdio:'inherit',windowsHide:true,shell:false});
|
|
23
|
+
if(result.error) throw result.error;
|
|
24
|
+
if(result.signal) throw new Error(`regression batch ${index} terminated by ${result.signal}`);
|
|
25
|
+
if(result.status!==0) throw new Error(`regression batch ${index} failed with exit code ${result.status}`);
|
|
26
|
+
total+=batch.length;
|
|
27
|
+
}
|
|
28
|
+
console.log(`R48-BOUNDED-REGRESSION PASS files=${total} batches=${batchCount}`);
|
package/src/coding-search.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import pathModule from 'node:path';
|
|
2
3
|
import { canonicalJson } from './canonical-json.mjs';
|
|
3
4
|
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
4
5
|
import { createSequenceStream } from './workload-substrate.mjs';
|
|
@@ -55,11 +56,13 @@ function preview(text,index,length){
|
|
|
55
56
|
const start=Math.max(0,index-48); const end=Math.min(text.length,index+Math.max(length,1)+112);
|
|
56
57
|
return text.slice(start,end);
|
|
57
58
|
}
|
|
58
|
-
function normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize,fuzzyTimeoutMs}){
|
|
59
|
+
function normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize,fuzzyTimeoutMs,includeHidden,filePattern,pathGlob,contextLines,earlyTermination}){
|
|
59
60
|
return {
|
|
60
61
|
schema:'deadbyte.coding-search-query.v3',root_id:rootId,path,target,mode,pattern,
|
|
61
62
|
case_sensitive:caseSensitive,max_results:maxResults,max_file_bytes:maxFileBytes,
|
|
62
|
-
max_scan_bytes:maxScanBytes,page_size:pageSize,fuzzy_timeout_ms:fuzzyTimeoutMs
|
|
63
|
+
max_scan_bytes:maxScanBytes,page_size:pageSize,fuzzy_timeout_ms:fuzzyTimeoutMs,
|
|
64
|
+
include_hidden:includeHidden,file_pattern:filePattern,path_glob:pathGlob,context_lines:contextLines,
|
|
65
|
+
early_termination:earlyTermination
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -70,17 +73,37 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
|
|
|
70
73
|
return async function search({
|
|
71
74
|
rootId,path='.',pattern,target='content',mode='literal',caseSensitive=false,
|
|
72
75
|
maxResults=200,maxFileBytes=262144,maxScanBytes=4*1024*1024,pageSize=100,cursor=null,
|
|
73
|
-
fuzzyTimeoutMs=FUZZY_SEARCH_TIMEOUT_MS
|
|
76
|
+
fuzzyTimeoutMs=FUZZY_SEARCH_TIMEOUT_MS,includeHidden=true,filePattern=null,signal=null,pathGlob=false,contextLines=null,
|
|
77
|
+
earlyTermination=false
|
|
74
78
|
}){
|
|
79
|
+
const checkCancellation=()=>{if(signal?.aborted) throw new Error('coding search cancelled');};
|
|
80
|
+
checkCancellation();
|
|
75
81
|
if(!['content','path'].includes(target)) throw new Error('coding search target must be content or path');
|
|
76
82
|
if(!['literal','regex','fuzzy'].includes(mode)) throw new Error('coding search mode must be literal, regex, or fuzzy');
|
|
77
83
|
validateInt(maxResults,'max_results',1,1000);
|
|
78
84
|
validateInt(maxFileBytes,'max_file_bytes',1,4*1024*1024);
|
|
79
85
|
validateInt(maxScanBytes,'max_scan_bytes',1,64*1024*1024);
|
|
80
86
|
validateInt(pageSize,'page_size',1,500);
|
|
87
|
+
if(contextLines!==null) validateInt(contextLines,'contextLines',0,100);
|
|
88
|
+
if(typeof earlyTermination!=='boolean') throw new Error('earlyTermination must be boolean');
|
|
89
|
+
if(typeof includeHidden!=='boolean') throw new Error('includeHidden must be boolean');
|
|
90
|
+
if(typeof pathGlob!=='boolean'||(pathGlob&&target!=='path')) throw new Error('pathGlob requires path search');
|
|
91
|
+
if(filePattern!==null&&filePattern!==undefined&&(typeof filePattern!=='string'||filePattern.length>4096)) throw new Error('filePattern must be a bounded string');
|
|
92
|
+
const globs=(filePattern??'').split('|').map(x=>x.trim()).filter(Boolean);
|
|
93
|
+
if(globs.length>64) throw new Error('too many file patterns');
|
|
94
|
+
if(globs.length&&typeof pathModule.posix.matchesGlob!=='function') throw new Error('filePattern requires Node glob support');
|
|
81
95
|
if(!Number.isInteger(fuzzyTimeoutMs)||fuzzyTimeoutMs<100||fuzzyTimeoutMs>FUZZY_SEARCH_TIMEOUT_MS) throw new Error('fuzzy_timeout_ms must be in [100, 30000]');
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
let matcher=mode==='fuzzy'?null:compileMatcher({pattern,mode,caseSensitive});
|
|
97
|
+
if(pathGlob){
|
|
98
|
+
if(typeof pathModule.posix.matchesGlob!=='function') throw new Error('path glob requires Node glob support');
|
|
99
|
+
const glob=/[*?\[\]{}]/.test(pattern)||/\.[a-zA-Z0-9]+$/.test(pattern)?pattern:`*${pattern}*`;
|
|
100
|
+
matcher={path(value){
|
|
101
|
+
const subject=glob.includes('/')?value.replaceAll('\\','/'):pathModule.posix.basename(value.replaceAll('\\','/'));
|
|
102
|
+
const dotAware=s=>s.replace(/(^|\/)\./g,'$1\u0001');
|
|
103
|
+
return pathModule.posix.matchesGlob(dotAware(caseSensitive?subject:subject.toLowerCase()),dotAware(caseSensitive?glob:glob.toLowerCase()));
|
|
104
|
+
}};
|
|
105
|
+
}
|
|
106
|
+
const query=normalizeQuery({rootId,path,target,mode,pattern,caseSensitive,maxResults,maxFileBytes,maxScanBytes,pageSize,fuzzyTimeoutMs,includeHidden,filePattern:filePattern??null,pathGlob,contextLines,earlyTermination});
|
|
84
107
|
const querySha=sha256Object(query);
|
|
85
108
|
let decodedCursor=null;
|
|
86
109
|
if(cursor!==null){
|
|
@@ -89,14 +112,30 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
|
|
|
89
112
|
}
|
|
90
113
|
|
|
91
114
|
const listed=await tree({rootId,path,maxDepth:12,maxEntries:2000});
|
|
92
|
-
|
|
115
|
+
checkCancellation();
|
|
116
|
+
const entries=listed.entries.filter(entry=>{
|
|
117
|
+
if(entry.type!=='file') return false;
|
|
118
|
+
const normalized=entry.path.replaceAll('\\','/');
|
|
119
|
+
if(!includeHidden&&normalized.split('/').some(part=>part.startsWith('.')&&part!=='.')) return false;
|
|
120
|
+
if(!globs.length) return true;
|
|
121
|
+
return globs.some(glob=>{
|
|
122
|
+
const insensitive=target==='path'&&!caseSensitive;
|
|
123
|
+
const subject=glob.includes('/')?normalized:pathModule.posix.basename(normalized);
|
|
124
|
+
// Hidden-file admission is enforced above, not by the glob engine's
|
|
125
|
+
// implicit dotfile exclusion. Preserve literal dot prefixes on both sides.
|
|
126
|
+
const dotAware=value=>value.replace(/(^|\/)\./g,'$1\u0001');
|
|
127
|
+
return pathModule.posix.matchesGlob(dotAware(insensitive?subject.toLowerCase():subject),dotAware(insensitive?glob.toLowerCase():glob));
|
|
128
|
+
});
|
|
129
|
+
}).sort((a,b)=>a.path.localeCompare(b.path,'en'));
|
|
93
130
|
const matches=[]; const snapshotEntries=[];
|
|
94
131
|
let filesScanned=0,bytesScanned=0,binaryFilesSkipped=0;
|
|
95
132
|
let truncated=listed.truncated===true;
|
|
96
133
|
let truncationReason=listed.truncated===true?'tree_limit':null;
|
|
97
134
|
|
|
98
135
|
if(target==='path'){
|
|
99
|
-
|
|
136
|
+
const exactFilename=pathGlob&&/\.[a-zA-Z0-9]+$/.test(pattern)&&!/[\*\?\[\]\{\}]/.test(pattern);
|
|
137
|
+
pathEntries: for(const entry of entries){
|
|
138
|
+
checkCancellation();
|
|
100
139
|
snapshotEntries.push({path:entry.path,bytes:entry.bytes,type:'file'});
|
|
101
140
|
const found=mode==='fuzzy'
|
|
102
141
|
? [fuzzyIndexOf(caseSensitive?entry.path:entry.path.toLowerCase(),caseSensitive?pattern:pattern.toLowerCase())]
|
|
@@ -107,16 +146,19 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
|
|
|
107
146
|
path:entry.path,line:null,column:null,preview:entry.path,file_sha256:null,
|
|
108
147
|
...(mode==='fuzzy'?{distance:hit.distance,similarity:hit.similarity,match_length:Math.max(1,hit.value.length)}:{})
|
|
109
148
|
});
|
|
149
|
+
if(earlyTermination&&exactFilename) break pathEntries;
|
|
110
150
|
if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
|
|
111
151
|
}
|
|
112
152
|
if(matches.length>=maxResults) break;
|
|
113
153
|
}
|
|
114
154
|
} else {
|
|
115
155
|
for(const entry of entries){
|
|
156
|
+
checkCancellation();
|
|
116
157
|
if(entry.bytes>maxFileBytes) continue;
|
|
117
158
|
if(bytesScanned+entry.bytes>maxScanBytes){truncated=true;truncationReason='scan_byte_limit';break;}
|
|
118
159
|
let item;
|
|
119
160
|
try{item=await fileRead({rootId,relativePath:entry.path,encoding:'base64'});}catch{continue;}
|
|
161
|
+
checkCancellation();
|
|
120
162
|
const raw=Buffer.from(item.content,'base64');
|
|
121
163
|
filesScanned+=1; bytesScanned+=item.bytes;
|
|
122
164
|
const fileSha=item.sha256??sha256Bytes(raw);
|
|
@@ -138,11 +180,27 @@ export function createCodingSearch({tree,fileRead,cursorCodec}){
|
|
|
138
180
|
}
|
|
139
181
|
} else {
|
|
140
182
|
const found=matcher.all(text);
|
|
183
|
+
if(contextLines!==null){
|
|
184
|
+
const lines=text.split(/\r?\n/);if(lines.at(-1)==='') lines.pop();
|
|
185
|
+
const matchingLines=new Map(),included=new Set();
|
|
186
|
+
for(const hit of found){
|
|
187
|
+
const lc=lineColumn(text,hit.index);
|
|
188
|
+
if(!matchingLines.has(lc.line)) matchingLines.set(lc.line,{hit,lc});
|
|
189
|
+
for(let n=Math.max(1,lc.line-contextLines);n<=Math.min(lines.length,lc.line+contextLines);n++) included.add(n);
|
|
190
|
+
}
|
|
191
|
+
for(const n of [...included].sort((a,b)=>a-b)){
|
|
192
|
+
const matched=matchingLines.get(n);
|
|
193
|
+
const value=matched?text.slice(matched.hit.index,matched.hit.index+matched.hit.length):lines[n-1].trim();
|
|
194
|
+
matches.push({path:entry.path,line:n,column:matched?.lc.column??null,preview:value.slice(0,4096),file_sha256:fileSha});
|
|
195
|
+
if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
|
|
196
|
+
}
|
|
197
|
+
}else{
|
|
141
198
|
for(const hit of found){
|
|
142
199
|
const lc=lineColumn(text,hit.index);
|
|
143
200
|
matches.push({path:entry.path,line:lc.line,column:lc.column,preview:preview(text,hit.index,hit.length),file_sha256:fileSha});
|
|
144
201
|
if(matches.length>=maxResults){truncated=true;truncationReason='max_results';break;}
|
|
145
202
|
}
|
|
203
|
+
}
|
|
146
204
|
}
|
|
147
205
|
if(matches.length>=maxResults) break;
|
|
148
206
|
}
|
|
@@ -553,7 +553,7 @@ export function createCodingTaskRuntime({ codingRuntime, signingKeyPath, verifyK
|
|
|
553
553
|
journal_head_sha256: all.at(-1)?.event_sha256 ?? ZERO_HASH,
|
|
554
554
|
stream_id: snapshot.stream_id, stream_generation: snapshot.generation, stream_next_seq: snapshot.next_seq,
|
|
555
555
|
stream_coverage: stream.coverage(),
|
|
556
|
-
events: page.items.map(item=>item.
|
|
556
|
+
events: page.items.map(item => all[item.seq - 1])
|
|
557
557
|
};
|
|
558
558
|
}
|
|
559
559
|
|
|
@@ -2,6 +2,16 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
|
|
3
3
|
export const COMMANDER_COMPAT_SCHEMA = 'deadbyte.commander-compat.v2';
|
|
4
4
|
|
|
5
|
+
export const COMMANDER_REFERENCE_PUBLIC_TOOLS = Object.freeze([
|
|
6
|
+
'get_config','set_config_value','read_file','read_multiple_files','write_file','write_pdf',
|
|
7
|
+
'create_directory','list_directory','move_file','start_search','get_more_search_results',
|
|
8
|
+
'stop_search','list_searches','get_file_info','edit_block','start_process',
|
|
9
|
+
'read_process_output','interact_with_process','force_terminate','list_sessions',
|
|
10
|
+
'list_processes','kill_process','get_usage_stats','get_recent_tool_calls',
|
|
11
|
+
'give_feedback_to_desktop_commander','get_prompts'
|
|
12
|
+
]);
|
|
13
|
+
export const COMMANDER_REFERENCE_HIDDEN_DISPATCH = Object.freeze(['track_ui_event']);
|
|
14
|
+
|
|
5
15
|
const sha256 = value => createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
|
|
6
16
|
|
|
7
17
|
// Behavioral/interface surface observed from the local reference package.
|
|
@@ -2,9 +2,9 @@ import { createCommanderWireAdapter } from './commander-wire-adapter-r47.mjs';
|
|
|
2
2
|
|
|
3
3
|
export function registerCommanderCoreBridge(
|
|
4
4
|
server,
|
|
5
|
-
{ machineFsRuntime=null, processRuntime=null, searchRuntime=null, extendedRuntime=null, includeMarkdownEditor=false }={}
|
|
5
|
+
{ machineFsRuntime=null, processRuntime=null, searchRuntime=null, extendedRuntime=null, externalProcessRuntime=null, includeMarkdownEditor=false }={}
|
|
6
6
|
){
|
|
7
|
-
const adapter=createCommanderWireAdapter({machineFsRuntime,processRuntime,searchRuntime,extendedRuntime});
|
|
7
|
+
const adapter=createCommanderWireAdapter({machineFsRuntime,processRuntime,searchRuntime,extendedRuntime,externalProcessRuntime});
|
|
8
8
|
const names=Object.keys(adapter.handlers);
|
|
9
9
|
for(const name of names){
|
|
10
10
|
server.registerTool(name,{
|
|
@@ -1,17 +1,121 @@
|
|
|
1
1
|
import * as z from 'zod/v4';
|
|
2
2
|
|
|
3
|
+
const origin=z.enum(['ui','llm']).optional();
|
|
4
|
+
const configValue=z.union([z.string(),z.number(),z.boolean(),z.array(z.string()),z.null()]);
|
|
5
|
+
|
|
6
|
+
function ok(text,structuredContent){
|
|
7
|
+
return {content:[{type:'text',text:String(text)}],structuredContent};
|
|
8
|
+
}
|
|
9
|
+
function jsonText(prefix,value){
|
|
10
|
+
return `${prefix}\n${JSON.stringify(value,null,2)}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
export function registerCommanderExtendedBridge(server,{extendedRuntime,observability=null,skillPlane=null}={}){
|
|
4
|
-
|
|
5
|
-
|
|
14
|
+
void skillPlane;
|
|
15
|
+
if(!extendedRuntime)throw new Error('Commander extended runtime is required');
|
|
16
|
+
const reg=(name,schema,handler)=>server.registerTool(name,{
|
|
17
|
+
description:'Desktop Commander wire-compatible adapter backed by DEADBYTE native state/evidence planes.',
|
|
18
|
+
inputSchema:schema
|
|
19
|
+
},async args=>{
|
|
6
20
|
try{return await handler(args??{});}
|
|
7
|
-
catch(error){
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
catch(error){
|
|
22
|
+
const message=error instanceof Error?error.message:String(error);
|
|
23
|
+
return {
|
|
24
|
+
content:[{type:'text',text:`Error: ${name}: ${message}`}],
|
|
25
|
+
isError:true,
|
|
26
|
+
structuredContent:{status:'error',tool:name,error:message}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
reg('get_config',z.object({origin}),async()=>{
|
|
32
|
+
const current=await extendedRuntime.getConfig();
|
|
33
|
+
return ok(jsonText('Current configuration:',current.config),current);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
reg('set_config_value',z.object({key:z.string(),value:configValue,origin}),async args=>{
|
|
37
|
+
const before=await extendedRuntime.getConfig();
|
|
38
|
+
const committed=await extendedRuntime.setConfigValue({
|
|
39
|
+
key:args.key,
|
|
40
|
+
value:args.value,
|
|
41
|
+
expected_sha256:before.config_sha256
|
|
42
|
+
});
|
|
43
|
+
const after=await extendedRuntime.getConfig();
|
|
44
|
+
return ok(
|
|
45
|
+
`Successfully set ${args.key} to ${JSON.stringify(args.value)}\n\nUpdated configuration:\n${JSON.stringify(after.config,null,2)}`,
|
|
46
|
+
{...committed,config:after.config,config_sha256:after.config_sha256}
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
reg('give_feedback_to_desktop_commander',z.object({}),async()=>ok(
|
|
51
|
+
'Feedback compatibility endpoint is available locally. DEADBYTE does not open a vendor browser or transmit telemetry.',
|
|
52
|
+
{status:'ready',mode:'local',external_browser_opened:false,telemetry_sent:false}
|
|
53
|
+
));
|
|
54
|
+
|
|
55
|
+
reg('get_prompts',z.object({
|
|
56
|
+
action:z.literal('get_prompt'),
|
|
57
|
+
promptId:z.string()
|
|
58
|
+
}),async args=>{
|
|
59
|
+
const result=extendedRuntime.getPrompt(args.promptId);
|
|
60
|
+
const prompt=result.prompt;
|
|
61
|
+
return ok(`# ${prompt.title}\n\n${prompt.prompt}`,result);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
reg('resources',z.object({}),async()=>{
|
|
65
|
+
const result=extendedRuntime.getResources();
|
|
66
|
+
return ok(jsonText('Resources:',result.resources),result);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
reg('file_preview',z.strictObject({
|
|
70
|
+
root_id:z.string(),path:z.string(),
|
|
71
|
+
offset:z.number().int().min(0).optional(),
|
|
72
|
+
max_bytes:z.number().int().min(1).max(65536).optional(),
|
|
73
|
+
mime_type:z.string().optional()
|
|
74
|
+
}),async args=>{
|
|
75
|
+
const result=await extendedRuntime.filePreview({
|
|
76
|
+
rootId:args.root_id,path:args.path,offset:args.offset,maxBytes:args.max_bytes,mimeType:args.mime_type
|
|
77
|
+
});
|
|
78
|
+
return ok(result.text??`Preview ${result.bytes} bytes`,result);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
reg('get_recent_tool_calls',z.object({
|
|
82
|
+
maxResults:z.number().min(1).max(1000).optional().default(50),
|
|
83
|
+
toolName:z.string().optional(),
|
|
84
|
+
since:z.iso.datetime().optional()
|
|
85
|
+
}),async args=>{
|
|
86
|
+
if(!observability)throw new Error('observability disabled');
|
|
87
|
+
const result=observability.recentToolCalls({
|
|
88
|
+
maxResults:args.maxResults,
|
|
89
|
+
toolName:args.toolName??null,
|
|
90
|
+
since:args.since??null
|
|
91
|
+
});
|
|
92
|
+
return ok(jsonText(`Tool Call History (${result.events.length} events)`,result.events),result);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
reg('get_usage_stats',z.object({}),async()=>{
|
|
96
|
+
if(!observability)throw new Error('observability disabled');
|
|
97
|
+
const result=observability.usageStats();
|
|
98
|
+
return ok(jsonText('Usage statistics:',result),result);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function registerCommanderHiddenUiDispatch(server){
|
|
103
|
+
const registry=server?._registeredTools;
|
|
104
|
+
if(!registry||typeof registry!=='object'||Array.isArray(registry)) throw new Error('MCP SDK hidden-tool registry unavailable');
|
|
105
|
+
if(Object.hasOwn(registry,'track_ui_event')) throw new Error('duplicate hidden tool registration: track_ui_event');
|
|
106
|
+
const registered=server.registerTool('track_ui_event',{
|
|
107
|
+
description:'Hidden local UI compatibility dispatch; never advertised and never transmits vendor telemetry.',
|
|
108
|
+
inputSchema:z.object({
|
|
109
|
+
event:z.string().min(1).max(80),
|
|
110
|
+
component:z.string().optional().default('file_preview'),
|
|
111
|
+
params:z.record(z.string(),z.union([z.string(),z.number(),z.boolean(),z.null()])).optional().default({})
|
|
112
|
+
})
|
|
113
|
+
},async args=>({
|
|
114
|
+
content:[{type:'text',text:`Tracked UI event: ${args.event}`}],
|
|
115
|
+
structuredContent:{accepted:true,local_only:true,event:args.event,component:args.component}
|
|
116
|
+
}));
|
|
117
|
+
const descriptor=Object.getOwnPropertyDescriptor(registry,'track_ui_event');
|
|
118
|
+
if(!descriptor||descriptor.value!==registered) throw new Error('MCP SDK hidden-tool registration identity mismatch');
|
|
119
|
+
Object.defineProperty(registry,'track_ui_event',{...descriptor,enumerable:false});
|
|
120
|
+
return registered;
|
|
17
121
|
}
|
|
@@ -2,9 +2,30 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, readFile, writeFile, rename } from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { canonicalJson } from './canonical-json.mjs';
|
|
5
|
+
import { createDeadbytePdf } from './commander-pdf-r48.mjs';
|
|
6
|
+
import { DEADBYTE_VERSION } from './version.mjs';
|
|
5
7
|
|
|
6
8
|
export const COMMANDER_EXTENDED_SCHEMA='deadbyte.commander-extended.v1';
|
|
7
9
|
const MAX_VALUE=16384,MAX_FEEDBACK=4096,MAX_PREVIEW=65536;
|
|
10
|
+
const COMPAT_CONFIG_DEFAULTS=Object.freeze({
|
|
11
|
+
blockedCommands:[],
|
|
12
|
+
defaultShell:process.platform==='win32'?'powershell.exe':(process.env.SHELL||'/bin/sh'),
|
|
13
|
+
allowedDirectories:[],
|
|
14
|
+
fileReadLineLimit:1000,
|
|
15
|
+
fileWriteLineLimit:50,
|
|
16
|
+
telemetryEnabled:false,
|
|
17
|
+
currentClient:{name:'deadbyte-mcp',version:DEADBYTE_VERSION},
|
|
18
|
+
clientHistory:[],
|
|
19
|
+
version:DEADBYTE_VERSION,
|
|
20
|
+
systemInfo:{platform:process.platform,architecture:process.arch,node:process.version,authority:'native-policy-roots'}
|
|
21
|
+
});
|
|
22
|
+
const DEFAULT_PROMPTS=Object.freeze([
|
|
23
|
+
{id:'onb2_01',title:'Explore codebase',prompt:'Inspect the workspace, map the smallest relevant surface, and report evidence before changing bytes.'},
|
|
24
|
+
{id:'onb2_02',title:'Diagnose failure',prompt:'Reproduce the failure, isolate the defect surface, and show the check that catches it.'},
|
|
25
|
+
{id:'onb2_03',title:'Surgical change',prompt:'Make the smallest state-bound change that fixes the verified defect, then rerun the focused check.'},
|
|
26
|
+
{id:'onb2_04',title:'Verify release',prompt:'Run focused and bounded regression gates, verify receipts and manifests, and separate source truth from active runtime truth.'},
|
|
27
|
+
{id:'onb2_05',title:'Operate machine',prompt:'Use typed DEADBYTE machine, process, filesystem, desktop, and remote capabilities with explicit authority and observable evidence.'}
|
|
28
|
+
]);
|
|
8
29
|
const sha256=v=>createHash('sha256').update(Buffer.isBuffer(v)?v:Buffer.from(typeof v==='string'?v:canonicalJson(v),'utf8')).digest('hex');
|
|
9
30
|
const keyOk=k=>typeof k==='string'&&/^[A-Za-z0-9._-]{1,128}$/.test(k);
|
|
10
31
|
|
|
@@ -20,14 +41,14 @@ function makePdf(text){
|
|
|
20
41
|
return Buffer.from(out,'utf8');
|
|
21
42
|
}
|
|
22
43
|
|
|
23
|
-
export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=
|
|
44
|
+
export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=DEFAULT_PROMPTS,resources=[]}={}){
|
|
24
45
|
if(typeof stateRoot!=='string'||!stateRoot)throw new Error('extended runtime stateRoot required');
|
|
25
46
|
const configPath=path.join(stateRoot,'commander-config.json'),feedbackDir=path.join(stateRoot,'feedback');
|
|
26
47
|
let configCache=null;
|
|
27
48
|
async function loadConfig(){
|
|
28
49
|
if(configCache)return configCache;
|
|
29
|
-
try{configCache=JSON.parse(await readFile(configPath,'utf8'));}
|
|
30
|
-
catch(e){if(e?.code!=='ENOENT')throw e;configCache=
|
|
50
|
+
try{configCache={...structuredClone(COMPAT_CONFIG_DEFAULTS),...JSON.parse(await readFile(configPath,'utf8'))};}
|
|
51
|
+
catch(e){if(e?.code!=='ENOENT')throw e;configCache=structuredClone(COMPAT_CONFIG_DEFAULTS);}
|
|
31
52
|
return configCache;
|
|
32
53
|
}
|
|
33
54
|
async function getConfig({key=null}={}){
|
|
@@ -58,6 +79,12 @@ export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=[
|
|
|
58
79
|
return {status:'committed',feedback_id:id,feedback_sha256:sha256(record)};
|
|
59
80
|
}
|
|
60
81
|
function getPrompts(){return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',prompts:prompts.map(x=>({...x})).slice(0,128)};}
|
|
82
|
+
function getPrompt(promptId){
|
|
83
|
+
if(typeof promptId!=='string'||promptId.length<1||Buffer.byteLength(promptId,'utf8')>MAX_VALUE)throw new Error('promptId invalid');
|
|
84
|
+
const prompt=prompts.find(x=>x?.id===promptId);
|
|
85
|
+
if(!prompt)throw new Error(`prompt '${promptId}' not found`);
|
|
86
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',prompt:{...prompt}};
|
|
87
|
+
}
|
|
61
88
|
function getResources(){return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',resources:resources.map(x=>({...x})).slice(0,128)};}
|
|
62
89
|
async function filePreview({rootId,path:relativePath,offset=0,maxBytes=MAX_PREVIEW,mimeType='text/plain'}){
|
|
63
90
|
if(!Number.isInteger(offset)||offset<0||offset>64*1024*1024)throw new Error('preview offset invalid');
|
|
@@ -68,8 +95,8 @@ export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=[
|
|
|
68
95
|
}
|
|
69
96
|
function writePdf({text,metadata={}}){
|
|
70
97
|
if(typeof text!=='string'||Buffer.byteLength(text)>MAX_PREVIEW)throw new Error('PDF text exceeds byte limit');
|
|
71
|
-
const
|
|
72
|
-
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',mime_type:'application/pdf',bytes_base64:
|
|
98
|
+
const pdf=createDeadbytePdf([text],{metadata});
|
|
99
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',mime_type:'application/pdf',bytes_base64:pdf.bytes_base64,bytes_sha256:pdf.bytes_sha256,bytes:pdf.bytes.length,page_count:pdf.page_count,profile:pdf.profile,metadata};
|
|
73
100
|
}
|
|
74
|
-
return Object.freeze({getConfig,setConfigValue,feedback,getPrompts,getResources,filePreview,writePdf,history});
|
|
101
|
+
return Object.freeze({getConfig,setConfigValue,feedback,getPrompts,getPrompt,getResources,filePreview,writePdf,history});
|
|
75
102
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const MAX_PROCESSES=8192;
|
|
2
|
+
const PAGE_SIZE=256;
|
|
3
|
+
const ISO_UTC=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
|
|
4
|
+
|
|
5
|
+
function bounded(value,max=2048){
|
|
6
|
+
const s=String(value??'').replace(/[\r\n\t]+/g,' ').replace(/\s{2,}/g,' ').trim();
|
|
7
|
+
return s.length<=max?s:s.slice(0,max)+'...';
|
|
8
|
+
}
|
|
9
|
+
function normalizeIdentity(item){
|
|
10
|
+
if(!item||!Number.isInteger(item.pid)||item.pid<=0) throw new Error('external process observation PID invalid');
|
|
11
|
+
const creation=typeof item.creation_time_utc==='string'&&ISO_UTC.test(item.creation_time_utc)&&Number.isFinite(Date.parse(item.creation_time_utc))
|
|
12
|
+
? new Date(item.creation_time_utc).toISOString()
|
|
13
|
+
: null;
|
|
14
|
+
return {
|
|
15
|
+
pid:item.pid,
|
|
16
|
+
parent_pid:Number.isInteger(item.parent_pid)&&item.parent_pid>=0?item.parent_pid:0,
|
|
17
|
+
name:String(item.name??'').slice(0,260),
|
|
18
|
+
ownership:String(item.ownership??'external'),
|
|
19
|
+
creation_time_utc:creation,
|
|
20
|
+
executable_path:item.executable_path??null,
|
|
21
|
+
command_line:item.command_line??null
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function selectHostBinding(hostPolicy){
|
|
25
|
+
const spec=hostPolicy?.commands?.powershell_inline;
|
|
26
|
+
if(!spec||spec.kind!=='inline') throw new Error('external process termination requires host powershell_inline policy');
|
|
27
|
+
const requested=process.env.DEADBYTE_COMMANDER_HOST_ROOT;
|
|
28
|
+
if(requested){
|
|
29
|
+
if(!spec.root_ids?.includes(requested)) throw new Error('DEADBYTE_COMMANDER_HOST_ROOT is not allowed for powershell_inline');
|
|
30
|
+
return {commandId:'powershell_inline',rootId:requested};
|
|
31
|
+
}
|
|
32
|
+
if(spec.root_ids?.includes('core')) return {commandId:'powershell_inline',rootId:'core'};
|
|
33
|
+
if(Array.isArray(spec.root_ids)&&spec.root_ids.length===1) return {commandId:'powershell_inline',rootId:spec.root_ids[0]};
|
|
34
|
+
throw new Error('external process termination requires DEADBYTE_COMMANDER_HOST_ROOT when powershell_inline has multiple non-core roots');
|
|
35
|
+
}
|
|
36
|
+
function terminateCommand(identity){
|
|
37
|
+
if(!identity.creation_time_utc) throw new Error('external process creation time identity unavailable');
|
|
38
|
+
const nameB64=Buffer.from(identity.name,'utf8').toString('base64');
|
|
39
|
+
const creation=identity.creation_time_utc;
|
|
40
|
+
if(!ISO_UTC.test(creation)) throw new Error('external process creation time identity invalid');
|
|
41
|
+
return [
|
|
42
|
+
"$ErrorActionPreference='Stop';",
|
|
43
|
+
`$pidExpected=${identity.pid};`,
|
|
44
|
+
`$createdExpected='${creation}';`,
|
|
45
|
+
`$nameExpected=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${nameB64}'));`,
|
|
46
|
+
'$cim=Get-CimInstance Win32_Process -Filter ("ProcessId={0}" -f $pidExpected) -ErrorAction Stop;',
|
|
47
|
+
"if($null -eq $cim){throw 'process missing before termination'};",
|
|
48
|
+
'$live=Get-Process -Id $pidExpected -ErrorAction Stop;',
|
|
49
|
+
"$created=$live.StartTime.ToUniversalTime().ToString('o');",
|
|
50
|
+
"if($created -ne $createdExpected){throw 'process identity mismatch: creation_time'};",
|
|
51
|
+
"if([string]$cim.Name -cne $nameExpected){throw 'process identity mismatch: name'};",
|
|
52
|
+
'Stop-Process -Id $pidExpected -Force -ErrorAction Stop;',
|
|
53
|
+
"Write-Output 'TERMINATED';"
|
|
54
|
+
].join(' ');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function createCommanderExternalProcessRuntime({observationRuntime,hostPolicy=null,hostExecInlineFn=null}={}){
|
|
58
|
+
if(!observationRuntime||typeof observationRuntime.processList!=='function') throw new Error('external process runtime requires observationRuntime');
|
|
59
|
+
|
|
60
|
+
async function list(){
|
|
61
|
+
let cursor=null,first=null;
|
|
62
|
+
const processes=[];
|
|
63
|
+
const receiptIds=[];
|
|
64
|
+
do{
|
|
65
|
+
const page=await observationRuntime.processList({limit:PAGE_SIZE,includeExternal:true,cursor});
|
|
66
|
+
if(page?.status!=='ok'||!Array.isArray(page.processes)) throw new Error('external process observation failed');
|
|
67
|
+
if(!first) first=page;
|
|
68
|
+
else if(page.snapshot_id!==first.snapshot_id) throw new Error('external process observation snapshot changed during pagination');
|
|
69
|
+
for(const item of page.processes){
|
|
70
|
+
if(processes.length>=MAX_PROCESSES) throw new Error('external process observation exceeds bounded process cap');
|
|
71
|
+
processes.push(normalizeIdentity(item));
|
|
72
|
+
}
|
|
73
|
+
if(typeof page.receipt_id==='string') receiptIds.push(page.receipt_id);
|
|
74
|
+
cursor=page.next_cursor??null;
|
|
75
|
+
}while(cursor!==null);
|
|
76
|
+
return {
|
|
77
|
+
status:'ok',
|
|
78
|
+
snapshot_id:first?.snapshot_id??null,
|
|
79
|
+
observed_at_utc:first?.observed_at_utc??null,
|
|
80
|
+
processes,
|
|
81
|
+
receipt_ids:[...new Set(receiptIds)]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function findFresh(pid){
|
|
86
|
+
if(!Number.isInteger(pid)||pid<=0) throw new Error('external process PID invalid');
|
|
87
|
+
const snapshot=await list();
|
|
88
|
+
const process=snapshot.processes.find(item=>item.pid===pid)??null;
|
|
89
|
+
return {snapshot,process};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function terminate({pid}){
|
|
93
|
+
if(!hostPolicy||typeof hostExecInlineFn!=='function') throw new Error('external process termination requires configured Host plane');
|
|
94
|
+
const before=await findFresh(pid);
|
|
95
|
+
if(!before.process) throw new Error(`external process PID ${pid} not found`);
|
|
96
|
+
const identity=before.process;
|
|
97
|
+
if(!identity.creation_time_utc) throw new Error('external process creation time identity unavailable');
|
|
98
|
+
const binding=selectHostBinding(hostPolicy);
|
|
99
|
+
const result=await hostExecInlineFn(hostPolicy,{
|
|
100
|
+
commandId:binding.commandId,
|
|
101
|
+
rootId:binding.rootId,
|
|
102
|
+
command:terminateCommand(identity),
|
|
103
|
+
cwd:'.',
|
|
104
|
+
timeoutMs:30000
|
|
105
|
+
});
|
|
106
|
+
if(result?.timed_out===true||result?.exit_code!==0){
|
|
107
|
+
throw new Error(`external process termination failed: ${bounded(result?.stderr||result?.stdout||`exit=${result?.exit_code}`)}`);
|
|
108
|
+
}
|
|
109
|
+
const after=await findFresh(pid);
|
|
110
|
+
const same=after.process&&after.process.creation_time_utc===identity.creation_time_utc&&after.process.name===identity.name;
|
|
111
|
+
if(same) throw new Error('external process termination returned but the same process identity is still alive');
|
|
112
|
+
return {
|
|
113
|
+
status:'terminated',
|
|
114
|
+
pid:identity.pid,
|
|
115
|
+
name:identity.name,
|
|
116
|
+
creation_time_utc:identity.creation_time_utc,
|
|
117
|
+
before_snapshot_id:before.snapshot.snapshot_id,
|
|
118
|
+
after_snapshot_id:after.snapshot.snapshot_id,
|
|
119
|
+
before_receipt_ids:before.snapshot.receipt_ids,
|
|
120
|
+
after_receipt_ids:after.snapshot.receipt_ids
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Object.freeze({list,terminate});
|
|
125
|
+
}
|