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.
Files changed (127) hide show
  1. package/MANIFEST.SHA256 +126 -54
  2. package/R41-PARENT.json +15 -0
  3. package/README.txt +5 -4
  4. package/acceptance/r40/desktop-live-drift.mjs +106 -0
  5. package/acceptance/r40/desktop-live-ground.mjs +110 -0
  6. package/acceptance/r40/desktop-live-input.mjs +128 -0
  7. package/acceptance/r40/desktop-live-observe.mjs +100 -0
  8. package/acceptance/r40/desktop-live-uia.mjs +121 -0
  9. package/bin/appcontainer-stage.obj +0 -0
  10. package/bin/bootstrap-advapi32.obj +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.obj +0 -0
  13. package/bin/child-control-probe.obj +0 -0
  14. package/bin/child-control-stage.obj +0 -0
  15. package/bin/contained-reverse-worker.obj +0 -0
  16. package/bin/contained-transform-worker.obj +0 -0
  17. package/bin/containment-probe.obj +0 -0
  18. package/bin/deadbyte-contain.obj +0 -0
  19. package/bin/deadbyte-desktop-input-r40.exe +0 -0
  20. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  21. package/bin/deadbyte-desktop-observer-r40.exe +0 -0
  22. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  23. package/bin/deadbyte-desktop-uia-r40.exe +0 -0
  24. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  25. package/bin/deadbyte-exec.obj +0 -0
  26. package/bin/deadbyte-process-host.obj +0 -0
  27. package/bin/deadbyte-tunnel-host.obj +0 -0
  28. package/controller/README.TXT +7 -5
  29. package/controller/deadbyte-controller.config.psd1.example +3 -0
  30. package/controller/deadbyte-controller.ps1 +149 -4
  31. package/controller/deadbyte-desktop-policy.json +41 -0
  32. package/controller/deadbyte-process-policy.json +4 -4
  33. package/docs/ARCHITECTURE.md +6 -1
  34. package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
  35. package/docs/superpowers/specs/2026-09-20-r41-deterministic-memory-design.md +132 -0
  36. package/native/deadbyte-desktop-input.cpp +455 -0
  37. package/native/deadbyte-desktop-observer.cpp +253 -0
  38. package/native/deadbyte-desktop-uia.cpp +307 -0
  39. package/package.json +12 -8
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/build-containment.ps1 +24 -14
  42. package/scripts/build-desktop-r40.ps1 +112 -0
  43. package/scripts/deferred-slot-operation-r40.mjs +126 -0
  44. package/scripts/deferred-slot-operation-r41.mjs +127 -0
  45. package/scripts/final-closure-r40.mjs +114 -0
  46. package/scripts/final-closure-r41.mjs +116 -0
  47. package/scripts/final-closure-verify-r40.mjs +212 -0
  48. package/scripts/final-closure-verify-r41.mjs +215 -0
  49. package/scripts/gate-windows-r40.ps1 +43 -0
  50. package/scripts/gate-windows-r41.ps1 +48 -0
  51. package/scripts/init-desktop-policy-r40.ps1 +85 -0
  52. package/scripts/r40-compact-ledger-probe.mjs +4 -0
  53. package/scripts/release-parity-r40.mjs +212 -0
  54. package/scripts/release-parity-r41.mjs +221 -0
  55. package/scripts/release-parity-tests-r40.ps1 +9 -0
  56. package/scripts/release-parity-tests-r41.ps1 +9 -0
  57. package/scripts/verify-memory-r41.mjs +88 -0
  58. package/scripts/verify-r41-parent.mjs +61 -0
  59. package/scripts/windows-gate-evidence-r40.mjs +52 -0
  60. package/scripts/windows-gate-evidence-r41.mjs +53 -0
  61. package/src/autonomous-context-engine.mjs +3 -1
  62. package/src/autonomous-planner-contracts.mjs +19 -1
  63. package/src/autonomous-planner.mjs +24 -0
  64. package/src/autonomous-runtime.mjs +164 -25
  65. package/src/capability-ledger.mjs +6 -1
  66. package/src/compact-gateway-mcp-tools.mjs +14 -2
  67. package/src/deadbyte-cli.mjs +72 -44
  68. package/src/desktop-contract.mjs +263 -0
  69. package/src/desktop-input-r40.mjs +148 -0
  70. package/src/desktop-mcp-tools-r40.mjs +126 -0
  71. package/src/desktop-memory-r40.mjs +272 -0
  72. package/src/desktop-observer-r40.mjs +231 -0
  73. package/src/desktop-policy-r40.mjs +110 -0
  74. package/src/desktop-runtime-r40.mjs +318 -0
  75. package/src/desktop-uia-r40.mjs +123 -0
  76. package/src/mcp-server.mjs +87 -19
  77. package/src/memory-r41.mjs +692 -0
  78. package/src/r40-compact-disarmed-probe.mjs +71 -0
  79. package/src/r40-full-surface-probe.mjs +34 -0
  80. package/src/release-generation.mjs +38 -5
  81. package/src/remote-console.mjs +41 -0
  82. package/src/remote-live-log.mjs +208 -45
  83. package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
  84. package/src/remote-mcp-r40-parity-client.mjs +200 -0
  85. package/src/remote-mcp-smoke-client.mjs +20 -6
  86. package/src/remote-result-journal.mjs +10 -2
  87. package/src/runtime-update.mjs +55 -17
  88. package/src/tool-audit.mjs +29 -2
  89. package/src/version.mjs +1 -1
  90. package/test/autonomous-capability-r36.test.mjs +43 -1
  91. package/test/autonomous-desktop-r40.test.mjs +188 -0
  92. package/test/capability-ledger-r36.test.mjs +1 -0
  93. package/test/cli-entrypoint.test.mjs +1 -1
  94. package/test/compact-catalog-r36.test.mjs +2 -2
  95. package/test/compact-gateway-r36.test.mjs +56 -4
  96. package/test/compact-max-catalog-r40.test.mjs +95 -0
  97. package/test/controller-desktop-r40.test.mjs +86 -0
  98. package/test/core.test.mjs +1 -1
  99. package/test/desktop-adversarial-r40.test.mjs +62 -0
  100. package/test/desktop-contract-r40.test.mjs +200 -0
  101. package/test/desktop-input-r40.test.mjs +63 -0
  102. package/test/desktop-mcp-tools-r40.test.mjs +61 -0
  103. package/test/desktop-memory-r40.test.mjs +141 -0
  104. package/test/desktop-observer-r40.test.mjs +119 -0
  105. package/test/desktop-policy-r40.test.mjs +111 -0
  106. package/test/desktop-runtime-r40.test.mjs +278 -0
  107. package/test/desktop-uia-r40.test.mjs +42 -0
  108. package/test/full-max-catalog-r40.test.mjs +91 -0
  109. package/test/helpers/autonomous-r34-fixture.mjs +3 -3
  110. package/test/memory-autonomous-integration-r41.test.mjs +31 -0
  111. package/test/memory-history-r41.test.mjs +126 -0
  112. package/test/memory-projections-r41.test.mjs +92 -0
  113. package/test/memory-retrieval-r41.test.mjs +124 -0
  114. package/test/memory-snapshot-r41.test.mjs +106 -0
  115. package/test/r33-closeout-regression.test.mjs +7 -7
  116. package/test/r33-finalization.test.mjs +15 -15
  117. package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
  118. package/test/r40-finalization.test.mjs +118 -0
  119. package/test/r41-finalization.test.mjs +38 -0
  120. package/test/release-generation.test.mjs +41 -9
  121. package/test/release-version.test.mjs +20 -61
  122. package/test/remote-console-r40.test.mjs +38 -0
  123. package/test/remote-live-log-r37.test.mjs +89 -32
  124. package/test/remote-r24-catalog.test.mjs +12 -3
  125. package/test/remote-result-journal.test.mjs +15 -0
  126. package/test/remote-session-cli.test.mjs +69 -46
  127. 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&&!/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
