deadbyte-mcp 0.10.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 (82) hide show
  1. package/MANIFEST.SHA256 +81 -59
  2. package/README.txt +13 -14
  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 +3 -3
  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-r36.mjs +113 -0
  39. package/scripts/final-closure-r37-0110.mjs +113 -0
  40. package/scripts/final-closure-verify-r36.mjs +208 -0
  41. package/scripts/final-closure-verify-r37-0110.mjs +208 -0
  42. package/scripts/final-closure-verify.mjs +48 -48
  43. package/scripts/final-closure.mjs +13 -13
  44. package/scripts/gate-windows-r36.ps1 +42 -0
  45. package/scripts/gate-windows-r37-0110.ps1 +42 -0
  46. package/scripts/gate-windows.ps1 +3 -3
  47. package/scripts/release-parity-r36.mjs +200 -0
  48. package/scripts/release-parity-r37-0110.mjs +200 -0
  49. package/scripts/release-parity-tests.ps1 +2 -2
  50. package/scripts/release-parity.mjs +37 -37
  51. package/scripts/windows-gate-evidence-r36.mjs +52 -0
  52. package/scripts/windows-gate-evidence-r37-0110.mjs +52 -0
  53. package/scripts/windows-gate-evidence.mjs +5 -5
  54. package/src/agent-memory-plane.mjs +98 -0
  55. package/src/autonomous-output-contracts.mjs +5 -2
  56. package/src/autonomous-runtime.mjs +155 -31
  57. package/src/autonomous-timeout-recovery.mjs +27 -0
  58. package/src/deadbyte-cli.mjs +90 -94
  59. package/src/effect-ledger.mjs +47 -0
  60. package/src/observation-lease.mjs +32 -0
  61. package/src/remote-live-log.mjs +81 -0
  62. package/src/remote-mcp-autonomous-smoke-client.mjs +15 -9
  63. package/src/remote-result-journal.mjs +23 -4
  64. package/src/version.mjs +1 -1
  65. package/src/worker-scheduler.mjs +53 -0
  66. package/test/autonomous-output-contract.test.mjs +1 -1
  67. package/test/cli-entrypoint.test.mjs +9 -0
  68. package/test/containment.test.mjs +18 -0
  69. package/test/core.test.mjs +1 -1
  70. package/test/production-docs.test.mjs +5 -5
  71. package/test/r33-closeout-regression.test.mjs +4 -4
  72. package/test/r33-finalization.test.mjs +6 -6
  73. package/test/r36-finalization.test.mjs +5 -5
  74. package/test/r36-release-identity.test.mjs +12 -19
  75. package/test/r37-0110-history.test.mjs +20 -0
  76. package/test/r37-agent-fabric.test.mjs +92 -0
  77. package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
  78. package/test/r37-finalization.test.mjs +74 -0
  79. package/test/r37-remote-tool-call-timeline.test.mjs +94 -0
  80. package/test/release-version.test.mjs +35 -36
  81. package/test/remote-live-log-r37.test.mjs +50 -0
  82. package/test/remote-session-cli.test.mjs +86 -68
