deadbyte-mcp 0.9.0 → 0.11.0

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 (136) hide show
  1. package/MANIFEST.SHA256 +134 -92
  2. package/README.txt +19 -15
  3. package/bench/fixtures/corpus.json +32 -13
  4. package/bench/fixtures/r36/case-map.json +8 -0
  5. package/bin/WORKER-REGISTRY.txt +3 -3
  6. package/bin/appcontainer-stage.exe +0 -0
  7. package/bin/appcontainer-stage.obj +0 -0
  8. package/bin/bootstrap-advapi32.exe +0 -0
  9. package/bin/bootstrap-advapi32.obj +0 -0
  10. package/bin/bootstrap-exitcode.exe +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.exe +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.exe +0 -0
  15. package/bin/child-control-probe.obj +0 -0
  16. package/bin/child-control-stage.exe +0 -0
  17. package/bin/child-control-stage.obj +0 -0
  18. package/bin/contained-reverse-worker.exe +0 -0
  19. package/bin/contained-reverse-worker.obj +0 -0
  20. package/bin/contained-transform-worker.exe +0 -0
  21. package/bin/contained-transform-worker.obj +0 -0
  22. package/bin/containment-probe.exe +0 -0
  23. package/bin/containment-probe.obj +0 -0
  24. package/bin/deadbyte-contain.exe +0 -0
  25. package/bin/deadbyte-contain.obj +0 -0
  26. package/bin/deadbyte-exec.exe +0 -0
  27. package/bin/deadbyte-exec.obj +0 -0
  28. package/bin/deadbyte-process-host.exe +0 -0
  29. package/bin/deadbyte-process-host.obj +0 -0
  30. package/bin/deadbyte-tunnel-host.exe +0 -0
  31. package/bin/deadbyte-tunnel-host.obj +0 -0
  32. package/controller/README.TXT +8 -6
  33. package/controller/deadbyte-controller.ps1 +48 -2
  34. package/controller/deadbyte-process-policy.json +5 -5
  35. package/docs/ARCHITECTURE.md +1 -1
  36. package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
  37. package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
  38. package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
  39. package/package.json +1 -1
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/deferred-slot-operation.mjs +53 -15
  42. package/scripts/final-closure-r36.mjs +113 -0
  43. package/scripts/final-closure-verify-r36.mjs +208 -0
  44. package/scripts/final-closure-verify.mjs +88 -39
  45. package/scripts/final-closure.mjs +63 -56
  46. package/scripts/gate-windows-r36.ps1 +42 -0
  47. package/scripts/gate-windows.ps1 +3 -3
  48. package/scripts/release-parity-r36.mjs +200 -0
  49. package/scripts/release-parity-tests.ps1 +9 -0
  50. package/scripts/release-parity.mjs +149 -139
  51. package/scripts/windows-gate-evidence-r36.mjs +52 -0
  52. package/scripts/windows-gate-evidence.mjs +5 -5
  53. package/src/agent-memory-plane.mjs +98 -0
  54. package/src/autonomous-context-engine.mjs +2 -1
  55. package/src/autonomous-mcp-tools.mjs +5 -1
  56. package/src/autonomous-output-contracts.mjs +9 -6
  57. package/src/autonomous-planner-contracts.mjs +36 -1
  58. package/src/autonomous-planner.mjs +25 -3
  59. package/src/autonomous-runtime.mjs +337 -43
  60. package/src/autonomous-timeout-recovery.mjs +27 -0
  61. package/src/capability-benchmark-runner.mjs +86 -1
  62. package/src/capability-benchmark.mjs +45 -0
  63. package/src/capability-ledger.mjs +158 -0
  64. package/src/coding-mcp-tools.mjs +31 -17
  65. package/src/coding-output-contracts.mjs +48 -10
  66. package/src/coding-plane.mjs +45 -9
  67. package/src/coding-search.mjs +130 -47
  68. package/src/compact-gateway-mcp-tools.mjs +128 -0
  69. package/src/compact-gateway-output-contracts.mjs +16 -0
  70. package/src/deadbyte-cli.mjs +62 -78
  71. package/src/effect-ledger.mjs +47 -0
  72. package/src/machine-fs-smoke-client.mjs +1 -1
  73. package/src/machine-fs.mjs +51 -8
  74. package/src/mcp-server.mjs +32 -9
  75. package/src/native-diff-v2.mjs +337 -0
  76. package/src/native-diff.mjs +50 -2
  77. package/src/observation-lease.mjs +32 -0
  78. package/src/observation-smoke-client.mjs +1 -1
  79. package/src/operator-surface.mjs +7 -1
  80. package/src/process-runtime.mjs +98 -11
  81. package/src/process-smoke-client.mjs +11 -3
  82. package/src/r36-compact-disarmed-probe.mjs +50 -0
  83. package/src/r36-full-surface-probe.mjs +56 -0
  84. package/src/remote-live-log.mjs +135 -0
  85. package/src/remote-mcp-autonomous-smoke-client.mjs +22 -12
  86. package/src/remote-mcp-coding-smoke-client.mjs +1 -1
  87. package/src/remote-mcp-r36-parity-client.mjs +175 -0
  88. package/src/remote-mcp-smoke-client.mjs +112 -56
  89. package/src/runtime-update.mjs +51 -1
  90. package/src/search-cursor.mjs +45 -0
  91. package/src/semantic-code.mjs +72 -5
  92. package/src/semantic-lsp-backend.mjs +39 -3
  93. package/src/version.mjs +1 -1
  94. package/src/worker-scheduler.mjs +53 -0
  95. package/test/autonomous-capability-r36.test.mjs +151 -0
  96. package/test/autonomous-output-contract.test.mjs +1 -1
  97. package/test/autonomous-runtime.test.mjs +2 -2
  98. package/test/capability-benchmark-r36.test.mjs +107 -0
  99. package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
  100. package/test/capability-claims-r36.test.mjs +23 -0
  101. package/test/capability-ledger-r36.test.mjs +102 -0
  102. package/test/coding-catalog-consistency.test.mjs +2 -2
  103. package/test/coding-plane.test.mjs +2 -2
  104. package/test/coding-runtime.test.mjs +16 -0
  105. package/test/coding-search-r36.test.mjs +90 -0
  106. package/test/compact-catalog-r36.test.mjs +37 -0
  107. package/test/compact-gateway-r36.test.mjs +66 -0
  108. package/test/contained.test.mjs +1 -1
  109. package/test/core.test.mjs +1 -1
  110. package/test/deferred-slot-operation.test.mjs +9 -0
  111. package/test/fixtures/lsp-framed-server.mjs +2 -0
  112. package/test/helpers/autonomous-r34-fixture.mjs +5 -3
  113. package/test/multi-file-read.test.mjs +3 -0
  114. package/test/native-diff-topology-r36.test.mjs +105 -0
  115. package/test/package-boundary.test.mjs +6 -0
  116. package/test/process-longrun-r36.test.mjs +82 -0
  117. package/test/process-release-gate.test.mjs +9 -0
  118. package/test/production-docs.test.mjs +5 -5
  119. package/test/prompts-resources.test.mjs +7 -1
  120. package/test/r33-closeout-regression.test.mjs +4 -3
  121. package/test/r33-finalization.test.mjs +14 -9
  122. package/test/r36-finalization.test.mjs +104 -0
  123. package/test/r36-release-identity.test.mjs +45 -0
  124. package/test/r37-agent-fabric.test.mjs +92 -0
  125. package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
  126. package/test/r37-finalization.test.mjs +74 -0
  127. package/test/release-version.test.mjs +42 -33
  128. package/test/remote-live-log-r36.test.mjs +37 -0
  129. package/test/remote-live-log-r37.test.mjs +47 -0
  130. package/test/remote-live-log-recovery-r36.test.mjs +41 -0
  131. package/test/remote-r24-catalog.test.mjs +13 -0
  132. package/test/remote-session-cli.test.mjs +53 -66
  133. package/test/runtime-self-update.test.mjs +58 -1
  134. package/test/semantic-lsp-backend.test.mjs +9 -0
  135. package/test/semantic-r36-rename.test.mjs +82 -0
  136. package/deadbyte-mcp-0.8.9.tgz +0 -0