88
- assert.match(output.stdout,/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
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&&!/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
141
- assert.match(output.stdout,/🚀 \[DEVICE\] Starting DEADBYTE MCP/);
142
- assert.match(output.stdout,/🔌 \[BRIDGE\] Local bridge connected/);
143
- assert.match(output.stdout,/🔌 \[CLOUD\] Remote MCP connected/);
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,/✅ \[READY\] DEADBYTE MCP online/);
148
- assert.match(output.stdout,/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
149
- assert.match(output.stdout,/\[LOG\] Unified live timeline\. Ctrl\+C disconnects and revokes authority\./);
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&&!/✗ \[fedcba98\] ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
173
- 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}\+07:00 \| coding_tree\s+\| Arguments: .*\"path\":\"src\".*\"root_id\":\"projects\".*\[cursor r1:[0-9a-f]{24}:1\]$/m);
174
- assert.match(output.stdout,/✓ \[abcdef12\] OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:2\]/);
175
- 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}\+07:00 \| coding_file_read\s+\| Arguments: \{\"path\":\"src\/deadbyte-cli\.mjs\",\"root_id\":\"projects\"\} \[cursor r1:[0-9a-f]{24}:3\]$/m);
176
- assert.match(output.stdout,/✗ \[fedcba98\] ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:4\]/);
177
- assert.equal((output.stdout.match(/✓ \[abcdef12\] OK coding_tree 42ms/g)||[]).length,1,'duplicate terminal result must print once');
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.match(output.stdout,/\[BRIDGE\.STDOUT\] IN coding_tree/);
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,/Unified live timeline/i);
199
- assert.match(output.stdout,/🔐 \[AUTH\] Revoking session authority/);
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
- const withoutApprovedEmoji=output.stdout.replace(/[📜📥📤✅❌⚠♻🛡▶🔐🚀🔌]/gu,'');
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\] Unified live timeline/.test(firstOutput.stdout)) await sleep(50);
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&&!/coding_tree .*\[cursor r1:[0-9a-f]{24}:2\]/.test(firstOutput.stdout)) await sleep(50);
227
- const cursor1=firstOutput.stdout.match(/coding_tree .*\[cursor (r1:[0-9a-f]{24}:2)\]/)?.[1];
228
- assert.ok(cursor1,'first result cursor missing');
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&&!/coding_file_read .*\[cursor r1:[0-9a-f]{24}:4\]/.test(firstOutput.stdout)) await sleep(50);
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: .*REPLAY cursor r1:[0-9a-f]{24}:3/.test(secondOutput.stdout)||
244
- !/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:4\]/.test(secondOutput.stdout)||
245
- !/\[LOG\] Unified live timeline/.test(secondOutput.stdout))) await sleep(50);
246
- assert.match(secondOutput.stdout,/^2026-09-18T23:30:01\.000\+07:00 \| coding_file_read\s+\| Arguments: .*\"path\":\"src\/deadbyte-cli\.mjs\".*REPLAY cursor r1:[0-9a-f]{24}:3$/m);
247
- assert.match(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:4\]/);
248
- assert.match(secondOutput.stdout,/♻️ \[SESSION\] Previous result stream replayed/);
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\] \[OK\] coding_tree/);
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\] \[OK\] coding_file_read/g)||[]).length,1,'terminal replay duplicated');
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] [OK] coding_file_read');
255
- const liveIndex=secondOutput.stdout.indexOf('[LOG] Unified live timeline');
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 is rejected because R37 has one unified remote mode',async()=>{
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
- const child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
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.notEqual(ended.code,0);
275
- assert.match(output.stderr,/unsupported remote command '--verbose'/i);
276
- assert.deepEqual(await traceCommands(fx.trace),[],'rejected legacy flag must not touch runtime authority');
277
- }finally{await rm(fx.dir,{recursive:true,force:true});}
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,/♻️ \[RECOVERY\] Stale remote session pid=2147483000; revoking authority before restart\./);
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 the approved warning emoji without changing the warning token',async()=>{
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,/⚠️ \[WARN\] remote session marker cleanup:/);
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);