@@ -0,0 +1,94 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { createLiveTimelineTracker, formatRemoteToolCall } from '../src/remote-live-log.mjs';
7
+ import { openRemoteResultJournal } from '../src/remote-result-journal.mjs';
8
+
9
+ const H=c=>c.repeat(64);
10
+ const session='c'.repeat(24);
11
+
12
+ test('R37 unified timeline renders one sanitized tool-call line with ISO timestamp, padded tool name, and JSON arguments',()=>{
13
+ const event={
14
+ event_type:'tool.request',
15
+ request_id:'abcdef1234567890',
16
+ tool:'coding_file_write',
17
+ created_at_utc:'2026-09-18T16:24:30.125Z',
18
+ args:{
19
+ root_id:'projects',
20
+ path:'src/example.mjs',
21
+ content:'TOP_SECRET_PAYLOAD',
22
+ token:'TOP_SECRET_TOKEN',
23
+ nested:{authorization:'Bearer abc.def.ghi',safe:true}
24
+ }
25
+ };
26
+ const line=formatRemoteToolCall(event);
27
+ assert.match(line,/^2026-09-18T16:24:30\.125Z \| coding_file_write\s+\| Arguments: \{/);
28
+ assert.match(line,/"root_id":"projects"/);
29
+ assert.match(line,/"path":"src\/example\.mjs"/);
30
+ assert.match(line,/"reason":"payload"/);
31
+ assert.match(line,/"reason":"secret"/);
32
+ assert.doesNotMatch(line,/TOP_SECRET_PAYLOAD|TOP_SECRET_TOKEN|Bearer abc\.def\.ghi/);
33
+ });
34
+
35
+ test('R37 live tracker uses the tool-call line for request and keeps correlated terminal result',()=>{
36
+ let now=Date.parse('2026-09-18T16:24:30.125Z');
37
+ const tracker=createLiveTimelineTracker({clock:()=>now});
38
+ const call=tracker.observe({event_type:'tool.request',request_id:'abcdef1234567890',tool:'coding_tree',
39
+ created_at_utc:'2026-09-18T16:24:30.125Z',args:{root_id:'projects',path:'src'}});
40
+ assert.match(call,/^2026-09-18T16:24:30\.125Z \| coding_tree\s+\| Arguments: \{"path":"src","root_id":"projects"\}$/);
41
+ now+=250;
42
+ assert.equal(tracker.observe({event_type:'tool.result',request_id:'abcdef1234567890',tool:'coding_tree',
43
+ outcome:{is_error:false,duration_ms:250}}),'✓ [abcdef12] OK coding_tree 250ms');
44
+ });
45
+
46
+ test('R37 remote result journal persists sanitized tool-call and result in one monotonic cursor stream',async()=>{
47
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r37-toolcall-journal-'));
48
+ const file=path.join(root,'journal.json');
49
+ try{
50
+ const journal=await openRemoteResultJournal({filePath:file,sessionId:session,maxEntries:16});
51
+ const call=await journal.recordCall({
52
+ request_id:'req-1',tool:'coding_file_write',observed_at_utc:'2026-09-18T16:24:30.125Z',
53
+ arguments:{root_id:'projects',path:'x.txt',content:'DO_NOT_STORE_PAYLOAD',token:'DO_NOT_STORE_TOKEN'},
54
+ payload_sha256:H('1')
55
+ });
56
+ const duplicate=await journal.recordCall({
57
+ request_id:'req-1',tool:'coding_file_write',observed_at_utc:'2026-09-18T16:24:30.125Z',
58
+ arguments:{root_id:'projects',path:'x.txt',content:'DO_NOT_STORE_PAYLOAD',token:'DO_NOT_STORE_TOKEN'},
59
+ payload_sha256:H('1')
60
+ });
61
+ const result=await journal.recordTerminal({
62
+ request_id:'req-1',tool:'coding_file_write',status:'ok',observed_at_utc:'2026-09-18T16:24:30.500Z',
63
+ payload_sha256:H('2')
64
+ });
65
+ assert.equal(call.event.seq,1);
66
+ assert.equal(call.event.event_type,'tool.call');
67
+ assert.equal(duplicate.duplicate,true);
68
+ assert.equal(result.event.seq,2);
69
+ assert.equal(result.event.event_type,'tool.result');
70
+ assert.equal(journal.cursor(),'r1:'+session+':2');
71
+ const replay=await journal.replayAfter('r1:'+session+':0',{limit:8});
72
+ assert.deepEqual(replay.events.map(e=>[e.seq,e.event_type]),[[1,'tool.call'],[2,'tool.result']]);
73
+ const disk=await readFile(file,'utf8');
74
+ assert.doesNotMatch(disk,/DO_NOT_STORE_PAYLOAD|DO_NOT_STORE_TOKEN/);
75
+ assert.match(disk,/"reason":"payload"/);
76
+ assert.match(disk,/"reason":"secret"/);
77
+ }finally{await rm(root,{recursive:true,force:true});}
78
+ });
79
+
80
+ test('R37 journal remains backward-compatible with pre-tool-call result-only events',async()=>{
81
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r37-old-result-journal-'));
82
+ const file=path.join(root,'journal.json');
83
+ try{
84
+ await import('node:fs/promises').then(({writeFile})=>writeFile(file,JSON.stringify({
85
+ schema:'deadbyte.remote-result-journal.v1',session_id:session,next_seq:2,
86
+ events:[{session_id:session,seq:1,request_id:'old-1',tool:'coding_tree',status:'ok',
87
+ observed_at_utc:'2026-09-17T00:00:00.000Z',payload_sha256:H('3')}]
88
+ })+'\n','utf8'));
89
+ const journal=await openRemoteResultJournal({filePath:file,sessionId:session,maxEntries:16});
90
+ const replay=await journal.replayAfter('r1:'+session+':0',{limit:8});
91
+ assert.equal(replay.events[0].event_type,'tool.result');
92
+ assert.equal(replay.events[0].status,'ok');
93
+ }finally{await rm(root,{recursive:true,force:true});}
94
+ });
@@ -3,43 +3,42 @@ import assert from 'node:assert/strict';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { createHash } from 'node:crypto';
5
5
 
6
- const root = new URL('../', import.meta.url);
7
- const text = rel => readFile(new URL(rel, root), 'utf8');
6
+ const root=new URL('../',import.meta.url);
7
+ const text=rel=>readFile(new URL(rel,root),'utf8');
8
+ const R37_PREDECESSOR_RELEASE_ID='v0.11.0-08c2a0008b1f84c9';
9
+ const R37_PREDECESSOR_MANIFEST='08c2a0008b1f84c90f7ce7faa1e3decbd03f33612bc0fdc7d1ba591b548798e8';
8
10
 
9
- test('R36 successor release authority agrees on V0.10.0 / R36 full+compact identity', async () => {
10
- const pkg = JSON.parse(await text('package.json'));
11
- const [version, controller, gate, readme, architecture, controllerReadme, deferred, parity, closure, closureVerify, windowsGate, semanticConfig] = await Promise.all([
12
- text('src/version.mjs'),
13
- text('controller/deadbyte-controller.ps1'),
14
- text('scripts/gate-windows.ps1'),
15
- text('README.txt'),
16
- text('docs/ARCHITECTURE.md'),
17
- text('controller/README.TXT'),
18
- text('scripts/deferred-slot-operation.mjs'),
19
- text('src/remote-mcp-r36-parity-client.mjs'),
20
- text('scripts/final-closure.mjs'),
21
- text('scripts/final-closure-verify.mjs'),
22
- text('scripts/windows-gate-evidence.mjs'),
11
+ test('R37 patch release authority agrees on V0.11.1 and preserves exact public 0.11.0 predecessor',async()=>{
12
+ const pkg=JSON.parse(await text('package.json'));
13
+ const [version,controller,gate,readme,architecture,controllerReadme,deferred,parity,closure,closureVerify,windowsGate,semanticConfig]=await Promise.all([
14
+ text('src/version.mjs'),text('controller/deadbyte-controller.ps1'),text('scripts/gate-windows.ps1'),
15
+ text('README.txt'),text('docs/ARCHITECTURE.md'),text('controller/README.TXT'),
16
+ text('scripts/deferred-slot-operation.mjs'),text('scripts/release-parity.mjs'),text('scripts/final-closure.mjs'),
17
+ text('scripts/final-closure-verify.mjs'),text('scripts/windows-gate-evidence.mjs'),
23
18
  text('controller/deadbyte-semantic-lsp-config.json')
24
19
  ]);
25
- assert.equal(pkg.version, '0.10.0');
26
- assert.match(version, /DEADBYTE_VERSION = '0\.10\.0'/);
27
- assert.match(controller, /CONTROLLER V0\.10\.0 \/ R36/);
28
- assert.match(gate, /DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
29
- assert.match(readme, /^DEADBYTE MCP V0\.10\.0 \/ R36 PRODUCTION BASELINE$/m);
30
- assert.match(architecture, /^# DEADBYTE MCP V0\.10\.0 \/ R36 ARCHITECTURE$/m);
31
- assert.match(controllerReadme, /^DEADBYTE MCP V0\.10\.0 \/ R36 CONTROLLER$/m);
32
- assert.match(controllerReadme, /full 76-tool (?:profile|catalog)/);
33
- assert.match(controllerReadme, /compact capability profile[^\n]*<=42 tools/);
34
- assert.match(deferred, /--expected-version','0\.10\.0'/);
35
- assert.match(parity, /version:'0\.10\.0'/);
36
- assert.match(parity, /R36_EXPECTED_TOOLS\.length !== 76/);
37
- assert.match(parity, /R36_COMPACT_TOOL_LIMIT=42/);
38
- assert.match(closure, /version:'0\.10\.0'/);
39
- assert.match(closureVerify, /DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
40
- assert.match(windowsGate, /schema:'deadbyte\.r36-windows-gate\.v1'/);
41
- assert.equal(createHash('sha256').update(Buffer.from(semanticConfig,'utf8')).digest('hex'), 'd274b099a298c1033fea8f1e8849751693bbc0448778d619400acb82d4f53a3b');
42
- assert.match(deferred, /--semantic-lsp-config/);
43
- assert.match(deferred, /--semantic-lsp-config-sha256/);
44
- assert.match(deferred, /deadbyte-semantic-lsp-config\.json/);
20
+ assert.equal(pkg.version,'0.11.1');
21
+ assert.match(version,/DEADBYTE_VERSION = '0\.11\.1'/);
22
+ assert.match(controller,/CONTROLLER V0\.11\.1 \/ R37/);
23
+ assert.match(gate,/DEADBYTE V0\.11\.1 \/ R37 WINDOWS BATCH GATE: PASS/);
24
+ assert.match(readme,/^DEADBYTE MCP V0\.11\.1 \/ R37 PRODUCTION BASELINE$/m);
25
+ assert.match(architecture,/^# DEADBYTE MCP V0\.11\.1 \/ R37 ARCHITECTURE$/m);
26
+ assert.match(controllerReadme,/^DEADBYTE MCP V0\.11\.1 \/ R37 CONTROLLER$/m);
27
+ assert.match(deferred,/--expected-version','0\.11\.1'/);
28
+ assert.match(parity,/R37_PREDECESSOR_RELEASE_ID='v0\.11\.0-08c2a0008b1f84c9'/);
29
+ assert.match(parity,new RegExp(R37_PREDECESSOR_MANIFEST));
30
+ assert.match(parity,/version==='0\.11\.1'|version:'0\.11\.1'/);
31
+ assert.match(closure,/version:'0\.11\.1'/);
32
+ assert.match(closureVerify,/DEADBYTE V0\.11\.1 \/ R37 WINDOWS BATCH GATE: PASS/);
33
+ assert.match(windowsGate,/version:'0\.11\.1'/);
34
+ assert.equal(createHash('sha256').update(Buffer.from(semanticConfig,'utf8')).digest('hex'),'d274b099a298c1033fea8f1e8849751693bbc0448778d619400acb82d4f53a3b');
35
+ });
36
+
37
+ test('R37 patch keeps capability surface and fixes ONLINE remote startup without restoring verbose mode',async()=>{
38
+ const [parity,cli]=await Promise.all([text('src/remote-mcp-r36-parity-client.mjs'),text('src/deadbyte-cli.mjs')]);
39
+ assert.match(parity,/R36_EXPECTED_TOOLS\.length !== 76/);
40
+ assert.match(parity,/R36_COMPACT_TOOL_LIMIT=42/);
41
+ assert.match(cli,/controllerRuntimeReady/);
42
+ assert.match(cli,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
43
+ assert.doesNotMatch(cli,/remote --verbose/);
45
44
  });
@@ -0,0 +1,50 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createLiveTimelineTracker } from '../src/remote-live-log.mjs';
4
+
5
+ test('R37 live timeline renders sanitized tool-call detail and correlates terminal result with stable short span id',()=>{
6
+ let now=1000;
7
+ const tracker=createLiveTimelineTracker({clock:()=>now});
8
+ const run=tracker.observe({event_type:'tool.request',tool:'coding_file_read',request_id:'abcdef1234567890',
9
+ args:{root_id:'projects',path:'src/a.mjs',content:'SECRET',token:'SECRET'}});
10
+ assert.match(run,/^1970-01-01T00:00:01\.000Z \| coding_file_read\s+\| Arguments: \{/);
11
+ assert.match(run,/\"path\":\"src\/a\.mjs\"/);
12
+ assert.match(run,/\"reason\":\"payload\"/);
13
+ assert.match(run,/\"reason\":\"secret\"/);
14
+ now=1250;
15
+ const done=tracker.observe({event_type:'tool.result',tool:'coding_file_read',request_id:'abcdef1234567890',
16
+ outcome:{is_error:false,duration_ms:250}});
17
+ assert.equal(done,'✓ [abcdef12] OK coding_file_read 250ms');
18
+ assert.deepEqual(tracker.pending(),[]);
19
+ assert.doesNotMatch(run+done,/SECRET/);
20
+ });
21
+
22
+ test('R37 live timeline distinguishes result whose request was outside live tail',()=>{
23
+ const tracker=createLiveTimelineTracker();
24
+ const line=tracker.observe({event_type:'tool.result',tool:'coding_tree',request_id:'old-request-12345678',
25
+ outcome:{is_error:false,duration_ms:15}});
26
+ assert.equal(line,'↩ [old-requ] OK coding_tree 15ms (request before live window)');
27
+ });
28
+
29
+ test('R37 live timeline deduplicates terminal result and reports interrupted spans with ids',()=>{
30
+ const tracker=createLiveTimelineTracker();
31
+ tracker.observe({event_type:'tool.request',tool:'coding_command_run',request_id:'request-23456789',args:{command_id:'node_script'}});
32
+ const first=tracker.observe({event_type:'tool.result',tool:'coding_command_run',request_id:'request-23456789',outcome:{is_error:true,duration_ms:900}});
33
+ const duplicate=tracker.observe({event_type:'tool.result',tool:'coding_command_run',request_id:'request-23456789',outcome:{is_error:true,duration_ms:900}});
34
+ assert.equal(first,'✗ [request-] ERR coding_command_run 900ms');
35
+ assert.equal(duplicate,null);
36
+
37
+ tracker.observe({event_type:'tool.request',tool:'coding_file_read',request_id:'pending-12345678',args:{path:'src/a.mjs'}});
38
+ assert.deepEqual(tracker.reconcileInterrupted('recovery'),[
39
+ '⚠ [pending-] INTERRUPTED coding_file_read — recovery ended before terminal result'
40
+ ]);
41
+ });
42
+
43
+ test('R37 CLI uses timeline tracker for compact live mode while keeping legacy formatter API',async()=>{
44
+ const {readFile}=await import('node:fs/promises');
45
+ const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
46
+ assert.match(src,/createLiveTimelineTracker/);
47
+ assert.match(src,/const activityTracker=createLiveTimelineTracker\(\)/);
48
+ assert.doesNotMatch(src,/remote --verbose/);
49
+ assert.doesNotMatch(src,/args\[0\]==='--verbose'/);
50
+ });
@@ -11,16 +11,16 @@ const cliPath=path.join(root,'src','deadbyte-cli.mjs');
11
11
  const isWindows=process.platform==='win32';
12
12
  const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
13
13
 
14
- async function fixture({failCommand=''}={}){
14
+ async function fixture({failCommand='',statusOnline=false}={}){
15
15
  const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-remote-session-'));
16
16
  const state=path.join(dir,'state');
17
17
  const bin=path.join(state,'bin');
18
18
  const trace=path.join(dir,'trace.txt');
19
19
  await mkdir(bin,{recursive:true});
20
20
  const launcher=path.join(bin,'DEADBYTE-MCP.ps1');
21
- const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -eq $env:DEADBYTE_REMOTE_TEST_FAIL) { Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { Write-Host 'DEADBYTE MCP = ONLINE' }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
21
+ const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -eq $env:DEADBYTE_REMOTE_TEST_FAIL) { Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { Write-Host 'DEADBYTE MCP = ONLINE' }\n 'status' { if ($env:DEADBYTE_REMOTE_TEST_STATUS_ONLINE -eq '1') { Write-Host 'DEADBYTE MCP = ONLINE'; Write-Host 'Bridge PID : 1001 [verified]'; Write-Host 'Cloud PID : 1002 [verified]'; Write-Host 'Local ready : True'; Write-Host 'Loop supervisor: RUNNING [verified]' } else { Write-Host 'DEADBYTE MCP = OFFLINE' } }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
22
22
  await writeFile(launcher,ps1,'utf8');
23
- return {dir,state,trace,env:{...process.env,DEADBYTE_STATE_ROOT:state,DEADBYTE_REMOTE_TEST_TRACE:trace,DEADBYTE_REMOTE_TEST_FAIL:failCommand}};
23
+ return {dir,state,trace,env:{...process.env,DEADBYTE_STATE_ROOT:state,DEADBYTE_REMOTE_TEST_TRACE:trace,DEADBYTE_REMOTE_TEST_FAIL:failCommand,DEADBYTE_REMOTE_TEST_STATUS_ONLINE:statusOnline?'1':'0'}};
24
24
  }
25
25
 
26
26
  async function traceCommands(file,{read=readFile,retryDelayMs=10,maxAttempts=20}={}){
@@ -65,15 +65,40 @@ async function collect(child){
65
65
 
66
66
  const winTest=isWindows?test:test.skip;
67
67
 
68
- winTest('bare remote uses compact human logs by default, keeps authority semantics, then disarms and parks when the foreground session ends',async()=>{
69
- const fx=await fixture();
68
+ winTest('bare remote reuses a fully verified ONLINE runtime without invoking blocking start',async()=>{
69
+ const fx=await fixture({failCommand:'start',statusOnline:true});
70
70
  let child=null;
71
71
  try{
72
72
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
73
73
  const output=await collect(child);
74
- const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
74
+ const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'),{timeoutMs:8000});
75
75
  assert.deepEqual(ready.slice(0,8),[
76
- 'start','armprocess','armcoding','armautonomous',
76
+ 'status','armprocess','armcoding','armautonomous',
77
+ 'processstatus','codingstatus','autonomousstatus','hoststatus'
78
+ ]);
79
+ assert.doesNotMatch(ready.join(','),/(^|,)start(,|$)/);
80
+ assert.equal(child.exitCode,null,'remote session must remain foreground after reusing ONLINE runtime');
81
+ assert.match(output.stdout,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
82
+ const authDeadline=Date.now()+3000;
83
+ while(Date.now()<authDeadline&&!/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
84
+ assert.match(output.stdout,/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
85
+ child.kill('SIGINT');
86
+ await output.closed;
87
+ }finally{
88
+ if(child&&child.exitCode===null) child.kill('SIGKILL');
89
+ await rm(fx.dir,{recursive:true,force:true});
90
+ }
91
+ });
92
+
93
+ winTest('bare remote uses one unified live timeline, keeps authority semantics, then disarms and parks when the foreground session ends',async()=>{
94
+ const fx=await fixture();
95
+ let child=null;
96
+ try{
97
+ child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
98
+ const output=await collect(child);
99
+ const ready=await waitFor(fx.trace,commands=>commands.slice(0,9).join(',')==='status,start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
100
+ assert.deepEqual(ready.slice(0,9),[
101
+ 'status','start','armprocess','armcoding','armautonomous',
77
102
  'processstatus','codingstatus','autonomousstatus','hoststatus'
78
103
  ]);
79
104
  assert.equal(child.exitCode,null,'remote session must remain foreground after becoming ready');
@@ -87,7 +112,7 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
87
112
  assert.match(output.stdout,/\[OK\] ONLINE/);
88
113
  assert.match(output.stdout,/✅ \[READY\] DEADBYTE MCP online/);
89
114
  assert.match(output.stdout,/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
90
- assert.match(output.stdout,/\[LOG\] Live activity\. Ctrl\+C disconnects and revokes authority\./);
115
+ assert.match(output.stdout,/\[LOG\] Unified live timeline\. Ctrl\+C disconnects and revokes authority\./);
91
116
  assert.doesNotMatch(output.stdout,/Process plane\s*:/);
92
117
  assert.doesNotMatch(output.stdout,/Coding plane\s*:/);
93
118
  const auditDir=path.join(fx.state,'audit');
@@ -110,20 +135,24 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
110
135
  '[TOOL OUT] 2026-09-16T00:00:00.002Z #000002 coding_file_read {"is_error":true}'
111
136
  ].join('\n')+'\n','utf8');
112
137
  const logDeadline=Date.now()+3000;
113
- while(Date.now()<logDeadline&&!/❌ ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
114
- assert.match(output.stdout,/▶ RUN coding_tree root=projects path=src/);
115
- assert.match(output.stdout,/✅ OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:1\]/);
116
- assert.match(output.stdout,/▶ RUN coding_file_read root=projects path=src\/deadbyte-cli\.mjs/);
117
- assert.match(output.stdout,/❌ ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:2\]/);
118
- assert.equal((output.stdout.match(/✅ OK coding_tree 42ms/g)||[]).length,1,'duplicate terminal result must print once');
138
+ while(Date.now()<logDeadline&&!/✗ \[fedcba98\] ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
139
+ assert.match(output.stdout,/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z \| coding_tree\s+\| Arguments: .*\"path\":\"src\".*\"root_id\":\"projects\".*\[cursor r1:[0-9a-f]{24}:1\]$/m);
140
+ assert.match(output.stdout,/✓ \[abcdef12\] OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:2\]/);
141
+ assert.match(output.stdout,/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z \| coding_file_read\s+\| Arguments: \{\"path\":\"src\/deadbyte-cli\.mjs\",\"root_id\":\"projects\"\} \[cursor r1:[0-9a-f]{24}:3\]$/m);
142
+ assert.match(output.stdout,/✗ \[fedcba98\] ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:4\]/);
143
+ assert.equal((output.stdout.match(/✓ \[abcdef12\] OK coding_tree 42ms/g)||[]).length,1,'duplicate terminal result must print once');
119
144
  const resultJournal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',`${marker.result_session_id}.json`),'utf8'));
120
- assert.deepEqual(resultJournal.events.map(event=>event.seq),[1,2]);
145
+ assert.deepEqual(resultJournal.events.map(event=>event.seq),[1,2,3,4]);
146
+ assert.deepEqual(resultJournal.events.map(event=>event.event_type),['tool.call','tool.result','tool.call','tool.result']);
121
147
  assert.equal(resultJournal.events[0].request_id,'abcdef1234567890');
122
148
  assert.ok(resultJournal.events.every(event=>/^[0-9a-f]{64}$/.test(event.payload_sha256)));
123
149
  assert.doesNotMatch(JSON.stringify(resultJournal),/TOOL IN|TOOL OUT|stdout_excerpt|stderr_excerpt/);
124
- assert.doesNotMatch(output.stdout,/abcdef12|fedcba98/);
150
+ assert.match(output.stdout,/abcdef12/);
151
+ assert.match(output.stdout,/fedcba98/);
125
152
  assert.doesNotMatch(output.stdout,/DO_NOT_PRINT_PAYLOAD|DO_NOT_PRINT_TOKEN/);
126
- assert.doesNotMatch(output.stdout,/BRIDGE\.STDOUT/);
153
+ assert.match(output.stdout,/\[BRIDGE\.STDOUT\] IN coding_tree/);
154
+ assert.match(output.stdout,/\[BRIDGE\.STDOUT\] OUT coding_tree/);
155
+ assert.match(output.stdout,/\[BRIDGE\.STDOUT\] ERR coding_file_read/);
127
156
  assert.doesNotMatch(output.stdout,/\[TOOL IN \]|\[TOOL OUT\]/);
128
157
  assert.doesNotMatch(output.stdout,/payload bytes=/);
129
158
  child.stdin.end();
@@ -132,7 +161,7 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
132
161
  const commands=await traceCommands(fx.trace);
133
162
  assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
134
163
  assert.match(output.stdout,/^DEADBYTE MCP$/m);
135
- assert.match(output.stdout,/Live activity/i);
164
+ assert.match(output.stdout,/Unified live timeline/i);
136
165
  assert.match(output.stdout,/🔐 \[AUTH\] Revoking session authority/);
137
166
  assert.doesNotMatch(output.stdout,/disarmautonomous OK|disarmcoding OK|disarmprocess OK|disarmhost OK|DEADBYTE MCP = PARKED/);
138
167
  const withoutApprovedEmoji=output.stdout.replace(/[📜📥📤✅❌⚠♻🛡▶🔐🚀🔌]/gu,'');
@@ -143,7 +172,7 @@ winTest('bare remote uses compact human logs by default, keeps authority semanti
143
172
  }
144
173
  });
145
174
 
146
- winTest('remote --resume replays only terminal results after the cursor before starting a new live session',async()=>{
175
+ winTest('remote --resume replays sanitized tool-call and terminal result events after the cursor without duplicates or loss',async()=>{
147
176
  const fx=await fixture();
148
177
  let first=null,second=null;
149
178
  try{
@@ -151,17 +180,25 @@ winTest('remote --resume replays only terminal results after the cursor before s
151
180
  const firstOutput=await collect(first);
152
181
  await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
153
182
  const liveDeadline=Date.now()+3000;
154
- while(Date.now()<liveDeadline&&!/\[LOG\] Live activity/.test(firstOutput.stdout)) await sleep(50);
183
+ while(Date.now()<liveDeadline&&!/\[LOG\] Unified live timeline/.test(firstOutput.stdout)) await sleep(50);
155
184
  const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
156
185
  const auditFile=path.join(auditDir,'segment-00000001.jsonl');
157
- await writeFile(auditFile,JSON.stringify({seq:1,event_type:'tool.result',tool:'coding_tree',request_id:'resume-request-1',outcome:{is_error:false}})+'\n','utf8');
186
+ const firstBatch=[
187
+ {seq:1,event_type:'tool.request',tool:'coding_tree',request_id:'resume-request-1',created_at_utc:'2026-09-18T16:30:00.000Z',args:{root_id:'projects',path:'src'}},
188
+ {seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'resume-request-1',created_at_utc:'2026-09-18T16:30:00.100Z',outcome:{is_error:false}}
189
+ ];
190
+ await writeFile(auditFile,firstBatch.map(JSON.stringify).join('\n')+'\n','utf8');
158
191
  let deadline=Date.now()+3000;
159
- while(Date.now()<deadline&&!/coding_tree \[cursor r1:[0-9a-f]{24}:1\]/.test(firstOutput.stdout)) await sleep(50);
160
- const cursor1=firstOutput.stdout.match(/coding_tree \[cursor (r1:[0-9a-f]{24}:1)\]/)?.[1];
192
+ while(Date.now()<deadline&&!/coding_tree .*\[cursor r1:[0-9a-f]{24}:2\]/.test(firstOutput.stdout)) await sleep(50);
193
+ const cursor1=firstOutput.stdout.match(/coding_tree .*\[cursor (r1:[0-9a-f]{24}:2)\]/)?.[1];
161
194
  assert.ok(cursor1,'first result cursor missing');
162
- await writeFile(auditFile,JSON.stringify({seq:2,event_type:'tool.result',tool:'coding_file_read',request_id:'resume-request-2',outcome:{is_error:false}})+'\n',{encoding:'utf8',flag:'a'});
195
+ const secondBatch=[
196
+ {seq:3,event_type:'tool.request',tool:'coding_file_read',request_id:'resume-request-2',created_at_utc:'2026-09-18T16:30:01.000Z',args:{root_id:'projects',path:'src/deadbyte-cli.mjs',token:'DO_NOT_REPLAY_SECRET'}},
197
+ {seq:4,event_type:'tool.result',tool:'coding_file_read',request_id:'resume-request-2',created_at_utc:'2026-09-18T16:30:01.100Z',outcome:{is_error:false}}
198
+ ];
199
+ await writeFile(auditFile,secondBatch.map(JSON.stringify).join('\n')+'\n',{encoding:'utf8',flag:'a'});
163
200
  deadline=Date.now()+3000;
164
- while(Date.now()<deadline&&!/coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/.test(firstOutput.stdout)) await sleep(50);
201
+ while(Date.now()<deadline&&!/coding_file_read .*\[cursor r1:[0-9a-f]{24}:4\]/.test(firstOutput.stdout)) await sleep(50);
165
202
  first.stdin.end();
166
203
  const firstEnded=await Promise.race([firstOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
167
204
  assert.equal(firstEnded.timeout,undefined);
@@ -169,14 +206,20 @@ winTest('remote --resume replays only terminal results after the cursor before s
169
206
  second=spawn(process.execPath,[cliPath,'remote','--resume',cursor1],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
170
207
  const secondOutput=await collect(second);
171
208
  deadline=Date.now()+5000;
172
- while(Date.now()<deadline&&(!/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/.test(secondOutput.stdout)||
173
- !/\[LOG\] Live activity/.test(secondOutput.stdout))) await sleep(50);
174
- assert.match(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:2\]/);
209
+ while(Date.now()<deadline&&(!/coding_file_read\s+\| Arguments: .*REPLAY cursor r1:[0-9a-f]{24}:3/.test(secondOutput.stdout)||
210
+ !/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:4\]/.test(secondOutput.stdout)||
211
+ !/\[LOG\] Unified live timeline/.test(secondOutput.stdout))) await sleep(50);
212
+ assert.match(secondOutput.stdout,/^2026-09-18T16:30:01\.000Z \| coding_file_read\s+\| Arguments: .*\"path\":\"src\/deadbyte-cli\.mjs\".*REPLAY cursor r1:[0-9a-f]{24}:3$/m);
213
+ assert.match(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:4\]/);
175
214
  assert.match(secondOutput.stdout,/♻️ \[SESSION\] Previous result stream replayed/);
215
+ assert.doesNotMatch(secondOutput.stdout,/DO_NOT_REPLAY_SECRET/);
176
216
  assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_tree/);
177
- const replayIndex=secondOutput.stdout.indexOf('[REPLAY]');
178
- const liveIndex=secondOutput.stdout.indexOf('[LOG] Live activity');
179
- assert.ok(replayIndex>=0&&liveIndex>replayIndex,'resume replay must happen before new live tail');
217
+ assert.equal((secondOutput.stdout.match(/\| coding_file_read\s+\| Arguments:/g)||[]).length,1,'tool-call replay duplicated');
218
+ assert.equal((secondOutput.stdout.match(/\[REPLAY\] \[OK\] coding_file_read/g)||[]).length,1,'terminal replay duplicated');
219
+ const callReplayIndex=secondOutput.stdout.indexOf('| coding_file_read');
220
+ const resultReplayIndex=secondOutput.stdout.indexOf('[REPLAY] [OK] coding_file_read');
221
+ const liveIndex=secondOutput.stdout.indexOf('[LOG] Unified live timeline');
222
+ assert.ok(callReplayIndex>=0&&resultReplayIndex>callReplayIndex&&liveIndex>resultReplayIndex,'resume ordering must be call -> result -> live tail');
180
223
  second.stdin.end();
181
224
  const secondEnded=await Promise.race([secondOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
182
225
  assert.equal(secondEnded.timeout,undefined);
@@ -187,43 +230,17 @@ winTest('remote --resume replays only terminal results after the cursor before s
187
230
  }
188
231
  });
189
232
 
190
- winTest('remote --verbose preserves detailed audit and bridge log streaming for debugging',async()=>{
233
+ winTest('remote --verbose is rejected because R37 has one unified remote mode',async()=>{
191
234
  const fx=await fixture();
192
- let child=null;
193
235
  try{
194
- child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
236
+ const child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
195
237
  const output=await collect(child);
196
- const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
197
- assert.equal(ready.length>=8,true);
198
- const liveDeadline=Date.now()+3000;
199
- while(Date.now()<liveDeadline&&!/Live logs active/.test(output.stdout)) await sleep(50);
200
- assert.match(output.stdout,/Live logs active/);
201
- const auditDir=path.join(fx.state,'audit');
202
- const runtimeLogs=path.join(fx.state,'runtime','logs');
203
- await mkdir(auditDir,{recursive:true});
204
- await mkdir(runtimeLogs,{recursive:true});
205
- await writeFile(path.join(auditDir,'segment-00000001.jsonl'),[
206
- JSON.stringify({seq:1,event_type:'tool.request',tool:'coding_tree',request_id:'abcdef1234567890'}),
207
- JSON.stringify({seq:2,event_type:'tool.result',tool:'coding_tree',request_id:'abcdef1234567890',outcome:{is_error:false}})
208
- ].join('\n')+'\n','utf8');
209
- await writeFile(path.join(runtimeLogs,'bridge.stdout.log'),[
210
- '[TOOL IN ] 2026-09-16T00:00:00.000Z #000001 coding_tree {}',
211
- '[TOOL OUT] 2026-09-16T00:00:00.001Z #000001 coding_tree {"is_error":false}'
212
- ].join('\n')+'\n','utf8');
213
- const deadline=Date.now()+3000;
214
- while(Date.now()<deadline&&(!/\[AUDIT #2\] tool\.result coding_tree abcdef12/.test(output.stdout)||!/\[BRIDGE\.STDOUT\] \[TOOL OUT\].*coding_tree/.test(output.stdout))) await sleep(50);
215
- assert.match(output.stdout,/\[AUDIT #1\] tool\.request coding_tree abcdef12/);
216
- assert.match(output.stdout,/\[AUDIT #2\] tool\.result coding_tree abcdef12/);
217
- assert.match(output.stdout,/\[BRIDGE\.STDOUT\] \[TOOL IN \].*coding_tree/);
218
- assert.match(output.stdout,/\[BRIDGE\.STDOUT\] \[TOOL OUT\].*coding_tree/);
219
- child.stdin.end();
220
238
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
221
- assert.equal(ended.timeout,undefined,'verbose remote session did not exit after input closed');
222
- assert.equal(ended.code,0,output.stderr);
223
- }finally{
224
- if(child?.exitCode===null) child.kill();
225
- await rm(fx.dir,{recursive:true,force:true});
226
- }
239
+ assert.equal(ended.timeout,undefined);
240
+ assert.notEqual(ended.code,0);
241
+ assert.match(output.stderr,/unsupported remote command '--verbose'/i);
242
+ assert.deepEqual(await traceCommands(fx.trace),[],'rejected legacy flag must not touch runtime authority');
243
+ }finally{await rm(fx.dir,{recursive:true,force:true});}
227
244
  });
228
245
 
229
246
  winTest('detached guardian revokes authority, parks, and removes the session marker after the owner is hard-killed',async()=>{
@@ -233,8 +250,8 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
233
250
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
234
251
  const output=await collect(child);
235
252
  const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
236
- assert.deepEqual(ready.slice(0,8),[
237
- 'start','armprocess','armcoding','armautonomous',
253
+ assert.deepEqual(ready.slice(0,9),[
254
+ 'status','start','armprocess','armcoding','armautonomous',
238
255
  'processstatus','codingstatus','autonomousstatus','hoststatus'
239
256
  ]);
240
257
  const markerPath=path.join(fx.state,'runtime','remote-session.json');
@@ -277,7 +294,7 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
277
294
  assert.equal(ended.timeout,undefined,'failed startup did not terminate');
278
295
  assert.notEqual(ended.code,0);
279
296
  const commands=await traceCommands(fx.trace);
280
- assert.deepEqual(commands.slice(0,3),['start','armprocess','armcoding']);
297
+ assert.deepEqual(commands.slice(0,4),['status','start','armprocess','armcoding']);
281
298
  assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
282
299
  assert.doesNotMatch(output.stdout,/Live logs/i);
283
300
  }finally{await rm(fx.dir,{recursive:true,force:true});}
@@ -294,7 +311,8 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
294
311
  const output=await collect(child);
295
312
  const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
296
313
  assert.deepEqual(ready.slice(0,5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
297
- assert.equal(ready[5],'start');
314
+ assert.equal(ready[5],'status');
315
+ assert.equal(ready[6],'start');
298
316
  assert.match(output.stdout,/♻️ \[RECOVERY\] Stale remote session pid=2147483000; revoking authority before restart\./);
299
317
  child.stdin.end();
300
318
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);