deadbyte-mcp 0.11.2 → 0.12.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 (102) hide show
  1. package/MANIFEST.SHA256 +101 -39
  2. package/README.txt +10 -9
  3. package/acceptance/r40/desktop-live-drift.mjs +106 -0
  4. package/acceptance/r40/desktop-live-ground.mjs +110 -0
  5. package/acceptance/r40/desktop-live-input.mjs +128 -0
  6. package/acceptance/r40/desktop-live-observe.mjs +100 -0
  7. package/acceptance/r40/desktop-live-uia.mjs +121 -0
  8. package/bin/deadbyte-desktop-input-r40.exe +0 -0
  9. package/bin/deadbyte-desktop-input-r40.obj +0 -0
  10. package/bin/deadbyte-desktop-observer-r40.exe +0 -0
  11. package/bin/deadbyte-desktop-observer-r40.obj +0 -0
  12. package/bin/deadbyte-desktop-uia-r40.exe +0 -0
  13. package/bin/deadbyte-desktop-uia-r40.obj +0 -0
  14. package/controller/README.TXT +7 -5
  15. package/controller/deadbyte-controller.config.psd1.example +3 -0
  16. package/controller/deadbyte-controller.ps1 +149 -4
  17. package/controller/deadbyte-desktop-policy.json +41 -0
  18. package/docs/ARCHITECTURE.md +6 -1
  19. package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
  20. package/native/deadbyte-desktop-input.cpp +455 -0
  21. package/native/deadbyte-desktop-observer.cpp +253 -0
  22. package/native/deadbyte-desktop-uia.cpp +307 -0
  23. package/package.json +10 -8
  24. package/scripts/build-desktop-r40.ps1 +106 -0
  25. package/scripts/deferred-slot-operation-r39.mjs +124 -0
  26. package/scripts/deferred-slot-operation-r40.mjs +126 -0
  27. package/scripts/final-closure-r39.mjs +113 -0
  28. package/scripts/final-closure-r40.mjs +114 -0
  29. package/scripts/final-closure-verify-r39.mjs +208 -0
  30. package/scripts/final-closure-verify-r40.mjs +212 -0
  31. package/scripts/gate-windows-r39.ps1 +42 -0
  32. package/scripts/gate-windows-r40.ps1 +43 -0
  33. package/scripts/init-desktop-policy-r40.ps1 +79 -0
  34. package/scripts/r40-compact-ledger-probe.mjs +4 -0
  35. package/scripts/release-parity-r39.mjs +200 -0
  36. package/scripts/release-parity-r40.mjs +212 -0
  37. package/scripts/release-parity-tests-r39.ps1 +9 -0
  38. package/scripts/release-parity-tests-r40.ps1 +9 -0
  39. package/scripts/windows-gate-evidence-r39.mjs +52 -0
  40. package/scripts/windows-gate-evidence-r40.mjs +52 -0
  41. package/src/autonomous-context-engine.mjs +2 -1
  42. package/src/autonomous-planner-contracts.mjs +19 -1
  43. package/src/autonomous-planner.mjs +22 -0
  44. package/src/autonomous-runtime.mjs +144 -24
  45. package/src/capability-ledger.mjs +6 -1
  46. package/src/compact-gateway-mcp-tools.mjs +14 -2
  47. package/src/deadbyte-cli.mjs +79 -47
  48. package/src/desktop-contract.mjs +263 -0
  49. package/src/desktop-input-r40.mjs +148 -0
  50. package/src/desktop-mcp-tools-r40.mjs +126 -0
  51. package/src/desktop-memory-r40.mjs +272 -0
  52. package/src/desktop-observer-r40.mjs +231 -0
  53. package/src/desktop-policy-r40.mjs +110 -0
  54. package/src/desktop-runtime-r40.mjs +318 -0
  55. package/src/desktop-uia-r40.mjs +123 -0
  56. package/src/mcp-server.mjs +74 -19
  57. package/src/r40-compact-disarmed-probe.mjs +71 -0
  58. package/src/r40-full-surface-probe.mjs +34 -0
  59. package/src/release-generation.mjs +38 -5
  60. package/src/remote-console.mjs +41 -0
  61. package/src/remote-live-log.mjs +215 -45
  62. package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
  63. package/src/remote-mcp-r40-parity-client.mjs +200 -0
  64. package/src/remote-mcp-smoke-client.mjs +20 -6
  65. package/src/remote-result-journal.mjs +10 -2
  66. package/src/runtime-update.mjs +55 -17
  67. package/src/tool-audit.mjs +29 -2
  68. package/src/version.mjs +1 -1
  69. package/test/autonomous-capability-r36.test.mjs +43 -1
  70. package/test/autonomous-desktop-r40.test.mjs +188 -0
  71. package/test/capability-ledger-r36.test.mjs +1 -0
  72. package/test/cli-entrypoint.test.mjs +1 -1
  73. package/test/compact-catalog-r36.test.mjs +2 -2
  74. package/test/compact-gateway-r36.test.mjs +56 -4
  75. package/test/compact-max-catalog-r40.test.mjs +95 -0
  76. package/test/controller-desktop-r40.test.mjs +85 -0
  77. package/test/core.test.mjs +1 -1
  78. package/test/desktop-adversarial-r40.test.mjs +62 -0
  79. package/test/desktop-contract-r40.test.mjs +200 -0
  80. package/test/desktop-input-r40.test.mjs +63 -0
  81. package/test/desktop-mcp-tools-r40.test.mjs +61 -0
  82. package/test/desktop-memory-r40.test.mjs +141 -0
  83. package/test/desktop-observer-r40.test.mjs +119 -0
  84. package/test/desktop-policy-r40.test.mjs +111 -0
  85. package/test/desktop-runtime-r40.test.mjs +278 -0
  86. package/test/desktop-uia-r40.test.mjs +42 -0
  87. package/test/full-max-catalog-r40.test.mjs +91 -0
  88. package/test/helpers/autonomous-r34-fixture.mjs +3 -3
  89. package/test/production-docs.test.mjs +3 -3
  90. package/test/r33-closeout-regression.test.mjs +8 -8
  91. package/test/r33-finalization.test.mjs +14 -14
  92. package/test/r37-remote-tool-call-timeline.test.mjs +7 -6
  93. package/test/r39-finalization.test.mjs +74 -0
  94. package/test/r40-finalization.test.mjs +118 -0
  95. package/test/release-generation.test.mjs +41 -9
  96. package/test/release-version.test.mjs +19 -58
  97. package/test/remote-console-r40.test.mjs +38 -0
  98. package/test/remote-live-log-r37.test.mjs +89 -32
  99. package/test/remote-r24-catalog.test.mjs +12 -3
  100. package/test/remote-result-journal.test.mjs +15 -0
  101. package/test/remote-session-cli.test.mjs +112 -55
  102. package/test/runtime-self-update.test.mjs +44 -0
