deadbyte-mcp 0.15.1 → 0.15.3
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 +95 -64
- package/PROVENANCE.json +6 -5
- package/README.txt +36 -20
- package/RELEASE.json +24 -8
- package/SBOM.cdx.json +3 -3
- package/controller/README.TXT +2 -2
- package/controller/deadbyte-controller.ps1 +50 -2
- package/controller/deadbyte-process-policy.json +63 -1
- package/docs/ARCHITECTURE.md +3 -4
- package/package.json +88 -86
- package/scripts/autonomous-build.ps1 +1 -0
- package/scripts/autonomous-tests.ps1 +1 -0
- package/scripts/check-agent-backend-r46.mjs +77 -0
- package/scripts/check-atomic-resource-r46.mjs +84 -0
- package/scripts/check-autonomous-race-r46.mjs +160 -0
- package/scripts/check-coding-task-deadline-r46.mjs +59 -0
- package/scripts/check-deadline-cancellation-r46.mjs +49 -0
- package/scripts/check-process-deadline-r46.mjs +52 -0
- package/scripts/check-r46-background.mjs +5 -0
- package/scripts/deferred-slot-operation-r46.mjs +126 -0
- package/scripts/extract-r46-failures.mjs +5 -0
- package/scripts/final-closure-r46.mjs +98 -0
- package/scripts/final-closure-verify-r46.mjs +119 -0
- package/scripts/full-regression-r46.mjs +28 -0
- package/scripts/gate-windows-r46.ps1 +58 -0
- package/scripts/inspect-r46-active.mjs +2 -0
- package/scripts/r47-core-failures.mjs +1 -0
- package/scripts/refresh-process-policy.mjs +64 -7
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/release-parity-r46.mjs +187 -0
- package/scripts/run-r46-full-regression-background.mjs +7 -0
- package/scripts/run-r46-windows-gate-background.mjs +6 -0
- package/scripts/run-r46-windows-gate-background.ps1 +10 -0
- package/scripts/windows-gate-evidence-r46.mjs +111 -0
- package/scripts/workload-contract-regression-r47.mjs +14 -0
- package/scripts/workload-stream-regression.mjs +16 -0
- package/scripts/workload-substrate-r47-regression.mjs +12 -0
- package/src/agent-backend-r44.mjs +75 -10
- package/src/atomic-mutation.mjs +55 -0
- package/src/autonomous-mcp-tools.mjs +4 -3
- package/src/autonomous-output-contracts.mjs +16 -3
- package/src/autonomous-runtime.mjs +83 -30
- package/src/capability-ledger.mjs +1 -0
- package/src/coding-mcp-tools.mjs +2 -2
- package/src/coding-output-contracts.mjs +18 -4
- package/src/coding-plane.mjs +3 -24
- package/src/coding-search.mjs +11 -2
- package/src/coding-task-runtime.mjs +41 -6
- package/src/compact-gateway-mcp-tools.mjs +7 -5
- package/src/deadbyte-cli.mjs +7 -3
- package/src/deadline-cancellation.mjs +83 -0
- package/src/machine-fs-mcp-tools.mjs +3 -3
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-read-worker.mjs +46 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +66 -33
- package/src/mcp-server.mjs +10 -0
- package/src/mcp-smoke-client.mjs +4 -1
- package/src/observation-runtime.mjs +25 -4
- package/src/observation-smoke-client.mjs +1 -1
- package/src/process-mcp-tools.mjs +29 -8
- package/src/process-output-contracts.mjs +43 -3
- package/src/process-policy.mjs +3 -1
- package/src/process-runtime.mjs +152 -23
- package/src/process-smoke-client.mjs +67 -22
- package/src/release-generation.mjs +22 -3
- package/src/remote-device-r44.mjs +53 -15
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/resource-identity.mjs +59 -0
- package/src/runtime-update.mjs +526 -162
- package/src/stdio-console-guard.mjs +24 -0
- package/src/version.mjs +1 -1
- package/src/workload-substrate.mjs +87 -0
- package/test/cli-entrypoint.test.mjs +3 -3
- package/test/coding-runtime.test.mjs +12 -0
- package/test/coding-search-r36.test.mjs +7 -0
- package/test/compact-gateway-r36.test.mjs +28 -0
- package/test/core.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +92 -0
- package/test/helpers/autonomous-r34-fixture.mjs +10 -1
- package/test/machine-fs-mcp-tools.test.mjs +4 -1
- package/test/machine-fs-runtime.test.mjs +18 -2
- package/test/observation-runtime.test.mjs +29 -2
- package/test/process-mcp-tools.test.mjs +27 -6
- package/test/process-policy-refresh.test.mjs +87 -10
- package/test/process-runtime.test.mjs +129 -2
- package/test/process-supervision.test.mjs +4 -2
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r45-finalization.test.mjs +1 -1
- package/test/release-generation.test.mjs +25 -11
- package/test/release-version.test.mjs +99 -44
- package/test/remote-device-r44.test.mjs +75 -0
- package/test/remote-r24-catalog.test.mjs +2 -2
- package/test/runtime-self-update.test.mjs +118 -1
- package/test/workload-substrate.test.mjs +22 -0
|
@@ -26,7 +26,7 @@ export const codingInspectInputSchema=z.discriminatedUnion('op',[
|
|
|
26
26
|
export const machineFsGatewayInputSchema=z.discriminatedUnion('op',[
|
|
27
27
|
z.strictObject({op:z.literal('stat'),root_id:id,path:rel.default('.'),hash:z.boolean().default(false)}),
|
|
28
28
|
z.strictObject({op:z.literal('list'),root_id:id,path:rel.default('.'),cursor,limit:z.number().int().min(1).max(1000).default(100),hash_files:z.boolean().default(false)}),
|
|
29
|
-
z.strictObject({op:z.literal('read'),root_id:id,path:rel,offset:nonneg.default(0),max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536),hash_file:z.boolean().default(false)}),
|
|
29
|
+
z.strictObject({op:z.literal('read'),root_id:id,path:rel,offset:nonneg.default(0),max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536),hash_file:z.boolean().default(false),timeout_ms:z.number().int().min(100).max(180000).default(30000)}),
|
|
30
30
|
z.strictObject({op:z.literal('write'),root_id:id,path:rel,content:z.string().max(12*1024*1024),encoding:z.enum(['utf8','base64']).default('utf8'),expected_sha256:z.string().regex(/^(?:absent|[0-9a-f]{64})$/)}),
|
|
31
31
|
z.strictObject({op:z.literal('edit_exact'),root_id:id,path:rel,expected_sha256:hex64,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)}),
|
|
32
32
|
z.strictObject({op:z.literal('mkdir'),root_id:id,path:rel}),
|
|
@@ -36,7 +36,8 @@ export const machineFsGatewayInputSchema=z.discriminatedUnion('op',[
|
|
|
36
36
|
|
|
37
37
|
export const processSessionGatewayInputSchema=z.discriminatedUnion('op',[
|
|
38
38
|
z.strictObject({op:z.literal('start'),profile_id:id,root_id:id,cwd:rel.default('.'),args:z.array(z.string().max(8192)).max(64).default([])}),
|
|
39
|
-
z.strictObject({op:z.literal('read'),session_id:hex64,offset:
|
|
39
|
+
z.strictObject({op:z.literal('read'),session_id:hex64,offset:z.number().int().min(-(16*1024*1024)).default(0),max_bytes:z.number().int().min(1).max(262144).default(65536),wait_ms:z.number().int().min(0).max(30000).default(0),wait_for:z.string().min(1).max(256).nullable().default(null)}),
|
|
40
|
+
z.strictObject({op:z.literal('write'),session_id:hex64,input:z.string().min(1).max(1024*1024),append_newline:z.boolean().default(true)}),
|
|
40
41
|
z.strictObject({op:z.literal('status'),session_id:hex64}),
|
|
41
42
|
z.strictObject({op:z.literal('list')}),
|
|
42
43
|
z.strictObject({op:z.literal('action'),session_id:hex64,action_id:id}),
|
|
@@ -83,7 +84,7 @@ async function machineFs(args,runtime){
|
|
|
83
84
|
const map={
|
|
84
85
|
stat:r=>r.stat({rootId:args.root_id,relativePath:args.path,hash:args.hash}),
|
|
85
86
|
list:r=>r.list({rootId:args.root_id,relativePath:args.path,cursor:args.cursor,limit:args.limit,hashFiles:args.hash_files}),
|
|
86
|
-
read:r=>r.read({rootId:args.root_id,relativePath:args.path,offset:args.offset,maxBytes:args.max_bytes,hashFile:args.hash_file}),
|
|
87
|
+
read:r=>r.read({rootId:args.root_id,relativePath:args.path,offset:args.offset,maxBytes:args.max_bytes,hashFile:args.hash_file,timeoutMs:args.timeout_ms}),
|
|
87
88
|
write:r=>r.write({rootId:args.root_id,relativePath:args.path,content:args.content,encoding:args.encoding,expectedSha256:args.expected_sha256}),
|
|
88
89
|
edit_exact:r=>r.editExact({rootId:args.root_id,relativePath:args.path,expectedSha256:args.expected_sha256,oldString:args.old_string,newString:args.new_string,expectedReplacements:args.expected_replacements}),
|
|
89
90
|
mkdir:r=>r.mkdir({rootId:args.root_id,relativePath:args.path}),
|
|
@@ -96,7 +97,8 @@ async function machineFs(args,runtime){
|
|
|
96
97
|
async function processSession(args,runtime){
|
|
97
98
|
const map={
|
|
98
99
|
start:r=>r.start({profileId:args.profile_id,rootId:args.root_id,cwd:args.cwd,args:args.args}),
|
|
99
|
-
read:r=>r.read({sessionId:args.session_id,offset:args.offset,maxBytes:args.max_bytes}),
|
|
100
|
+
read:r=>r.read({sessionId:args.session_id,offset:args.offset,maxBytes:args.max_bytes,waitMs:args.wait_ms,waitFor:args.wait_for}),
|
|
101
|
+
write:r=>r.write({sessionId:args.session_id,input:args.input,appendNewline:args.append_newline}),
|
|
100
102
|
status:r=>r.status({sessionId:args.session_id}),
|
|
101
103
|
list:r=>r.list(),
|
|
102
104
|
action:r=>r.action({sessionId:args.session_id,actionId:args.action_id}),
|
|
@@ -136,7 +138,7 @@ export function registerCompactGatewayTools(server,{profile,ledger,codingRuntime
|
|
|
136
138
|
});
|
|
137
139
|
server.registerTool('coding_inspect',{description:'Typed compact coding inspection and semantic navigation gateway. No arbitrary tool dispatch.',inputSchema:codingInspectInputSchema,outputSchema:compactGatewayOutputSchema},args=>codingInspect(args,codingRuntime,semanticRuntime));
|
|
138
140
|
server.registerTool('machine_fs',{description:'Typed compact machine filesystem gateway preserving root policy and mutation preconditions.',inputSchema:machineFsGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>machineFs(args,machineFsRuntime));
|
|
139
|
-
server.registerTool('process_session',{description:'Typed compact DEADBYTE-owned process session gateway.
|
|
141
|
+
server.registerTool('process_session',{description:'Typed compact DEADBYTE-owned process session gateway. Raw input is available only for locally opt-in profiles and remains grant-bounded; numeric PID authority is never exposed.',inputSchema:processSessionGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>processSession(args,processRuntime));
|
|
140
142
|
server.registerTool('machine_observe',{description:'Typed compact machine identity and read-only observation gateway. Revoke is intentionally excluded.',inputSchema:machineObserveInputSchema,outputSchema:compactGatewayOutputSchema},args=>machineObserve(args,machineRuntime,observationRuntime));
|
|
141
143
|
server.registerTool('desktop_control',{description:'Typed Desktop observe, target, status and guarded action gateway. No arbitrary dispatch; mutation still requires separate Desktop authority.',inputSchema:desktopControlInputSchema,outputSchema:compactGatewayOutputSchema},args=>invokeDesktopOperation(args,desktopRuntime));
|
|
142
144
|
}
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -87,6 +87,9 @@ function controllerRuntimeReady(text){
|
|
|
87
87
|
/^Local ready\s*:\s*True\s*$/m.test(value)&&
|
|
88
88
|
/^Loop supervisor:\s*RUNNING\s+\[verified\]\s*$/m.test(value);
|
|
89
89
|
}
|
|
90
|
+
function controllerRuntimeDegraded(text){
|
|
91
|
+
return /^DEADBYTE MCP\s*=\s*DEGRADED\s*$/m.test(String(text||''));
|
|
92
|
+
}
|
|
90
93
|
async function listLogFiles(root){
|
|
91
94
|
const files=[];
|
|
92
95
|
const audit=path.join(root,'audit');
|
|
@@ -319,10 +322,11 @@ async function runRemoteSession({resumeCursor=null,verbose=false}={}){
|
|
|
319
322
|
if(preflight.status===0&&controllerRuntimeReady(preflight.stdout)){
|
|
320
323
|
process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
|
|
321
324
|
}else{
|
|
322
|
-
|
|
323
|
-
|
|
325
|
+
const recovery=preflight.status===0&&controllerRuntimeDegraded(preflight.stdout);
|
|
326
|
+
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});
|
|
324
328
|
const started=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
325
|
-
if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error(
|
|
329
|
+
if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error(`remote controller ${recovery?'restart':'start'} returned without a verified ONLINE runtime`);
|
|
326
330
|
startText=started.stdout||'';
|
|
327
331
|
}
|
|
328
332
|
process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const ID=/^[A-Za-z0-9._:-]{1,128}$/;
|
|
2
|
+
|
|
3
|
+
export function createDeadline({timeoutMs=null, nowMs=Date.now(), id=null}={}) {
|
|
4
|
+
if(timeoutMs!==null && (!Number.isInteger(timeoutMs)||timeoutMs<1)) throw new Error('deadline timeout_ms invalid');
|
|
5
|
+
if(!Number.isFinite(nowMs)) throw new Error('deadline now_ms invalid');
|
|
6
|
+
const deadlineMs=timeoutMs===null?null:nowMs+timeoutMs;
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
schema:'deadbyte.deadline.v1',
|
|
9
|
+
id:id===null?null:validateId(id),
|
|
10
|
+
created_at_ms:nowMs,
|
|
11
|
+
timeout_ms:timeoutMs,
|
|
12
|
+
deadline_ms:deadlineMs
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function deadlineState(deadline,{nowMs=Date.now()}={}) {
|
|
17
|
+
if(!deadline||deadline.schema!=='deadbyte.deadline.v1') throw new Error('deadline invalid');
|
|
18
|
+
if(!Number.isFinite(nowMs)) throw new Error('deadline now_ms invalid');
|
|
19
|
+
if(deadline.deadline_ms===null) return Object.freeze({expired:false,remaining_ms:null});
|
|
20
|
+
const remainingMs=deadline.deadline_ms-nowMs;
|
|
21
|
+
return Object.freeze({expired:remainingMs<=0,remaining_ms:Math.max(0,remainingMs)});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createCancellationToken({id=null}={}) {
|
|
25
|
+
const controller=new AbortController();
|
|
26
|
+
let requestedAtMs=null;
|
|
27
|
+
let reason=null;
|
|
28
|
+
const request=(value='cancelled')=>{
|
|
29
|
+
if(controller.signal.aborted)return false;
|
|
30
|
+
requestedAtMs=Date.now();
|
|
31
|
+
reason=String(value);
|
|
32
|
+
controller.abort(reason);
|
|
33
|
+
return true;
|
|
34
|
+
};
|
|
35
|
+
return Object.freeze({
|
|
36
|
+
schema:'deadbyte.cancellation.v1',
|
|
37
|
+
id:id===null?null:validateId(id),
|
|
38
|
+
signal:controller.signal,
|
|
39
|
+
request,
|
|
40
|
+
snapshot:()=>Object.freeze({
|
|
41
|
+
requested:controller.signal.aborted,
|
|
42
|
+
requested_at_ms:requestedAtMs,
|
|
43
|
+
reason
|
|
44
|
+
})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function composeAbortSignal({deadline=null,cancellation=null}={}) {
|
|
49
|
+
const controller=new AbortController();
|
|
50
|
+
let timeout=null;
|
|
51
|
+
const abort=(reason)=>{
|
|
52
|
+
if(!controller.signal.aborted)controller.abort(reason);
|
|
53
|
+
};
|
|
54
|
+
if(cancellation?.signal){
|
|
55
|
+
if(cancellation.signal.aborted)abort(cancellation.signal.reason??'cancelled');
|
|
56
|
+
else cancellation.signal.addEventListener('abort',()=>abort(cancellation.signal.reason??'cancelled'),{once:true});
|
|
57
|
+
}
|
|
58
|
+
if(deadline?.deadline_ms!==null&&deadline?.deadline_ms!==undefined){
|
|
59
|
+
const remaining=Math.max(0,deadline.deadline_ms-Date.now());
|
|
60
|
+
if(remaining===0)abort('deadline_exceeded');
|
|
61
|
+
else timeout=setTimeout(()=>abort('deadline_exceeded'),remaining);
|
|
62
|
+
timeout?.unref?.();
|
|
63
|
+
}
|
|
64
|
+
const close=()=>{if(timeout)clearTimeout(timeout);};
|
|
65
|
+
return Object.freeze({signal:controller.signal,close});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function classifyTermination({deadlineExceeded=false,cancellationRequested=false,physicallyTerminated=false,verified=false}={}) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
deadline_exceeded:deadlineExceeded===true,
|
|
71
|
+
cancellation_requested:cancellationRequested===true,
|
|
72
|
+
physical_termination:physicallyTerminated===true,
|
|
73
|
+
verified:verified===true,
|
|
74
|
+
terminal_semantics:
|
|
75
|
+
verified ? (physicallyTerminated ? 'verified_termination' : 'verified_stop')
|
|
76
|
+
: physicallyTerminated ? 'termination_unverified'
|
|
77
|
+
: cancellationRequested ? 'cancellation_requested'
|
|
78
|
+
: deadlineExceeded ? 'deadline_exceeded'
|
|
79
|
+
: 'running'
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateId(id){if(typeof id!=='string'||!ID.test(id))throw new Error('deadline/cancellation id invalid');return id;}
|
|
@@ -32,10 +32,10 @@ export function registerMachineFsTools(server, { runtime }) {
|
|
|
32
32
|
});
|
|
33
33
|
server.registerTool('machine_fs_read', {
|
|
34
34
|
description:'Read one bounded byte range from a root-relative regular file.',
|
|
35
|
-
inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().nonnegative().default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false) }),
|
|
35
|
+
inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().nonnegative().default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false), timeout_ms:z.number().int().min(100).max(180000).default(30000) }),
|
|
36
36
|
outputSchema:machineFsReadOutputSchema
|
|
37
|
-
}, async ({root_id,path,offset,max_bytes,hash_file}) => {
|
|
38
|
-
try { return ok(machineFsReadOutputSchema, await runtime.read({rootId:root_id,relativePath:path,offset,maxBytes:max_bytes,hashFile:hash_file})); }
|
|
37
|
+
}, async ({root_id,path,offset,max_bytes,hash_file,timeout_ms}) => {
|
|
38
|
+
try { return ok(machineFsReadOutputSchema, await runtime.read({rootId:root_id,relativePath:path,offset,maxBytes:max_bytes,hashFile:hash_file,timeoutMs:timeout_ms})); }
|
|
39
39
|
catch (error) { return fail(machineFsReadOutputSchema,error); }
|
|
40
40
|
});
|
|
41
41
|
server.registerTool('machine_fs_write', {
|
|
@@ -33,7 +33,7 @@ export const machineFsReadOutputSchema = z.strictObject({
|
|
|
33
33
|
offset: z.number().int().nonnegative().optional(), next_offset: z.number().int().nonnegative().optional(),
|
|
34
34
|
file_size: z.number().int().nonnegative().optional(), eof: z.boolean().optional(),
|
|
35
35
|
data_base64: z.string().max(12 * 1024 * 1024).optional(), data_sha256: hex64.optional(),
|
|
36
|
-
file_sha256: hex64.nullable().optional()
|
|
36
|
+
file_sha256: hex64.nullable().optional(), timeout_ms: z.number().int().min(100).max(180000).optional()
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
const commitBase = {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createReadStream, promises as fs } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
const [file, offsetText, lengthText, hashText] = process.argv.slice(2);
|
|
5
|
+
const offset = Number(offsetText);
|
|
6
|
+
const length = Number(lengthText);
|
|
7
|
+
if (typeof file !== 'string' || !Number.isSafeInteger(offset) || offset < 0 ||
|
|
8
|
+
!Number.isSafeInteger(length) || length < 0 || length > 4 * 1024 * 1024 ||
|
|
9
|
+
!['0','1'].includes(hashText ?? '0')) {
|
|
10
|
+
process.stderr.write('invalid machine-fs read worker arguments\n');
|
|
11
|
+
process.exit(64);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const handle = await fs.open(file, 'r');
|
|
16
|
+
let bytes;
|
|
17
|
+
try {
|
|
18
|
+
const buffer = Buffer.alloc(length);
|
|
19
|
+
const result = length ? await handle.read(buffer, 0, length, offset) : { bytesRead: 0 };
|
|
20
|
+
bytes = buffer.subarray(0, result.bytesRead);
|
|
21
|
+
} finally {
|
|
22
|
+
await handle.close();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let fileSha256 = null;
|
|
26
|
+
if (hashText === '1') {
|
|
27
|
+
const hash = createHash('sha256');
|
|
28
|
+
await new Promise((resolve, reject) => {
|
|
29
|
+
const stream = createReadStream(file);
|
|
30
|
+
stream.on('data', chunk => hash.update(chunk));
|
|
31
|
+
stream.once('error', reject);
|
|
32
|
+
stream.once('end', resolve);
|
|
33
|
+
});
|
|
34
|
+
fileSha256 = hash.digest('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.stdout.write(JSON.stringify({
|
|
38
|
+
schema: 'deadbyte.machine-fs-read-worker.v1',
|
|
39
|
+
bytes_read: bytes.length,
|
|
40
|
+
data_base64: bytes.toString('base64'),
|
|
41
|
+
file_sha256: fileSha256
|
|
42
|
+
}));
|
|
43
|
+
} catch (error) {
|
|
44
|
+
process.stderr.write(String(error?.stack ?? error));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
@@ -56,7 +56,7 @@ const transport = new StdioClientTransport({
|
|
|
56
56
|
});
|
|
57
57
|
const stderrChunks = [];
|
|
58
58
|
transport.stderr?.on('data', chunk => stderrChunks.push(Buffer.from(chunk)));
|
|
59
|
-
const baseTools = ['contained_transform','evidence_verify','file_read','proof_execute','skill_catalog','workspace_open'];
|
|
59
|
+
const baseTools = ['contained_transform','evidence_verify','file_read','host_exec','host_file_read','host_file_write','proof_execute','skill_catalog','workspace_open'];
|
|
60
60
|
const codingTools = [
|
|
61
61
|
'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read',
|
|
62
62
|
'coding_receipt_verify','coding_search','coding_session_status','coding_stat',
|
package/src/machine-fs.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createHash, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
2
2
|
import { createReadStream } from 'node:fs';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
3
4
|
import { lstat, mkdir as fsMkdir, open, readFile, readdir, realpath, rename, rm, rmdir } from 'node:fs/promises';
|
|
4
5
|
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
5
7
|
import { canonicalJson } from './canonical-json.mjs';
|
|
6
8
|
import { assertCodingArmed, recheckCodingAuthority } from './coding-plane.mjs';
|
|
7
9
|
import { isInside, validatePortableRelativePath } from './path-policy.mjs';
|
|
8
10
|
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
9
11
|
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
12
|
+
import { atomicMutation } from './atomic-mutation.mjs';
|
|
10
13
|
|
|
11
14
|
export const MACHINE_FS_RECEIPT_SCHEMA = 'deadbyte.machine-fs-receipt.v1';
|
|
12
15
|
const RECEIPT_DOMAIN = Buffer.from('DEADBYTE-MACHINE-FS-RECEIPT-V1\0', 'utf8');
|
|
@@ -29,6 +32,55 @@ async function sha256File(file) {
|
|
|
29
32
|
return hash.digest('hex');
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
const machineFsReadWorkerPath = fileURLToPath(new URL('./machine-fs-read-worker.mjs', import.meta.url));
|
|
36
|
+
async function boundedReadWorker({ file, offset, length, hashFile, timeoutMs, workerPath = machineFsReadWorkerPath }) {
|
|
37
|
+
return await new Promise((resolve, reject) => {
|
|
38
|
+
const child = spawn(process.execPath, [workerPath, file, String(offset), String(length), hashFile ? '1' : '0'], {
|
|
39
|
+
windowsHide: true, shell: false, stdio: ['ignore','pipe','pipe']
|
|
40
|
+
});
|
|
41
|
+
const stdout = [];
|
|
42
|
+
let stdoutBytes = 0;
|
|
43
|
+
let stderr = '';
|
|
44
|
+
let settled = false;
|
|
45
|
+
let timedOut = false;
|
|
46
|
+
const finish = (fn, value) => {
|
|
47
|
+
if (settled) return;
|
|
48
|
+
settled = true;
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
fn(value);
|
|
51
|
+
};
|
|
52
|
+
child.stdout.on('data', chunk => {
|
|
53
|
+
stdoutBytes += chunk.length;
|
|
54
|
+
if (stdoutBytes > 8 * 1024 * 1024) {
|
|
55
|
+
child.kill();
|
|
56
|
+
finish(reject, new Error('machine fs read worker output exceeded bound'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
stdout.push(Buffer.from(chunk));
|
|
60
|
+
});
|
|
61
|
+
child.stderr.on('data', chunk => { stderr = (stderr + chunk.toString('utf8')).slice(-16384); });
|
|
62
|
+
child.once('error', error => finish(reject, error));
|
|
63
|
+
child.once('close', code => {
|
|
64
|
+
if (settled) return;
|
|
65
|
+
if (timedOut) return finish(reject, new Error(`machine fs read timed out after ${timeoutMs}ms`));
|
|
66
|
+
if (code !== 0) return finish(reject, new Error(`machine fs read worker failed exit=${String(code)}: ${stderr}`));
|
|
67
|
+
let value;
|
|
68
|
+
try { value = JSON.parse(Buffer.concat(stdout).toString('utf8')); }
|
|
69
|
+
catch { return finish(reject, new Error('machine fs read worker returned invalid JSON')); }
|
|
70
|
+
if (value?.schema !== 'deadbyte.machine-fs-read-worker.v1' || !Number.isInteger(value.bytes_read) ||
|
|
71
|
+
typeof value.data_base64 !== 'string' || (value.file_sha256 !== null && !SHA256_RE.test(value.file_sha256 ?? ''))) {
|
|
72
|
+
return finish(reject, new Error('machine fs read worker response invalid'));
|
|
73
|
+
}
|
|
74
|
+
finish(resolve, value);
|
|
75
|
+
});
|
|
76
|
+
const timer = setTimeout(() => {
|
|
77
|
+
timedOut = true;
|
|
78
|
+
child.kill();
|
|
79
|
+
}, timeoutMs);
|
|
80
|
+
timer.unref?.();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
32
84
|
function getRoot(policy, rootId, permission) {
|
|
33
85
|
const root = policy.roots[rootId];
|
|
34
86
|
if (!root) throw new Error(`machine fs root '${rootId}' is not allowed`);
|
|
@@ -220,32 +272,11 @@ function requestSha(operation, args) {
|
|
|
220
272
|
}
|
|
221
273
|
|
|
222
274
|
async function atomicWrite(target, bytes, expectedSha256, authorityCheck = null) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const temp = path.join(path.dirname(target), `.${path.basename(target)}.deadbyte-machine-fs-${process.pid}-${randomBytes(8).toString('hex')}.tmp`);
|
|
226
|
-
let handle;
|
|
227
|
-
try {
|
|
228
|
-
if (authorityCheck) await authorityCheck();
|
|
229
|
-
handle = await open(temp, 'wx', 0o600);
|
|
230
|
-
await handle.writeFile(bytes);
|
|
231
|
-
await handle.sync();
|
|
232
|
-
await handle.close();
|
|
233
|
-
handle = null;
|
|
234
|
-
checkExpectedFile(await regularState(target), expectedSha256);
|
|
235
|
-
if (authorityCheck) await authorityCheck();
|
|
236
|
-
await rename(temp, target);
|
|
237
|
-
} catch (error) {
|
|
238
|
-
if (handle) await handle.close().catch(() => {});
|
|
239
|
-
await rm(temp, { force: true }).catch(() => {});
|
|
240
|
-
throw error;
|
|
241
|
-
}
|
|
242
|
-
const after = await regularState(target);
|
|
243
|
-
const expectedAfter = sha256Bytes(bytes);
|
|
244
|
-
if (!after.exists || after.sha256 !== expectedAfter) throw new Error('machine fs post-commit hash mismatch');
|
|
245
|
-
return { before, after };
|
|
275
|
+
return atomicMutation({ target, bytes, expected: expectedSha256, readState: regularState,
|
|
276
|
+
assertExpected: checkExpectedFile, authorityCheck, tempTag: 'deadbyte-machine-fs' });
|
|
246
277
|
}
|
|
247
278
|
|
|
248
|
-
export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
279
|
+
export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath, readWorkerPath = machineFsReadWorkerPath }) {
|
|
249
280
|
if (!policy) throw new Error('machine fs runtime requires coding policy');
|
|
250
281
|
if (!signingKeyPath || !verifyKeyPath) throw new Error('machine fs runtime requires trust key paths');
|
|
251
282
|
|
|
@@ -292,25 +323,27 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
|
|
|
292
323
|
const nextCursor = filtered.length > selected.length && selected.length ? encodeCursor(selected.at(-1).name) : null;
|
|
293
324
|
return { status: 'ok', root_id: rootId, path: found.relative, entries, next_cursor: nextCursor };
|
|
294
325
|
}
|
|
295
|
-
async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false }) {
|
|
326
|
+
async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false, timeoutMs = 30000 }) {
|
|
296
327
|
if (!Number.isInteger(offset) || offset < 0) throw new Error('machine fs read offset must be nonnegative integer');
|
|
297
328
|
if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > policy.max_read_bytes) throw new Error(`machine fs max_bytes must be in [1, ${policy.max_read_bytes}]`);
|
|
329
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 180000) throw new Error('machine fs read timeout_ms must be in [100, 180000]');
|
|
298
330
|
const grant = await assertCodingArmed(policy, rootId);
|
|
299
331
|
const found = await resolveExisting(policy, grant, rootId, relativePath, 'read', false);
|
|
300
332
|
const info = await lstat(found.resolved);
|
|
301
333
|
if (!info.isFile()) throw new Error('machine fs read target must be a regular file');
|
|
302
334
|
if (offset > info.size) throw new Error('machine fs read offset exceeds file size');
|
|
303
|
-
const handle = await open(found.resolved, 'r');
|
|
304
335
|
const length = Math.min(maxBytes, info.size - offset);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
336
|
+
await recheckCodingAuthority(policy, grant, rootId);
|
|
337
|
+
const worker = await boundedReadWorker({ file: found.resolved, offset, length, hashFile, timeoutMs, workerPath: readWorkerPath });
|
|
338
|
+
const buffer = Buffer.from(worker.data_base64, 'base64');
|
|
339
|
+
if (buffer.length !== worker.bytes_read || buffer.length > length) throw new Error('machine fs read worker byte count mismatch');
|
|
340
|
+
await recheckCodingAuthority(policy, grant, rootId);
|
|
309
341
|
return {
|
|
310
342
|
status: 'ok', root_id: rootId, path: found.relative,
|
|
311
|
-
offset, next_offset: offset + length, file_size: info.size,
|
|
312
|
-
eof: offset + length >= info.size, data_base64: buffer.toString('base64'),
|
|
313
|
-
data_sha256: sha256Bytes(buffer), file_sha256:
|
|
343
|
+
offset, next_offset: offset + buffer.length, file_size: info.size,
|
|
344
|
+
eof: offset + buffer.length >= info.size, data_base64: buffer.toString('base64'),
|
|
345
|
+
data_sha256: sha256Bytes(buffer), file_sha256: worker.file_sha256,
|
|
346
|
+
timeout_ms: timeoutMs
|
|
314
347
|
};
|
|
315
348
|
}
|
|
316
349
|
|
package/src/mcp-server.mjs
CHANGED
|
@@ -14,6 +14,8 @@ import { defaultPrivateKeyPath, defaultPublicKeyPath, loadPrivateSigningKey, loa
|
|
|
14
14
|
import { DEADBYTE_VERSION } from './version.mjs';
|
|
15
15
|
import { currentIngressAdapterMode, TUNNEL_STDIO_PROBE_COMPAT_MODE } from './ingress-mode.mjs';
|
|
16
16
|
import { installTunnelStdioProbeCompat } from './tunnel-stdio-compat.mjs';
|
|
17
|
+
import { installStdioConsoleGuard } from './stdio-console-guard.mjs';
|
|
18
|
+
import { recoverInterruptedSlotUpdates } from './runtime-update.mjs';
|
|
17
19
|
import { decodeUtf8Strict } from './text-policy.mjs';
|
|
18
20
|
import { assertHostArmed, hostExec, hostExecInline, hostExecNative, hostFileRead, hostFileWrite, loadHostPolicy } from './host-plane.mjs';
|
|
19
21
|
import { createMachineRuntime } from './machine-identity.mjs';
|
|
@@ -73,6 +75,11 @@ const verifyKeyPath = process.env.DEADBYTE_VERIFY_KEY ?? defaultPublicKeyPath();
|
|
|
73
75
|
const auditRuntimeMode = process.env.DEADBYTE_AUDIT_RUNTIME ?? '0';
|
|
74
76
|
if (!['0','1'].includes(auditRuntimeMode)) throw new Error('DEADBYTE_AUDIT_RUNTIME must be 0 or 1');
|
|
75
77
|
const runtimeStateRoot = path.resolve(process.env.DEADBYTE_STATE_ROOT || path.join(os.homedir(),'.deadbyte-mcp'));
|
|
78
|
+
const interruptedUpdateRecovery = await recoverInterruptedSlotUpdates({ stateRoot: runtimeStateRoot });
|
|
79
|
+
if (interruptedUpdateRecovery.launched > 0) {
|
|
80
|
+
console.error(`[runtime-update-recovery] launched ${interruptedUpdateRecovery.launched} detached supervisor(s)`);
|
|
81
|
+
}
|
|
82
|
+
|
|
76
83
|
const surfaceProfile = capabilityProfileFromEnv(process.env);
|
|
77
84
|
const localPrivilegedPolicyPath = path.join(packageRoot, 'controller', 'deadbyte-host-policy.json');
|
|
78
85
|
const privilegedPolicyExplicit = Object.hasOwn(process.env, 'DEADBYTE_PRIVILEGED_POLICY');
|
|
@@ -231,6 +238,9 @@ const skillRoot=path.join(runtimeStateRoot,'skills');
|
|
|
231
238
|
await mkdir(skillRoot,{recursive:true});
|
|
232
239
|
const skillRuntime=await createSkillPlane({root:skillRoot,verifyKeyPath});
|
|
233
240
|
|
|
241
|
+
installStdioConsoleGuard();
|
|
242
|
+
if (process.env.DEADBYTE_STDIO_CONSOLE_GUARD_PROBE === '1') console.log('DEADBYTE_STDIO_CONSOLE_GUARD_PROBE');
|
|
243
|
+
|
|
234
244
|
const ingressAdapterMode = currentIngressAdapterMode();
|
|
235
245
|
installTunnelStdioProbeCompat({
|
|
236
246
|
enabled: ingressAdapterMode === TUNNEL_STDIO_PROBE_COMPAT_MODE,
|
package/src/mcp-smoke-client.mjs
CHANGED
|
@@ -38,7 +38,8 @@ const transport = new StdioClientTransport({
|
|
|
38
38
|
...getDefaultEnvironment(),
|
|
39
39
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
40
40
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
41
|
-
DEADBYTE_VERIFY_KEY: keys.publicKeyPath
|
|
41
|
+
DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
|
|
42
|
+
DEADBYTE_STDIO_CONSOLE_GUARD_PROBE: '1'
|
|
42
43
|
},
|
|
43
44
|
stderr: 'pipe'
|
|
44
45
|
});
|
|
@@ -64,6 +65,8 @@ try {
|
|
|
64
65
|
|
|
65
66
|
const listed = await client.listTools();
|
|
66
67
|
const names = listed.tools.map(tool => tool.name).sort();
|
|
68
|
+
const guardStderr = Buffer.concat(serverStderr).toString('utf8');
|
|
69
|
+
if (!guardStderr.includes('DEADBYTE MCP LOG DEADBYTE_STDIO_CONSOLE_GUARD_PROBE')) throw new Error('stdio console guard probe did not reach stderr');
|
|
67
70
|
const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
|
|
68
71
|
const privilegedTools = ['host_exec', 'host_file_read', 'host_file_write'];
|
|
69
72
|
const allowedCatalogs = [
|
|
@@ -133,14 +133,32 @@ function requestSha(policy,operation,args){
|
|
|
133
133
|
}
|
|
134
134
|
export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,processRuntime=null,stateRoot=path.join(os.homedir(),'.deadbyte-mcp'),
|
|
135
135
|
processSnapshotProvider=defaultProcessSnapshotProvider,systemSnapshotProvider=defaultSystemSnapshotProvider,
|
|
136
|
-
ownedRuntimePidProvider=defaultOwnedRuntimePidProvider,hardwareSnapshotProvider=defaultHardwareSnapshotProvider
|
|
136
|
+
ownedRuntimePidProvider=defaultOwnedRuntimePidProvider,hardwareSnapshotProvider=defaultHardwareSnapshotProvider,
|
|
137
|
+
searchSessionTtlMs=5*60*1000,maxSearchSessions=128,clockMs=()=>Date.now()}={}){
|
|
137
138
|
if(!policy||!signingKeyPath||!verifyKeyPath) throw new Error('observation runtime requires policy and trust keys');
|
|
139
|
+
if(!Number.isInteger(searchSessionTtlMs)||searchSessionTtlMs<10||searchSessionTtlMs>24*60*60*1000) throw new Error('searchSessionTtlMs out of range');
|
|
140
|
+
if(!Number.isInteger(maxSearchSessions)||maxSearchSessions<1||maxSearchSessions>4096) throw new Error('maxSearchSessions out of range');
|
|
141
|
+
if(typeof clockMs!=='function') throw new Error('clockMs must be a function');
|
|
138
142
|
const searches=new Map(),snapshots=new Map();
|
|
139
143
|
let signingPromise=null;
|
|
140
144
|
const runtime={policy,signing:()=>signingPromise??=(loadPrivateSigningKey(signingKeyPath))};
|
|
145
|
+
const terminalSearchState=state=>['completed','cancelled','failed'].includes(state);
|
|
146
|
+
function pruneSearches(){
|
|
147
|
+
const now=Number(clockMs());
|
|
148
|
+
if(!Number.isFinite(now)) throw new Error('search clock invalid');
|
|
149
|
+
for(const [id,session] of searches){
|
|
150
|
+
if(terminalSearchState(session.state)&&Number.isFinite(session.completed_at_ms)&&now-session.completed_at_ms>=searchSessionTtlMs) searches.delete(id);
|
|
151
|
+
}
|
|
152
|
+
if(searches.size>maxSearchSessions){
|
|
153
|
+
const terminal=[...searches.values()].filter(s=>terminalSearchState(s.state))
|
|
154
|
+
.sort((a,b)=>(a.completed_at_ms??Infinity)-(b.completed_at_ms??Infinity)||a.search_id.localeCompare(b.search_id));
|
|
155
|
+
for(const session of terminal){ if(searches.size<=maxSearchSessions) break; searches.delete(session.search_id); }
|
|
156
|
+
}
|
|
157
|
+
}
|
|
141
158
|
function searchFor(searchId){
|
|
142
159
|
if(!HEX64.test(searchId??'')) throw new Error('invalid search id');
|
|
143
|
-
|
|
160
|
+
pruneSearches();
|
|
161
|
+
const session=searches.get(searchId); if(!session) throw new Error(`unknown or expired search '${searchId}'`); return session;
|
|
144
162
|
}
|
|
145
163
|
async function finalizeSearch(session,state,error=null){
|
|
146
164
|
session.error=error?String(error):null;
|
|
@@ -149,7 +167,8 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
|
|
|
149
167
|
bytes_scanned:session.bytes_scanned,truncated:session.truncated,error:session.error
|
|
150
168
|
});
|
|
151
169
|
session.receipt_id=receipt.receipt_id; session.receipt_sha256=receipt.receipt_sha256;
|
|
152
|
-
session.state=state;
|
|
170
|
+
session.state=state; session.completed_at_ms=Number(clockMs());
|
|
171
|
+
pruneSearches();
|
|
153
172
|
}
|
|
154
173
|
async function executeSearch(session){
|
|
155
174
|
if(session.cancel_requested){ await finalizeSearch(session,'cancelled'); return; }
|
|
@@ -200,11 +219,13 @@ export function createObservationRuntime({policy,signingKeyPath,verifyKeyPath,pr
|
|
|
200
219
|
if(!Number.isInteger(maxBytes)||maxBytes<1||maxBytes>64*1024*1024) throw new Error('max_bytes out of range');
|
|
201
220
|
if(!Number.isInteger(maxFileBytes)||maxFileBytes<1||maxFileBytes>4*1024*1024) throw new Error('max_file_bytes out of range');
|
|
202
221
|
rootFor(policy,rootId); if(relativePath!=='.') validatePortableRelativePath(relativePath);
|
|
222
|
+
pruneSearches();
|
|
223
|
+
if(searches.size>=maxSearchSessions) throw new Error('observation search session limit reached');
|
|
203
224
|
const args={root_id:rootId,path:relativePath,pattern,case_sensitive:caseSensitive,max_results:maxResults,max_files:maxFiles,max_bytes:maxBytes,max_file_bytes:maxFileBytes};
|
|
204
225
|
const session={search_id:randomBytes(32).toString('hex'),root_id:rootId,path:relativePath,pattern,case_sensitive:caseSensitive,
|
|
205
226
|
max_results:maxResults,max_files:maxFiles,max_bytes:maxBytes,max_file_bytes:maxFileBytes,state:'pending',cancel_requested:false,
|
|
206
227
|
results:[],files_scanned:0,bytes_scanned:0,truncated:false,result_head_sha256:ZERO,error:null,receipt_id:null,receipt_sha256:null,
|
|
207
|
-
request_sha256:requestSha(policy,'machine_search_start',args)};
|
|
228
|
+
created_at_ms:Number(clockMs()),completed_at_ms:null,request_sha256:requestSha(policy,'machine_search_start',args)};
|
|
208
229
|
searches.set(session.search_id,session); setImmediate(()=>{ void executeSearch(session); });
|
|
209
230
|
return {status:'accepted',search_id:session.search_id,state:session.state,request_sha256:session.request_sha256};
|
|
210
231
|
}
|
|
@@ -51,7 +51,7 @@ const transport=new StdioClientTransport({command:process.execPath,args:[serverP
|
|
|
51
51
|
env:{...getDefaultEnvironment(),DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_OBSERVATION_RUNTIME:'1',
|
|
52
52
|
DEADBYTE_CODING_POLICY:policyPath,DEADBYTE_STATE_ROOT:state,DEADBYTE_SIGNING_KEY:keys.privateKeyPath,DEADBYTE_VERIFY_KEY:keys.publicKeyPath},stderr:'pipe'});
|
|
53
53
|
const stderr=[]; transport.stderr?.on('data',chunk=>stderr.push(Buffer.from(chunk)));
|
|
54
|
-
const base=['contained_transform','evidence_verify','file_read','proof_execute','skill_catalog','workspace_open'];
|
|
54
|
+
const base=['contained_transform','evidence_verify','file_read','host_exec','host_file_read','host_file_write','proof_execute','skill_catalog','workspace_open'];
|
|
55
55
|
const machine=['machine_list','machine_ping','machine_revoke','machine_status'];
|
|
56
56
|
const coding=['coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read','coding_receipt_verify','coding_search',
|
|
57
57
|
'coding_session_status','coding_stat','coding_symbol_definition','coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import * as z from 'zod/v4';
|
|
2
2
|
import {
|
|
3
3
|
processActionOutputSchema,
|
|
4
4
|
processListOutputSchema,
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
processStartOutputSchema,
|
|
7
7
|
processStatusOutputSchema,
|
|
8
8
|
processTerminateOutputSchema,
|
|
9
|
+
processWriteOutputSchema,
|
|
9
10
|
processStructuredResult
|
|
10
11
|
} from './process-output-contracts.mjs';
|
|
11
12
|
|
|
@@ -21,23 +22,35 @@ export function registerProcessTools(server, { runtime }) {
|
|
|
21
22
|
description: 'Start one locally registered process profile inside an approved root. Executable identity comes only from local policy.',
|
|
22
23
|
inputSchema: z.strictObject({
|
|
23
24
|
profile_id: id, root_id: id, cwd: z.string().min(1).max(1024).default('.'),
|
|
24
|
-
args: z.array(z.string().max(8192)).max(64).default([])
|
|
25
|
+
args: z.array(z.string().max(8192)).max(64).default([]), timeout_ms: z.number().int().min(100).max(900000).nullable().default(null)
|
|
25
26
|
}), outputSchema: processStartOutputSchema
|
|
26
|
-
}, async ({ profile_id, root_id, cwd, args }) => {
|
|
27
|
-
try { return ok(processStartOutputSchema, { status: 'ok', session: await runtime.start({ profileId: profile_id, rootId: root_id, cwd, args }) }); }
|
|
27
|
+
}, async ({ profile_id, root_id, cwd, args, timeout_ms }) => {
|
|
28
|
+
try { return ok(processStartOutputSchema, { status: 'ok', session: await runtime.start({ profileId: profile_id, rootId: root_id, cwd, args, timeoutMs: timeout_ms }) }); }
|
|
28
29
|
catch (error) { return fail(processStartOutputSchema, 'denied', error); }
|
|
29
30
|
});
|
|
30
31
|
server.registerTool('process_session_read', {
|
|
31
32
|
description: 'Read a bounded byte range from one DEADBYTE-owned process session using ordered stream offsets.',
|
|
32
33
|
inputSchema: z.strictObject({
|
|
33
|
-
session_id: hex64, offset: z.number().int().
|
|
34
|
-
max_bytes: z.number().int().min(1).max(262144).default(65536)
|
|
34
|
+
session_id: hex64, offset: z.number().int().min(-(16 * 1024 * 1024)).default(0),
|
|
35
|
+
max_bytes: z.number().int().min(1).max(262144).default(65536),
|
|
36
|
+
wait_ms: z.number().int().min(0).max(30000).default(0),
|
|
37
|
+
wait_for: z.string().min(1).max(256).nullable().default(null)
|
|
35
38
|
}), outputSchema: processReadOutputSchema
|
|
36
|
-
}, async ({ session_id, offset, max_bytes }) => {
|
|
37
|
-
try { return ok(processReadOutputSchema, { status: 'ok', ...(await runtime.read({ sessionId: session_id, offset, maxBytes: max_bytes })) }); }
|
|
39
|
+
}, async ({ session_id, offset, max_bytes, wait_ms, wait_for }) => {
|
|
40
|
+
try { return ok(processReadOutputSchema, { status: 'ok', ...(await runtime.read({ sessionId: session_id, offset, maxBytes: max_bytes, waitMs: wait_ms, waitFor: wait_for })) }); }
|
|
38
41
|
catch (error) { return fail(processReadOutputSchema, 'denied', error); }
|
|
39
42
|
});
|
|
40
43
|
|
|
44
|
+
server.registerTool('process_session_write', {
|
|
45
|
+
description: 'Write one bounded UTF-8 input payload to a running DEADBYTE-owned pipe session only when its local profile explicitly enables raw input. Input text is never stored in the signed journal; only byte count and SHA-256 identity are recorded.',
|
|
46
|
+
inputSchema: z.strictObject({
|
|
47
|
+
session_id: hex64, input: z.string().min(1).max(1024 * 1024), append_newline: z.boolean().default(true)
|
|
48
|
+
}), outputSchema: processWriteOutputSchema
|
|
49
|
+
}, async ({ session_id, input, append_newline }) => {
|
|
50
|
+
try { return ok(processWriteOutputSchema, { status: 'ok', ...(await runtime.write({ sessionId: session_id, input, appendNewline: append_newline })) }); }
|
|
51
|
+
catch (error) { return fail(processWriteOutputSchema, 'denied', error); }
|
|
52
|
+
});
|
|
53
|
+
|
|
41
54
|
server.registerTool('process_session_status', {
|
|
42
55
|
description: 'Read the verified state of one DEADBYTE-owned process session by session identity.',
|
|
43
56
|
inputSchema: z.strictObject({ session_id: hex64 }), outputSchema: processStatusOutputSchema
|
|
@@ -53,6 +66,14 @@ export function registerProcessTools(server, { runtime }) {
|
|
|
53
66
|
try { return ok(processListOutputSchema, { status: 'ok', ...(await runtime.list()) }); }
|
|
54
67
|
catch (error) { return fail(processListOutputSchema, 'failed', error); }
|
|
55
68
|
});
|
|
69
|
+
server.registerTool('process_session_cancel', {
|
|
70
|
+
description: 'Request cooperative cancellation of one DEADBYTE-owned process session. Cancellation is recorded separately from physical termination.',
|
|
71
|
+
inputSchema: z.strictObject({ session_id: hex64, reason: z.string().min(1).max(256).default('operator_request') }), outputSchema: processTerminateOutputSchema
|
|
72
|
+
}, async ({ session_id, reason }) => {
|
|
73
|
+
try { return ok(processTerminateOutputSchema, { status: 'ok', session: await runtime.cancel({ sessionId: session_id, reason }) }); }
|
|
74
|
+
catch (error) { return fail(processTerminateOutputSchema, 'denied', error); }
|
|
75
|
+
});
|
|
76
|
+
|
|
56
77
|
server.registerTool('process_session_terminate', {
|
|
57
78
|
description: 'Terminate one DEADBYTE-owned process session through its owned host boundary. Numeric PID is never an input.',
|
|
58
79
|
inputSchema: z.strictObject({ session_id: hex64 }), outputSchema: processTerminateOutputSchema
|