deadbyte-mcp 0.11.0 → 0.11.1

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 (65) hide show
  1. package/MANIFEST.SHA256 +64 -56
  2. package/README.txt +7 -7
  3. package/bin/WORKER-REGISTRY.txt +3 -3
  4. package/bin/appcontainer-stage.exe +0 -0
  5. package/bin/appcontainer-stage.obj +0 -0
  6. package/bin/bootstrap-advapi32.exe +0 -0
  7. package/bin/bootstrap-advapi32.obj +0 -0
  8. package/bin/bootstrap-exitcode.exe +0 -0
  9. package/bin/bootstrap-exitcode.obj +0 -0
  10. package/bin/bootstrap-kernel32.exe +0 -0
  11. package/bin/bootstrap-kernel32.obj +0 -0
  12. package/bin/child-control-probe.exe +0 -0
  13. package/bin/child-control-probe.obj +0 -0
  14. package/bin/child-control-stage.exe +0 -0
  15. package/bin/child-control-stage.obj +0 -0
  16. package/bin/contained-reverse-worker.exe +0 -0
  17. package/bin/contained-reverse-worker.obj +0 -0
  18. package/bin/contained-transform-worker.exe +0 -0
  19. package/bin/contained-transform-worker.obj +0 -0
  20. package/bin/containment-probe.exe +0 -0
  21. package/bin/containment-probe.obj +0 -0
  22. package/bin/deadbyte-contain.exe +0 -0
  23. package/bin/deadbyte-contain.obj +0 -0
  24. package/bin/deadbyte-exec.exe +0 -0
  25. package/bin/deadbyte-exec.obj +0 -0
  26. package/bin/deadbyte-process-host.exe +0 -0
  27. package/bin/deadbyte-process-host.obj +0 -0
  28. package/bin/deadbyte-tunnel-host.exe +0 -0
  29. package/bin/deadbyte-tunnel-host.obj +0 -0
  30. package/controller/README.TXT +1 -1
  31. package/controller/deadbyte-controller.ps1 +1 -1
  32. package/controller/deadbyte-process-policy.json +1 -1
  33. package/docs/ARCHITECTURE.md +1 -1
  34. package/package.json +1 -1
  35. package/scripts/build-containment.ps1 +15 -15
  36. package/scripts/deferred-slot-operation-r37-0110.mjs +124 -0
  37. package/scripts/deferred-slot-operation.mjs +1 -1
  38. package/scripts/final-closure-r37-0110.mjs +113 -0
  39. package/scripts/final-closure-verify-r37-0110.mjs +208 -0
  40. package/scripts/final-closure-verify.mjs +16 -16
  41. package/scripts/final-closure.mjs +8 -8
  42. package/scripts/gate-windows-r37-0110.ps1 +42 -0
  43. package/scripts/gate-windows.ps1 +3 -3
  44. package/scripts/release-parity-r37-0110.mjs +200 -0
  45. package/scripts/release-parity-tests.ps1 +1 -1
  46. package/scripts/release-parity.mjs +14 -14
  47. package/scripts/windows-gate-evidence-r37-0110.mjs +52 -0
  48. package/scripts/windows-gate-evidence.mjs +4 -4
  49. package/src/deadbyte-cli.mjs +58 -23
  50. package/src/remote-live-log.mjs +23 -1
  51. package/src/remote-result-journal.mjs +23 -4
  52. package/src/version.mjs +1 -1
  53. package/test/cli-entrypoint.test.mjs +9 -0
  54. package/test/containment.test.mjs +18 -0
  55. package/test/core.test.mjs +1 -1
  56. package/test/production-docs.test.mjs +3 -3
  57. package/test/r33-closeout-regression.test.mjs +2 -2
  58. package/test/r33-finalization.test.mjs +3 -3
  59. package/test/r36-release-identity.test.mjs +4 -4
  60. package/test/r37-0110-history.test.mjs +20 -0
  61. package/test/r37-finalization.test.mjs +14 -14
  62. package/test/r37-remote-tool-call-timeline.test.mjs +94 -0
  63. package/test/release-version.test.mjs +22 -29
  64. package/test/remote-live-log-r37.test.mjs +5 -2
  65. package/test/remote-session-cli.test.mjs +66 -25