@@ -66,17 +66,26 @@ test('R32 remote gate accepts observation only with machine plus coding configur
66
66
  assert.match(source, /observation_runtime: observationRuntime/);
67
67
  });
68
68
 
69
- test('R36 remote smoke accepts only the exact compact production catalog and uses read-only gateway proof', async () => {
69
+ test('R40 remote smoke preserves exact R36 compact compatibility and accepts only exact attested R40 compact catalog', async () => {
70
70
  const source = await readFile(sourcePath, 'utf8');
71
71
  assert.match(source, /const r36CompactTools = \[/);
72
72
  assert.match(source, /r36CompactTools\.length !== 41/);
73
- assert.match(source, /const compactProfile = JSON\.stringify\(names\) === JSON\.stringify\(r36CompactTools\)/);
73
+ assert.match(source, /const r40CompactTools = \[\.\.\.r36CompactTools, 'desktop_control'\]\.sort\(\)/);
74
+ assert.match(source, /r40CompactTools\.length !== 42/);
75
+ assert.match(source, /const exactR36Compact = JSON\.stringify\(names\) === JSON\.stringify\(r36CompactTools\)/);
76
+ assert.match(source, /const exactR40Compact = JSON\.stringify\(names\) === JSON\.stringify\(r40CompactTools\)/);
77
+ assert.match(source, /const compactProfile = exactR36Compact \|\| exactR40Compact/);
78
+ assert.match(source, /deadbyte\.capability-manifest\.v1/);
79
+ assert.match(source, /deadbyte\.capability-manifest\.v2/);
80
+ assert.match(source, /manifest\.tool_count !== names\.length/);
81
+ assert.match(source, /JSON\.stringify\(manifest\.tool_catalog\) !== JSON\.stringify\(names\)/);
82
+ assert.match(source, /manifest\.catalog_sha256 !== expectedCatalogSha/);
74
83
  assert.match(source, /capability_manifest/);
75
84
  assert.match(source, /machine_observe/);
76
85
  assert.match(source, /op: 'machine_status'/);
77
86
  assert.match(source, /op: 'machine_ping'/);
78
87
  assert.match(source, /if \(compactProfile\) \{[\s\S]*?\} else \{[\s\S]*?workspace_open/);
79
- assert.doesNotMatch(source, /r36CompactTools\.includes\(name\).*extras/s);
88
+ assert.doesNotMatch(source, /r40CompactTools\.includes\(name\).*extras/s);
80
89
  });
81
90
 
82
91
  test('R34 disarmed public gate requires the exact canonical 73-tool catalog', async () => {
@@ -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
+ });
@@ -16,11 +16,15 @@ async function fixture({failCommand='',statusOnline=false}={}){
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
+ const startedMarker=path.join(dir,'started.txt');
20
+ const spawnedStdout=path.join(dir,'spawned.stdout.log');
21
+ const spawnedStderr=path.join(dir,'spawned.stderr.log');
22
+ const spawnedPid=path.join(dir,'spawned.pid');
19
23
  await mkdir(bin,{recursive:true});
20
24
  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 '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`;
25
+ 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' { if ($env:DEADBYTE_REMOTE_TEST_SPAWN_CHILD -eq '1') { $node=(Get-Command node.exe).Source; $spawned=Start-Process -FilePath $node -ArgumentList @('-e','setTimeout(()=>process.exit(0),10000)') -PassThru -WindowStyle Hidden -RedirectStandardOutput $env:DEADBYTE_REMOTE_TEST_SPAWN_STDOUT -RedirectStandardError $env:DEADBYTE_REMOTE_TEST_SPAWN_STDERR; if ($env:DEADBYTE_REMOTE_TEST_SPAWN_PID) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_SPAWN_PID,[string]$spawned.Id,[Text.Encoding]::UTF8) } }; if ($env:DEADBYTE_REMOTE_TEST_STARTED) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_STARTED,'1',[Text.Encoding]::UTF8) }; Write-Host 'DEADBYTE MCP = ONLINE' }\n 'status' { if ($env:DEADBYTE_REMOTE_TEST_STATUS_ONLINE -eq '1' -or ($env:DEADBYTE_REMOTE_TEST_STARTED -and (Test-Path -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED))) { 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' { if ($env:DEADBYTE_REMOTE_TEST_STARTED) { Remove-Item -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED -Force -ErrorAction SilentlyContinue }; Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
22
26
  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,DEADBYTE_REMOTE_TEST_STATUS_ONLINE:statusOnline?'1':'0'}};
27
+ 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',DEADBYTE_REMOTE_TEST_STARTED:startedMarker,DEADBYTE_REMOTE_TEST_SPAWN_STDOUT:spawnedStdout,DEADBYTE_REMOTE_TEST_SPAWN_STDERR:spawnedStderr,DEADBYTE_REMOTE_TEST_SPAWN_PID:spawnedPid},spawnedPid};
24
28
  }
25
29
 
26
30
  async function traceCommands(file,{read=readFile,retryDelayMs=10,maxAttempts=20}={}){
@@ -80,8 +84,9 @@ winTest('bare remote reuses a fully verified ONLINE runtime without invoking blo
80
84
  assert.equal(child.exitCode,null,'remote session must remain foreground after reusing ONLINE runtime');
81
85
  assert.match(output.stdout,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
82
86
  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/);
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/);
85
90
  child.kill('SIGINT');
86
91
  await output.closed;
87
92
  }finally{
@@ -90,29 +95,60 @@ winTest('bare remote reuses a fully verified ONLINE runtime without invoking blo
90
95
  }
91
96
  });
92
97
 
98
+ winTest('bare remote PARKED startup does not wait for a long-lived child holding inherited pipe handles',async()=>{
99
+ const fx=await fixture();
100
+ fx.env.DEADBYTE_REMOTE_TEST_SPAWN_CHILD='1';
101
+ let child=null;
102
+ try{
103
+ const startedAt=Date.now();
104
+ child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
105
+ const output=await collect(child);
106
+ const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'),{timeoutMs:8000});
107
+ const elapsed=Date.now()-startedAt;
108
+ assert.deepEqual(ready.slice(0,10),[
109
+ 'status','start','status','armprocess','armcoding','armautonomous',
110
+ 'processstatus','codingstatus','autonomousstatus','hoststatus'
111
+ ]);
112
+ assert.ok(elapsed<7000,`PARKED startup waited on inherited child pipe handles: ${elapsed}ms`);
113
+ assert.equal(child.exitCode,null,'remote session must remain foreground after PARKED startup');
114
+ assert.match(output.stdout,/\[BOOT\] Starting bridge\/runtime\.\.\./);
115
+ assert.match(output.stdout,/\[OK\] ONLINE/);
116
+ child.stdin.end();
117
+ const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
118
+ assert.equal(ended.timeout,undefined,'remote session did not exit after PARKED startup proof');
119
+ assert.equal(ended.code,0,output.stderr);
120
+ }finally{
121
+ if(child?.exitCode===null) child.kill('SIGKILL');
122
+ try{const pid=Number((await readFile(fx.spawnedPid,'utf8')).trim());if(Number.isInteger(pid)&&pid>0) process.kill(pid,'SIGKILL');}catch{}
123
+ await sleep(100);
124
+ await rm(fx.dir,{recursive:true,force:true});
125
+ }
126
+ });
127
+
93
128
  winTest('bare remote uses one unified live timeline, keeps authority semantics, then disarms and parks when the foreground session ends',async()=>{
94
129
  const fx=await fixture();
95
130
  let child=null;
96
131
  try{
97
132
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
98
133
  const output=await collect(child);
99
- const ready=await waitFor(fx.trace,commands=>commands.slice(0,9).join(',')==='status,start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
100
- assert.deepEqual(ready.slice(0,9),[
101
- 'status','start','armprocess','armcoding','armautonomous',
134
+ const ready=await waitFor(fx.trace,commands=>commands.slice(0,10).join(',')==='status,start,status,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
135
+ assert.deepEqual(ready.slice(0,10),[
136
+ 'status','start','status','armprocess','armcoding','armautonomous',
102
137
  'processstatus','codingstatus','autonomousstatus','hoststatus'
103
138
  ]);
104
139
  assert.equal(child.exitCode,null,'remote session must remain foreground after becoming ready');
105
140
  const tailDeadline=Date.now()+3000;
106
- while(Date.now()<tailDeadline&&!/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/.test(output.stdout)) await sleep(50);
107
- assert.match(output.stdout,/🚀 \[DEVICE\] Starting DEADBYTE MCP/);
108
- assert.match(output.stdout,/🔌 \[BRIDGE\] Local bridge connected/);
109
- 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/);
110
146
  assert.match(output.stdout,/\[SESSION\] New foreground session/);
111
147
  assert.match(output.stdout,/\[BOOT\] Starting local \+ remote runtime/);
112
148
  assert.match(output.stdout,/\[OK\] ONLINE/);
113
- assert.match(output.stdout,/✅ \[READY\] DEADBYTE MCP online/);
114
- assert.match(output.stdout,/🔐 \[AUTH\] ARMED: process, coding, autonomous \| host DISARMED/);
115
- 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\./);
116
152
  assert.doesNotMatch(output.stdout,/Process plane\s*:/);
117
153
  assert.doesNotMatch(output.stdout,/Coding plane\s*:/);
118
154
  const auditDir=path.join(fx.state,'audit');
@@ -135,12 +171,12 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
135
171
  '[TOOL OUT] 2026-09-16T00:00:00.002Z #000002 coding_file_read {"is_error":true}'
136
172
  ].join('\n')+'\n','utf8');
137
173
  const logDeadline=Date.now()+3000;
138
- while(Date.now()<logDeadline&&!/✗ \[fedcba98\] ERR coding_file_read 1\.73s/.test(output.stdout)) await sleep(50);
139
- assert.match(output.stdout,/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z \| coding_tree\s+\| Arguments: .*\"path\":\"src\".*\"root_id\":\"projects\".*\[cursor r1:[0-9a-f]{24}:1\]$/m);
140
- assert.match(output.stdout,/✓ \[abcdef12\] OK coding_tree 42ms \[cursor r1:[0-9a-f]{24}:2\]/);
141
- assert.match(output.stdout,/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z \| coding_file_read\s+\| Arguments: \{\"path\":\"src\/deadbyte-cli\.mjs\",\"root_id\":\"projects\"\} \[cursor r1:[0-9a-f]{24}:3\]$/m);
142
- assert.match(output.stdout,/✗ \[fedcba98\] ERR coding_file_read 1\.73s \[cursor r1:[0-9a-f]{24}:4\]/);
143
- assert.equal((output.stdout.match(/✓ \[abcdef12\] OK coding_tree 42ms/g)||[]).length,1,'duplicate terminal result must print once');
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');
144
180
  const resultJournal=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-results',`${marker.result_session_id}.json`),'utf8'));
145
181
  assert.deepEqual(resultJournal.events.map(event=>event.seq),[1,2,3,4]);
146
182
  assert.deepEqual(resultJournal.events.map(event=>event.event_type),['tool.call','tool.result','tool.call','tool.result']);
@@ -150,9 +186,7 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
150
186
  assert.match(output.stdout,/abcdef12/);
151
187
  assert.match(output.stdout,/fedcba98/);
152
188
  assert.doesNotMatch(output.stdout,/DO_NOT_PRINT_PAYLOAD|DO_NOT_PRINT_TOKEN/);
153
- assert.match(output.stdout,/\[BRIDGE\.STDOUT\] IN coding_tree/);
154
- assert.match(output.stdout,/\[BRIDGE\.STDOUT\] OUT coding_tree/);
155
- assert.match(output.stdout,/\[BRIDGE\.STDOUT\] ERR coding_file_read/);
189
+ assert.doesNotMatch(output.stdout,/\[BRIDGE\.(?:STDOUT|DEBUG)\]/,'transport diagnostics are hidden in default human mode');
156
190
  assert.doesNotMatch(output.stdout,/\[TOOL IN \]|\[TOOL OUT\]/);
157
191
  assert.doesNotMatch(output.stdout,/payload bytes=/);
158
192
  child.stdin.end();
@@ -161,11 +195,10 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
161
195
  const commands=await traceCommands(fx.trace);
162
196
  assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
163
197
  assert.match(output.stdout,/^DEADBYTE MCP$/m);
164
- assert.match(output.stdout,/Unified live timeline/i);
165
- 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/);
166
200
  assert.doesNotMatch(output.stdout,/disarmautonomous OK|disarmcoding OK|disarmprocess OK|disarmhost OK|DEADBYTE MCP = PARKED/);
167
- const withoutApprovedEmoji=output.stdout.replace(/[📜📥📤✅❌⚠♻🛡▶🔐🚀🔌]/gu,'');
168
- 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');
169
202
  }finally{
170
203
  if(child?.exitCode===null) child.kill();
171
204
  await rm(fx.dir,{recursive:true,force:true});
@@ -180,7 +213,9 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
180
213
  const firstOutput=await collect(first);
181
214
  await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
182
215
  const liveDeadline=Date.now()+3000;
183
- 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}$/);
184
219
  const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
185
220
  const auditFile=path.join(auditDir,'segment-00000001.jsonl');
186
221
  const firstBatch=[
@@ -189,16 +224,19 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
189
224
  ];
190
225
  await writeFile(auditFile,firstBatch.map(JSON.stringify).join('\n')+'\n','utf8');
191
226
  let deadline=Date.now()+3000;
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];
194
- 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:/);
195
233
  const secondBatch=[
196
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'}},
197
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}}
198
236
  ];
199
237
  await writeFile(auditFile,secondBatch.map(JSON.stringify).join('\n')+'\n',{encoding:'utf8',flag:'a'});
200
238
  deadline=Date.now()+3000;
201
- 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);
202
240
  first.stdin.end();
