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.
Files changed (96) hide show
  1. package/MANIFEST.SHA256 +95 -64
  2. package/PROVENANCE.json +6 -5
  3. package/README.txt +36 -20
  4. package/RELEASE.json +24 -8
  5. package/SBOM.cdx.json +3 -3
  6. package/controller/README.TXT +2 -2
  7. package/controller/deadbyte-controller.ps1 +50 -2
  8. package/controller/deadbyte-process-policy.json +63 -1
  9. package/docs/ARCHITECTURE.md +3 -4
  10. package/package.json +88 -86
  11. package/scripts/autonomous-build.ps1 +1 -0
  12. package/scripts/autonomous-tests.ps1 +1 -0
  13. package/scripts/check-agent-backend-r46.mjs +77 -0
  14. package/scripts/check-atomic-resource-r46.mjs +84 -0
  15. package/scripts/check-autonomous-race-r46.mjs +160 -0
  16. package/scripts/check-coding-task-deadline-r46.mjs +59 -0
  17. package/scripts/check-deadline-cancellation-r46.mjs +49 -0
  18. package/scripts/check-process-deadline-r46.mjs +52 -0
  19. package/scripts/check-r46-background.mjs +5 -0
  20. package/scripts/deferred-slot-operation-r46.mjs +126 -0
  21. package/scripts/extract-r46-failures.mjs +5 -0
  22. package/scripts/final-closure-r46.mjs +98 -0
  23. package/scripts/final-closure-verify-r46.mjs +119 -0
  24. package/scripts/full-regression-r46.mjs +28 -0
  25. package/scripts/gate-windows-r46.ps1 +58 -0
  26. package/scripts/inspect-r46-active.mjs +2 -0
  27. package/scripts/r47-core-failures.mjs +1 -0
  28. package/scripts/refresh-process-policy.mjs +64 -7
  29. package/scripts/release-manifest.mjs +1 -1
  30. package/scripts/release-parity-r46.mjs +187 -0
  31. package/scripts/run-r46-full-regression-background.mjs +7 -0
  32. package/scripts/run-r46-windows-gate-background.mjs +6 -0
  33. package/scripts/run-r46-windows-gate-background.ps1 +10 -0
  34. package/scripts/windows-gate-evidence-r46.mjs +111 -0
  35. package/scripts/workload-contract-regression-r47.mjs +14 -0
  36. package/scripts/workload-stream-regression.mjs +16 -0
  37. package/scripts/workload-substrate-r47-regression.mjs +12 -0
  38. package/src/agent-backend-r44.mjs +75 -10
  39. package/src/atomic-mutation.mjs +55 -0
  40. package/src/autonomous-mcp-tools.mjs +4 -3
  41. package/src/autonomous-output-contracts.mjs +16 -3
  42. package/src/autonomous-runtime.mjs +83 -30
  43. package/src/capability-ledger.mjs +1 -0
  44. package/src/coding-mcp-tools.mjs +2 -2
  45. package/src/coding-output-contracts.mjs +18 -4
  46. package/src/coding-plane.mjs +3 -24
  47. package/src/coding-search.mjs +11 -2
  48. package/src/coding-task-runtime.mjs +41 -6
  49. package/src/compact-gateway-mcp-tools.mjs +7 -5
  50. package/src/deadbyte-cli.mjs +7 -3
  51. package/src/deadline-cancellation.mjs +83 -0
  52. package/src/machine-fs-mcp-tools.mjs +3 -3
  53. package/src/machine-fs-output-contracts.mjs +1 -1
  54. package/src/machine-fs-read-worker.mjs +46 -0
  55. package/src/machine-fs-smoke-client.mjs +1 -1
  56. package/src/machine-fs.mjs +66 -33
  57. package/src/mcp-server.mjs +10 -0
  58. package/src/mcp-smoke-client.mjs +4 -1
  59. package/src/observation-runtime.mjs +25 -4
  60. package/src/observation-smoke-client.mjs +1 -1
  61. package/src/process-mcp-tools.mjs +29 -8
  62. package/src/process-output-contracts.mjs +43 -3
  63. package/src/process-policy.mjs +3 -1
  64. package/src/process-runtime.mjs +152 -23
  65. package/src/process-smoke-client.mjs +67 -22
  66. package/src/release-generation.mjs +22 -3
  67. package/src/remote-device-r44.mjs +53 -15
  68. package/src/remote-mcp-smoke-client.mjs +1 -1
  69. package/src/resource-identity.mjs +59 -0
  70. package/src/runtime-update.mjs +526 -162
  71. package/src/stdio-console-guard.mjs +24 -0
  72. package/src/version.mjs +1 -1
  73. package/src/workload-substrate.mjs +87 -0
  74. package/test/cli-entrypoint.test.mjs +3 -3
  75. package/test/coding-runtime.test.mjs +12 -0
  76. package/test/coding-search-r36.test.mjs +7 -0
  77. package/test/compact-gateway-r36.test.mjs +28 -0
  78. package/test/core.test.mjs +1 -1
  79. package/test/full-max-catalog-r46.test.mjs +92 -0
  80. package/test/helpers/autonomous-r34-fixture.mjs +10 -1
  81. package/test/machine-fs-mcp-tools.test.mjs +4 -1
  82. package/test/machine-fs-runtime.test.mjs +18 -2
  83. package/test/observation-runtime.test.mjs +29 -2
  84. package/test/process-mcp-tools.test.mjs +27 -6
  85. package/test/process-policy-refresh.test.mjs +87 -10
  86. package/test/process-runtime.test.mjs +129 -2
  87. package/test/process-supervision.test.mjs +4 -2
  88. package/test/production-docs.test.mjs +3 -3
  89. package/test/r33-finalization.test.mjs +3 -3
  90. package/test/r45-finalization.test.mjs +1 -1
  91. package/test/release-generation.test.mjs +25 -11
  92. package/test/release-version.test.mjs +99 -44
  93. package/test/remote-device-r44.test.mjs +75 -0
  94. package/test/remote-r24-catalog.test.mjs +2 -2
  95. package/test/runtime-self-update.test.mjs +118 -1
  96. 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:nonneg.default(0),max_bytes:z.number().int().min(1).max(262144).default(65536)}),
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. No raw stdin or PID authority.',inputSchema:processSessionGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>processSession(args,processRuntime));
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
  }
@@ -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
- process.stdout.write('[BOOT] Starting bridge/runtime...\n');
323
- invokeController(ctx,'start',{discardOutput:true,timeoutMs:REMOTE_START_TIMEOUT_MS});
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('remote controller start returned without a verified ONLINE runtime');
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',
@@ -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
- const before = await regularState(target);
224
- checkExpectedFile(before, expectedSha256);
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
- const buffer = Buffer.alloc(length);
306
- try { if (length) await handle.read(buffer, 0, length, offset); }
307
- finally { await handle.close(); }
308
- const fileSha = hashFile ? await sha256File(found.resolved) : null;
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: fileSha
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
 
@@ -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,
@@ -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
- const session=searches.get(searchId); if(!session) throw new Error(`unknown search '${searchId}'`); return session;
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
- import * as z from 'zod/v4';
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().nonnegative().default(0),
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