@@ -0,0 +1,200 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
3
+ import { loadPowerShellDataFile } from '../src/runtime-update.mjs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { probeR36FullSurface } from '../src/r36-full-surface-probe.mjs';
9
+ import { runR36CompactParity } from '../src/remote-mcp-r36-parity-client.mjs';
10
+ import { probeR36CompactDisarmed } from '../src/r36-compact-disarmed-probe.mjs';
11
+
12
+ const here=path.dirname(fileURLToPath(import.meta.url));
13
+ const root=path.resolve(here,'..');
14
+ const stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
15
+ const deferred=path.join(root,'scripts','deferred-slot-operation.mjs');
16
+ const ps='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
17
+ const launcher=path.join(stateRoot,'bin','DEADBYTE-MCP.ps1');
18
+ const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
19
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
20
+
21
+ export const R36_PREDECESSOR_RELEASE_ID='v0.10.0-8d1e5f09745e7647';
22
+ export const R36_PREDECESSOR_MANIFEST='8d1e5f09745e764796b60e0a8a0168be7334ed4bf834fad5974a80eba295b4e8';
23
+
24
+ function assert(condition,message){if(!condition) throw new Error(message);}
25
+ function parseFrozenManifest(text){
26
+ const entries=[];const seen=new Set();
27
+ for(const [index,line] of text.split(/\r?\n/).entries()){
28
+ if(!line) continue;
29
+ const match=/^([0-9a-f]{64}) ([^\0]+)$/.exec(line);
30
+ assert(match,`frozen manifest line ${index+1} malformed`);
31
+ const rel=match[2];
32
+ assert(!path.posix.isAbsolute(rel)&&!rel.split('/').includes('..')&&!seen.has(rel),`unsafe/duplicate frozen manifest path: ${rel}`);
33
+ seen.add(rel);entries.push({sha256:match[1],path:rel});
34
+ }
35
+ return entries;
36
+ }
37
+ // Restore the mutable development stage from the immutable active release after live parity work may drift it.
38
+ export async function restoreFrozenStage({developmentRoot,releaseRoot,expectedManifestSha256}){
39
+ const dev=path.resolve(developmentRoot);const release=path.resolve(releaseRoot);
40
+ const manifestBytes=await readFile(path.join(release,'MANIFEST.SHA256'));
41
+ const manifestSha256=sha256(manifestBytes);
42
+ assert(manifestSha256===expectedManifestSha256,`immutable release manifest drift: ${manifestSha256}`);
43
+ const entries=parseFrozenManifest(manifestBytes.toString('utf8'));
44
+ for(const entry of entries){
45
+ const source=path.join(release,...entry.path.split('/'));const bytes=await readFile(source);
46
+ assert(sha256(bytes)===entry.sha256,`immutable release file drift: ${entry.path}`);
47
+ const destination=path.join(dev,...entry.path.split('/'));await mkdir(path.dirname(destination),{recursive:true});
48
+ await writeFile(destination,bytes);assert(sha256(await readFile(destination))===entry.sha256,`development restore verification failed: ${entry.path}`);
49
+ }
50
+ await writeFile(path.join(dev,'MANIFEST.SHA256'),manifestBytes);
51
+ assert(sha256(await readFile(path.join(dev,'MANIFEST.SHA256')))===expectedManifestSha256,'development manifest restore verification failed');
52
+ return {status:'restored',file_count:entries.length,manifest_sha256:manifestSha256};
53
+ }
54
+ function parseJsonOutput(text,label){
55
+ const trimmed=String(text).trim();const starts=[...trimmed.matchAll(/\{/g)].map(match=>match.index);
56
+ for(const start of starts){try{return JSON.parse(trimmed.slice(start));}catch{}}
57
+ throw new Error(`${label} did not emit JSON`);
58
+ }
59
+ function run(command,args,{timeoutMs=900000,env=process.env,cwd=root}={}){
60
+ return new Promise((resolve,reject)=>{
61
+ const child=spawn(command,args,{cwd,shell:false,windowsHide:true,env,stdio:['ignore','pipe','pipe']});
62
+ const out=[],err=[];const timer=setTimeout(()=>{child.kill();reject(new Error(`command timeout: ${path.basename(command)}`));},timeoutMs);
63
+ child.stdout.on('data',chunk=>out.push(Buffer.from(chunk)));child.stderr.on('data',chunk=>err.push(Buffer.from(chunk)));
64
+ child.on('error',error=>{clearTimeout(timer);reject(error);});child.on('exit',code=>{
65
+ clearTimeout(timer);const stdout=Buffer.concat(out).toString('utf8');const stderr=Buffer.concat(err).toString('utf8');
66
+ if(code!==0) reject(new Error(`command failed exit=${code}: ${stderr||stdout}`));else resolve({stdout,stderr,exitCode:code});
67
+ });
68
+ });
69
+ }
70
+ async function publicUrl(){
71
+ const value=(await readFile(path.join(stateRoot,'bridge','mcp-url.txt'),'utf8')).trim();const url=new URL(value);
72
+ if(url.protocol!=='https:') throw new Error('public MCP URL must be HTTPS');return value;
73
+ }
74
+ async function pointer(){return JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));}
75
+ async function disarmAll(){
76
+ for(const command of ['disarmautonomous','disarmcoding','disarmprocess','disarmhost']){
77
+ await run(ps,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{timeoutMs:60000});
78
+ }
79
+ }
80
+ async function waitDeferred(statePath,timeoutMs=180000){
81
+ const deadline=Date.now()+timeoutMs;
82
+ while(Date.now()<deadline){const state=JSON.parse(await readFile(statePath,'utf8'));if(state.state==='completed') return state;if(state.state==='failed') throw new Error(`deferred ${state.mode} failed: ${state.error}`);await sleep(500);}
83
+ throw new Error(`deferred operation timeout: ${statePath}`);
84
+ }
85
+ async function scheduleDeferred(mode,value,{bridgeRoot=null,bridgeScriptSha256=null}={}){
86
+ if((bridgeRoot===null)!==(bridgeScriptSha256===null)) throw new Error('deferred bridge override requires exact root + SHA pair');
87
+ const args=[deferred,mode,value];
88
+ if(bridgeRoot!==null) args.push('--bridge-root',bridgeRoot,'--bridge-script-sha256',bridgeScriptSha256);
89
+ const scheduled=parseJsonOutput((await run(process.execPath,args,{timeoutMs:30000})).stdout,`deferred ${mode}`);
90
+ assert(scheduled.status==='scheduled',`deferred ${mode} was not scheduled`);
91
+ assert(scheduled.execution_provider==='deadbyte_mcp'&&scheduled.rdc_operation_count===0,'deferred operation provider mismatch');
92
+ return {scheduled,completed:await waitDeferred(scheduled.state_path)};
93
+ }
94
+ async function publicR36Disarmed(url){
95
+ const result=await probeR36CompactDisarmed(url);
96
+ assert(result.status==='passed'&&result.tool_count<=42&&result.capability_closure===true,'R36 predecessor public compact catalog mismatch');
97
+ assert(result.release_id===R36_PREDECESSOR_RELEASE_ID&&result.release_manifest_sha256===R36_PREDECESSOR_MANIFEST,'R36 predecessor machine binding mismatch');
98
+ assert(result.coding_armed===false&&result.autonomous_armed===false,'R36 predecessor authority remained armed');
99
+ return result;
100
+ }
101
+ async function mutableCore(pointerValue){
102
+ const policy=JSON.parse(await readFile(path.join(pointerValue.generation_root,'coding-policy.json'),'utf8'));
103
+ const raw=policy?.roots?.core?.path??policy?.roots?.core?.real_path;
104
+ assert(typeof raw==='string'&&raw,'R37 generation missing coding core root');
105
+ const core=path.resolve(raw);const release=path.resolve(pointerValue.release_root);
106
+ assert(core.toLowerCase()!==release.toLowerCase(),'R37 coding core must not point at immutable active release slot');
107
+ return core;
108
+ }
109
+
110
+ export function validateParityEvidence(value){
111
+ assert(value?.schema==='deadbyte.r37-parity.v1','R37 parity schema mismatch');
112
+ assert(value.status==='passed','R37 parity status is not passed');
113
+ assert(value.rdc_operation_count===0&&value.execution_provider==='deadbyte_mcp','R37 parity used non-DEADBYTE execution');
114
+ assert(value.candidate?.version==='0.11.0'&&/^[0-9a-f]{64}$/.test(value.candidate?.manifest_sha256??''),'R37 candidate identity missing');
115
+ assert(value.local_full?.status==='passed'&&value.local_full?.tool_count===76&&value.local_full?.strict_output_schemas===true,'R37 local full 76-tool proof missing');
116
+ assert(value.public_compact?.status==='passed'&&value.public_compact?.tool_count<=42&&value.public_compact?.capability_closure===true&&value.public_compact?.gateway_count===5,'R37 public compact closure missing');
117
+ assert(value.autonomous?.status==='passed'&&value.autonomous?.signed_release_verified===true,'R37 autonomous live proof missing');
118
+ assert(value.before_rollback_disarmed?.status==='passed'&&value.before_rollback_disarmed?.capability_closure===true&&value.before_rollback_disarmed?.coding_armed===false&&value.before_rollback_disarmed?.autonomous_armed===false,'R37 pre-rollback disarm proof missing');
119
+ assert(value.rollback?.state==='completed'&&value.restore?.state==='completed','R37 rollback/restore not completed');
120
+ assert(value.stage_restore?.status==='restored'&&value.stage_restore?.manifest_sha256===value.candidate.manifest_sha256,'R37 stage restore proof missing');
121
+ assert(value.predecessor?.release_id===R36_PREDECESSOR_RELEASE_ID&&value.predecessor?.manifest_sha256===R36_PREDECESSOR_MANIFEST&&value.predecessor?.version==='0.10.0','R36 exact predecessor identity mismatch');
122
+ assert(value.predecessor_disarmed?.status==='passed'&&value.predecessor_disarmed?.tool_count<=42&&value.predecessor_disarmed?.capability_closure===true,'R36 predecessor public proof missing');
123
+ assert(value.successor?.version==='0.11.0'&&value.successor?.manifest_sha256===value.candidate.manifest_sha256,'R37 exact successor restore identity mismatch');
124
+ assert(value.successor_disarmed?.status==='passed'&&value.successor_disarmed?.capability_closure===true&&value.successor_disarmed?.coding_armed===false&&value.successor_disarmed?.autonomous_armed===false,'R37 successor disarmed proof missing');
125
+ assert(value.immutable_slot?.protected===true&&value.immutable_slot?.development_root!==value.immutable_slot?.release_root,'R37 immutable-slot separation missing');
126
+ for(const key of ['tree_read','precondition_mutation','search','owned_process','observation','coding_loop','autonomy','interrupted_recovery','evidence_verification','compact_closure','full_surface']) assert(value.requirements?.[key]===true,`R37 parity requirement not proven: ${key}`);
127
+ return true;
128
+ }
129
+
130
+ export async function runReleaseParity(activationReceiptPath){
131
+ const active=await pointer();const candidateManifest=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
132
+ assert(active.version==='0.11.0','R37 parity requires active 0.11.0 candidate');
133
+ assert(active.manifest_sha256===candidateManifest,'active release is not canonical R37 candidate');
134
+ const activeConfig=await loadPowerShellDataFile(active.config_path);
135
+ const successorBridgeRootRaw=activeConfig?.Bridge?.WorkingDirectory;
136
+ const successorBridgeConfiguredSha=activeConfig?.Bridge?.ScriptSha256;
137
+ assert(typeof successorBridgeRootRaw==='string'&&path.isAbsolute(successorBridgeRootRaw),'R37 active bridge root missing');
138
+ assert(typeof successorBridgeConfiguredSha==='string'&&/^[0-9a-f]{64}$/.test(successorBridgeConfiguredSha),'R37 active bridge SHA missing');
139
+ const successorBridgeRoot=await realpath(path.resolve(successorBridgeRootRaw));
140
+ const successorBridgeActualSha=sha256(await readFile(path.join(successorBridgeRoot,'src','bridge.mjs')));
141
+ assert(successorBridgeActualSha===successorBridgeConfiguredSha,'R37 active bridge bytes do not match generation pin');
142
+ const successorBridge={root:successorBridgeRoot,sha256:successorBridgeActualSha};
143
+ const developmentRoot=await mutableCore(active);
144
+ const localFull=await probeR36FullSurface({packageRoot:active.release_root,generationRoot:active.generation_root,stateRoot});
145
+ const url=await publicUrl();
146
+ let publicCompact,autonomous;
147
+ try{
148
+ publicCompact=await runR36CompactParity(url);
149
+ autonomous=parseJsonOutput((await run(process.execPath,[path.join(root,'src','remote-mcp-autonomous-smoke-client.mjs'),url],{timeoutMs:900000})).stdout,'R37 autonomous compact parity');
150
+ assert(autonomous.status==='passed'&&autonomous.signed_release_verified===true,'R37 autonomous compact parity failed');
151
+ }catch(error){await disarmAll().catch(()=>{});throw error;}
152
+
153
+ await disarmAll();
154
+ const beforeRollback=await probeR36CompactDisarmed(url);
155
+ const rollbackPair=await scheduleDeferred('rollback',path.resolve(activationReceiptPath));
156
+ const predecessor=await pointer();
157
+ assert(predecessor.release_id===R36_PREDECESSOR_RELEASE_ID&&predecessor.manifest_sha256===R36_PREDECESSOR_MANIFEST&&predecessor.version==='0.10.0','rollback did not reach exact sealed R36 predecessor');
158
+ const predecessorDisarmed=await publicR36Disarmed(url);
159
+ const stageRestore=await restoreFrozenStage({developmentRoot,releaseRoot:active.release_root,expectedManifestSha256:candidateManifest});
160
+ const restorePair=await scheduleDeferred('activate',candidateManifest,{bridgeRoot:successorBridge.root,bridgeScriptSha256:successorBridge.sha256});
161
+ const successor=await pointer();
162
+ assert(successor.version==='0.11.0'&&successor.manifest_sha256===candidateManifest,'restore did not return exact R37 candidate');
163
+ const successorDisarmed=await probeR36CompactDisarmed(url);
164
+ await disarmAll();
165
+
166
+ const evidence={
167
+ schema:'deadbyte.r37-parity.v1',status:'passed',completed_at_utc:new Date().toISOString(),execution_provider:'deadbyte_mcp',rdc_operation_count:0,
168
+ candidate:{release_id:active.release_id,version:active.version,manifest_sha256:candidateManifest},
169
+ immutable_slot:{protected:true,release_root:path.resolve(active.release_root),development_root:developmentRoot},
170
+ local_full:localFull,public_compact:publicCompact,autonomous,before_rollback_disarmed:beforeRollback,
171
+ rollback:{...rollbackPair.completed,scheduled_operation_id:rollbackPair.scheduled.operation_id},
172
+ predecessor:{release_id:predecessor.release_id,version:predecessor.version,manifest_sha256:predecessor.manifest_sha256},predecessor_disarmed:predecessorDisarmed,
173
+ stage_restore:stageRestore,
174
+ restore:{...restorePair.completed,scheduled_operation_id:restorePair.scheduled.operation_id},
175
+ successor:{release_id:successor.release_id,version:successor.version,manifest_sha256:successor.manifest_sha256},successor_disarmed:successorDisarmed,
176
+ requirements:{
177
+ tree_read:publicCompact.tree_entries>=0&&publicCompact.coding_stat_verified===true,
178
+ precondition_mutation:publicCompact.machine_fs_receipts_verified>=3,
179
+ search:publicCompact.coding_search_result_count>=1&&publicCompact.observation_search_result_count>=1,
180
+ owned_process:typeof publicCompact.process_session_id==='string'&&publicCompact.process_output_bytes>0,
181
+ observation:publicCompact.observation_receipts_verified>=2,
182
+ coding_loop:publicCompact.coding_receipts_verified===2,
183
+ autonomy:autonomous.signed_release_verified===true&&autonomous.observed_failure_before_replan===true,
184
+ interrupted_recovery:rollbackPair.completed.state==='completed'&&restorePair.completed.state==='completed',
185
+ evidence_verification:publicCompact.machine_fs_receipts_verified>=3&&autonomous.signed_release_verified===true,
186
+ compact_closure:publicCompact.capability_closure===true&&successorDisarmed.capability_closure===true,
187
+ full_surface:localFull.tool_count===76&&localFull.strict_output_schemas===true
188
+ }
189
+ };
190
+ validateParityEvidence(evidence);
191
+ const evidenceDir=path.join(stateRoot,'evidence','release');await mkdir(evidenceDir,{recursive:true});
192
+ const evidencePath=path.join(evidenceDir,`r37-parity-${candidateManifest.slice(0,16)}.json`);
193
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');await writeFile(evidencePath,bytes);
194
+ return {evidence,evidence_path:evidencePath,evidence_sha256:sha256(bytes)};
195
+ }
196
+
197
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
198
+ const receipt=process.argv[2];if(!receipt) throw new Error('usage: node scripts/release-parity.mjs <activation-receipt.json>');
199
+ runReleaseParity(receipt).then(result=>console.log(JSON.stringify({status:'passed',...result},null,2))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
200
+ }
@@ -3,7 +3,7 @@ Set-StrictMode -Version 2.0
3
3
  $root = Split-Path -Parent $PSScriptRoot