203
241
  const firstEnded=await Promise.race([firstOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
204
242
  assert.equal(firstEnded.timeout,undefined);
@@ -206,19 +244,19 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
206
244
  second=spawn(process.execPath,[cliPath,'remote','--resume',cursor1],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
207
245
  const secondOutput=await collect(second);
208
246
  deadline=Date.now()+5000;
209
- while(Date.now()<deadline&&(!/coding_file_read\s+\| Arguments: .*REPLAY cursor r1:[0-9a-f]{24}:3/.test(secondOutput.stdout)||
210
- !/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:4\]/.test(secondOutput.stdout)||
211
- !/\[LOG\] Unified live timeline/.test(secondOutput.stdout))) await sleep(50);
212
- assert.match(secondOutput.stdout,/^2026-09-18T16:30:01\.000Z \| coding_file_read\s+\| Arguments: .*\"path\":\"src\/deadbyte-cli\.mjs\".*REPLAY cursor r1:[0-9a-f]{24}:3$/m);
213
- assert.match(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_file_read \[cursor r1:[0-9a-f]{24}:4\]/);
214
- 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/);
215
253
  assert.doesNotMatch(secondOutput.stdout,/DO_NOT_REPLAY_SECRET/);
216
- assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\] \[OK\] coding_tree/);
254
+ assert.doesNotMatch(secondOutput.stdout,/\[REPLAY\].*coding_tree/);
217
255
  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');
