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.
- package/MANIFEST.SHA256 +64 -56
- package/README.txt +7 -7
- 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 +1 -1
- 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-r37-0110.mjs +113 -0
- package/scripts/final-closure-verify-r37-0110.mjs +208 -0
- package/scripts/final-closure-verify.mjs +16 -16
- package/scripts/final-closure.mjs +8 -8
- package/scripts/gate-windows-r37-0110.ps1 +42 -0
- package/scripts/gate-windows.ps1 +3 -3
- package/scripts/release-parity-r37-0110.mjs +200 -0
- package/scripts/release-parity-tests.ps1 +1 -1
- package/scripts/release-parity.mjs +14 -14
- package/scripts/windows-gate-evidence-r37-0110.mjs +52 -0
- package/scripts/windows-gate-evidence.mjs +4 -4
- package/src/deadbyte-cli.mjs +58 -23
- package/src/remote-live-log.mjs +23 -1
- package/src/remote-result-journal.mjs +23 -4
- package/src/version.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 +3 -3
- package/test/r33-closeout-regression.test.mjs +2 -2
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r36-release-identity.test.mjs +4 -4
- package/test/r37-0110-history.test.mjs +20 -0
- package/test/r37-finalization.test.mjs +14 -14
- package/test/r37-remote-tool-call-timeline.test.mjs +94 -0
- package/test/release-version.test.mjs +22 -29
- package/test/remote-live-log-r37.test.mjs +5 -2
- package/test/remote-session-cli.test.mjs +66 -25
package/src/version.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
6
7
|
|
|
@@ -16,6 +17,14 @@ test('package exposes npx-first deadbyte-mcp bin and is registry-publishable',as
|
|
|
16
17
|
assert.equal(pkg.publishConfig?.access,'public');
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
test('npx CLI reports the canonical package version through --version, -v and version',()=>{
|
|
21
|
+
for(const arg of ['--version','-v','version']){
|
|
22
|
+
const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
|
|
23
|
+
assert.equal(result.status,0,result.stderr);
|
|
24
|
+
assert.equal(result.stdout.trim(),'0.11.1');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
19
28
|
test('npx CLI remote path invokes stable PS1 directly and never routes through CMD',async()=>{
|
|
20
29
|
const source=await readFile(cliPath,'utf8');
|
|
21
30
|
assert.match(source,/^#!\/usr\/bin\/env node/m);
|
|
@@ -230,6 +230,24 @@ test('native build script targets Windows 10 and preserves MSVC diagnostics', as
|
|
|
230
230
|
assert.doesNotMatch(source, /\$VsWhere = Join-Path \$\{env:ProgramFiles\(x86\)\}/);
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
+
test('native build is reproducible at both MSVC object and linker boundaries', async () => {
|
|
234
|
+
const buildScript = fileURLToPath(new URL('../scripts/build-containment.ps1', import.meta.url));
|
|
235
|
+
const source = await readFile(buildScript, 'utf8');
|
|
236
|
+
const lines = source.split(/\r?\n/);
|
|
237
|
+
const commonLine = lines.find(line => line.startsWith('$Common ='));
|
|
238
|
+
const bootstrapLine = lines.find(line => line.startsWith('$BootstrapCommon ='));
|
|
239
|
+
assert.ok(commonLine?.includes('/Brepro'), 'common compiler flags must include /Brepro');
|
|
240
|
+
assert.ok(bootstrapLine?.includes('/Brepro'), 'bootstrap compiler flags must include /Brepro');
|
|
241
|
+
for (const name of [
|
|
242
|
+
'Launcher','Probe','BootstrapKernel','BootstrapAdvapi','StageLauncher','BootstrapExit',
|
|
243
|
+
'ChildControlStage','ChildControlProbe','ContainedLauncher','ContainedWorker',
|
|
244
|
+
'ContainedReverseWorker','TunnelHost','ProcessHost'
|
|
245
|
+
]) {
|
|
246
|
+
const line = lines.find(candidate => candidate.startsWith(`$${name}Command =`));
|
|
247
|
+
assert.ok(line?.includes('/link /Brepro /INCREMENTAL:NO'), `${name} linker flags must be reproducible and non-incremental`);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
233
251
|
|
|
234
252
|
|
|
235
253
|
test('native build command composition is Windows PowerShell 5.1 parser-safe', async () => {
|
package/test/core.test.mjs
CHANGED
|
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
|
|
|
105
105
|
assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
|
|
106
106
|
assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
|
|
107
107
|
assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
|
|
108
|
-
assert.equal(result.receipt.runtime.package_version, '0.11.
|
|
108
|
+
assert.equal(result.receipt.runtime.package_version, '0.11.1');
|
|
109
109
|
assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
|
|
110
110
|
assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
|
|
111
111
|
assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);
|
|
@@ -9,9 +9,9 @@ const readme=()=>readFile(path.join(root,'README.txt'),'utf8');
|
|
|
9
9
|
|
|
10
10
|
test('production README documents one-command stdio configs for major MCP clients',async()=>{
|
|
11
11
|
const text=await readme();
|
|
12
|
-
assert.match(text,/npx -y deadbyte-mcp@0\.11\.
|
|
12
|
+
assert.match(text,/npx -y deadbyte-mcp@0\.11\.1 mcp/);
|
|
13
13
|
assert.match(text,/CLAUDE DESKTOP/);
|
|
14
|
-
assert.match(text,/"mcpServers"[\s\S]*?"command"\s*:\s*"npx"[\s\S]*?"-y"[\s\S]*?"deadbyte-mcp@0\.11\.
|
|
14
|
+
assert.match(text,/"mcpServers"[\s\S]*?"command"\s*:\s*"npx"[\s\S]*?"-y"[\s\S]*?"deadbyte-mcp@0\.11\.1"[\s\S]*?"mcp"/);
|
|
15
15
|
assert.match(text,/CURSOR/);
|
|
16
16
|
assert.match(text,/\.cursor\/mcp\.json/);
|
|
17
17
|
assert.match(text,/VS CODE/);
|
|
@@ -20,7 +20,7 @@ test('production README documents one-command stdio configs for major MCP client
|
|
|
20
20
|
assert.match(text,/CODEX/);
|
|
21
21
|
assert.match(text,/\[mcp_servers\.deadbyte\]/);
|
|
22
22
|
assert.match(text,/command\s*=\s*"npx"/);
|
|
23
|
-
assert.match(text,/args\s*=\s*\["-y",\s*"deadbyte-mcp@0\.11\.
|
|
23
|
+
assert.match(text,/args\s*=\s*\["-y",\s*"deadbyte-mcp@0\.11\.1",\s*"mcp"\]/);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
test('production README records tracer bullet, semantic scope, remote UX, safety and document extension',async()=>{
|
|
@@ -102,9 +102,9 @@ test('Windows R37 gate emits manifest-bound machine-readable evidence outside th
|
|
|
102
102
|
assert.equal(evidence.schema,'deadbyte.r37-windows-gate.v1');
|
|
103
103
|
assert.equal(evidence.status,'passed');
|
|
104
104
|
assert.equal(evidence.exit_code,0);
|
|
105
|
-
assert.equal(evidence.version,'0.11.
|
|
105
|
+
assert.equal(evidence.version,'0.11.1');
|
|
106
106
|
assert.equal(evidence.manifest_sha256,manifestSha);
|
|
107
|
-
assert.equal(evidence.marker,'DEADBYTE V0.11.
|
|
107
|
+
assert.equal(evidence.marker,'DEADBYTE V0.11.1 / R37 WINDOWS BATCH GATE: PASS');
|
|
108
108
|
assert.ok(summary.path.startsWith(path.join(stateRoot,'evidence','release')));
|
|
109
109
|
}finally{await rm(stateRoot,{recursive:true,force:true});}
|
|
110
110
|
});
|
|
@@ -33,10 +33,10 @@ test('R37 final closure verifier is implementation-independent from production h
|
|
|
33
33
|
assert.match(verifier, /DEADBYTE-R27-SLOT-UPDATE-V1/);
|
|
34
34
|
assert.match(verifier, /DEADBYTE-MACHINE-PING-V1/);
|
|
35
35
|
assert.match(verifier, /DEADBYTE-R37-FINAL-CLOSURE-V1/);
|
|
36
|
-
assert.match(writer, /pointer\.version==='0\.11\.
|
|
37
|
-
assert.match(verifier, /envelope\.body\.version==='0\.11\.
|
|
36
|
+
assert.match(writer, /pointer\.version==='0\.11\.1'/);
|
|
37
|
+
assert.match(verifier, /envelope\.body\.version==='0\.11\.1'/);
|
|
38
38
|
assert.match(verifier, /release_label==='R37 FINAL MACHINE BASELINE'/);
|
|
39
|
-
assert.match(verifier, /
|
|
39
|
+
assert.match(verifier, /08c2a0008b1f84c90f7ce7faa1e3decbd03f33612bc0fdc7d1ba591b548798e8/);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
test('final closure verifier canonicalizes traversal order before exact-tree comparison', async () => {
|
|
@@ -10,10 +10,10 @@ const R36_RELEASE_ID='v0.10.0-8d1e5f09745e7647';
|
|
|
10
10
|
const R36_MANIFEST='8d1e5f09745e764796b60e0a8a0168be7334ed4bf834fad5974a80eba295b4e8';
|
|
11
11
|
|
|
12
12
|
test('sealed R36 historical proof scripts preserve 0.10.0 / R36 and exact R35 predecessor',async()=>{
|
|
13
|
-
const [gate,parity,closure,closureVerify,windowsGate,
|
|
13
|
+
const [gate,parity,closure,closureVerify,windowsGate,r37_0110Parity]=await Promise.all([
|
|
14
14
|
text('scripts/gate-windows-r36.ps1'),text('scripts/release-parity-r36.mjs'),
|
|
15
15
|
text('scripts/final-closure-r36.mjs'),text('scripts/final-closure-verify-r36.mjs'),
|
|
16
|
-
text('scripts/windows-gate-evidence-r36.mjs'),text('scripts/release-parity.mjs')
|
|
16
|
+
text('scripts/windows-gate-evidence-r36.mjs'),text('scripts/release-parity-r37-0110.mjs')
|
|
17
17
|
]);
|
|
18
18
|
assert.match(gate,/DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
|
|
19
19
|
assert.match(parity,/deadbyte\.r36-parity\.v1/);
|
|
@@ -23,8 +23,8 @@ test('sealed R36 historical proof scripts preserve 0.10.0 / R36 and exact R35 pr
|
|
|
23
23
|
assert.match(closure,/version:'0\.10\.0'/);
|
|
24
24
|
assert.match(closureVerify,/DEADBYTE-R36-FINAL-CLOSURE-V1/);
|
|
25
25
|
assert.match(windowsGate,/deadbyte\.r36-windows-gate\.v1/);
|
|
26
|
-
assert.match(
|
|
27
|
-
assert.match(
|
|
26
|
+
assert.match(r37_0110Parity,new RegExp(R36_RELEASE_ID.replaceAll('.','\\.')));
|
|
27
|
+
assert.match(r37_0110Parity,new RegExp(R36_MANIFEST));
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test('current R37 controller still binds production remote to compact profile and restores parent environment',async()=>{
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
const root=new URL('../',import.meta.url);
|
|
5
|
+
const text=rel=>readFile(new URL(rel,root),'utf8');
|
|
6
|
+
|
|
7
|
+
test('sealed public 0.11.0 release scripts remain preserved as historical R37 authority',async()=>{
|
|
8
|
+
const [gate,evidence,parity,closure,verify,deferred]=await Promise.all([
|
|
9
|
+
text('scripts/gate-windows-r37-0110.ps1'),text('scripts/windows-gate-evidence-r37-0110.mjs'),
|
|
10
|
+
text('scripts/release-parity-r37-0110.mjs'),text('scripts/final-closure-r37-0110.mjs'),
|
|
11
|
+
text('scripts/final-closure-verify-r37-0110.mjs'),text('scripts/deferred-slot-operation-r37-0110.mjs')
|
|
12
|
+
]);
|
|
13
|
+
assert.match(gate,/V0\.11\.0 \/ R37/);
|
|
14
|
+
assert.match(evidence,/version:'0\.11\.0'/);
|
|
15
|
+
assert.match(parity,/v0\.10\.0-8d1e5f09745e7647/);
|
|
16
|
+
assert.match(parity,/candidate\?\.version==='0\.11\.0'/);
|
|
17
|
+
assert.match(closure,/version:'0\.11\.0'/);
|
|
18
|
+
assert.match(verify,/value\?\.version==='0\.11\.0'/);
|
|
19
|
+
assert.match(deferred,/--expected-version','0\.11\.0'/);
|
|
20
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import { validateParityEvidence,
|
|
4
|
+
import { validateParityEvidence, R37_PREDECESSOR_RELEASE_ID, R37_PREDECESSOR_MANIFEST } from '../scripts/release-parity.mjs';
|
|
5
5
|
import { verifyFinalClosureObject } from '../scripts/final-closure-verify.mjs';
|
|
6
6
|
|
|
7
7
|
const root=new URL('../',import.meta.url);
|
|
@@ -11,22 +11,22 @@ function parityFixture(){
|
|
|
11
11
|
const manifest='a'.repeat(64);
|
|
12
12
|
return {
|
|
13
13
|
schema:'deadbyte.r37-parity.v1',status:'passed',execution_provider:'deadbyte_mcp',rdc_operation_count:0,
|
|
14
|
-
candidate:{release_id:`v0.11.
|
|
14
|
+
candidate:{release_id:`v0.11.1-${manifest.slice(0,16)}`,version:'0.11.1',manifest_sha256:manifest},
|
|
15
15
|
immutable_slot:{protected:true,release_root:'C:/immutable/r37',development_root:'D:/Projects/deadbyte-mcp-r36-dev'},
|
|
16
16
|
local_full:{status:'passed',tool_count:76,strict_output_schemas:true},
|
|
17
17
|
public_compact:{status:'passed',tool_count:41,capability_closure:true,gateway_count:5},
|
|
18
18
|
autonomous:{status:'passed',signed_release_verified:true,observed_failure_before_replan:true},
|
|
19
19
|
before_rollback_disarmed:{status:'passed',capability_closure:true,coding_armed:false,autonomous_armed:false},
|
|
20
20
|
rollback:{state:'completed'},stage_restore:{status:'restored',manifest_sha256:manifest},restore:{state:'completed'},
|
|
21
|
-
predecessor:{release_id:
|
|
21
|
+
predecessor:{release_id:R37_PREDECESSOR_RELEASE_ID,version:'0.11.0',manifest_sha256:R37_PREDECESSOR_MANIFEST},
|
|
22
22
|
predecessor_disarmed:{status:'passed',tool_count:41,capability_closure:true},
|
|
23
|
-
successor:{release_id:`v0.11.
|
|
23
|
+
successor:{release_id:`v0.11.1-${manifest.slice(0,16)}`,version:'0.11.1',manifest_sha256:manifest},
|
|
24
24
|
successor_disarmed:{status:'passed',capability_closure:true,coding_armed:false,autonomous_armed:false},
|
|
25
25
|
requirements:Object.fromEntries(['tree_read','precondition_mutation','search','owned_process','observation','coding_loop','autonomy','interrupted_recovery','evidence_verification','compact_closure','full_surface'].map(key=>[key,true]))
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
test('R37 parity validator requires full-76, compact closure, exact
|
|
29
|
+
test('R37 parity validator requires full-76, compact closure, exact public 0.11.0 predecessor and immutable-slot separation',()=>{
|
|
30
30
|
const good=parityFixture();
|
|
31
31
|
assert.equal(validateParityEvidence(good),true);
|
|
32
32
|
assert.throws(()=>validateParityEvidence({...good,local_full:{...good.local_full,tool_count:75}}),/full 76/i);
|
|
@@ -36,33 +36,33 @@ test('R37 parity validator requires full-76, compact closure, exact R36 predeces
|
|
|
36
36
|
assert.throws(()=>validateParityEvidence({...good,stage_restore:{status:'missing',manifest_sha256:good.candidate.manifest_sha256}}),/stage restore/i);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
test('R37 parity captures R37 bridge, exact
|
|
39
|
+
test('R37 parity captures R37 bridge, exact public 0.11.0 predecessor, stage reseed and successor restore',async()=>{
|
|
40
40
|
const parity=await text('scripts/release-parity.mjs');
|
|
41
|
-
assert.match(parity,/
|
|
42
|
-
assert.match(parity,/
|
|
41
|
+
assert.match(parity,/R37_PREDECESSOR_RELEASE_ID='v0\.11\.0-08c2a0008b1f84c9'/);
|
|
42
|
+
assert.match(parity,/08c2a0008b1f84c90f7ce7faa1e3decbd03f33612bc0fdc7d1ba591b548798e8/);
|
|
43
43
|
assert.match(parity,/const stageRestore=await restoreFrozenStage/);
|
|
44
|
-
assert.match(parity,/
|
|
44
|
+
assert.match(parity,/publicR37Disarmed/);
|
|
45
45
|
assert.match(parity,/scheduleDeferred\('activate',candidateManifest,\{bridgeRoot:successorBridge\.root,bridgeScriptSha256:successorBridge\.sha256\}\)/);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
-
test('R37 current final closure writer and independent verifier bind R37 domains and
|
|
48
|
+
test('R37 current final closure writer and independent verifier bind R37 domains and public 0.11.0 predecessor',async()=>{
|
|
49
49
|
const [writer,verifier]=await Promise.all([text('scripts/final-closure.mjs'),text('scripts/final-closure-verify.mjs')]);
|
|
50
50
|
assert.equal(typeof verifyFinalClosureObject,'function');
|
|
51
51
|
assert.match(writer,/DEADBYTE-R37-FINAL-CLOSURE-V1/);
|
|
52
52
|
assert.match(writer,/deadbyte\.r37-final-closure\.v1/);
|
|
53
|
-
assert.match(writer,/pointer\.version==='0\.11\.
|
|
53
|
+
assert.match(writer,/pointer\.version==='0\.11\.1'/);
|
|
54
54
|
assert.match(writer,/R37 FINAL MACHINE BASELINE/);
|
|
55
55
|
assert.match(writer,/deadbyte\.r37-parity\.v1/);
|
|
56
56
|
assert.match(writer,/deadbyte\.r37-windows-gate\.v1/);
|
|
57
|
-
assert.match(writer,/
|
|
57
|
+
assert.match(writer,/R37_PREDECESSOR_RELEASE_ID/);
|
|
58
58
|
assert.doesNotMatch(verifier,/from ['"]\.\.\/src\//);
|
|
59
59
|
assert.match(verifier,/DEADBYTE-R37-FINAL-CLOSURE-V1/);
|
|
60
60
|
assert.match(verifier,/deadbyte\.r37-final-closure\.v1/);
|
|
61
|
-
assert.match(verifier,/envelope\.body\.version==='0\.11\.
|
|
61
|
+
assert.match(verifier,/envelope\.body\.version==='0\.11\.1'/);
|
|
62
62
|
assert.match(verifier,/R37 FINAL MACHINE BASELINE/);
|
|
63
63
|
assert.match(verifier,/deadbyte\.r37-parity\.v1/);
|
|
64
64
|
assert.match(verifier,/deadbyte\.r37-windows-gate\.v1/);
|
|
65
|
-
assert.match(verifier,/
|
|
65
|
+
assert.match(verifier,/08c2a0008b1f84c90f7ce7faa1e3decbd03f33612bc0fdc7d1ba591b548798e8/);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
test('R37 bounded public parity suite covers current release identity and agent fabric',async()=>{
|
|
@@ -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
|
+
});
|
|
@@ -5,10 +5,10 @@ import { createHash } from 'node:crypto';
|
|
|
5
5
|
|
|
6
6
|
const root=new URL('../',import.meta.url);
|
|
7
7
|
const text=rel=>readFile(new URL(rel,root),'utf8');
|
|
8
|
-
const
|
|
9
|
-
const
|
|
8
|
+
const R37_PREDECESSOR_RELEASE_ID='v0.11.0-08c2a0008b1f84c9';
|
|
9
|
+
const R37_PREDECESSOR_MANIFEST='08c2a0008b1f84c90f7ce7faa1e3decbd03f33612bc0fdc7d1ba591b548798e8';
|
|
10
10
|
|
|
11
|
-
test('R37
|
|
11
|
+
test('R37 patch release authority agrees on V0.11.1 and preserves exact public 0.11.0 predecessor',async()=>{
|
|
12
12
|
const pkg=JSON.parse(await text('package.json'));
|
|
13
13
|
const [version,controller,gate,readme,architecture,controllerReadme,deferred,parity,closure,closureVerify,windowsGate,semanticConfig]=await Promise.all([
|
|
14
14
|
text('src/version.mjs'),text('controller/deadbyte-controller.ps1'),text('scripts/gate-windows.ps1'),
|
|
@@ -17,35 +17,28 @@ test('R37 successor release authority agrees on V0.11.0 / R37 and preserves exac
|
|
|
17
17
|
text('scripts/final-closure-verify.mjs'),text('scripts/windows-gate-evidence.mjs'),
|
|
18
18
|
text('controller/deadbyte-semantic-lsp-config.json')
|
|
19
19
|
]);
|
|
20
|
-
assert.equal(pkg.version,'0.11.
|
|
21
|
-
assert.match(version,/DEADBYTE_VERSION = '0\.11\.
|
|
22
|
-
assert.match(controller,/CONTROLLER V0\.11\.
|
|
23
|
-
assert.match(gate,/DEADBYTE V0\.11\.
|
|
24
|
-
assert.match(readme,/^DEADBYTE MCP V0\.11\.
|
|
25
|
-
assert.match(architecture,/^# DEADBYTE MCP V0\.11\.
|
|
26
|
-
assert.match(controllerReadme,/^DEADBYTE MCP V0\.11\.
|
|
27
|
-
assert.match(
|
|
28
|
-
assert.match(
|
|
29
|
-
assert.match(
|
|
30
|
-
assert.match(parity,/
|
|
31
|
-
assert.match(
|
|
32
|
-
assert.match(
|
|
33
|
-
assert.match(
|
|
34
|
-
assert.match(closure,/DEADBYTE-R37-FINAL-CLOSURE-V1/);
|
|
35
|
-
assert.match(closure,/version:'0\.11\.0'/);
|
|
36
|
-
assert.match(closureVerify,/DEADBYTE V0\.11\.0 \/ R37 WINDOWS BATCH GATE: PASS/);
|
|
37
|
-
assert.match(windowsGate,/schema:'deadbyte\.r37-windows-gate\.v1'/);
|
|
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'/);
|
|
38
34
|
assert.equal(createHash('sha256').update(Buffer.from(semanticConfig,'utf8')).digest('hex'),'d274b099a298c1033fea8f1e8849751693bbc0448778d619400acb82d4f53a3b');
|
|
39
35
|
});
|
|
40
36
|
|
|
41
|
-
test('R37 keeps
|
|
42
|
-
const [parity,
|
|
43
|
-
text('src/remote-mcp-r36-parity-client.mjs'),
|
|
44
|
-
text('src/autonomous-runtime.mjs'),
|
|
45
|
-
text('src/remote-live-log.mjs')
|
|
46
|
-
]);
|
|
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')]);
|
|
47
39
|
assert.match(parity,/R36_EXPECTED_TOOLS\.length !== 76/);
|
|
48
40
|
assert.match(parity,/R36_COMPACT_TOOL_LIMIT=42/);
|
|
49
|
-
|
|
50
|
-
assert.match(
|
|
41
|
+
assert.match(cli,/controllerRuntimeReady/);
|
|
42
|
+
assert.match(cli,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
|
|
43
|
+
assert.doesNotMatch(cli,/remote --verbose/);
|
|
51
44
|
});
|
|
@@ -2,12 +2,15 @@ import test from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { createLiveTimelineTracker } from '../src/remote-live-log.mjs';
|
|
4
4
|
|
|
5
|
-
test('R37 live timeline
|
|
5
|
+
test('R37 live timeline renders sanitized tool-call detail and correlates terminal result with stable short span id',()=>{
|
|
6
6
|
let now=1000;
|
|
7
7
|
const tracker=createLiveTimelineTracker({clock:()=>now});
|
|
8
8
|
const run=tracker.observe({event_type:'tool.request',tool:'coding_file_read',request_id:'abcdef1234567890',
|
|
9
9
|
args:{root_id:'projects',path:'src/a.mjs',content:'SECRET',token:'SECRET'}});
|
|
10
|
-
assert.
|
|
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\"/);
|
|
11
14
|
now=1250;
|
|
12
15
|
const done=tracker.observe({event_type:'tool.result',tool:'coding_file_read',request_id:'abcdef1234567890',
|
|
13
16
|
outcome:{is_error:false,duration_ms:250}});
|
|
@@ -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 reuses a fully verified ONLINE runtime without invoking blocking start',async()=>{
|
|
69
|
+
const fx=await fixture({failCommand:'start',statusOnline:true});
|
|
70
|
+
let child=null;
|
|
71
|
+
try{
|
|
72
|
+
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
73
|
+
const output=await collect(child);
|
|
74
|
+
const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'),{timeoutMs:8000});
|
|
75
|
+
assert.deepEqual(ready.slice(0,8),[
|
|
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
|
+
|
|
68
93
|
winTest('bare remote uses one unified live timeline, keeps authority semantics, then disarms and parks when the foreground session ends',async()=>{
|
|
69
94
|
const fx=await fixture();
|
|
70
95
|
let child=null;
|
|
71
96
|
try{
|
|
72
97
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
73
98
|
const output=await collect(child);
|
|
74
|
-
const ready=await waitFor(fx.trace,commands=>commands.slice(0,
|
|
75
|
-
assert.deepEqual(ready.slice(0,
|
|
76
|
-
'start','armprocess','armcoding','armautonomous',
|
|
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');
|
|
@@ -111,13 +136,14 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
111
136
|
].join('\n')+'\n','utf8');
|
|
112
137
|
const logDeadline=Date.now()+3000;
|
|
113
138
|
while(Date.now()<logDeadline&&!/✗ \[fedcba98\] ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
|
|
114
|
-
assert.match(output.stdout
|
|
115
|
-
assert.match(output.stdout,/✓ \[abcdef12\] OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:
|
|
116
|
-
assert.match(output.stdout
|
|
117
|
-
assert.match(output.stdout,/✗ \[fedcba98\] ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:
|
|
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\]/);
|
|
118
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/);
|
|
@@ -146,7 +172,7 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
146
172
|
}
|
|
147
173
|
});
|
|
148
174
|
|
|
149
|
-
winTest('remote --resume replays
|
|
175
|
+
winTest('remote --resume replays sanitized tool-call and terminal result events after the cursor without duplicates or loss',async()=>{
|
|
150
176
|
const fx=await fixture();
|
|
151
177
|
let first=null,second=null;
|
|
152
178
|
try{
|
|
@@ -157,14 +183,22 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
157
183
|
while(Date.now()<liveDeadline&&!/\[LOG\] Unified live timeline/.test(firstOutput.stdout)) await sleep(50);
|
|
158
184
|
const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
|
|
159
185
|
const auditFile=path.join(auditDir,'segment-00000001.jsonl');
|
|
160
|
-
|
|
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');
|
|
161
191
|
let deadline=Date.now()+3000;
|
|
162
|
-
while(Date.now()<deadline&&!/coding_tree .*\[cursor r1:[0-9a-f]{24}:
|
|
163
|
-
const cursor1=firstOutput.stdout.match(/coding_tree .*\[cursor (r1:[0-9a-f]{24}:
|
|
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];
|
|
164
194
|
assert.ok(cursor1,'first result cursor missing');
|
|
165
|
-
|
|
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'});
|
|
166
200
|
deadline=Date.now()+3000;
|
|
167
|
-
while(Date.now()<deadline&&!/coding_file_read .*\[cursor r1:[0-9a-f]{24}:
|
|
201
|
+
while(Date.now()<deadline&&!/coding_file_read .*\[cursor r1:[0-9a-f]{24}:4\]/.test(firstOutput.stdout)) await sleep(50);
|
|
168
202
|
first.stdin.end();
|
|
169
203
|
const firstEnded=await Promise.race([firstOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
170
204
|
assert.equal(firstEnded.timeout,undefined);
|
|
@@ -172,14 +206,20 @@ winTest('remote --resume replays only terminal results after the cursor before s
|
|
|
172
206
|
second=spawn(process.execPath,[cliPath,'remote','--resume',cursor1],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
173
207
|
const secondOutput=await collect(second);
|
|
174
208
|
deadline=Date.now()+5000;
|
|
175
|
-
while(Date.now()<deadline&&(
|
|
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)||
|
|
176
211
|
!/\[LOG\] Unified live timeline/.test(secondOutput.stdout))) await sleep(50);
|
|
177
|
-
assert.match(secondOutput.stdout
|
|
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\]/);
|
|
178
214
|
assert.match(secondOutput.stdout,/♻️ \[SESSION\] Previous result stream replayed/);
|
|
215
|
+
assert.doesNotMatch(secondOutput.stdout,/DO_NOT_REPLAY_SECRET/);
|
|
179
216
|
assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_tree/);
|
|
180
|
-
|
|
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');
|
|
181
221
|
const liveIndex=secondOutput.stdout.indexOf('[LOG] Unified live timeline');
|
|
182
|
-
assert.ok(
|
|
222
|
+
assert.ok(callReplayIndex>=0&&resultReplayIndex>callReplayIndex&&liveIndex>resultReplayIndex,'resume ordering must be call -> result -> live tail');
|
|
183
223
|
second.stdin.end();
|
|
184
224
|
const secondEnded=await Promise.race([secondOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
185
225
|
assert.equal(secondEnded.timeout,undefined);
|
|
@@ -210,8 +250,8 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
|
|
|
210
250
|
child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
211
251
|
const output=await collect(child);
|
|
212
252
|
const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
|
|
213
|
-
assert.deepEqual(ready.slice(0,
|
|
214
|
-
'start','armprocess','armcoding','armautonomous',
|
|
253
|
+
assert.deepEqual(ready.slice(0,9),[
|
|
254
|
+
'status','start','armprocess','armcoding','armautonomous',
|
|
215
255
|
'processstatus','codingstatus','autonomousstatus','hoststatus'
|
|
216
256
|
]);
|
|
217
257
|
const markerPath=path.join(fx.state,'runtime','remote-session.json');
|
|
@@ -254,7 +294,7 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
|
|
|
254
294
|
assert.equal(ended.timeout,undefined,'failed startup did not terminate');
|
|
255
295
|
assert.notEqual(ended.code,0);
|
|
256
296
|
const commands=await traceCommands(fx.trace);
|
|
257
|
-
assert.deepEqual(commands.slice(0,
|
|
297
|
+
assert.deepEqual(commands.slice(0,4),['status','start','armprocess','armcoding']);
|
|
258
298
|
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
259
299
|
assert.doesNotMatch(output.stdout,/Live logs/i);
|
|
260
300
|
}finally{await rm(fx.dir,{recursive:true,force:true});}
|
|
@@ -271,7 +311,8 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
|
|
|
271
311
|
const output=await collect(child);
|
|
272
312
|
const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
|
|
273
313
|
assert.deepEqual(ready.slice(0,5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
274
|
-
assert.equal(ready[5],'
|
|
314
|
+
assert.equal(ready[5],'status');
|
|
315
|
+
assert.equal(ready[6],'start');
|
|
275
316
|
assert.match(output.stdout,/♻️ \[RECOVERY\] Stale remote session pid=2147483000; revoking authority before restart\./);
|
|
276
317
|
child.stdin.end();
|
|
277
318
|
const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
|