4
4
  Push-Location $root
5
5
  try {
6
- & node.exe --test test\r37-finalization.test.mjs test\release-version.test.mjs test\r37-agent-fabric.test.mjs test\remote-live-log-r37.test.mjs test\remote-session-cli.test.mjs test\r36-finalization.test.mjs test\r36-release-identity.test.mjs test\deferred-slot-operation.test.mjs
6
+ & node.exe --test test\r37-finalization.test.mjs test\release-version.test.mjs test\r37-0110-history.test.mjs test\r37-agent-fabric.test.mjs test\remote-live-log-r37.test.mjs test\remote-session-cli.test.mjs test\r36-finalization.test.mjs test\r36-release-identity.test.mjs test\deferred-slot-operation.test.mjs
7
7
  if ($LASTEXITCODE -ne 0) { throw "release parity tests failed with exit code $LASTEXITCODE" }
8
8
  Write-Output 'R37 RELEASE PARITY TEST PASS'
9
9
  } finally { Pop-Location }
@@ -18,8 +18,8 @@ const launcher=path.join(stateRoot,'bin','DEADBYTE-MCP.ps1');
18
18
  const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
19
19
  const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
20
20
 
21
- export const R36_PREDECESSOR_RELEASE_ID='v0.10.0-8d1e5f09745e7647';
22
- export const R36_PREDECESSOR_MANIFEST='8d1e5f09745e764796b60e0a8a0168be7334ed4bf834fad5974a80eba295b4e8';
21
+ export const R37_PREDECESSOR_RELEASE_ID='v0.11.0-08c2a0008b1f84c9';
22
+ export const R37_PREDECESSOR_MANIFEST='08c2a0008b1f84c90f7ce7faa1e3decbd03f33612bc0fdc7d1ba591b548798e8';
23
23
 