256
+ assert.equal((secondOutput.stdout.match(/\[REPLAY\] #4 coding_file_read OK/g)||[]).length,1,'terminal replay duplicated');
219
257
  const callReplayIndex=secondOutput.stdout.indexOf('| coding_file_read');
220
- const resultReplayIndex=secondOutput.stdout.indexOf('[REPLAY] [OK] coding_file_read');
221
- const liveIndex=secondOutput.stdout.indexOf('[LOG] Unified live timeline');
258
+ const resultReplayIndex=secondOutput.stdout.indexOf('[REPLAY] #4 coding_file_read OK');
259
+ const liveIndex=secondOutput.stdout.indexOf('[LOG] Human live timeline');
222
260
  assert.ok(callReplayIndex>=0&&resultReplayIndex>callReplayIndex&&liveIndex>resultReplayIndex,'resume ordering must be call -> result -> live tail');
223
261
  second.stdin.end();
224
262
  const secondEnded=await Promise.race([secondOutput.closed,sleep(6000).then(()=>({timeout:true}))]);
@@ -230,17 +268,35 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
230
268
  }
231
269
  });
232
270
 
233
- 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()=>{
234
272
  const fx=await fixture();
273
+ let child=null;
235
274
  try{
236
- 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});
237
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();
238
291
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
239
292
  assert.equal(ended.timeout,undefined);
