deadbyte-mcp 0.15.15 → 0.15.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +54 -22
- package/PROVENANCE.json +6 -6
- package/README.txt +6 -6
- package/RELEASE.json +12 -7
- package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +49 -0
- package/docs/COMMANDER_REFERENCE_INVENTORY.json +70 -0
- package/docs/R47-FULL-TOOL-CATALOG.json +123 -0
- package/package.json +5 -2
- package/scripts/commander-hardening-gate-r47.mjs +35 -0
- package/scripts/commander-parity-benchmark.mjs +6 -0
- package/scripts/commander-real-performance-benchmark-r47.mjs +41 -0
- package/scripts/deploy-r47.mjs +67 -8
- package/scripts/full-regression-r47.mjs +28 -0
- package/scripts/inventory-reference-dist.mjs +17 -0
- package/scripts/npm-release-r47.mjs +4 -1
- package/scripts/r47-syntax-check.mjs +6 -0
- package/scripts/r47-wire-remote-plane.mjs +23 -0
- package/src/atomic-mutation.mjs +59 -29
- package/src/commander-compat-contracts.mjs +90 -0
- package/src/commander-compat-runtime.mjs +91 -0
- package/src/commander-core-bridge.mjs +30 -0
- package/src/commander-extended-runtime.mjs +74 -0
- package/src/commander-observability.mjs +33 -0
- package/src/commander-remote-bridge-r47.mjs +41 -0
- package/src/commander-remote-relay-r47.mjs +44 -0
- package/src/machine-fs.mjs +108 -22
- package/src/mcp-server.mjs +94 -0
- package/src/release-slot.mjs +3 -1
- package/src/runtime-update.mjs +61 -7
- package/src/tool-audit.mjs +15 -2
- package/src/version.mjs +1 -2
- package/test/OBSERVED-COMMANDER-TOOLS.json +1 -0
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/coding-search-r36.test.mjs +14 -0
- package/test/commander-compat-runtime.test.mjs +34 -0
- package/test/commander-core-bridge-r47.test.mjs +31 -0
- package/test/commander-exhaustive-surface-r47.test.mjs +32 -0
- package/test/commander-extended-runtime-r47.test.mjs +46 -0
- package/test/commander-observability-r47.test.mjs +34 -0
- package/test/commander-parity-gate-r47.test.mjs +49 -0
- package/test/commander-protocol-r47.test.mjs +30 -0
- package/test/commander-real-parity-r47.test.mjs +121 -0
- package/test/commander-remote-bridge-r47.test.mjs +39 -0
- package/test/commander-remote-plane-r47.test.mjs +25 -0
- package/test/commander-torture-r47.test.mjs +146 -0
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/full-max-catalog-r47.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +97 -0
- package/test/output-schema.test.mjs +1 -1
- package/test/process-runtime.test.mjs +58 -0
- package/test/r47-release-contract.test.mjs +37 -0
- package/test/release-activation-gate.test.mjs +30 -0
- package/test/release-version.test.mjs +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, writeFile, rename } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
5
|
+
|
|
6
|
+
export const COMMANDER_EXTENDED_SCHEMA='deadbyte.commander-extended.v1';
|
|
7
|
+
const MAX_VALUE=16384,MAX_FEEDBACK=4096,MAX_PREVIEW=65536;
|
|
8
|
+
const sha256=v=>createHash('sha256').update(Buffer.isBuffer(v)?v:Buffer.from(typeof v==='string'?v:canonicalJson(v),'utf8')).digest('hex');
|
|
9
|
+
const keyOk=k=>typeof k==='string'&&/^[A-Za-z0-9._-]{1,128}$/.test(k);
|
|
10
|
+
|
|
11
|
+
function pdfEscape(s){return String(s).replaceAll('\\\\','\\\\\\\\').replaceAll('(','\\\\(').replaceAll(')','\\\\)');}
|
|
12
|
+
function makePdf(text){
|
|
13
|
+
const nl=String.fromCharCode(10),lines=String(text).replaceAll(String.fromCharCode(13),'').split(nl).slice(0,200);
|
|
14
|
+
const stream=['BT','/F1 11 Tf','50 790 14 TL',...lines.map(line=>'('+pdfEscape(line).slice(0,180)+') Tj 0 -14 Td'),'ET'].join(nl);
|
|
15
|
+
const objs=['<< /Type /Catalog /Pages 2 0 R >>','<< /Type /Pages /Kids [3 0 R] /Count 1 >>','<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> /Contents 5 0 R >>','<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>','<< /Length '+Buffer.byteLength(stream)+' >>'+nl+'stream'+nl+stream+nl+'endstream'];
|
|
16
|
+
let out='%PDF-1.4'+nl,offsets=[0];
|
|
17
|
+
for(let i=0;i<objs.length;i++){offsets.push(Buffer.byteLength(out));out+=(i+1)+' 0 obj'+nl+objs[i]+nl+'endobj'+nl;}
|
|
18
|
+
const xref=Buffer.byteLength(out);
|
|
19
|
+
out+='xref'+nl+'0 6'+nl+'0000000000 65535 f '+nl+offsets.slice(1).map(o=>String(o).padStart(10,'0')+' 00000 n ').join(nl)+nl+'trailer'+nl+'<< /Size 6 /Root 1 0 R >>'+nl+'startxref'+nl+xref+nl+'%%EOF'+nl;
|
|
20
|
+
return Buffer.from(out,'utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=[],resources=[]}={}){
|
|
24
|
+
if(typeof stateRoot!=='string'||!stateRoot)throw new Error('extended runtime stateRoot required');
|
|
25
|
+
const configPath=path.join(stateRoot,'commander-config.json'),feedbackDir=path.join(stateRoot,'feedback');
|
|
26
|
+
let configCache=null;
|
|
27
|
+
async function loadConfig(){
|
|
28
|
+
if(configCache)return configCache;
|
|
29
|
+
try{configCache=JSON.parse(await readFile(configPath,'utf8'));}
|
|
30
|
+
catch(e){if(e?.code!=='ENOENT')throw e;configCache={};}
|
|
31
|
+
return configCache;
|
|
32
|
+
}
|
|
33
|
+
async function getConfig({key=null}={}){
|
|
34
|
+
const cfg=await loadConfig();
|
|
35
|
+
if(key!==null&&!keyOk(key))throw new Error('config key invalid');
|
|
36
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',config:key===null?structuredClone(cfg):{[key]:cfg[key]},config_sha256:sha256(cfg)};
|
|
37
|
+
}
|
|
38
|
+
async function setConfigValue({key,value,expected_sha256}){
|
|
39
|
+
if(!keyOk(key))throw new Error('config key invalid');
|
|
40
|
+
if(!['string','number','boolean'].includes(typeof value)&&value!==null)throw new Error('config value must be scalar');
|
|
41
|
+
if(Buffer.byteLength(typeof value==='string'?value:canonicalJson(value))>MAX_VALUE)throw new Error('config value exceeds byte limit');
|
|
42
|
+
const cfg=await loadConfig(),beforeSha=sha256(cfg);
|
|
43
|
+
if(expected_sha256!==beforeSha)throw new Error('config precondition mismatch');
|
|
44
|
+
const next={...cfg,[key]:value};
|
|
45
|
+
await mkdir(stateRoot,{recursive:true});
|
|
46
|
+
const tmp=configPath+'.tmp-'+process.pid+'-'+randomUUID();
|
|
47
|
+
await writeFile(tmp,canonicalJson(next),{encoding:'utf8',flag:'wx',mode:0o600});
|
|
48
|
+
await rename(tmp,configPath); configCache=next;
|
|
49
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'committed',key,previous_sha256:beforeSha,config_sha256:sha256(next)};
|
|
50
|
+
}
|
|
51
|
+
async function feedback({message,category='general'}){
|
|
52
|
+
if(typeof message!=='string'||!message.trim()||Buffer.byteLength(message)>MAX_FEEDBACK)throw new Error('feedback message invalid');
|
|
53
|
+
if(!/^[A-Za-z0-9._-]{1,64}$/.test(category))throw new Error('feedback category invalid');
|
|
54
|
+
await mkdir(feedbackDir,{recursive:true});
|
|
55
|
+
const id=randomUUID(),record={schema:COMMANDER_EXTENDED_SCHEMA,id,category,message:message.trim(),created_at_utc:new Date().toISOString()};
|
|
56
|
+
await writeFile(path.join(feedbackDir,id+'.json'),canonicalJson(record),{encoding:'utf8',flag:'wx',mode:0o600});
|
|
57
|
+
return {status:'committed',feedback_id:id,feedback_sha256:sha256(record)};
|
|
58
|
+
}
|
|
59
|
+
function getPrompts(){return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',prompts:prompts.map(x=>({...x})).slice(0,128)};}
|
|
60
|
+
function getResources(){return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',resources:resources.map(x=>({...x})).slice(0,128)};}
|
|
61
|
+
async function filePreview({rootId,path:relativePath,offset=0,maxBytes=MAX_PREVIEW,mimeType='text/plain'}){
|
|
62
|
+
if(!Number.isInteger(offset)||offset<0||offset>64*1024*1024)throw new Error('preview offset invalid');
|
|
63
|
+
if(!Number.isInteger(maxBytes)||maxBytes<1||maxBytes>MAX_PREVIEW)throw new Error('preview maxBytes invalid');
|
|
64
|
+
if(typeof rootId!=='string'||typeof relativePath!=='string'||path.isAbsolute(relativePath))throw new Error('preview target invalid');
|
|
65
|
+
const data=await readFile(path.resolve(rootId,relativePath)),slice=data.subarray(offset,offset+maxBytes);
|
|
66
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',root_id:rootId,path:relativePath,mime_type:mimeType,offset,bytes:slice.length,total_bytes:data.length,truncated:offset+slice.length<data.length,sha256:sha256(slice),text:mimeType.startsWith('text/')?slice.toString('utf8'):null};
|
|
67
|
+
}
|
|
68
|
+
function writePdf({text,metadata={}}){
|
|
69
|
+
if(typeof text!=='string'||Buffer.byteLength(text)>MAX_PREVIEW)throw new Error('PDF text exceeds byte limit');
|
|
70
|
+
const bytes=makePdf(text);
|
|
71
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',mime_type:'application/pdf',bytes_base64:bytes.toString('base64'),bytes_sha256:sha256(bytes),bytes:bytes.length,metadata};
|
|
72
|
+
}
|
|
73
|
+
return Object.freeze({getConfig,setConfigValue,feedback,getPrompts,getResources,filePreview,writePdf,history});
|
|
74
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
2
|
+
|
|
3
|
+
const MAX_LIMIT=1000;
|
|
4
|
+
const SCHEMA='deadbyte.commander-observability.v1';
|
|
5
|
+
|
|
6
|
+
function boundLimit(limit){if(!Number.isInteger(limit)||limit<1||limit>MAX_LIMIT)throw new Error('observability limit out of range');return limit;}
|
|
7
|
+
|
|
8
|
+
export function createCommanderObservability({history}={}){
|
|
9
|
+
if(!history||typeof history.read!=='function'||typeof history.verify!=='function'||typeof history.operationId!=='string'||history.operationId.length<1) throw new Error('observability requires operation history');
|
|
10
|
+
function recentToolCalls({afterSequence=0,limit=100}={}){
|
|
11
|
+
boundLimit(limit);
|
|
12
|
+
const page=history.read({afterSequence,limit});
|
|
13
|
+
const events=page.events.filter(e=>e.event_type==='tool.call'||e.event_type==='tool.result'||e.event_type==='tool.error');
|
|
14
|
+
return {schema:SCHEMA,operation_id:page.operation_id,events,next_sequence:page.next_sequence,head_event_hash:page.head_event_hash,earliest_sequence:page.earliest_sequence,overflow:page.overflow};
|
|
15
|
+
}
|
|
16
|
+
function usageStats(){
|
|
17
|
+
const page=history.read({afterSequence:0,limit:MAX_LIMIT});
|
|
18
|
+
const counts={};
|
|
19
|
+
let toolCalls=0,toolResults=0,toolErrors=0;
|
|
20
|
+
for(const event of page.events){
|
|
21
|
+
if(event.event_type==='tool.call') toolCalls++;
|
|
22
|
+
else if(event.event_type==='tool.result') toolResults++;
|
|
23
|
+
else if(event.event_type==='tool.error') toolErrors++;
|
|
24
|
+
else continue;
|
|
25
|
+
const name=event.payload?.tool;
|
|
26
|
+
if(typeof name==='string'&&name.length) counts[name]=(counts[name]??0)+1;
|
|
27
|
+
}
|
|
28
|
+
return {schema:SCHEMA,operation_id:page.operation_id,events_visible:page.events.length,tool_calls:toolCalls,tool_results:toolResults,tool_errors:toolErrors,tool_counts:Object.fromEntries(Object.entries(counts).sort(([a],[b])=>a.localeCompare(b))),head_event_hash:page.head_event_hash,earliest_sequence:page.earliest_sequence,overflow:page.overflow,integrity:history.verify()};
|
|
29
|
+
}
|
|
30
|
+
return Object.freeze({usageStats,recentToolCalls});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const COMMANDER_OBSERVABILITY_SCHEMA=SCHEMA;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as z from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
4
|
+
const CAP=/^[A-Za-z0-9._-]{1,128}$/;
|
|
5
|
+
function register(server,name,schema,handler){
|
|
6
|
+
server.registerTool(name,{description:'Native remote Commander-compatible bridge backed directly by DEADBYTE remote-device-r44. No Commander runtime is invoked.',inputSchema:schema},handler);
|
|
7
|
+
}
|
|
8
|
+
function requirePlane(plane,name){
|
|
9
|
+
if(!plane) return {status:'unavailable',tool:name,error:'remote-device-r44 plane is not configured'};
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
export function registerCommanderRemoteBridge(server,{remoteDevicePlane=null}={}){
|
|
13
|
+
register(server,'remote_device',z.strictObject({
|
|
14
|
+
op:z.enum(['pair','status','report_online','probe','dispatch','revoke','rotate_credential','list']),
|
|
15
|
+
device_id:z.string().regex(HEX64).optional(),epoch:z.number().int().min(1).optional(),
|
|
16
|
+
public_key_spki_base64:z.string().min(1).max(4096).optional(),display_name:z.string().min(1).max(128).optional(),
|
|
17
|
+
connection_id:z.string().min(1).max(256).optional(),capability_id:z.string().regex(CAP).optional(),
|
|
18
|
+
payload:z.record(z.string(),z.unknown()).optional(),request_id:z.string().regex(HEX64).optional(),
|
|
19
|
+
encrypted_bundle_base64:z.string().min(1).max(1024*1024).optional(),expected_previous_sha256:z.string().regex(HEX64).nullable().optional()
|
|
20
|
+
}),async a=>{
|
|
21
|
+
const unavailable=requirePlane(remoteDevicePlane,'remote_device');if(unavailable)return unavailable;
|
|
22
|
+
switch(a.op){
|
|
23
|
+
case 'pair': return remoteDevicePlane.pair({publicKeySpkiBase64:a.public_key_spki_base64,displayName:a.display_name});
|
|
24
|
+
case 'status': return remoteDevicePlane.status({deviceId:a.device_id});
|
|
25
|
+
case 'report_online': return remoteDevicePlane.reportOnline({deviceId:a.device_id,epoch:a.epoch,connectionId:a.connection_id});
|
|
26
|
+
case 'probe': return remoteDevicePlane.probe({deviceId:a.device_id,epoch:a.epoch});
|
|
27
|
+
case 'dispatch': return remoteDevicePlane.dispatch({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload??{},requestId:a.request_id});
|
|
28
|
+
case 'revoke': return remoteDevicePlane.revoke({deviceId:a.device_id,epoch:a.epoch});
|
|
29
|
+
case 'rotate_credential': return remoteDevicePlane.rotateCredential({deviceId:a.device_id,epoch:a.epoch,encryptedBundleBase64:a.encrypted_bundle_base64,expectedPreviousSha256:a.expected_previous_sha256??null});
|
|
30
|
+
case 'list': return remoteDevicePlane.list();
|
|
31
|
+
default: throw new Error(`unsupported remote_device operation: ${String(a.op)}`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
register(server,'remote_mcp',z.strictObject({
|
|
35
|
+
device_id:z.string().regex(HEX64),epoch:z.number().int().min(1),capability_id:z.string().regex(CAP),
|
|
36
|
+
payload:z.record(z.string(),z.unknown()).default({}),request_id:z.string().regex(HEX64)
|
|
37
|
+
}),async a=>{
|
|
38
|
+
const unavailable=requirePlane(remoteDevicePlane,'remote_mcp');if(unavailable)return unavailable;
|
|
39
|
+
return remoteDevicePlane.dispatch({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload,requestId:a.request_id});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
2
|
+
|
|
3
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
4
|
+
function structured(result,label){
|
|
5
|
+
if(result?.isError) throw new Error(`${label} returned MCP error`);
|
|
6
|
+
const value=result?.structuredContent;
|
|
7
|
+
insist(value&&typeof value==='object',`${label} missing structured output`);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function createCommanderRemoteRelayR47({endpoint,protocolVersion='2026-07-28',timeoutMs=15000}={}){
|
|
12
|
+
const url=new URL(String(endpoint??''));
|
|
13
|
+
insist(url.protocol==='https:','remote relay endpoint must use HTTPS');
|
|
14
|
+
insist(Number.isInteger(timeoutMs)&&timeoutMs>=100&&timeoutMs<=120000,'remote relay timeout invalid');
|
|
15
|
+
const client=new Client({name:'deadbyte-r47-remote-relay',version:'0.15.15'},{versionNegotiation:{mode:{pin:protocolVersion}}});
|
|
16
|
+
const transport=new StreamableHTTPClientTransport(url);
|
|
17
|
+
let connected=false;
|
|
18
|
+
let connecting=null;
|
|
19
|
+
async function ensureConnected(){
|
|
20
|
+
if(connected)return;
|
|
21
|
+
if(!connecting){connecting=client.connect(transport).then(()=>{connected=true;}).finally(()=>{connecting=null;});}
|
|
22
|
+
await connecting;
|
|
23
|
+
}
|
|
24
|
+
async function call(name,arguments_,label){await ensureConnected();return structured(await client.callTool({name,arguments:arguments_}),label);}
|
|
25
|
+
async function probe({device_id,epoch,connection_id,nonce}){
|
|
26
|
+
const value=await call('remote_device',{op:'probe',device_id,epoch,connection_id,nonce},'remote readiness probe');
|
|
27
|
+
const result=value.result??value;
|
|
28
|
+
return {device_id,epoch,nonce,ready:result.ready===true||result.reachable===true};
|
|
29
|
+
}
|
|
30
|
+
async function request({device_id,epoch,capability_id,payload,request_id,request_sha256}){
|
|
31
|
+
insist(typeof request_sha256==='string'&&/^[0-9a-f]{64}$/.test(request_sha256),'remote request hash invalid');
|
|
32
|
+
const value=await call('remote_mcp',{device_id,epoch,capability_id,payload,request_id,request_sha256},'remote dispatch');
|
|
33
|
+
const result=value.result??value;
|
|
34
|
+
if(result?.request_id!==undefined) insist(result.request_id===request_id,'remote request identity mismatch');
|
|
35
|
+
if(result?.status==='completed'||result?.state==='completed'){
|
|
36
|
+
insist(typeof result.result_sha256==='string'&&/^[0-9a-f]{64}$/.test(result.result_sha256),'remote terminal result hash invalid');
|
|
37
|
+
return {ack:'accepted',request_id,terminal:{status:'completed',result_sha256:result.result_sha256}};
|
|
38
|
+
}
|
|
39
|
+
if(result?.status==='failed'||result?.state==='failed') return {ack:'accepted',request_id,terminal:{status:'failed',error_code:result.error_code??'remote_failed'}};
|
|
40
|
+
throw new Error('remote dispatch returned non-terminal state');
|
|
41
|
+
}
|
|
42
|
+
async function close(){connected=false;await client.close().catch(()=>{});}
|
|
43
|
+
return Object.freeze({probe,request,close,endpoint:url.toString()});
|
|
44
|
+
}
|
package/src/machine-fs.mjs
CHANGED
|
@@ -33,7 +33,36 @@ async function sha256File(file) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
const machineFsReadWorkerPath = fileURLToPath(new URL('./machine-fs-read-worker.mjs', import.meta.url));
|
|
36
|
+
async function boundedReadInProcess({ file, offset, length, hashFile, timeoutMs }) {
|
|
37
|
+
const handle = await open(file, 'r');
|
|
38
|
+
let timer;
|
|
39
|
+
let settled = false;
|
|
40
|
+
const abort = new Promise((_, reject) => {
|
|
41
|
+
timer = setTimeout(() => {
|
|
42
|
+
settled = true;
|
|
43
|
+
void handle.close().catch(() => {});
|
|
44
|
+
reject(new Error(`machine fs read timed out after ${timeoutMs}ms`));
|
|
45
|
+
}, timeoutMs);
|
|
46
|
+
timer.unref?.();
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
const operation = (async () => {
|
|
50
|
+
const buffer = Buffer.alloc(length);
|
|
51
|
+
const result = length ? await handle.read(buffer, 0, length, offset) : { bytesRead: 0 };
|
|
52
|
+
let fileSha256 = null;
|
|
53
|
+
if (hashFile) fileSha256 = await sha256File(file);
|
|
54
|
+
return { schema: 'deadbyte.machine-fs-read-worker.v1', bytes_read: result.bytesRead,
|
|
55
|
+
data_base64: buffer.subarray(0, result.bytesRead).toString('base64'), file_sha256: fileSha256 };
|
|
56
|
+
})();
|
|
57
|
+
operation.catch(() => {});
|
|
58
|
+
return await Promise.race([operation, abort]);
|
|
59
|
+
} finally {
|
|
60
|
+
if (!settled) { clearTimeout(timer); await handle.close().catch(() => {}); }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
36
64
|
async function boundedReadWorker({ file, offset, length, hashFile, timeoutMs, workerPath = machineFsReadWorkerPath }) {
|
|
65
|
+
if (workerPath === machineFsReadWorkerPath) return boundedReadInProcess({ file, offset, length, hashFile, timeoutMs });
|
|
37
66
|
return await new Promise((resolve, reject) => {
|
|
38
67
|
const child = spawn(process.execPath, [workerPath, file, String(offset), String(length), hashFile ? '1' : '0'], {
|
|
39
68
|
windowsHide: true, shell: false, stdio: ['ignore','pipe','pipe']
|
|
@@ -103,20 +132,27 @@ function assertNotDenied(root, relativePath) {
|
|
|
103
132
|
}
|
|
104
133
|
async function assertNoReparse(rootPath, target, allowMissingLeaf = false) {
|
|
105
134
|
const relative = path.relative(rootPath, target);
|
|
106
|
-
if (relative === '')
|
|
135
|
+
if (relative === '') {
|
|
136
|
+
const info = await lstat(rootPath);
|
|
137
|
+
if (info.isSymbolicLink()) throw new Error('machine fs root reparse point denied');
|
|
138
|
+
return info;
|
|
139
|
+
}
|
|
107
140
|
if (relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('machine fs path escapes allowed root');
|
|
108
141
|
const parts = relative.split(path.sep).filter(Boolean);
|
|
109
142
|
let cursor = rootPath;
|
|
143
|
+
let targetInfo = null;
|
|
110
144
|
for (let i = 0; i < parts.length; i += 1) {
|
|
111
145
|
cursor = path.join(cursor, parts[i]);
|
|
112
146
|
try {
|
|
113
147
|
const info = await lstat(cursor);
|
|
114
148
|
if (info.isSymbolicLink()) throw new Error(`machine fs reparse point denied at '${parts.slice(0, i + 1).join('/')}'`);
|
|
149
|
+
targetInfo = info;
|
|
115
150
|
} catch (error) {
|
|
116
|
-
if (allowMissingLeaf && i === parts.length - 1 && error?.code === 'ENOENT') return;
|
|
151
|
+
if (allowMissingLeaf && i === parts.length - 1 && error?.code === 'ENOENT') return null;
|
|
117
152
|
throw error;
|
|
118
153
|
}
|
|
119
154
|
}
|
|
155
|
+
return targetInfo;
|
|
120
156
|
}
|
|
121
157
|
|
|
122
158
|
async function resolveExisting(policy, grant, rootId, relativePath, permission, allowDot = false) {
|
|
@@ -126,10 +162,10 @@ async function resolveExisting(policy, grant, rootId, relativePath, permission,
|
|
|
126
162
|
if (normalized !== '.') assertNotDenied(root, normalized);
|
|
127
163
|
const lexical = normalized === '.' ? root.real_path : path.resolve(root.real_path, normalized);
|
|
128
164
|
if (!isInside(root.real_path, lexical) && lexical !== root.real_path) throw new Error('machine fs path escapes allowed root');
|
|
129
|
-
await assertNoReparse(root.real_path, lexical, false);
|
|
130
|
-
const resolved = await realpath(lexical);
|
|
165
|
+
const targetInfo = await assertNoReparse(root.real_path, lexical, false);
|
|
166
|
+
const resolved = lexical === root.real_path ? lexical : await realpath(lexical);
|
|
131
167
|
if (!isInside(root.real_path, resolved) && resolved !== root.real_path) throw new Error('machine fs path resolves outside allowed root');
|
|
132
|
-
return { root, lexical, resolved, relative: normalized };
|
|
168
|
+
return { root, lexical, resolved, relative: normalized, targetInfo };
|
|
133
169
|
}
|
|
134
170
|
async function resolveTarget(policy, grant, rootId, relativePath, permission) {
|
|
135
171
|
const normalized = normalizeRelative(relativePath, false);
|
|
@@ -208,15 +244,41 @@ async function objectIdentity(target, budget = { remaining: MAX_DIRECTORY_IDENTI
|
|
|
208
244
|
return { ...core, identity_sha256: sha256Object(core) };
|
|
209
245
|
}
|
|
210
246
|
|
|
211
|
-
|
|
247
|
+
const LIST_CURSOR_DOMAIN = Buffer.from('DEADBYTE-MACHINE-FS-LIST-CURSOR-V3\\0', 'utf8');
|
|
248
|
+
async function encodeListCursor({ path: relativePath, snapshot_sha256, after_name, signingKeyPath }) {
|
|
249
|
+
const signing = await loadPrivateSigningKey(signingKeyPath);
|
|
250
|
+
const body = { schema: 'deadbyte.machine-fs-list-cursor.v3', path: relativePath, snapshot_sha256, after_name };
|
|
251
|
+
const payload = Buffer.from(canonicalJson(body), 'utf8');
|
|
252
|
+
const signature_base64 = cryptoSign(null, Buffer.concat([LIST_CURSOR_DOMAIN, payload]), signing.privateKey).toString('base64');
|
|
253
|
+
return Buffer.from(canonicalJson({
|
|
254
|
+
...body,
|
|
255
|
+
attestation: { algorithm: 'ed25519', key_id: signing.keyId, signature_base64 }
|
|
256
|
+
}), 'utf8').toString('base64url');
|
|
257
|
+
}
|
|
258
|
+
async function decodeListCursor(cursor, verifyKeyPath) {
|
|
212
259
|
if (cursor === null || cursor === undefined) return null;
|
|
213
|
-
if (typeof cursor !== 'string' || cursor.length < 1 || cursor.length >
|
|
260
|
+
if (typeof cursor !== 'string' || cursor.length < 1 || cursor.length > 4096) throw new Error('machine fs cursor invalid');
|
|
214
261
|
let decoded;
|
|
215
|
-
try { decoded = Buffer.from(cursor, 'base64url').toString('utf8'); }
|
|
262
|
+
try { decoded = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')); }
|
|
216
263
|
catch { throw new Error('machine fs cursor invalid'); }
|
|
217
|
-
if (!decoded ||
|
|
264
|
+
if (!decoded || decoded.schema !== 'deadbyte.machine-fs-list-cursor.v3' ||
|
|
265
|
+
typeof decoded.path !== 'string' || !SHA256_RE.test(decoded.snapshot_sha256 ?? '') ||
|
|
266
|
+
typeof decoded.after_name !== 'string' || decoded.after_name.length < 1 ||
|
|
267
|
+
decoded.attestation?.algorithm !== 'ed25519' || typeof decoded.attestation?.key_id !== 'string' ||
|
|
268
|
+
typeof decoded.attestation?.signature_base64 !== 'string') throw new Error('machine fs cursor invalid');
|
|
269
|
+
if (Buffer.from(canonicalJson(decoded), 'utf8').toString('base64url') !== cursor) throw new Error('machine fs cursor invalid');
|
|
270
|
+
const { attestation, ...body } = decoded;
|
|
271
|
+
const verifyKey = await loadPublicVerifyKey(verifyKeyPath);
|
|
272
|
+
if (attestation.key_id !== verifyKey.keyId) throw new Error('machine fs cursor key mismatch');
|
|
273
|
+
const signature = Buffer.from(attestation.signature_base64, 'base64');
|
|
274
|
+
const valid = signature.length === 64 &&
|
|
275
|
+
cryptoVerify(null, Buffer.concat([LIST_CURSOR_DOMAIN, Buffer.from(canonicalJson(body), 'utf8')]), verifyKey.publicKey, signature);
|
|
276
|
+
if (!valid) throw new Error('machine fs cursor signature mismatch');
|
|
218
277
|
return decoded;
|
|
219
278
|
}
|
|
279
|
+
function directoryListingSnapshot(children) {
|
|
280
|
+
return sha256Object(children.map(child => ({ name: child.name, kind: child.isSymbolicLink() ? 'reparse' : child.isDirectory() ? 'directory' : child.isFile() ? 'file' : 'other' })));
|
|
281
|
+
}
|
|
220
282
|
|
|
221
283
|
function encodeCursor(name) { return Buffer.from(name, 'utf8').toString('base64url'); }
|
|
222
284
|
|
|
@@ -286,7 +348,14 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
|
|
|
286
348
|
const info = await lstat(found.resolved);
|
|
287
349
|
const kind = kindOf(info);
|
|
288
350
|
if (kind === 'reparse') throw new Error('machine fs reparse target denied');
|
|
289
|
-
|
|
351
|
+
let identity;
|
|
352
|
+
if (kind === 'file') {
|
|
353
|
+
const contentSha256 = await sha256File(found.resolved);
|
|
354
|
+
const core = { kind: 'file', bytes: info.size, content_sha256: contentSha256 };
|
|
355
|
+
identity = { ...core, identity_sha256: sha256Object(core) };
|
|
356
|
+
} else {
|
|
357
|
+
identity = await objectIdentity(found.resolved);
|
|
358
|
+
}
|
|
290
359
|
return {
|
|
291
360
|
status: 'ok', root_id: rootId, path: found.relative, kind,
|
|
292
361
|
bytes: kind === 'file' ? info.size : 0,
|
|
@@ -300,27 +369,29 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
|
|
|
300
369
|
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_LIST_LIMIT) throw new Error(`machine fs list limit must be in [1, ${MAX_LIST_LIMIT}]`);
|
|
301
370
|
const grant = await assertCodingArmed(policy, rootId);
|
|
302
371
|
const found = await resolveExisting(policy, grant, rootId, relativePath, 'read', true);
|
|
303
|
-
const info =
|
|
304
|
-
if (!info
|
|
305
|
-
const
|
|
372
|
+
const info = found.targetInfo;
|
|
373
|
+
if (!info?.isDirectory()) throw new Error('machine fs list path must be a directory');
|
|
374
|
+
const decodedCursor = await decodeListCursor(cursor, verifyKeyPath);
|
|
375
|
+
if (decodedCursor && decodedCursor.path !== found.relative) throw new Error('machine fs cursor path mismatch');
|
|
306
376
|
const children = await readdir(found.resolved, { withFileTypes: true });
|
|
307
377
|
children.sort((a, b) => a.name.localeCompare(b.name));
|
|
378
|
+
const snapshot_sha256 = directoryListingSnapshot(children);
|
|
379
|
+
if (decodedCursor && decodedCursor.snapshot_sha256 !== snapshot_sha256) throw new Error('machine fs cursor snapshot changed');
|
|
380
|
+
const afterName = decodedCursor?.after_name ?? null;
|
|
308
381
|
const filtered = children.filter(child => afterName === null || child.name.localeCompare(afterName) > 0);
|
|
309
382
|
const selected = filtered.slice(0, limit);
|
|
310
|
-
const entries =
|
|
311
|
-
for (const child of selected) {
|
|
383
|
+
const entries = await Promise.all(selected.map(async child => {
|
|
312
384
|
const rel = found.relative === '.' ? child.name : `${found.relative}/${child.name}`;
|
|
313
|
-
if (child.isSymbolicLink()) {
|
|
314
|
-
entries.push({ path: rel, kind: 'reparse', bytes: 0, mtime_utc: null, sha256: null });
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
385
|
+
if (child.isSymbolicLink()) return { path: rel, kind: 'reparse', bytes: 0, mtime_utc: null, sha256: null };
|
|
317
386
|
const childPath = path.join(found.resolved, child.name);
|
|
318
387
|
const childInfo = await lstat(childPath);
|
|
319
388
|
const kind = kindOf(childInfo);
|
|
320
389
|
const digest = hashFiles && kind === 'file' ? await sha256File(childPath) : null;
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
const nextCursor = filtered.length > selected.length && selected.length
|
|
390
|
+
return { path: rel, kind, bytes: kind === 'file' ? childInfo.size : 0, mtime_utc: childInfo.mtime.toISOString(), sha256: digest };
|
|
391
|
+
}));
|
|
392
|
+
const nextCursor = filtered.length > selected.length && selected.length
|
|
393
|
+
? await encodeListCursor({ path: found.relative, snapshot_sha256, after_name: selected.at(-1).name, signingKeyPath })
|
|
394
|
+
: null;
|
|
324
395
|
return { status: 'ok', root_id: rootId, path: found.relative, entries, next_cursor: nextCursor };
|
|
325
396
|
}
|
|
326
397
|
async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false, timeoutMs = 30000 }) {
|
|
@@ -348,6 +419,20 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
|
|
|
348
419
|
};
|
|
349
420
|
}
|
|
350
421
|
|
|
422
|
+
async function readMany({ rootId, paths, maxBytes = 65536, timeoutMs = 30000 }) {
|
|
423
|
+
if (!Array.isArray(paths) || paths.length < 1 || paths.length > 64) throw new Error('machine fs read_many paths must contain 1-64 entries');
|
|
424
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > policy.max_read_bytes) throw new Error(`machine fs read_many max_bytes must be in [1, ${policy.max_read_bytes}]`);
|
|
425
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 180000) throw new Error('machine fs read_many timeout_ms must be in [100, 180000]');
|
|
426
|
+
const unique = new Set();
|
|
427
|
+
for (const relativePath of paths) {
|
|
428
|
+
if (typeof relativePath !== 'string' || relativePath.length < 1 || relativePath.length > 512) throw new Error('machine fs read_many path invalid');
|
|
429
|
+
if (unique.has(relativePath)) throw new Error(`machine fs read_many duplicate path '${relativePath}'`);
|
|
430
|
+
unique.add(relativePath);
|
|
431
|
+
}
|
|
432
|
+
const results = await Promise.all(paths.map(relativePath => readRange({ rootId, relativePath, offset: 0, maxBytes, timeoutMs })));
|
|
433
|
+
return { status: 'ok', root_id: rootId, count: results.length, results };
|
|
434
|
+
}
|
|
435
|
+
|
|
351
436
|
async function writePath({ rootId, relativePath, content, encoding = 'utf8', expectedSha256, receiptId = null }) {
|
|
352
437
|
const grant = await assertCodingArmed(policy, rootId);
|
|
353
438
|
const target = await resolveTarget(policy, grant, rootId, relativePath, 'write');
|
|
@@ -551,6 +636,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
|
|
|
551
636
|
stat: statPath,
|
|
552
637
|
list: listPage,
|
|
553
638
|
read: readRange,
|
|
639
|
+
readMany,
|
|
554
640
|
write: writePath,
|
|
555
641
|
editExact,
|
|
556
642
|
move: movePath,
|
package/src/mcp-server.mjs
CHANGED
|
@@ -51,6 +51,11 @@ 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 { createCommanderExtendedRuntime } from './commander-extended-runtime.mjs';
|
|
55
|
+
import { registerCommanderCoreBridge } from './commander-core-bridge.mjs';
|
|
56
|
+
import { registerCommanderRemoteBridge } from './commander-remote-bridge-r47.mjs';
|
|
57
|
+
import { createCommanderRemoteRelayR47 } from './commander-remote-relay-r47.mjs';
|
|
58
|
+
import { createRemoteDevicePlaneR44 } from './remote-device-r44.mjs';
|
|
54
59
|
import { capabilityProfileFromEnv, COMPACT_SURFACE_TOOL_LIMIT, R36_CAPABILITY_LEDGER } from './capability-ledger.mjs';
|
|
55
60
|
import { registerCompactGatewayTools } from './compact-gateway-mcp-tools.mjs';
|
|
56
61
|
import { createSkillPlane } from './skill-plane-r44.mjs';
|
|
@@ -238,6 +243,17 @@ const skillRoot=path.join(runtimeStateRoot,'skills');
|
|
|
238
243
|
await mkdir(skillRoot,{recursive:true});
|
|
239
244
|
const skillRuntime=await createSkillPlane({root:skillRoot,verifyKeyPath});
|
|
240
245
|
|
|
246
|
+
const remoteRelayEndpoint=process.env.DEADBYTE_REMOTE_RELAY_ENDPOINT||process.env.DEADBYTE_REMOTE_ENDPOINT||'';
|
|
247
|
+
const remoteRelayCapabilities=new Set(String(process.env.DEADBYTE_REMOTE_CAPABILITIES||'').split(',').map(value=>value.trim()).filter(Boolean));
|
|
248
|
+
const remoteRelay=remoteRelayEndpoint ? await createCommanderRemoteRelayR47({endpoint:remoteRelayEndpoint}) : null;
|
|
249
|
+
const remoteDevicePlane=remoteRelay
|
|
250
|
+
? await createRemoteDevicePlaneR44({
|
|
251
|
+
stateDir:path.join(runtimeStateRoot,'remote-device-r44'),
|
|
252
|
+
relay:remoteRelay,
|
|
253
|
+
authorizeCapability:async({capabilityId})=>remoteRelayCapabilities.has(capabilityId)
|
|
254
|
+
})
|
|
255
|
+
: null;
|
|
256
|
+
|
|
241
257
|
installStdioConsoleGuard();
|
|
242
258
|
if (process.env.DEADBYTE_STDIO_CONSOLE_GUARD_PROBE === '1') console.log('DEADBYTE_STDIO_CONSOLE_GUARD_PROBE');
|
|
243
259
|
|
|
@@ -276,9 +292,83 @@ serveStdio(() => {
|
|
|
276
292
|
return server.registerTool(name,...args);
|
|
277
293
|
}};
|
|
278
294
|
registerOperatorSurface(server,{packageRoot,surfaceProfile,compactToolLimit:COMPACT_SURFACE_TOOL_LIMIT});
|
|
295
|
+
const commanderExtendedRuntime=createCommanderExtendedRuntime({
|
|
296
|
+
stateRoot:runtimeStateRoot,
|
|
297
|
+
history:null,
|
|
298
|
+
prompts:[
|
|
299
|
+
{name:'deadbyte.review',title:'Review'},
|
|
300
|
+
{name:'deadbyte.patch',title:'Patch'},
|
|
301
|
+
{name:'deadbyte.diagnose',title:'Diagnose'},
|
|
302
|
+
{name:'deadbyte.run-tests',title:'Run tests'}
|
|
303
|
+
],
|
|
304
|
+
resources:[
|
|
305
|
+
{uri:'deadbyte://context',mime_type:'text/markdown'},
|
|
306
|
+
{uri:'deadbyte://architecture',mime_type:'text/markdown'},
|
|
307
|
+
{uri:'deadbyte://surface-profile',mime_type:'application/json'}
|
|
308
|
+
]
|
|
309
|
+
});
|
|
279
310
|
if (toolAudit) installToolAudit(server,toolAudit);
|
|
280
311
|
|
|
281
312
|
if (surfaceProfile === 'full') {
|
|
313
|
+
toolServer.registerTool('get_config',{
|
|
314
|
+
description:'Read bounded DEADBYTE Commander-compatible configuration.',
|
|
315
|
+
inputSchema:z.strictObject({key:z.string().min(1).max(128).nullable().default(null)})
|
|
316
|
+
},async args=>commanderExtendedRuntime.getConfig(args));
|
|
317
|
+
|
|
318
|
+
toolServer.registerTool('set_config_value',{
|
|
319
|
+
description:'Atomically update one bounded configuration scalar with an exact hash precondition.',
|
|
320
|
+
inputSchema:z.strictObject({key:z.string().min(1).max(128),value:z.union([z.string().max(16384),z.number(),z.boolean(),z.null()]),expected_sha256:z.string().regex(/^[0-9a-f]{64}$/)})
|
|
321
|
+
},async args=>commanderExtendedRuntime.setConfigValue(args));
|
|
322
|
+
|
|
323
|
+
toolServer.registerTool('give_feedback_to_desktop_commander',{
|
|
324
|
+
description:'Persist bounded operator feedback locally; no external network transmission.',
|
|
325
|
+
inputSchema:z.strictObject({message:z.string().min(1).max(4096),category:z.string().min(1).max(64).default('general')})
|
|
326
|
+
},async args=>commanderExtendedRuntime.feedback(args));
|
|
327
|
+
|
|
328
|
+
toolServer.registerTool('get_prompts',{
|
|
329
|
+
description:'Return the bounded native prompt catalog.',
|
|
330
|
+
inputSchema:z.strictObject({})
|
|
331
|
+
},async()=>commanderExtendedRuntime.getPrompts());
|
|
332
|
+
|
|
333
|
+
toolServer.registerTool('resources',{
|
|
334
|
+
description:'Return the bounded native resource catalog.',
|
|
335
|
+
inputSchema:z.strictObject({})
|
|
336
|
+
},async()=>commanderExtendedRuntime.getResources());
|
|
337
|
+
|
|
338
|
+
toolServer.registerTool('file_preview',{
|
|
339
|
+
description:'Return a bounded preview slice from an explicitly rooted file.',
|
|
340
|
+
inputSchema:z.strictObject({root_id:z.string().min(1).max(512),path:z.string().min(1).max(512),offset:z.number().int().min(0).default(0),max_bytes:z.number().int().min(1).max(65536).default(65536),mime_type:z.string().max(128).default('text/plain')})
|
|
341
|
+
},async({root_id,path:relativePath,offset,max_bytes,mime_type})=>commanderExtendedRuntime.filePreview({rootId:root_id,path:relativePath,offset,maxBytes:max_bytes,mimeType:mime_type}));
|
|
342
|
+
|
|
343
|
+
toolServer.registerTool('write_pdf',{
|
|
344
|
+
description:'Generate a bounded self-contained PDF document from text.',
|
|
345
|
+
inputSchema:z.strictObject({text:z.string().max(65536),metadata:z.record(z.string(),z.unknown()).default({})})
|
|
346
|
+
},async args=>commanderExtendedRuntime.writePdf(args));
|
|
347
|
+
|
|
348
|
+
toolServer.registerTool('markdown_editor',{
|
|
349
|
+
description:'Hash-preconditioned bounded Markdown exact edit using the native machine filesystem transaction.',
|
|
350
|
+
inputSchema:z.strictObject({root_id:z.string().min(1).max(64),path:z.string().min(1).max(512),expected_sha256:z.string().regex(/^[0-9a-f]{64}$/),old_string:z.string().min(1).max(262144),new_string:z.string().max(524288),expected_replacements:z.number().int().min(1).max(64).default(1)})
|
|
351
|
+
},async args=>{
|
|
352
|
+
if(!machineFsRuntime) return textResult(z.any(),{status:'denied',error:'machine filesystem runtime is disabled'},true);
|
|
353
|
+
return machineFsRuntime.editExact({rootId:args.root_id,relativePath:args.path,expectedSha256:args.expected_sha256,oldString:args.old_string,newString:args.new_string,expectedReplacements:args.expected_replacements});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
toolServer.registerTool('get_usage_stats',{
|
|
357
|
+
description:'Return bounded audit/runtime usage status.',
|
|
358
|
+
inputSchema:z.strictObject({})
|
|
359
|
+
},async()=>{
|
|
360
|
+
if(!toolAudit) return {status:'unavailable',reason:'audit runtime is disabled'};
|
|
361
|
+
return {status:'ok',audit:await toolAudit.status()};
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
toolServer.registerTool('get_recent_tool_calls',{
|
|
365
|
+
description:'Return a bounded, cursorable recent signed audit event window.',
|
|
366
|
+
inputSchema:z.strictObject({limit:z.number().int().min(1).max(256).default(50),after_seq:z.number().int().min(0).default(0)})
|
|
367
|
+
},async({limit,after_seq})=>{
|
|
368
|
+
if(!toolAudit) return {status:'unavailable',reason:'audit runtime is disabled'};
|
|
369
|
+
return {status:'ok',audit:await toolAudit.recent({limit,after_seq})};
|
|
370
|
+
});
|
|
371
|
+
|
|
282
372
|
toolServer.registerTool(
|
|
283
373
|
'workspace_open',
|
|
284
374
|
{
|
|
@@ -589,5 +679,9 @@ serveStdio(() => {
|
|
|
589
679
|
);
|
|
590
680
|
}
|
|
591
681
|
|
|
682
|
+
if (surfaceProfile === 'full') {
|
|
683
|
+
registerCommanderCoreBridge(toolServer,{machineFsRuntime,processRuntime,searchRuntime:codingRuntime?.search??null});
|
|
684
|
+
registerCommanderRemoteBridge(toolServer,{remoteDevicePlane});
|
|
685
|
+
}
|
|
592
686
|
return server;
|
|
593
687
|
}, { legacy: 'reject' });
|
package/src/release-slot.mjs
CHANGED
|
@@ -288,7 +288,9 @@ export function renderStableLauncherPs1(){
|
|
|
288
288
|
"$env:DEADBYTE_CONTROLLER_RUNTIME_ROOT = Join-Path $StateRoot 'runtime'",
|
|
289
289
|
"$powershell = Join-Path $PSHOME 'powershell.exe'",
|
|
290
290
|
'& $powershell -NoLogo -NoProfile -ExecutionPolicy Bypass -File $controller @ControllerArgs',
|
|
291
|
-
'
|
|
291
|
+
'$exitCode = (Get-Variable -Name LASTEXITCODE -ValueOnly -ErrorAction SilentlyContinue)',
|
|
292
|
+
'if ($null -eq $exitCode) { $exitCode = 0 }',
|
|
293
|
+
'exit ([int]$exitCode)',
|
|
292
294
|
''
|
|
293
295
|
].join('\r\n');
|
|
294
296
|
}
|