deadbyte-mcp 0.11.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +126 -54
- package/R41-PARENT.json +15 -0
- package/README.txt +5 -4
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +12 -8
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-containment.ps1 +24 -14
- package/scripts/build-desktop-r40.ps1 +112 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/deferred-slot-operation-r41.mjs +127 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-r41.mjs +116 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/final-closure-verify-r41.mjs +215 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/gate-windows-r41.ps1 +48 -0
- package/scripts/init-desktop-policy-r40.ps1 +85 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-r41.mjs +221 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/release-parity-tests-r41.ps1 +9 -0
- package/scripts/verify-memory-r41.mjs +88 -0
- package/scripts/verify-r41-parent.mjs +61 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/scripts/windows-gate-evidence-r41.mjs +53 -0
- package/src/autonomous-context-engine.mjs +3 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +24 -0
- package/src/autonomous-runtime.mjs +164 -25
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +72 -44
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +87 -19
- package/src/memory-r41.mjs +692 -0
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +208 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +86 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/memory-autonomous-integration-r41.test.mjs +31 -0
- package/test/memory-history-r41.test.mjs +126 -0
- package/test/memory-projections-r41.test.mjs +92 -0
- package/test/memory-retrieval-r41.test.mjs +124 -0
- package/test/memory-snapshot-r41.test.mjs +106 -0
- package/test/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +15 -15
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/r41-finalization.test.mjs +38 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +20 -61
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -56,3 +56,18 @@ test('cursor parser rejects foreign sessions and malformed cursors',async()=>{
|
|
|
56
56
|
await assert.rejects(journal.replayAfter('r1:'+'b'.repeat(24)+':0',{limit:8}),/cursor session mismatch/);
|
|
57
57
|
}finally{await rm(root,{recursive:true,force:true});}
|
|
58
58
|
});
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
test('remote result journal preserves explicit unknown side-effect state across restart and replay',async()=>{
|
|
62
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-remote-unknown-'));
|
|
63
|
+
const file=path.join(root,'journal.json');
|
|
64
|
+
try{
|
|
65
|
+
let journal=await openRemoteResultJournal({filePath:file,sessionId:session,maxEntries:8});
|
|
66
|
+
await journal.recordTerminal({...item('req-unknown',H('4'),'error'),side_effect_state:'unknown',reason:'runtime_restart'});
|
|
67
|
+
journal=await openRemoteResultJournal({filePath:file,sessionId:session,maxEntries:8});
|
|
68
|
+
const replay=await journal.replayAfter('r1:'+session+':0',{limit:8});
|
|
69
|
+
assert.equal(replay.events[0].side_effect_state,'unknown');
|
|
70
|
+
assert.equal(replay.events[0].reason,'runtime_restart');
|
|
71
|
+
await assert.rejects(journal.recordTerminal({...item('req-invalid',H('5'),'error'),side_effect_state:'committed'}),/side_effect_state invalid/);
|
|
72
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
73
|
+
});
|
|
@@ -84,8 +84,9 @@ winTest('bare remote reuses a fully verified ONLINE runtime without invoking blo
|
|
|
84
84
|
assert.equal(child.exitCode,null,'remote session must remain foreground after reusing ONLINE runtime');
|
|
85
85
|
assert.match(output.stdout,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
|
|
86
86
|
const authDeadline=Date.now()+3000;
|
|
87
|
-
while(Date.now()<authDeadline
|
|
88
|
-
assert.match(output.stdout
|
|
87
|
+
while(Date.now()<authDeadline&&!/\[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
|
|
88
|
+
assert.match(output.stdout,/\[DISPLAY\] ASCII fallback: stdout_not_tty/);
|
|
89
|
+
assert.match(output.stdout,/\[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
|
|
89
90
|
child.kill('SIGINT');
|
|
90
91
|
await output.closed;
|
|
91
92
|
}finally{
|
|
@@ -137,16 +138,17 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
137
138
|
]);
|
|
138
139
|
assert.equal(child.exitCode,null,'remote session must remain foreground after becoming ready');
|
|
139
140
|
const tailDeadline=Date.now()+3000;
|
|
140
|
-
while(Date.now()<tailDeadline
|
|
141
|
-
assert.match(output.stdout
|
|
142
|
-
assert.match(output.stdout
|
|
143
|
-
assert.match(output.stdout
|
|
141
|
+
while(Date.now()<tailDeadline&&!/\[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
|
|
142
|
+
assert.match(output.stdout,/\[DISPLAY\] ASCII fallback: stdout_not_tty/);
|
|
143
|
+
assert.match(output.stdout,/\[DEVICE\] Starting DEADBYTE MCP/);
|
|
144
|
+
assert.match(output.stdout,/\[BRIDGE\] Local bridge connected/);
|
|
145
|
+
assert.match(output.stdout,/\[CLOUD\] Remote MCP connected/);
|
|
144
146
|
assert.match(output.stdout,/\[SESSION\] New foreground session/);
|
|
145
147
|
assert.match(output.stdout,/\[BOOT\] Starting local \+ remote runtime/);
|
|
146
148
|
assert.match(output.stdout,/\[OK\] ONLINE/);
|
|
147
|
-
assert.match(output.stdout
|
|
148
|
-
assert.match(output.stdout
|
|
149
|
-
assert.match(output.stdout,/\[LOG\]
|
|
149
|
+
assert.match(output.stdout,/\[READY\] DEADBYTE MCP online/);
|
|
150
|
+
assert.match(output.stdout,/\[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
|
|
151
|
+
assert.match(output.stdout,/\[LOG\] Human live timeline\. Transport IN\/OUT hidden; full replay\/audit detail remains on disk\./);
|
|
150
152
|
assert.doesNotMatch(output.stdout,/Process plane\s*:/);
|
|
151
153
|
assert.doesNotMatch(output.stdout,/Coding plane\s*:/);
|
|
152
154
|
const auditDir=path.join(fx.state,'audit');
|
|
@@ -169,12 +171,12 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
169
171
|
'[TOOL OUT] 2026-09-16T00:00:00.002Z #000002 coding_file_read {"is_error":true}'
|
|
170
172
|
].join('\n')+'\n','utf8');
|
|
171
173
|
const logDeadline=Date.now()+3000;
|
|
172
|
-
while(Date.now()<logDeadline
|
|
173
|
-
assert.
|
|
174
|
-
assert.match(output.stdout
|
|
175
|
-
assert.match(output.stdout,/^
|
|
176
|
-
assert.
|
|
177
|
-
assert.
|
|
174
|
+
while(Date.now()<logDeadline&&!/^R .*#4 \[fedcba98\] coding_file_read projects:src\/deadbyte-cli\.mjs 1\.73s ERR$/m.test(output.stdout)) await sleep(50);
|
|
175
|
+
assert.doesNotMatch(output.stdout,/\| Arguments:/,'default human view must not print request JSON');
|
|
176
|
+
assert.match(output.stdout,/^S .*#2 \[abcdef12\] coding_tree projects:src 42ms OK$/m);
|
|
177
|
+
assert.match(output.stdout,/^R .*#4 \[fedcba98\] coding_file_read projects:src\/deadbyte-cli\.mjs 1\.73s ERR$/m);
|
|
178
|
+
assert.equal((output.stdout.match(/\[abcdef12\].*coding_tree.*42ms.*OK/g)||[]).length,1,'duplicate terminal result must print once');
|
|
179
|
+
assert.doesNotMatch(output.stdout,/\[cursor r1:/,'full replay cursor belongs in the journal, not every human line');
|
|
178
180
|
const resultJournal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',`${marker.result_session_id}.json`),'utf8'));
|
|
179
181
|
assert.deepEqual(resultJournal.events.map(event=>event.seq),[1,2,3,4]);
|
|
180
182
|
assert.deepEqual(resultJournal.events.map(event=>event.event_type),['tool.call','tool.result','tool.call','tool.result']);
|
|
@@ -184,9 +186,7 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
184
186
|
assert.match(output.stdout,/abcdef12/);
|
|
185
187
|
assert.match(output.stdout,/fedcba98/);
|
|
186
188
|
assert.doesNotMatch(output.stdout,/DO_NOT_PRINT_PAYLOAD|DO_NOT_PRINT_TOKEN/);
|
|
187
|
-
assert.
|
|
188
|
-
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] OUT coding_tree/);
|
|
189
|
-
assert.match(output.stdout,/\[BRIDGE\.STDOUT\] ERR coding_file_read/);
|
|
189
|
+
assert.doesNotMatch(output.stdout,/\[BRIDGE\.(?:STDOUT|DEBUG)\]/,'transport diagnostics are hidden in default human mode');
|
|
190
190
|
assert.doesNotMatch(output.stdout,/\[TOOL IN \]|\[TOOL OUT\]/);
|
|
191
191
|
assert.doesNotMatch(output.stdout,/payload bytes=/);
|
|
192
192
|
child.stdin.end();
|
|
@@ -195,11 +195,10 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
|
|
|
195
195
|
const commands=await traceCommands(fx.trace);
|
|
196
196
|
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
197
197
|
assert.match(output.stdout,/^DEADBYTE MCP$/m);
|
|
198
|
-
assert.match(output.stdout,/
|
|
199
|
-
assert.match(output.stdout
|
|
198
|
+
assert.match(output.stdout,/Human live timeline/i);
|
|
199
|
+
assert.match(output.stdout,/\[AUTH\] Revoking session authority/);
|
|
200
200
|
assert.doesNotMatch(output.stdout,/disarmautonomous OK|disarmcoding OK|disarmprocess OK|disarmhost OK|DEADBYTE MCP = PARKED/);
|
|
201
|
-
|
|
202
|
-
assert.doesNotMatch(withoutApprovedEmoji,/\p{Extended_Pictographic}/u,'human live stream may use only the approved semantic emoji set');
|
|
201
|
+
assert.doesNotMatch(output.stdout,/\p{Extended_Pictographic}/u,'piped Windows fallback must remain ASCII-safe');
|
|
203
202
|
}finally{
|
|
204
203
|
if(child?.exitCode===null) child.kill();
|
|
205
204
|
await rm(fx.dir,{recursive:true,force:true});
|
|
@@ -214,7 +213,9 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
|
|
|
214
213
|
const firstOutput=await collect(first);
|
|
215
214
|
await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
|
|
216
215
|
const liveDeadline=Date.now()+3000;
|
|
217
|
-
while(Date.now()<liveDeadline&&!/\[LOG\]
|
|
216
|
+
while(Date.now()<liveDeadline&&!/\[LOG\] Human live timeline/.test(firstOutput.stdout)) await sleep(50);
|
|
217
|
+
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
218
|
+
assert.match(String(marker.result_session_id||''),/^[0-9a-f]{24}$/);
|
|
218
219
|
const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
|
|
219
220
|
const auditFile=path.join(auditDir,'segment-00000001.jsonl');
|
|
220
221
|
const firstBatch=[
|
|
@@ -223,16 +224,19 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
|
|
|
223
224
|
];
|
|
224
225
|
await writeFile(auditFile,firstBatch.map(JSON.stringify).join('\n')+'\n','utf8');
|
|
225
226
|
let deadline=Date.now()+3000;
|
|
226
|
-
while(Date.now()<deadline
|
|
227
|
-
const
|
|
228
|
-
|
|
227
|
+
while(Date.now()<deadline&&!/^S .*#2 \[resume-r\] coding_tree projects:src.*OK$/m.test(firstOutput.stdout)) await sleep(50);
|
|
228
|
+
const journalFile=path.join(fx.state,'runtime','remote-results',`${marker.result_session_id}.json`);
|
|
229
|
+
const firstJournal=JSON.parse(await readFile(journalFile,'utf8'));
|
|
230
|
+
assert.deepEqual(firstJournal.events.map(event=>event.seq),[1,2]);
|
|
231
|
+
const cursor1=`r1:${marker.result_session_id}:2`;
|
|
232
|
+
assert.doesNotMatch(firstOutput.stdout,/\[cursor r1:/);
|
|
229
233
|
const secondBatch=[
|
|
230
234
|
{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'}},
|
|
231
235
|
{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}}
|
|
232
236
|
];
|
|
233
237
|
await writeFile(auditFile,secondBatch.map(JSON.stringify).join('\n')+'\n',{encoding:'utf8',flag:'a'});
|
|
234
238
|
deadline=Date.now()+3000;
|
|
235
|
-
while(Date.now()<deadline
|
|
239
|
+
while(Date.now()<deadline&&!/^R .*#4 \[resume-r\] coding_file_read projects:src\/deadbyte-cli\.mjs.*OK$/m.test(firstOutput.stdout)) await sleep(50);
|
|
236
240
|
first.stdin.end();
|
|
237
241
|
const firstEnded=await Promise.race([firstOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
238
242
|
assert.equal(firstEnded.timeout,undefined);
|
|
@@ -240,19 +244,19 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
|
|
|
240
244
|
second=spawn(process.execPath,[cliPath,'remote','--resume',cursor1],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
241
245
|
const secondOutput=await collect(second);
|
|
242
246
|
deadline=Date.now()+5000;
|
|
243
|
-
while(Date.now()<deadline&&(!/coding_file_read\s+\| Arguments:
|
|
244
|
-
!/\[REPLAY\]
|
|
245
|
-
!/\[LOG\]
|
|
246
|
-
assert.match(secondOutput.stdout,/^2026-09-18T23:30:01\.000\+07:00 \| coding_file_read\s+\| Arguments:
|
|
247
|
-
assert.match(secondOutput.stdout,/\[REPLAY\]
|
|
248
|
-
assert.match(secondOutput.stdout
|
|
247
|
+
while(Date.now()<deadline&&(!/coding_file_read\s+\| Arguments: .*\| REPLAY #3/.test(secondOutput.stdout)||
|
|
248
|
+
!/\[REPLAY\] #4 coding_file_read OK/.test(secondOutput.stdout)||
|
|
249
|
+
!/\[LOG\] Human live timeline/.test(secondOutput.stdout))) await sleep(50);
|
|
250
|
+
assert.match(secondOutput.stdout,/^2026-09-18T23:30:01\.000\+07:00 \| coding_file_read\s+\| Arguments: .*"path":"src\/deadbyte-cli\.mjs".*\| REPLAY #3$/m);
|
|
251
|
+
assert.match(secondOutput.stdout,/\[REPLAY\] #4 coding_file_read OK/);
|
|
252
|
+
assert.match(secondOutput.stdout,/\[SESSION\] Previous result stream replayed/);
|
|
249
253
|
assert.doesNotMatch(secondOutput.stdout,/DO_NOT_REPLAY_SECRET/);
|
|
250
|
-
assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\]
|
|
254
|
+
assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\].*coding_tree/);
|
|
251
255
|
assert.equal((secondOutput.stdout.match(/\| coding_file_read\s+\| Arguments:/g)||[]).length,1,'tool-call replay duplicated');
|
|
252
|
-
assert.equal((secondOutput.stdout.match(/\[REPLAY\]
|
|
256
|
+
assert.equal((secondOutput.stdout.match(/\[REPLAY\] #4 coding_file_read OK/g)||[]).length,1,'terminal replay duplicated');
|
|
253
257
|
const callReplayIndex=secondOutput.stdout.indexOf('| coding_file_read');
|
|
254
|
-
const resultReplayIndex=secondOutput.stdout.indexOf('[REPLAY]
|
|
255
|
-
const liveIndex=secondOutput.stdout.indexOf('[LOG]
|
|
258
|
+
const resultReplayIndex=secondOutput.stdout.indexOf('[REPLAY] #4 coding_file_read OK');
|
|
259
|
+
const liveIndex=secondOutput.stdout.indexOf('[LOG] Human live timeline');
|
|
256
260
|
assert.ok(callReplayIndex>=0&&resultReplayIndex>callReplayIndex&&liveIndex>resultReplayIndex,'resume ordering must be call -> result -> live tail');
|
|
257
261
|
second.stdin.end();
|
|
258
262
|
const secondEnded=await Promise.race([secondOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
@@ -264,17 +268,35 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
|
|
|
264
268
|
}
|
|
265
269
|
});
|
|
266
270
|
|
|
267
|
-
winTest('remote --verbose
|
|
271
|
+
winTest('remote --verbose enables sanitized request detail while preserving foreground authority cleanup',async()=>{
|
|
268
272
|
const fx=await fixture();
|
|
273
|
+
let child=null;
|
|
269
274
|
try{
|
|
270
|
-
|
|
275
|
+
child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
271
276
|
const output=await collect(child);
|
|
277
|
+
await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
|
|
278
|
+
assert.equal(child.exitCode,null,'verbose remote remains a foreground session');
|
|
279
|
+
assert.match(output.stdout,/\[LOG\] Verbose live timeline\. Full sanitized request detail enabled/);
|
|
280
|
+
const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
|
|
281
|
+
const audit=[
|
|
282
|
+
{event_type:'tool.request',tool:'coding_tree',request_id:'verbose-request-1',created_at_utc:'2026-09-19T00:00:00.000Z',args:{root_id:'projects',path:'src',token:'DO_NOT_PRINT_VERBOSE_SECRET'}},
|
|
283
|
+
{event_type:'tool.result',tool:'coding_tree',request_id:'verbose-request-1',created_at_utc:'2026-09-19T00:00:00.050Z',outcome:{is_error:false,duration_ms:50}}
|
|
284
|
+
];
|
|
285
|
+
await writeFile(path.join(auditDir,'segment-00000001.jsonl'),audit.map(JSON.stringify).join('\n')+'\n','utf8');
|
|
286
|
+
const deadline=Date.now()+3000;
|
|
287
|
+
while(Date.now()<deadline&&!/coding_tree\s+\| Arguments:/.test(output.stdout)) await sleep(50);
|
|
288
|
+
assert.match(output.stdout,/coding_tree\s+\| Arguments: .*"path":"src".*"root_id":"projects"/);
|
|
289
|
+
assert.doesNotMatch(output.stdout,/DO_NOT_PRINT_VERBOSE_SECRET/);
|
|
290
|
+
child.stdin.end();
|
|
272
291
|
const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
273
292
|
assert.equal(ended.timeout,undefined);
|
|
274
|
-
assert.
|
|
275
|
-
|
|
276
|
-
assert.deepEqual(
|
|
277
|
-
}finally{
|
|
293
|
+
assert.equal(ended.code,0,output.stderr);
|
|
294
|
+
const commands=await traceCommands(fx.trace);
|
|
295
|
+
assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
296
|
+
}finally{
|
|
297
|
+
if(child?.exitCode===null) child.kill('SIGKILL');
|
|
298
|
+
await rm(fx.dir,{recursive:true,force:true});
|
|
299
|
+
}
|
|
278
300
|
});
|
|
279
301
|
|
|
280
302
|
winTest('detached guardian revokes authority, parks, and removes the session marker after the owner is hard-killed',async()=>{
|
|
@@ -347,7 +369,7 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
|
|
|
347
369
|
assert.deepEqual(ready.slice(0,5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
|
|
348
370
|
assert.equal(ready[5],'status');
|
|
349
371
|
assert.equal(ready[6],'start');
|
|
350
|
-
assert.match(output.stdout
|
|
372
|
+
assert.match(output.stdout,/\[RECOVERY\] Stale remote session pid=2147483000; revoking authority before restart\./);
|
|
351
373
|
child.stdin.end();
|
|
352
374
|
const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
353
375
|
assert.equal(ended.timeout,undefined);
|
|
@@ -358,7 +380,7 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
|
|
|
358
380
|
}
|
|
359
381
|
});
|
|
360
382
|
|
|
361
|
-
winTest('remote warning lines use
|
|
383
|
+
winTest('remote warning lines use deterministic ASCII fallback on piped Windows output',async()=>{
|
|
362
384
|
const fx=await fixture();
|
|
363
385
|
let child=null;
|
|
364
386
|
try{
|
|
@@ -370,7 +392,8 @@ winTest('remote warning lines use the approved warning emoji without changing th
|
|
|
370
392
|
child.stdin.end();
|
|
371
393
|
const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
|
|
372
394
|
assert.equal(ended.timeout,undefined,'remote session did not exit after warning fixture');
|
|
373
|
-
assert.match(output.stderr
|
|
395
|
+
assert.match(output.stderr,/^\[WARN\] remote session marker cleanup:/m);
|
|
396
|
+
assert.doesNotMatch(output.stderr,/\p{Extended_Pictographic}/u);
|
|
374
397
|
}finally{
|
|
375
398
|
if(child?.exitCode===null) child.kill();
|
|
376
399
|
await rm(fx.dir,{recursive:true,force:true});
|
|
@@ -263,6 +263,50 @@ test('R31 slot-update CLI binds process-policy bootstrap and machine-FS enable e
|
|
|
263
263
|
assert.equal(parsed.enableMachineFs,true);
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
test('R40 slot-update CLI binds Desktop policy as one exact pair and enables runtime only explicitly', () => {
|
|
267
|
+
const base=['--stage','stage','--development-root','dev','--expected-version','0.12.0','--expected-manifest',hex('a')];
|
|
268
|
+
assert.throws(()=>parseUpdateArgs([...base,'--desktop-policy','C:/candidate/desktop.json']),/desktop policy.*pair/i);
|
|
269
|
+
assert.throws(()=>parseUpdateArgs([...base,'--desktop-policy-sha256',hex('b')]),/desktop policy.*pair/i);
|
|
270
|
+
const plain=parseUpdateArgs([...base,'--desktop-policy','C:/candidate/desktop.json','--desktop-policy-sha256',hex('b')]);
|
|
271
|
+
assert.equal(plain.desktopPolicyBootstrap.path,path.resolve('C:/candidate/desktop.json'));
|
|
272
|
+
assert.equal(plain.desktopPolicyBootstrap.sha256,hex('b'));
|
|
273
|
+
assert.equal(plain.enableDesktop,false);
|
|
274
|
+
const enabled=parseUpdateArgs([...base,'--desktop-policy','C:/candidate/desktop.json','--desktop-policy-sha256',hex('b'),'--enable-desktop']);
|
|
275
|
+
assert.equal(enabled.enableDesktop,true);
|
|
276
|
+
assert.throws(()=>parseUpdateArgs([...base,'--enable-desktop','--enable-desktop']),/duplicate self-update argument/i);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test('R40 Desktop policy bootstrap accepts only exact pinned enabled policy bytes',async()=>{
|
|
280
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-bootstrap-'));
|
|
281
|
+
const file=path.join(root,'desktop.json');
|
|
282
|
+
const policy={schema:'deadbyte.desktop-policy.v1',enabled:true,grant_file:'C:/old/grant.json',evidence_dir:'C:/old/evidence',
|
|
283
|
+
allowed_actions:['click_primary'],allowed_input_desktops:['Default'],limits:{},
|
|
284
|
+
observer:{executable:'C:/old/observer.exe',sha256:hex('1')},
|
|
285
|
+
input:{executable:'C:/old/input.exe',sha256:hex('2')},
|
|
286
|
+
grounder:{executable:'C:/old/uia.exe',sha256:hex('3')}};
|
|
287
|
+
const bytes=Buffer.from(JSON.stringify(policy)+'\n','utf8');await writeFile(file,bytes);
|
|
288
|
+
assert.equal(typeof runtimeUpdate.prepareDesktopPolicyBootstrap,'function');
|
|
289
|
+
const accepted=await runtimeUpdate.prepareDesktopPolicyBootstrap({path:file,sha256:sha(bytes)});
|
|
290
|
+
assert.equal(accepted.sha256,sha(bytes));assert.deepEqual(accepted.policy,policy);
|
|
291
|
+
await assert.rejects(runtimeUpdate.prepareDesktopPolicyBootstrap({path:file,sha256:hex('f')}),/desktop policy.*SHA-256 mismatch/i);
|
|
292
|
+
const bad=path.join(root,'bad.json');await writeFile(bad,JSON.stringify({...policy,enabled:false}));
|
|
293
|
+
await assert.rejects(runtimeUpdate.prepareDesktopPolicyBootstrap({path:bad,sha256:sha(await readFile(bad))}),/desktop policy schema\/enabled/i);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('R40 updater threads Desktop policy into generation and never restores Desktop authority automatically',async()=>{
|
|
297
|
+
const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
|
|
298
|
+
assert.match(source,/prepareDesktopPolicyBootstrap\(desktopPolicyBootstrap\)/);
|
|
299
|
+
assert.match(source,/control\.desktopPolicy=desktopBootstrap\.policy/);
|
|
300
|
+
assert.match(source,/control\.config\.DesktopRuntimeEnabled=true/);
|
|
301
|
+
assert.match(source,/desktopPolicy:control\.desktopPolicy/);
|
|
302
|
+
assert.match(source,/desktopPolicyBootstrap:args\.desktopPolicyBootstrap/);
|
|
303
|
+
assert.match(source,/enableDesktop:args\.enableDesktop/);
|
|
304
|
+
assert.match(source,/controllerCaptured\(ctx,'disarmdesktop',15000\)/);
|
|
305
|
+
assert.doesNotMatch(source,/controllerCaptured\(ctx,'armdesktop',15000\)/);
|
|
306
|
+
assert.match(source,/desktop plane must remain disarmed after slot switch/);
|
|
307
|
+
assert.match(source,/desktop_restore_policy:'explicit-rearm-required'/);
|
|
308
|
+
});
|
|
309
|
+
|
|
266
310
|
test('R35 slot-update CLI binds semantic LSP config as one exact path plus SHA-256 pair', () => {
|
|
267
311
|
const base=['--stage','stage','--development-root','dev','--expected-version','0.9.0','--expected-manifest',hex('a')];
|
|
268
312
|
assert.throws(() => parseUpdateArgs([...base,'--semantic-lsp-config','C:/candidate/semantic.json']), /semantic LSP config.*pair/i);
|