24
24
  function assert(condition,message){if(!condition) throw new Error(message);}
25
25
  function parseFrozenManifest(text){
@@ -91,11 +91,11 @@ async function scheduleDeferred(mode,value,{bridgeRoot=null,bridgeScriptSha256=n
91
91
  assert(scheduled.execution_provider==='deadbyte_mcp'&&scheduled.rdc_operation_count===0,'deferred operation provider mismatch');
92
92
  return {scheduled,completed:await waitDeferred(scheduled.state_path)};
93
93
  }
94
- async function publicR36Disarmed(url){
94
+ async function publicR37Disarmed(url){
95
95
  const result=await probeR36CompactDisarmed(url);
96
- assert(result.status==='passed'&&result.tool_count<=42&&result.capability_closure===true,'R36 predecessor public compact catalog mismatch');
97
- assert(result.release_id===R36_PREDECESSOR_RELEASE_ID&&result.release_manifest_sha256===R36_PREDECESSOR_MANIFEST,'R36 predecessor machine binding mismatch');
98
- assert(result.coding_armed===false&&result.autonomous_armed===false,'R36 predecessor authority remained armed');
96
+ assert(result.status==='passed'&&result.tool_count<=42&&result.capability_closure===true,'R37 predecessor public compact catalog mismatch');
97
+ assert(result.release_id===R37_PREDECESSOR_RELEASE_ID&&result.release_manifest_sha256===R37_PREDECESSOR_MANIFEST,'R37 predecessor machine binding mismatch');
98
+ assert(result.coding_armed===false&&result.autonomous_armed===false,'R37 predecessor authority remained armed');
99
99
  return result;
100
100
  }
101
101
  async function mutableCore(pointerValue){
@@ -111,16 +111,16 @@ export function validateParityEvidence(value){
111
111
  assert(value?.schema==='deadbyte.r37-parity.v1','R37 parity schema mismatch');
112
112
  assert(value.status==='passed','R37 parity status is not passed');
113
113
  assert(value.rdc_operation_count===0&&value.execution_provider==='deadbyte_mcp','R37 parity used non-DEADBYTE execution');
114
- assert(value.candidate?.version==='0.11.0'&&/^[0-9a-f]{64}$/.test(value.candidate?.manifest_sha256??''),'R37 candidate identity missing');
114
+ assert(value.candidate?.version==='0.11.1'&&/^[0-9a-f]{64}$/.test(value.candidate?.manifest_sha256??''),'R37 candidate identity missing');
115
115
  assert(value.local_full?.status==='passed'&&value.local_full?.tool_count===76&&value.local_full?.strict_output_schemas===true,'R37 local full 76-tool proof missing');
116
116
  assert(value.public_compact?.status==='passed'&&value.public_compact?.tool_count<=42&&value.public_compact?.capability_closure===true&&value.public_compact?.gateway_count===5,'R37 public compact closure missing');
117
117
  assert(value.autonomous?.status==='passed'&&value.autonomous?.signed_release_verified===true,'R37 autonomous live proof missing');
118
118
  assert(value.before_rollback_disarmed?.status==='passed'&&value.before_rollback_disarmed?.capability_closure===true&&value.before_rollback_disarmed?.coding_armed===false&&value.before_rollback_disarmed?.autonomous_armed===false,'R37 pre-rollback disarm proof missing');
119
119
  assert(value.rollback?.state==='completed'&&value.restore?.state==='completed','R37 rollback/restore not completed');
120
120
  assert(value.stage_restore?.status==='restored'&&value.stage_restore?.manifest_sha256===value.candidate.manifest_sha256,'R37 stage restore proof missing');
121
- assert(value.predecessor?.release_id===R36_PREDECESSOR_RELEASE_ID&&value.predecessor?.manifest_sha256===R36_PREDECESSOR_MANIFEST&&value.predecessor?.version==='0.10.0','R36 exact predecessor identity mismatch');
122
- assert(value.predecessor_disarmed?.status==='passed'&&value.predecessor_disarmed?.tool_count<=42&&value.predecessor_disarmed?.capability_closure===true,'R36 predecessor public proof missing');
123
- assert(value.successor?.version==='0.11.0'&&value.successor?.manifest_sha256===value.candidate.manifest_sha256,'R37 exact successor restore identity mismatch');
121
+ assert(value.predecessor?.release_id===R37_PREDECESSOR_RELEASE_ID&&value.predecessor?.manifest_sha256===R37_PREDECESSOR_MANIFEST&&value.predecessor?.version==='0.11.0','R37 exact predecessor identity mismatch');
122
+ assert(value.predecessor_disarmed?.status==='passed'&&value.predecessor_disarmed?.tool_count<=42&&value.predecessor_disarmed?.capability_closure===true,'R37 predecessor public proof missing');
123
+ assert(value.successor?.version==='0.11.1'&&value.successor?.manifest_sha256===value.candidate.manifest_sha256,'R37 exact successor restore identity mismatch');
124
124
  assert(value.successor_disarmed?.status==='passed'&&value.successor_disarmed?.capability_closure===true&&value.successor_disarmed?.coding_armed===false&&value.successor_disarmed?.autonomous_armed===false,'R37 successor disarmed proof missing');
125
125
  assert(value.immutable_slot?.protected===true&&value.immutable_slot?.development_root!==value.immutable_slot?.release_root,'R37 immutable-slot separation missing');
126
126
  for(const key of ['tree_read','precondition_mutation','search','owned_process','observation','coding_loop','autonomy','interrupted_recovery','evidence_verification','compact_closure','full_surface']) assert(value.requirements?.[key]===true,`R37 parity requirement not proven: ${key}`);
@@ -129,7 +129,7 @@ export function validateParityEvidence(value){
129
129
 
130
130
  export async function runReleaseParity(activationReceiptPath){
131
131
  const active=await pointer();const candidateManifest=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
132
- assert(active.version==='0.11.0','R37 parity requires active 0.11.0 candidate');
132
+ assert(active.version==='0.11.1','R37 parity requires active 0.11.1 candidate');
133
133
  assert(active.manifest_sha256===candidateManifest,'active release is not canonical R37 candidate');
134
134
  const activeConfig=await loadPowerShellDataFile(active.config_path);
135
135
  const successorBridgeRootRaw=activeConfig?.Bridge?.WorkingDirectory;
@@ -154,12 +154,12 @@ export async function runReleaseParity(activationReceiptPath){
154
154
  const beforeRollback=await probeR36CompactDisarmed(url);
155
155
  const rollbackPair=await scheduleDeferred('rollback',path.resolve(activationReceiptPath));
156
156
  const predecessor=await pointer();
157
- assert(predecessor.release_id===R36_PREDECESSOR_RELEASE_ID&&predecessor.manifest_sha256===R36_PREDECESSOR_MANIFEST&&predecessor.version==='0.10.0','rollback did not reach exact sealed R36 predecessor');
158
- const predecessorDisarmed=await publicR36Disarmed(url);
157
+ assert(predecessor.release_id===R37_PREDECESSOR_RELEASE_ID&&predecessor.manifest_sha256===R37_PREDECESSOR_MANIFEST&&predecessor.version==='0.11.0','rollback did not reach exact sealed R37 predecessor');
158
+ const predecessorDisarmed=await publicR37Disarmed(url);
159
159
  const stageRestore=await restoreFrozenStage({developmentRoot,releaseRoot:active.release_root,expectedManifestSha256:candidateManifest});
160
160
  const restorePair=await scheduleDeferred('activate',candidateManifest,{bridgeRoot:successorBridge.root,bridgeScriptSha256:successorBridge.sha256});
161
161
  const successor=await pointer();
162
- assert(successor.version==='0.11.0'&&successor.manifest_sha256===candidateManifest,'restore did not return exact R37 candidate');
162
+ assert(successor.version==='0.11.1'&&successor.manifest_sha256===candidateManifest,'restore did not return exact R37 candidate');
163
163
  const successorDisarmed=await probeR36CompactDisarmed(url);
164
164
  await disarmAll();
165
165
 
@@ -0,0 +1,52 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here=path.dirname(fileURLToPath(import.meta.url));
8
+ const root=path.resolve(here,'..');
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const marker='DEADBYTE V0.11.0 / R37 WINDOWS BATCH GATE: PASS';
11
+ function insist(ok,message){if(!ok) throw new Error(message);}
12
+
13
+ export async function writeWindowsGateEvidence({
14
+ candidateRoot=root,
15
+ stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
16
+ clock=()=>new Date()
17
+ }={}){
18
+ const candidate=path.resolve(candidateRoot);
19
+ const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
20
+ const manifestSha256=sha256(manifestBytes);
21
+ const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
22
+ insist(pkg.version==='0.11.0',`Windows gate evidence requires V0.11.0, found ${pkg.version}`);
23
+ const completed=clock();
24
+ insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
25
+ const evidence={
26
+ schema:'deadbyte.r37-windows-gate.v1',status:'passed',exit_code:0,version:'0.11.0',
27
+ marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256
28
+ };
29
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
30
+ const dir=path.join(path.resolve(stateRoot),'evidence','release');
31
+ await mkdir(dir,{recursive:true});
32
+ const file=path.join(dir,`r37-windows-gate-${manifestSha256.slice(0,16)}.json`);
33
+ try{
34
+ const handle=await open(file,'wx',0o600);
35
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
36
+ return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256};
37
+ }catch(error){
38
+ if(error?.code!=='EEXIST') throw error;
39
+ const existingBytes=await readFile(file);
40
+ const existing=JSON.parse(existingBytes.toString('utf8'));
41
+ insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing Windows gate evidence invalid');
42
+ insist(existing.version==='0.11.0'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
43
+ return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256};
44
+ }
45
+ }
46
+
47
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
48
+ if(invoked){
49
+ writeWindowsGateEvidence()
50
+ .then(result=>console.log(JSON.stringify(result)))
51
+ .catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
52
+ }
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  const here=path.dirname(fileURLToPath(import.meta.url));
8
8
  const root=path.resolve(here,'..');
9
9
  const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
- const marker='DEADBYTE V0.11.0 / R37 WINDOWS BATCH GATE: PASS';
10
+ const marker='DEADBYTE V0.11.1 / R37 WINDOWS BATCH GATE: PASS';
11
11
  function insist(ok,message){if(!ok) throw new Error(message);}
12
12
 
13
13
  export async function writeWindowsGateEvidence({
@@ -19,11 +19,11 @@ export async function writeWindowsGateEvidence({
19
19
  const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
20
20
  const manifestSha256=sha256(manifestBytes);
21
21
  const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
22
- insist(pkg.version==='0.11.0',`Windows gate evidence requires V0.11.0, found ${pkg.version}`);
22
+ insist(pkg.version==='0.11.1',`Windows gate evidence requires V0.11.1, found ${pkg.version}`);
23
23
  const completed=clock();
24
24
  insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
25
25
  const evidence={
26
- schema:'deadbyte.r37-windows-gate.v1',status:'passed',exit_code:0,version:'0.11.0',
26
+ schema:'deadbyte.r37-windows-gate.v1',status:'passed',exit_code:0,version:'0.11.1',
27
27
  marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256
28
28
  };
29
29
  const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
@@ -39,7 +39,7 @@ export async function writeWindowsGateEvidence({
39
39
  const existingBytes=await readFile(file);
40
40
  const existing=JSON.parse(existingBytes.toString('utf8'));
41
41
  insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing Windows gate evidence invalid');
42
- insist(existing.version==='0.11.0'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
42
+ insist(existing.version==='0.11.1'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
43
43
  return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256};
44
44
  }
45
45
  }
@@ -9,14 +9,17 @@ 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
+ import { DEADBYTE_VERSION } from './version.mjs';
13
+ import { createLiveTimelineTracker, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
13
14
 
14
15
  const REMOTE_COMMANDS=new Set([
15
16
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
16
17
  'armprocess','disarmprocess','processstatus','armhost','disarmhost','hoststatus','hosttest',
17
18
  'armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
18
19
  ]);
19
- const SESSION_START=['start','armprocess','armcoding','armautonomous'];
20
+ const SESSION_ARM=['armprocess','armcoding','armautonomous'];
21
+ const REMOTE_STATUS_TIMEOUT_MS=30000;
22
+ const REMOTE_START_TIMEOUT_MS=150000;
20
23
  const SESSION_STATUS=[
21
24
  ['processstatus',/^Process plane\s*:\s*ARMED\b/m],
22
25
  ['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
@@ -52,11 +55,16 @@ async function remoteContext(){
52
55
  const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
53
56
  return {root,launcher,powershell};
54
57
  }
55
- function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true}={}){
56
- const result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],{
58
+ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null}={}){
59
+ const options={
57
60
  stdio:capture?['ignore','pipe','pipe']:'inherit',encoding:capture?'utf8':undefined,windowsHide:false,shell:false
58
- });
59
- if(result.error) throw result.error;
61
+ };
62
+ if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
63
+ const result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
64
+ if(result.error){
65
+ if(result.error?.code==='ETIMEDOUT') throw new Error(`remote controller '${command}' timed out after ${timeoutMs}ms`);
66
+ throw result.error;
67
+ }
60
68
  const status=Number.isInteger(result.status)?result.status:1;
61
69
  const stdout=capture?String(result.stdout||''):'';
62
70
  const stderr=capture?String(result.stderr||''):'';
@@ -64,6 +72,14 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
64
72
  if(status!==0&&!allowFailure) throw new Error(`remote controller '${command}' failed exit=${status}`);
65
73
  return {status,stdout,stderr};
66
74
  }
75
+ function controllerRuntimeReady(text){
76
+ const value=String(text||'');
77
+ return /^DEADBYTE MCP\s*=\s*ONLINE\s*$/m.test(value)&&
78
+ /^Bridge PID\s*:\s*\d+\s+\[verified\]\s*$/m.test(value)&&
79
+ /^Cloud PID\s*:\s*\d+\s+\[verified\]\s*$/m.test(value)&&
80
+ /^Local ready\s*:\s*True\s*$/m.test(value)&&
81
+ /^Loop supervisor:\s*RUNNING\s+\[verified\]\s*$/m.test(value);
82
+ }
67
83
  async function listLogFiles(root){
68
84
  const files=[];
69
85
  const audit=path.join(root,'audit');
@@ -88,13 +104,17 @@ function formatLogLine(file,line,{activityTracker=null}={}){
88
104
  return formatRuntimeDiagnosticLine(file,line);
89
105
  }
90
106
  function remoteResultJournalPath(root,sessionId){return path.join(root,'runtime','remote-results',`${sessionId}.json`);}
91
- function auditTerminalResult(file,line){
107
+ function auditTimelineRecord(file,line){
92
108
  if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
93
109
  let event;try{event=JSON.parse(line);}catch{return null;}
94
- if(event?.event_type!=='tool.result'||typeof event.request_id!=='string'||!event.request_id||typeof event.tool!=='string'||!event.tool) return null;
110
+ if(typeof event?.request_id!=='string'||!event.request_id||typeof event.tool!=='string'||!event.tool) return null;
95
111
  const observed=Number.isFinite(Date.parse(event.created_at_utc))?new Date(Date.parse(event.created_at_utc)).toISOString():new Date().toISOString();
96
- return {request_id:event.request_id,tool:event.tool,status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,
97
- payload_sha256:createHash('sha256').update(Buffer.from(line,'utf8')).digest('hex')};
112
+ const payload_sha256=createHash('sha256').update(Buffer.from(line,'utf8')).digest('hex');
113
+ if(event.event_type==='tool.request') return {event_type:'tool.call',request_id:event.request_id,tool:event.tool,
114
+ observed_at_utc:observed,arguments:event.args??{},payload_sha256};
115
+ if(event.event_type==='tool.result') return {event_type:'tool.result',request_id:event.request_id,tool:event.tool,
116
+ status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,payload_sha256};
117
+ return null;
98
118
  }
99
119
  async function replayRemoteResults(root,cursor){
100
120
  const parsed=parseRemoteResultCursor(cursor);
@@ -105,6 +125,12 @@ async function replayRemoteResults(root,cursor){
105
125
  for(;;){
106
126
  const page=await journal.replayAfter(next,{limit:200});
107
127
  for(const event of page.events){
128
+ if(event.event_type==='tool.call'){
129
+ const line=formatRemoteToolCall({event_type:'tool.request',tool:event.tool,request_id:event.request_id,
130
+ observed_at_utc:event.observed_at_utc,args:event.arguments});
131
+ process.stdout.write(`${line} | REPLAY cursor r1:${event.session_id}:${event.seq}\n`);
132
+ continue;
133
+ }
108
134
  const marker=event.status==='error'?'[ERR]':'[OK]';
109
135
  process.stdout.write(`[REPLAY] ${marker} ${event.tool} [cursor r1:${event.session_id}:${event.seq}]\n`);
110
136
  }
@@ -139,9 +165,11 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
139
165
  const next=await readAppend(file,prior);
140
166
  activeOffsets.set(file,next.offset);
141
167
  for(const line of next.text.split(/\r?\n/)){
142
- const terminal=auditTerminalResult(file,line);
168
+ const timeline=auditTimelineRecord(file,line);
143
169
  let recorded=null;
144
- if(terminal&&journal) recorded=await journal.recordTerminal(terminal);
170
+ if(timeline&&journal) recorded=timeline.event_type==='tool.call'
171
+ ? await journal.recordCall(timeline)
172
+ : await journal.recordTerminal(timeline);
145
173
  if(recorded?.duplicate) continue;
146
174
  const formatted=formatLogLine(file,line,{activityTracker});
147
175
  if(!formatted) continue;
@@ -257,18 +285,21 @@ async function runRemoteSession({resumeCursor=null}={}){
257
285
  const logOffsets=await snapshotRemoteLogOffsets(ctx.root);
258
286
  logTask=tailRemoteLogs(ctx.root,aborter.signal,{journal:resultJournal,offsets:logOffsets,activityTracker});
259
287
  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
- }
288
+ const preflight=invokeController(ctx,'status',{capture:true,allowFailure:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
289
+ let startText=preflight.stdout||'';
290
+ if(preflight.status===0&&controllerRuntimeReady(preflight.stdout)){
291
+ process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
292
+ }else{
293
+ process.stdout.write('[BOOT] Starting bridge/runtime...\n');
294
+ const started=invokeController(ctx,'start',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS});
295
+ startText=started.stdout||'';
296
+ }
297
+ process.stdout.write(`${formatRemoteLifecycle('bridge_connected')}\n`);
298
+ process.stdout.write(`${formatRemoteLifecycle('cloud_connected')}\n`);
299
+ for(const command of SESSION_ARM){
300
+ process.stdout.write(`🔐 [AUTH] Arming ${command.slice(3)}...\n`);
301
+ invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
270
302
  }
271
- const startText=started[0]?.stdout||'';
272
303
  const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
273
304
  process.stdout.write('[OK] ONLINE\n');
274
305
  process.stdout.write(`${formatRemoteLifecycle('ready')}\n`);
@@ -320,6 +351,10 @@ async function runAudit(args){
320
351
  }
321
352
  async function main(argv){
322
353
  const [command,...rest]=argv;
354
+ if(command==='--version'||command==='-v'||command==='version'){
355
+ if(rest.length) throw new Error('version accepts no arguments');
356
+ process.stdout.write(`${DEADBYTE_VERSION}\n`);return;
357
+ }
323
358
  if(!command||command==='help'||command==='--help'||command==='-h'){printHelp();return;}
324
359
  if(command==='mcp'){
325
360
  if(rest.length) throw new Error('mcp accepts no arguments');
@@ -1,3 +1,6 @@
1
+ import { canonicalJson } from './canonical-json.mjs';
2
+ import { sanitizeAuditArgs } from './tool-audit.mjs';
3
+
1
4
  const SAFE_ARG_KEYS=Object.freeze(['root_id','path','pattern','command_id','profile_id','session_id','goal_id','task_id','step_id','mode']);
2
5
  const LIFECYCLE=Object.freeze({
3
6
  device_start:'🚀 [DEVICE] Starting DEADBYTE MCP...',
@@ -30,6 +33,25 @@ function summarizeArgs(args){
30
33
  return parts.length?` ${parts.join(' ')}`:'';
31
34
  }
32
35
 
36
+ const TOOL_COLUMN_WIDTH=32;
37
+
38
+ function eventTimestamp(event,clock){
39
+ for(const value of [event?.created_at_utc,event?.observed_at_utc]){
40
+ const parsed=Date.parse(value);
41
+ if(typeof value==='string'&&Number.isFinite(parsed)) return new Date(parsed).toISOString();
42
+ }
43
+ const value=Number(clock());
44
+ return new Date(Number.isFinite(value)?value:Date.now()).toISOString();
45
+ }
46
+
47
+ export function formatRemoteToolCall(event,{clock=()=>Date.now()}={}){
48
+ if(!event||typeof event!=='object'||Array.isArray(event)||event.event_type!=='tool.request'||
49
+ typeof event.tool!=='string'||!event.tool) return null;
50
+ if(typeof clock!=='function') throw new Error('remote tool-call clock must be a function');
51
+ const args=sanitizeAuditArgs(event.args??{});
52
+ return `${eventTimestamp(event,clock)} | ${event.tool.padEnd(TOOL_COLUMN_WIDTH)} | Arguments: ${canonicalJson(args)}`;
53
+ }
54
+
33
55
  function formatDuration(ms){
34
56
  const value=Number(ms);
35
57
  if(!Number.isFinite(value)||value<0) return '';
@@ -92,7 +114,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now()}={}){
92
114
  if(event.event_type==='tool.request'&&requestId&&tool){
93
115
  if(pending.has(requestId)||completed.has(requestId)) return null;
94
116
  pending.set(requestId,{request_id:requestId,tool,started_at_ms:clock()});
95
- return `▶ [${shortSpanId(requestId)}] RUN ${tool}${summarizeArgs(event.args)}`;
117
+ return formatRemoteToolCall(event,{clock});
96
118
  }
97
119
  if(event.event_type==='tool.result'&&requestId&&tool){
98
120
  if(completed.has(requestId)) return null;
@@ -1,10 +1,12 @@
1
1
  import { lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { sanitizeAuditArgs } from './tool-audit.mjs';
3
4
 
4
5
  export const REMOTE_RESULT_JOURNAL_SCHEMA='deadbyte.remote-result-journal.v1';
5
6
  const SESSION=/^[0-9a-f]{24}$/;
6
7
  const HASH=/^[0-9a-f]{64}$/;
7
8
  const STATUS=new Set(['ok','error']);
9
+ const EVENT_TYPES=new Set(['tool.call','tool.result']);
8
10
 
9
11
  function text(value,label,max){
10
12
  if(typeof value!=='string'||value.length<1||value.length>max||value.includes('\0')) throw new Error(`${label} invalid`);
@@ -26,14 +28,22 @@ function validateEvent(raw,index=null){
26
28
  const sessionId=validateSession(raw.session_id);
27
29
  const requestId=text(raw.request_id,'remote result request_id',256);
28
30
  const tool=text(raw.tool,'remote result tool',128);
29
- if(!STATUS.has(raw.status)) throw new Error('remote result status invalid');
31
+ const eventType=raw.event_type===undefined?'tool.result':raw.event_type;
32
+ if(!EVENT_TYPES.has(eventType)) throw new Error('remote result event_type invalid');
30
33
  const observed=iso(raw.observed_at_utc,'remote result observed_at_utc');
31
34
  if(typeof raw.payload_sha256!=='string'||!HASH.test(raw.payload_sha256)) throw new Error('remote result payload_sha256 invalid');
32
35
  if(index!==null&&seq!==index) throw new Error('remote result sequence mismatch');
33
- return Object.freeze({session_id:sessionId,seq,request_id:requestId,tool,status:raw.status,
36
+ if(eventType==='tool.call'){
37
+ const rawArgs=raw.arguments??{};
38
+ if(!rawArgs||typeof rawArgs!=='object'||Array.isArray(rawArgs)) throw new Error('remote result call arguments invalid');
39
+ return Object.freeze({session_id:sessionId,seq,event_type:eventType,request_id:requestId,tool,
40
+ observed_at_utc:observed,payload_sha256:raw.payload_sha256,arguments:sanitizeAuditArgs(rawArgs)});
41
+ }
42
+ if(!STATUS.has(raw.status)) throw new Error('remote result status invalid');
43
+ return Object.freeze({session_id:sessionId,seq,event_type:eventType,request_id:requestId,tool,status:raw.status,
34
44
  observed_at_utc:observed,payload_sha256:raw.payload_sha256});
35
45
  }
36
- function identity(item){return `${item.request_id}\0${item.status}\0${item.payload_sha256}`;}
46
+ function identity(item){return `${item.event_type}\0${item.request_id}\0${item.status??''}\0${item.payload_sha256}`;}
37
47
  export function parseRemoteResultCursor(cursor){
38
48
  if(typeof cursor!=='string') throw new Error('remote result cursor invalid');
39
49
  const match=/^r1:([0-9a-f]{24}):(0|[1-9][0-9]{0,15})$/.exec(cursor);
@@ -89,8 +99,17 @@ export async function openRemoteResultJournal({filePath,sessionId,maxEntries=204
89
99
  return Object.freeze({
90
100
  file_path:file,session_id:sid,
91
101
  cursor(){return cursorFor(sid,state.next_seq-1);},
102
+ async recordCall(input){
103
+ const candidate=validateEvent({session_id:sid,seq:state.next_seq,event_type:'tool.call',...input});
104
+ const key=identity(candidate);
105
+ const existing=state.events.find(event=>identity(event)===key);
106
+ if(existing) return {duplicate:true,event:existing,cursor:cursorFor(sid,state.next_seq-1)};
107
+ const event=candidate;
108
+ state.events.push(event);state.next_seq+=1;await persist();
109
+ return {duplicate:false,event,cursor:cursorFor(sid,event.seq)};
110
+ },
92
111
  async recordTerminal(input){
93
- const candidate=validateEvent({session_id:sid,seq:state.next_seq,...input});
112
+ const candidate=validateEvent({session_id:sid,seq:state.next_seq,event_type:'tool.result',...input});
94
113
  const key=identity(candidate);
95
114
  const existing=state.events.find(event=>identity(event)===key);
96
115
  if(existing) return {duplicate:true,event:existing,cursor:cursorFor(sid,state.next_seq-1)};