@@ -9,18 +9,19 @@ import { fileURLToPath } from 'node:url';
9
9
  import { defaultPublicKeyPath } from './trust.mjs';
10
10
  import { verifyAuditChain } from './audit-verify.mjs';
11
11
  import { openRemoteResultJournal, parseRemoteResultCursor } from './remote-result-journal.mjs';
12
+ import { createLiveTimelineTracker, formatCompactAuditEvent, formatRemoteLifecycle, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
12
13
 
13
14
  const REMOTE_COMMANDS=new Set([
14
15
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
15
16
  'armprocess','disarmprocess','processstatus','armhost','disarmhost','hoststatus','hosttest',
16
17
  'armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
17
18
  ]);
18
- const SESSION_START=['start','armprocess','armcoding','armautonomous','armhost'];
19
+ const SESSION_START=['start','armprocess','armcoding','armautonomous'];
19
20
  const SESSION_STATUS=[
20
21
  ['processstatus',/^Process plane\s*:\s*ARMED\b/m],
21
22
  ['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
22
23
  ['autonomousstatus',/^Autonomous\s*:\s*ARMED\b/m],
23
- ['hoststatus',/^Host plane\s*:\s*ARMED\b/m]
24
+ ['hoststatus',/^Host plane\s*:\s*DISARMED\b/m]
24
25
  ];
25
26
  const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park'];
26
27
  const SUPERVISOR_COMMANDS=Object.freeze({
@@ -33,9 +34,8 @@ function printHelp(){
33
34
  process.stdout.write([
34
35
  'DEADBYTE MCP CLI','',
35
36
  ' npx deadbyte-mcp@latest mcp # stdio MCP server for local clients',
36
- ' npx deadbyte-mcp@latest remote # compact foreground device session',
37
- ' npx deadbyte-mcp@latest remote --verbose # detailed audit + bridge logs',
38
- ' npx deadbyte-mcp@latest remote --resume <cursor> # replay missed compact results, then reconnect',
37
+ ' npx deadbyte-mcp@latest remote # unified foreground device session + live timeline',
38
+ ' npx deadbyte-mcp@latest remote --resume <cursor> # replay missed results, then reconnect',
39
39
  ' npx deadbyte-mcp@latest remote status',
40
40
  ' npx deadbyte-mcp@latest remote restart',
41
41
  ' npx deadbyte-mcp@latest supervisor status',
@@ -77,39 +77,15 @@ async function listLogFiles(root){
77
77
  }
78
78
  return files;
79
79
  }
80
- function formatLogLine(file,line,{verbose=false}={}){
80
+ function formatLogLine(file,line,{activityTracker=null}={}){
81
81
  if(!line) return null;
82
82
  if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))){
83
83
  try{
84
84
  const event=JSON.parse(line);
85
- if(!verbose){
86
- if(event.event_type!=='tool.result') return null;
87
- const tool=typeof event.tool==='string'&&event.tool?event.tool:'tool';
88
- return `${event.outcome?.is_error===true?'[ERR]':'[OK]'} ${tool}`;
89
- }
90
- const seq=Number.isInteger(event.seq)?`#${event.seq}`:'#?';
91
- const request=typeof event.request_id==='string'?` ${event.request_id.slice(0,8)}`:'';
92
- const tool=typeof event.tool==='string'?` ${event.tool}`:'';
93
- let marker='';
94
- if(event.event_type==='tool.request') marker='📥 ';
95
- else if(event.event_type==='tool.result'){
96
- if(event.outcome?.is_error===true) marker='📤❌ ';
97
- else if(event.outcome?.is_error===false) marker='📤✅ ';
98
- else marker='📤 ';
99
- }
100
- return `${marker}[AUDIT ${seq}] ${event.event_type||'event'}${tool}${request}`;
101
- }catch{return verbose?`[AUDIT] ${line}`:null;}
85
+ return activityTracker?activityTracker.observe(event):formatCompactAuditEvent(event);
86
+ }catch{return null;}
102
87
  }
103
- if(!verbose) return null;
104
- const label=path.basename(file).replace(/\.log$/i,'').toUpperCase();
105
- let marker='';
106
- if(/\[TOOL IN \]/.test(line)) marker='📥 ';
107
- else if(/\[TOOL OUT\]/.test(line)){
108
- if(/"is_error"\s*:\s*true/.test(line)) marker='📤❌ ';
109
- else if(/"is_error"\s*:\s*false/.test(line)) marker='📤✅ ';
110
- else marker='📤 ';
111
- }
112
- return `${marker}[${label}] ${line}`;
88
+ return formatRuntimeDiagnosticLine(file,line);
113
89
  }
114
90
  function remoteResultJournalPath(root,sessionId){return path.join(root,'runtime','remote-results',`${sessionId}.json`);}
115
91
  function auditTerminalResult(file,line){
@@ -120,7 +96,7 @@ function auditTerminalResult(file,line){
120
96
  return {request_id:event.request_id,tool:event.tool,status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,
121
97
  payload_sha256:createHash('sha256').update(Buffer.from(line,'utf8')).digest('hex')};
122
98
  }
123
- async function replayRemoteResults(root,cursor,{verbose=false}={}){
99
+ async function replayRemoteResults(root,cursor){
124
100
  const parsed=parseRemoteResultCursor(cursor);
125
101
  const file=remoteResultJournalPath(root,parsed.session_id);
126
102
  await requireRegular(file,'remote result journal');
@@ -130,7 +106,7 @@ async function replayRemoteResults(root,cursor,{verbose=false}={}){
130
106
  const page=await journal.replayAfter(next,{limit:200});
131
107
  for(const event of page.events){
132
108
  const marker=event.status==='error'?'[ERR]':'[OK]';
133
- process.stdout.write(`${verbose?'[REPLAY] ': '[REPLAY] '}${marker} ${event.tool} [cursor r1:${event.session_id}:${event.seq}]\n`);
109
+ process.stdout.write(`[REPLAY] ${marker} ${event.tool} [cursor r1:${event.session_id}:${event.seq}]\n`);
134
110
  }
135
111
  next=page.next_cursor;
136
112
  if(!page.has_more) return next;
@@ -147,39 +123,39 @@ async function readAppend(file,offset){
147
123
  try{await handle.read(buffer,0,length,start);}finally{await handle.close();}
148
124
  return {offset:info.size,text:buffer.toString('utf8')};
149
125
  }
150
- async function tailRemoteLogs(root,signal,{verbose=false,journal=null}={}){
126
+ async function snapshotRemoteLogOffsets(root){
151
127
  const offsets=new Map();
152
128
  for(const file of await listLogFiles(root)){
153
129
  try{offsets.set(file,(await stat(file)).size);}catch{}
154
130
  }
155
- process.stdout.write(verbose
156
- ?'📜 [LOG] Live logs active. Press Ctrl+C to disconnect and revoke authority.\n'
157
- :'[LOG] Live tool results. Ctrl+C disconnects and revokes authority.\n');
131
+ return offsets;
132
+ }
133
+ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null}={}){
134
+ process.stdout.write('[LOG] Unified live timeline. Ctrl+C disconnects and revokes authority.\n');
135
+ const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
158
136
  while(!signal.aborted){
159
137
  for(const file of await listLogFiles(root)){
160
- const prior=offsets.has(file)?offsets.get(file):0;
138
+ const prior=activeOffsets.has(file)?activeOffsets.get(file):0;
161
139
  const next=await readAppend(file,prior);
162
- offsets.set(file,next.offset);
140
+ activeOffsets.set(file,next.offset);
163
141
  for(const line of next.text.split(/\r?\n/)){
164
142
  const terminal=auditTerminalResult(file,line);
165
143
  let recorded=null;
166
144
  if(terminal&&journal) recorded=await journal.recordTerminal(terminal);
167
- if(recorded?.duplicate&&!verbose) continue;
168
- const formatted=formatLogLine(file,line,{verbose});
145
+ if(recorded?.duplicate) continue;
146
+ const formatted=formatLogLine(file,line,{activityTracker});
169
147
  if(!formatted) continue;
170
- if(!verbose&&recorded) process.stdout.write(`${formatted} [cursor ${recorded.cursor}]\n`);
148
+ if(recorded) process.stdout.write(`${formatted} [cursor ${recorded.cursor}]\n`);
171
149
  else process.stdout.write(`${formatted}\n`);
172
150
  }
173
151
  }
174
152
  try{await sleep(250,null,{signal});}catch(error){if(error?.name!=='AbortError') throw error;}
175
153
  }
176
154
  }
177
- async function cleanupRemoteSession(ctx,{verbose=false}={}){
155
+ async function cleanupRemoteSession(ctx){
178
156
  for(const command of SESSION_CLEANUP){
179
157
  try{
180
- const result=invokeController(ctx,command,verbose
181
- ?{allowFailure:true}
182
- :{allowFailure:true,capture:true,echoCaptured:false});
158
+ const result=invokeController(ctx,command,{allowFailure:true,capture:true,echoCaptured:false});
183
159
  if(result.status!==0) process.stderr.write(`⚠️ [WARN] cleanup ${command} exit=${result.status}\n`);
184
160
  }
185
161
  catch(error){process.stderr.write(`⚠️ [WARN] cleanup ${command}: ${String(error?.message||error)}\n`);}
@@ -258,62 +234,70 @@ async function releaseRemoteSession(ctx,marker){
258
234
  const same=current?.schema==='deadbyte.remote-session.v2'&&current.owner_pid===marker.owner_pid&&current.session_nonce===marker.session_nonce;
259
235
  if(same) await rm(remoteSessionMarker(ctx.root),{force:true});
260
236
  }
261
- async function runRemoteSession({verbose=false,resumeCursor=null}={}){
237
+ async function runRemoteSession({resumeCursor=null}={}){
262
238
  const ctx=await remoteContext();
263
- if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{verbose});
239
+ if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor);
264
240
  await recoverRemoteSession(ctx);
265
241
  const marker=await claimRemoteSession(ctx);
266
242
  const resultJournal=await openRemoteResultJournal({
267
243
  filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
268
244
  });
245
+ const activityTracker=createLiveTimelineTracker();
269
246
  const aborter=new AbortController();
270
247
  const abort=()=>aborter.abort();
271
248
  const watchStdin=!process.stdin.isTTY;
272
249
  process.once('SIGINT',abort);process.once('SIGTERM',abort);
273
250
  if(watchStdin){process.stdin.once('end',abort);process.stdin.once('close',abort);process.stdin.resume();}
274
251
  let ready=false;
252
+ let logTask=null;
275
253
  try{
276
- if(verbose){
277
- process.stdout.write('DEADBYTE MCP DEVICE SESSION\n');
278
- process.stdout.write('[INFO] Starting local + remote runtime...\n');
279
- for(const command of SESSION_START) invokeController(ctx,command);
280
- process.stdout.write('🛡️ [AUTHORITY] Verifying runtime planes...\n');
281
- }else{
282
- process.stdout.write('DEADBYTE MCP\n');
283
- const started=[];
284
- for(const command of SESSION_START) started.push(invokeController(ctx,command,{capture:true,echoCaptured:false}));
285
- const startText=started[0]?.stdout||'';
286
- const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
287
- process.stdout.write('[OK] ONLINE\n');
288
- if(url) process.stdout.write(`URL: ${url}\n`);
254
+ process.stdout.write('DEADBYTE MCP\n');
255
+ process.stdout.write(`${formatRemoteLifecycle('device_start')}\n`);
256
+ process.stdout.write(`${formatRemoteLifecycle(resumeCursor!==null?'session_restored':'session_new')}\n`);
257
+ const logOffsets=await snapshotRemoteLogOffsets(ctx.root);
258
+ logTask=tailRemoteLogs(ctx.root,aborter.signal,{journal:resultJournal,offsets:logOffsets,activityTracker});
259
+ process.stdout.write('[BOOT] Starting local + remote runtime...\n');
260
+ const started=[];
261
+ for(const command of SESSION_START){
262
+ if(command==='start') process.stdout.write('[BOOT] Starting bridge/runtime...\n');
263
+ else process.stdout.write(`🔐 [AUTH] Arming ${command.slice(3)}...\n`);
264
+ const controllerResult=invokeController(ctx,command,{capture:true,echoCaptured:false});
265
+ started.push(controllerResult);
266
+ if(command==='start'){
267
+ process.stdout.write(`${formatRemoteLifecycle('bridge_connected')}\n`);
268
+ process.stdout.write(`${formatRemoteLifecycle('cloud_connected')}\n`);
269
+ }
289
270
  }
271
+ const startText=started[0]?.stdout||'';
272
+ const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
273
+ process.stdout.write('[OK] ONLINE\n');
274
+ process.stdout.write(`${formatRemoteLifecycle('ready')}\n`);
275
+ if(url) process.stdout.write(`URL: ${url}\n`);
276
+ process.stdout.write('🔐 [AUTH] Verifying process, coding, autonomous; host must stay DISARMED...\n');
290
277
  for(const [command,pattern] of SESSION_STATUS){
291
- const result=invokeController(ctx,command,{capture:true,echoCaptured:verbose});
292
- if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} did not report ARMED`);
278
+ const result=invokeController(ctx,command,{capture:true,echoCaptured:false});
279
+ if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} state mismatch`);
293
280
  }
294
281
  ready=true;
295
- if(verbose) process.stdout.write('[DEVICE] DEADBYTE MCP online; all authority planes ARMED.\n');
296
- else process.stdout.write('[OK] Authority ARMED: process, coding, autonomous, host\n');
297
- await tailRemoteLogs(ctx.root,aborter.signal,{verbose,journal:resultJournal});
282
+ process.stdout.write('🔐 [AUTH] ARMED: process, coding, autonomous | host DISARMED\n');
283
+ await logTask;
298
284
  }finally{
285
+ const wasAborted=aborter.signal.aborted;
286
+ aborter.abort();
287
+ if(logTask) await logTask.catch(()=>{});
288
+ for(const line of activityTracker.reconcileInterrupted('session_end')) process.stdout.write(`${line}\n`);
299
289
  process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
300
290
  if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
301
- if(ready||!aborter.signal.aborted) process.stdout.write(verbose?'[INFO] Closing remote device session and revoking authority...\n':'[INFO] Disconnecting; revoking authority...\n');
302
- await cleanupRemoteSession(ctx,{verbose});
291
+ process.stdout.write(`${formatRemoteLifecycle('cleanup')}\n`);
292
+ if(ready||!wasAborted) process.stdout.write('[INFO] Disconnecting; revoking authority...\n');
293
+ await cleanupRemoteSession(ctx);
303
294
  await releaseRemoteSession(ctx,marker);
304
295
  }
305
296
  }
306
297
  async function runRemote(args){
307
298
  if(args.length===0){await runRemoteSession();return;}
308
- if(args.length===1&&args[0]==='--verbose'){await runRemoteSession({verbose:true});return;}
309
299
  if(args.length===2&&args[0]==='--resume'){await runRemoteSession({resumeCursor:args[1]});return;}
310
- if(args.length===3&&args[0]==='--verbose'&&args[1]==='--resume'){
311
- await runRemoteSession({verbose:true,resumeCursor:args[2]});return;
312
- }
313
- if(args.length===3&&args[0]==='--resume'&&args[2]==='--verbose'){
314
- await runRemoteSession({verbose:true,resumeCursor:args[1]});return;
315
- }
316
- if(args.length>1) throw new Error('remote accepts one controller command, --verbose, or --resume <cursor>');
300
+ if(args.length>1) throw new Error('remote accepts one controller command or --resume <cursor>');
317
301
  const command=args[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
318
302
  const ctx=await remoteContext();
319
303
  const result=invokeController(ctx,command,{allowFailure:true});
@@ -0,0 +1,47 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from './canonical-json.mjs';
3
+
4
+ export const EFFECT_LEDGER_SCHEMA='deadbyte.effect-ledger.v1';
5
+ const HEX64=/^[0-9a-f]{64}$/;
6
+ const ID=/^[A-Za-z0-9._-]{1,128}$/;
7
+ const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
8
+
9
+ function assertHex(name,value){if(!HEX64.test(value??'')) throw new Error(`${name} must be sha256`);}
10
+ function assertId(name,value){if(!ID.test(value??'')) throw new Error(`${name} invalid`);}
11
+
12
+ export function effectIdentity({goalId,planId,stepId,mutationEpoch,action,observationHeadSha256=null}){
13
+ assertHex('goalId',goalId);
14
+ assertHex('planId',planId);
15
+ assertId('stepId',stepId);
16
+ if(!Number.isInteger(mutationEpoch)||mutationEpoch<0) throw new Error('mutationEpoch invalid');
17
+ if(!action||typeof action!=='object'||Array.isArray(action)||typeof action.kind!=='string') throw new Error('action invalid');
18
+ if(observationHeadSha256!==null) assertHex('observationHeadSha256',observationHeadSha256);
19
+ const effectShape={schema:EFFECT_LEDGER_SCHEMA,kind:action.kind,mutation_epoch:mutationEpoch,
20
+ observation_head_sha256:observationHeadSha256,action};
21
+ const fingerprint=sha(effectShape);
22
+ const identity={schema:EFFECT_LEDGER_SCHEMA,goal_id:goalId,plan_id:planId,step_id:stepId,
23
+ mutation_epoch:mutationEpoch,fingerprint};
24
+ return Object.freeze({...identity,effect_id:sha(identity)});
25
+ }
26
+
27
+ export function loopGuardDisposition(events,candidate,{maxRepeats=3,window=24}={}){
28
+ if(!Array.isArray(events)) throw new Error('effect events must be array');
29
+ if(!candidate||!HEX64.test(candidate.fingerprint??'')) throw new Error('effect candidate invalid');
30
+ if(!Number.isInteger(maxRepeats)||maxRepeats<1) throw new Error('maxRepeats invalid');
31
+ if(!Number.isInteger(window)||window<1) throw new Error('window invalid');
32
+ const recent=events.slice(-window);
33
+ const started=recent.filter(event=>event?.type==='effect_started'&&event.payload?.fingerprint===candidate.fingerprint);
34
+ const terminal=new Set(recent.filter(event=>['effect_completed','effect_failed'].includes(event?.type)&&HEX64.test(event.payload?.effect_id??''))
35
+ .map(event=>event.payload.effect_id));
36
+ const repeated=started.filter(event=>terminal.has(event.payload?.effect_id)).length;
37
+ return Object.freeze({allowed:repeated<maxRepeats,reason:repeated<maxRepeats?'allowed':'effect_repeat_limit',
38
+ repeated_count:repeated,max_repeats:maxRepeats,fingerprint:candidate.fingerprint});
39
+ }
40
+
41
+ export function bindEffectCompletion(effect,outcome){
42
+ if(!effect||!HEX64.test(effect.effect_id??'')||!HEX64.test(effect.fingerprint??'')) throw new Error('effect identity invalid');
43
+ if(!outcome||typeof outcome!=='object'||Array.isArray(outcome)) throw new Error('effect outcome invalid');
44
+ const result_sha256=sha(outcome);
45
+ return Object.freeze({schema:'deadbyte.effect-completion.v1',effect_id:effect.effect_id,
46
+ fingerprint:effect.fingerprint,result_sha256});
47
+ }
@@ -60,7 +60,7 @@ const baseTools = ['contained_transform','evidence_verify','file_read','proof_ex
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',
63
- 'coding_symbol_definition','coding_symbol_outline','coding_symbol_references','coding_symbol_replace',
63
+ 'coding_symbol_definition','coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace',
64
64
  'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run',
65
65
  'coding_task_status','coding_task_submit','coding_task_verify','coding_text_replace','coding_tree'
66
66
  ];
@@ -168,9 +168,10 @@ function decodeCursor(cursor) {
168
168
 
169
169
  function encodeCursor(name) { return Buffer.from(name, 'utf8').toString('base64url'); }
170
170
 
171
- async function signReceipt(policy, signingKeyPath, grant, operation, requestSha256, body) {
171
+ async function signReceipt(policy, signingKeyPath, grant, operation, requestSha256, body, requestedReceiptId = null) {
172
172
  await fsMkdir(path.join(policy.evidence_dir, 'machine-fs'), { recursive: true });
173
- const receiptId = randomBytes(12).toString('hex');
173
+ const receiptId = requestedReceiptId ?? randomBytes(12).toString('hex');
174
+ if (!RECEIPT_ID_RE.test(receiptId)) throw new Error('machine fs receipt id must be 24 lowercase hex characters');
174
175
  const core = {
175
176
  schema: MACHINE_FS_RECEIPT_SCHEMA, receipt_id: receiptId,
176
177
  policy_sha256: policy.policy_sha256, grant_instance_nonce: grant.instance_nonce,
@@ -311,7 +312,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
311
312
  };
312
313
  }
313
314
 
314
- async function writePath({ rootId, relativePath, content, encoding = 'utf8', expectedSha256 }) {
315
+ async function writePath({ rootId, relativePath, content, encoding = 'utf8', expectedSha256, receiptId = null }) {
315
316
  const grant = await assertCodingArmed(policy, rootId);
316
317
  const target = await resolveTarget(policy, grant, rootId, relativePath, 'write');
317
318
  const bytes = decodeContent(content, encoding);
@@ -325,7 +326,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
325
326
  const signed = await signReceipt(policy, signingKeyPath, grant, 'machine_fs_write', request, {
326
327
  root_id: rootId, path: target.relative, started_at_utc: started, completed_at_utc: nowUtc(),
327
328
  before_sha256: before.sha256, after_sha256: after.sha256, bytes: after.bytes
328
- });
329
+ }, receiptId);
329
330
  return {
330
331
  status: 'committed', root_id: rootId, path: target.relative,
331
332
  previous_sha256: before.sha256, sha256: after.sha256, bytes: after.bytes,
@@ -368,7 +369,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
368
369
  receipt_id: signed.receipt.receipt_id, receipt_sha256: signed.receipt.receipt_sha256, request_sha256: request
369
370
  };
370
371
  }
371
- async function mkdirPath({ rootId, relativePath }) {
372
+ async function mkdirPath({ rootId, relativePath, receiptId = null }) {
372
373
  const grant = await assertCodingArmed(policy, rootId);
373
374
  const target = await resolveTarget(policy, grant, rootId, relativePath, 'write');
374
375
  try { await lstat(target.lexical); throw new Error('machine fs mkdir precondition mismatch: expected absent'); }
@@ -382,7 +383,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
382
383
  const signed = await signReceipt(policy, signingKeyPath, grant, 'machine_fs_mkdir', request, {
383
384
  root_id: rootId, path: target.relative, started_at_utc: started, completed_at_utc: nowUtc(),
384
385
  before_identity_sha256: null, after_identity_sha256: identity.identity_sha256
385
- });
386
+ }, receiptId);
386
387
  return {
387
388
  status: 'committed', root_id: rootId, path: target.relative, identity_sha256: identity.identity_sha256,
388
389
  receipt_id: signed.receipt.receipt_id, receipt_sha256: signed.receipt.receipt_sha256, request_sha256: request
@@ -392,7 +393,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
392
393
  throw error;
393
394
  }
394
395
  }
395
- async function movePath({ rootId, sourcePath, destinationPath, expectedSourceIdentity }) {
396
+ async function movePath({ rootId, sourcePath, destinationPath, expectedSourceIdentity, receiptId = null }) {
396
397
  if (!SHA256_RE.test(expectedSourceIdentity ?? '')) throw new Error('machine fs expected_source_identity must be lowercase SHA-256');
397
398
  const grant = await assertCodingArmed(policy, rootId);
398
399
  const source = await resolveExisting(policy, grant, rootId, sourcePath, 'write', false);
@@ -415,7 +416,7 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
415
416
  const signed = await signReceipt(policy, signingKeyPath, grant, 'machine_fs_move', request, {
416
417
  root_id: rootId, source_path: source.relative, destination_path: destination.relative,
417
418
  started_at_utc: started, completed_at_utc: nowUtc(), source_identity_sha256: expectedSourceIdentity
418
- });
419
+ }, receiptId);
419
420
  return {
420
421
  status: 'committed', root_id: rootId, source_path: source.relative, destination_path: destination.relative,
421
422
  identity_sha256: expectedSourceIdentity, receipt_id: signed.receipt.receipt_id,
@@ -460,6 +461,46 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
460
461
  }
461
462
  }
462
463
 
464
+ async function statePath({ rootId, relativePath }) {
465
+ try {
466
+ const value = await statPath({ rootId, relativePath, hash: true });
467
+ return { exists: true, root_id: rootId, path: value.path, kind: value.kind, bytes: value.bytes,
468
+ sha256: value.sha256, identity_sha256: value.identity_sha256 };
469
+ } catch (error) {
470
+ if (error?.code === 'ENOENT') return { exists: false, root_id: rootId, path: relativePath, kind: null, bytes: 0, sha256: null, identity_sha256: null };
471
+ throw error;
472
+ }
473
+ }
474
+
475
+ async function removeEmptyDir({ rootId, relativePath, expectedSourceIdentity, receiptId = null }) {
476
+ if (!SHA256_RE.test(expectedSourceIdentity ?? '')) throw new Error('machine fs expected_source_identity must be lowercase SHA-256');
477
+ const grant = await assertCodingArmed(policy, rootId);
478
+ const source = await resolveExisting(policy, grant, rootId, relativePath, 'write', false);
479
+ const before = await objectIdentity(source.resolved);
480
+ if (before.kind !== 'directory' || before.entries.length !== 0) throw new Error('machine fs rmdir requires an empty directory');
481
+ if (before.identity_sha256 !== expectedSourceIdentity) throw new Error(`machine fs source identity mismatch: expected ${expectedSourceIdentity}, found ${before.identity_sha256}`);
482
+ const request = requestSha('machine_fs_rmdir_empty', { root_id: rootId, path: source.relative, expected_source_identity: expectedSourceIdentity });
483
+ const started = nowUtc();
484
+ const race = await objectIdentity(source.resolved);
485
+ if (race.kind !== 'directory' || race.entries.length !== 0 || race.identity_sha256 !== expectedSourceIdentity) {
486
+ throw new Error('machine fs rmdir precondition changed before commit');
487
+ }
488
+ await rmdir(source.lexical);
489
+ try {
490
+ try { await lstat(source.lexical); throw new Error('machine fs rmdir post-commit path still exists'); }
491
+ catch (error) { if (error?.code !== 'ENOENT') throw error; }
492
+ const signed = await signReceipt(policy, signingKeyPath, grant, 'machine_fs_rmdir_empty', request, {
493
+ root_id: rootId, path: source.relative, started_at_utc: started, completed_at_utc: nowUtc(),
494
+ before_identity_sha256: expectedSourceIdentity, after_identity_sha256: null
495
+ }, receiptId);
496
+ return { status: 'committed', root_id: rootId, path: source.relative, identity_sha256: expectedSourceIdentity,
497
+ receipt_id: signed.receipt.receipt_id, receipt_sha256: signed.receipt.receipt_sha256, request_sha256: request };
498
+ } catch (error) {
499
+ await fsMkdir(source.lexical, { recursive: false }).catch(() => {});
500
+ throw error;
501
+ }
502
+ }
503
+
463
504
  return Object.freeze({
464
505
  policy,
465
506
  stat: statPath,
@@ -470,6 +511,8 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath }
470
511
  move: movePath,
471
512
  mkdir: mkdirPath,
472
513
  delete: deletePath,
514
+ state: statePath,
515
+ removeEmptyDir,
473
516
  verifyReceipt: receiptId => verifyMachineFsReceiptFile({ policy, verifyKeyPath, receiptId })
474
517
  });
475
518
  }
@@ -39,6 +39,8 @@ import { registerAutonomousTools } from './autonomous-mcp-tools.mjs';
39
39
  import { createToolAudit, installToolAudit } from './tool-audit.mjs';
40
40
  import { createAuditContextProvider } from './audit-context.mjs';
41
41
  import { registerOperatorSurface } from './operator-surface.mjs';
42
+ import { capabilityProfileFromEnv, COMPACT_SURFACE_TOOL_LIMIT, R36_CAPABILITY_LEDGER } from './capability-ledger.mjs';
43
+ import { registerCompactGatewayTools } from './compact-gateway-mcp-tools.mjs';
42
44
  import {
43
45
  containedTransformOutputSchema,
44
46
  evidenceVerifyOutputSchema,
@@ -58,6 +60,7 @@ const verifyKeyPath = process.env.DEADBYTE_VERIFY_KEY ?? defaultPublicKeyPath();
58
60
  const auditRuntimeMode = process.env.DEADBYTE_AUDIT_RUNTIME ?? '0';
59
61
  if (!['0','1'].includes(auditRuntimeMode)) throw new Error('DEADBYTE_AUDIT_RUNTIME must be 0 or 1');
60
62
  const runtimeStateRoot = path.resolve(process.env.DEADBYTE_STATE_ROOT || path.join(os.homedir(),'.deadbyte-mcp'));
63
+ const surfaceProfile = capabilityProfileFromEnv(process.env);
61
64
  const localPrivilegedPolicyPath = path.join(packageRoot, 'controller', 'deadbyte-host-policy.json');
62
65
  let privilegedPolicyPath = process.env.DEADBYTE_PRIVILEGED_POLICY ?? '';
63
66
  if (!privilegedPolicyPath) {
@@ -106,7 +109,7 @@ const semanticLspBinding = semanticConfigBindingFromEnv(process.env);
106
109
  const semanticLsp = codingRuntime && semanticLspBinding
107
110
  ? await loadSemanticLspBackends({ ...semanticLspBinding, codingRuntime })
108
111
  : { backends: [], required_ids: [] };
109
- const semanticRuntime = codingRuntime ? createSemanticCodeRuntime({ codingRuntime, backends: semanticLsp.backends }) : null;
112
+ let semanticRuntime = null;
110
113
  const machineFsRuntimeMode = process.env.DEADBYTE_MACHINE_FS_RUNTIME ?? '0';
111
114
  if (!['0','1'].includes(machineFsRuntimeMode)) throw new Error('DEADBYTE_MACHINE_FS_RUNTIME must be 0 or 1');
112
115
  if (machineFsRuntimeMode === '1' && !codingPolicy) throw new Error('DEADBYTE_MACHINE_FS_RUNTIME=1 requires DEADBYTE_CODING_RUNTIME=1');
@@ -137,9 +140,10 @@ if (autonomousRuntimeMode === '1' && !autonomousPolicyPath) {
137
140
  }
138
141
  if (autonomousRuntimeMode === '1' && !autonomousPolicyPath) throw new Error('DEADBYTE_AUTONOMOUS_RUNTIME=1 requires an autonomous policy');
139
142
  const autonomousPolicy = autonomousRuntimeMode === '1' ? await loadAutonomousPolicy(autonomousPolicyPath,codingPolicy) : null;
140
- const nativeDiffRuntime = autonomousPolicy ? createNativeDiffRuntime({ policy:autonomousPolicy,codingRuntime,signingKeyPath,verifyKeyPath }) : null;
143
+ const nativeDiffRuntime = autonomousPolicy ? createNativeDiffRuntime({ policy:autonomousPolicy,codingRuntime,machineFsRuntime,signingKeyPath,verifyKeyPath }) : null;
144
+ semanticRuntime = codingRuntime ? createSemanticCodeRuntime({ codingRuntime, backends: semanticLsp.backends, nativeDiffRuntime }) : null;
141
145
  const autonomousRuntime = autonomousPolicy ? createAutonomousRuntime({ policy:autonomousPolicy,codingRuntime,nativeDiffRuntime,
142
- machineFsRuntime,observationRuntime,processRuntime,signingKeyPath,verifyKeyPath }) : null;
146
+ machineFsRuntime,observationRuntime,processRuntime,semanticRuntime,signingKeyPath,verifyKeyPath }) : null;
143
147
  const autonomousSupervisor = autonomousRuntime && autonomousPolicy.planner.backend === 'openai_compatible' &&
144
148
  autonomousSupervisorMode === 'in_process'
145
149
  ? startAutonomousSupervisor({ runtime: autonomousRuntime, policy: autonomousPolicy })
@@ -169,11 +173,22 @@ function textResult(schema, object, isError = false) {
169
173
  return structuredToolResult(schema, object, isError);
170
174
  }
171
175
 
176
+ const COMPACT_CODING_DIRECT_TOOLS=new Set([
177
+ 'coding_session_status','coding_tree','coding_symbol_replace','coding_file_read','coding_file_write',
178
+ 'coding_text_replace','coding_command_run','coding_benchmark_run','coding_receipt_verify',
179
+ 'coding_task_submit','coding_task_run','coding_task_status','coding_task_events','coding_task_cancel',
180
+ 'coding_task_approve','coding_task_verify'
181
+ ]);
182
+ function filteredToolServer(server,allowed){
183
+ return {registerTool(name,...args){if(allowed.has(name)) return server.registerTool(name,...args);}};
184
+ }
185
+
172
186
  serveStdio(() => {
173
187
  const server = new McpServer({ name: 'deadbyte-mcp', version: DEADBYTE_VERSION });
174
- registerOperatorSurface(server,{packageRoot});
188
+ registerOperatorSurface(server,{packageRoot,surfaceProfile,compactToolLimit:COMPACT_SURFACE_TOOL_LIMIT});
175
189
  if (toolAudit) installToolAudit(server,toolAudit);
176
190
 
191
+ if (surfaceProfile === 'full') {
177
192
  server.registerTool(
178
193
  'workspace_open',
179
194
  {
@@ -289,6 +304,8 @@ serveStdio(() => {
289
304
  }
290
305
  );
291
306
 
307
+ }
308
+
292
309
  server.registerTool(
293
310
  'evidence_verify',
294
311
  {
@@ -319,23 +336,24 @@ serveStdio(() => {
319
336
  });
320
337
  }
321
338
 
322
- if (agentRuntime) {
339
+ if (agentRuntime && surfaceProfile === 'full') {
323
340
  registerAgentTools(server, { runtime: agentRuntime, workspacePathFromId });
324
341
  }
325
342
 
326
- if (processRuntime) {
343
+ if (processRuntime && surfaceProfile === 'full') {
327
344
  registerProcessTools(server, { runtime: processRuntime });
328
345
  }
329
346
 
330
347
  if (codingRuntime && codingTaskRuntime && semanticRuntime) {
331
- registerCodingTools(server, { codingRuntime, taskRuntime: codingTaskRuntime, semanticRuntime });
348
+ const codingServer=surfaceProfile==='full'?server:filteredToolServer(server,COMPACT_CODING_DIRECT_TOOLS);
349
+ registerCodingTools(codingServer, { codingRuntime, taskRuntime: codingTaskRuntime, semanticRuntime });
332
350
  }
333
351
 
334
- if (machineFsRuntime) {
352
+ if (machineFsRuntime && surfaceProfile === 'full') {
335
353
  registerMachineFsTools(server, { runtime: machineFsRuntime });
336
354
  }
337
355
 
338
- if (observationRuntime) {
356
+ if (observationRuntime && surfaceProfile === 'full') {
339
357
  registerObservationTools(server, { runtime: observationRuntime });
340
358
  }
341
359
 
@@ -343,6 +361,11 @@ serveStdio(() => {
343
361
  registerAutonomousTools(server, { autonomousRuntime, nativeDiffRuntime });
344
362
  }
345
363
 
364
+ registerCompactGatewayTools(server,{
365
+ profile:surfaceProfile,ledger:R36_CAPABILITY_LEDGER,codingRuntime,semanticRuntime,machineFsRuntime,
366
+ processRuntime,observationRuntime,machineRuntime
367
+ });
368
+
346
369
  if (hostPolicy) {
347
370
  server.registerTool(
348
371
  'host_file_read',