240
- assert.notEqual(ended.code,0);
241
- assert.match(output.stderr,/unsupported remote command '--verbose'/i);
242
- assert.deepEqual(await traceCommands(fx.trace),[],'rejected legacy flag must not touch runtime authority');
243
- }finally{await rm(fx.dir,{recursive:true,force:true});}
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
+ }
244
300
  });
245
301
 
246
302
  winTest('detached guardian revokes authority, parks, and removes the session marker after the owner is hard-killed',async()=>{
@@ -249,9 +305,9 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
249
305
  try{
250
306
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
251
307
  const output=await collect(child);
252
- const ready=await waitFor(fx.trace,commands=>commands.slice(0,8).join(',')==='start,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
253
- assert.deepEqual(ready.slice(0,9),[
254
- 'status','start','armprocess','armcoding','armautonomous',
308
+ const ready=await waitFor(fx.trace,commands=>commands.slice(0,10).join(',')==='status,start,status,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
309
+ assert.deepEqual(ready.slice(0,10),[
310
+ 'status','start','status','armprocess','armcoding','armautonomous',
255
311
  'processstatus','codingstatus','autonomousstatus','hoststatus'
256
312
  ]);
257
313
  const markerPath=path.join(fx.state,'runtime','remote-session.json');
@@ -294,7 +350,7 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
294
350
  assert.equal(ended.timeout,undefined,'failed startup did not terminate');
295
351
  assert.notEqual(ended.code,0);
296
352
  const commands=await traceCommands(fx.trace);
297
- assert.deepEqual(commands.slice(0,4),['status','start','armprocess','armcoding']);
353
+ assert.deepEqual(commands.slice(0,5),['status','start','status','armprocess','armcoding']);
298
354
  assert.deepEqual(commands.slice(-5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
299
355
  assert.doesNotMatch(output.stdout,/Live logs/i);
300
356
  }finally{await rm(fx.dir,{recursive:true,force:true});}
@@ -313,7 +369,7 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
313
369
  assert.deepEqual(ready.slice(0,5),['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park']);
314
370
  assert.equal(ready[5],'status');
315
371
  assert.equal(ready[6],'start');
316
- 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\./);
317
373
  child.stdin.end();
318
374
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
319
375
  assert.equal(ended.timeout,undefined);
@@ -324,7 +380,7 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
324
380
  }
325
381
  });
326
382
 
327
- 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()=>{
328
384
  const fx=await fixture();
329
385
  let child=null;
330
386
  try{
@@ -336,7 +392,8 @@ winTest('remote warning lines use the approved warning emoji without changing th
336
392
  child.stdin.end();
337
393
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
338
394
  assert.equal(ended.timeout,undefined,'remote session did not exit after warning fixture');
339
- 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);
340
397
  }finally{
341
398
  if(child?.exitCode===null) child.kill();
342
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);