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.
- package/MANIFEST.SHA256 +81 -59
- package/README.txt +13 -14
- package/bin/WORKER-REGISTRY.txt +3 -3
- package/bin/appcontainer-stage.exe +0 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.exe +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.exe +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.exe +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.exe +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.exe +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.exe +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.exe +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.exe +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.exe +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.exe +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.exe +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +3 -3
- package/controller/deadbyte-controller.ps1 +1 -1
- package/controller/deadbyte-process-policy.json +1 -1
- package/docs/ARCHITECTURE.md +1 -1
- package/package.json +1 -1
- package/scripts/build-containment.ps1 +15 -15
- package/scripts/deferred-slot-operation-r37-0110.mjs +124 -0
- package/scripts/deferred-slot-operation.mjs +1 -1
- package/scripts/final-closure-r36.mjs +113 -0
- package/scripts/final-closure-r37-0110.mjs +113 -0
- package/scripts/final-closure-verify-r36.mjs +208 -0
- package/scripts/final-closure-verify-r37-0110.mjs +208 -0
- package/scripts/final-closure-verify.mjs +48 -48
- package/scripts/final-closure.mjs +13 -13
- package/scripts/gate-windows-r36.ps1 +42 -0
- package/scripts/gate-windows-r37-0110.ps1 +42 -0
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-r36.mjs +200 -0
- package/scripts/release-parity-r37-0110.mjs +200 -0
- package/scripts/release-parity-tests.ps1 +2 -2
- package/scripts/release-parity.mjs +37 -37
- package/scripts/windows-gate-evidence-r36.mjs +52 -0
- package/scripts/windows-gate-evidence-r37-0110.mjs +52 -0
- package/scripts/windows-gate-evidence.mjs +5 -5
- package/src/agent-memory-plane.mjs +98 -0
- package/src/autonomous-output-contracts.mjs +5 -2
- package/src/autonomous-runtime.mjs +155 -31
- package/src/autonomous-timeout-recovery.mjs +27 -0
- package/src/deadbyte-cli.mjs +90 -94
- package/src/effect-ledger.mjs +47 -0
- package/src/observation-lease.mjs +32 -0
- package/src/remote-live-log.mjs +81 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +15 -9
- package/src/remote-result-journal.mjs +23 -4
- package/src/version.mjs +1 -1
- package/src/worker-scheduler.mjs +53 -0
- package/test/autonomous-output-contract.test.mjs +1 -1
- package/test/cli-entrypoint.test.mjs +9 -0
- package/test/containment.test.mjs +18 -0
- package/test/core.test.mjs +1 -1
- package/test/production-docs.test.mjs +5 -5
- package/test/r33-closeout-regression.test.mjs +4 -4
- package/test/r33-finalization.test.mjs +6 -6
- package/test/r36-finalization.test.mjs +5 -5
- package/test/r36-release-identity.test.mjs +12 -19
- package/test/r37-0110-history.test.mjs +20 -0
- package/test/r37-agent-fabric.test.mjs +92 -0
- package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
- package/test/r37-finalization.test.mjs +74 -0
- package/test/r37-remote-tool-call-timeline.test.mjs +94 -0
- package/test/release-version.test.mjs +35 -36
- package/test/remote-live-log-r37.test.mjs +50 -0
- 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
|
|
7
|
-
const text
|
|
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('
|
|
10
|
-
const pkg
|
|
11
|
-
const [version,
|
|
12
|
-
text('src/version.mjs'),
|
|
13
|
-
text('controller/
|
|
14
|
-
text('scripts/
|
|
15
|
-
text('
|
|
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,
|
|
26
|
-
assert.match(version
|
|
27
|
-
assert.match(controller
|
|
28
|
-
assert.match(gate
|
|
29
|
-
assert.match(readme
|
|
30
|
-
assert.match(architecture
|
|
31
|
-
assert.match(controllerReadme
|
|
32
|
-
assert.match(
|
|
33
|
-
assert.match(
|
|
34
|
-
assert.match(
|
|
35
|
-
assert.match(parity
|
|
36
|
-
assert.match(
|
|
37
|
-
assert.match(
|
|
38
|
-
assert.match(
|
|
39
|
-
assert.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
assert.match(
|
|
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
|
|
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.
|
|
74
|
+
const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'),{timeoutMs:8000});
|
|
75
75
|
assert.deepEqual(ready.slice(0,8),[
|
|
76
|
-
'
|
|
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\]
|
|
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
|
|
114
|
-
assert.match(output.stdout
|
|
115
|
-
assert.match(output.stdout
|
|
116
|
-
assert.match(output.stdout
|
|
117
|
-
assert.match(output.stdout
|
|
118
|
-
assert.equal((output.stdout.match(
|
|
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.
|
|
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.
|
|
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,/
|
|
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
|
|
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\]
|
|
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
|
-
|
|
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
|
|
160
|
-
const cursor1=firstOutput.stdout.match(/coding_tree
|
|
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
|
-
|
|
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
|
|
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&&(
|
|
173
|
-
!/\[
|
|
174
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
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:['
|
|
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
|
|
222
|
-
assert.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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,
|
|
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,
|
|
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],'
|
|
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}))]);
|