deadbyte-mcp 0.15.35 → 0.15.37

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 (92) hide show
  1. package/MANIFEST.SHA256 +91 -46
  2. package/PROVENANCE.json +76 -30
  3. package/README.txt +62 -8
  4. package/RELEASE.json +78 -23
  5. package/SBOM.cdx.json +10 -25
  6. package/TEST-MATRIX.json +109 -44
  7. package/bridge/package.json +4 -2
  8. package/bridge/src/bridge-health-r59.mjs +53 -0
  9. package/bridge/src/bridge.mjs +158 -26
  10. package/bridge/src/canonical-json-r60.mjs +13 -0
  11. package/bridge/src/r40-governor.mjs +56 -10
  12. package/bridge/src/reconnect-delay-r60.mjs +31 -0
  13. package/bridge/src/remote-call-claims-r60.mjs +157 -0
  14. package/bridge/src/remote-dispatch-r60.mjs +75 -0
  15. package/controller/deadbyte-controller.ps1 +76 -13
  16. package/controller/deadbyte-process-policy.json +4 -4
  17. package/docs/R59-PROVENANCE.json +87 -0
  18. package/docs/R59-RELEASE.json +187 -0
  19. package/docs/R59-SBOM.cdx.json +68 -0
  20. package/docs/R59-TASK-CONTRACT.md +26 -0
  21. package/docs/R59-TEST-MATRIX.json +258 -0
  22. package/docs/R59-bridge-package.json +13 -0
  23. package/docs/R59-package-lock.json +184 -0
  24. package/docs/R59-package.json +106 -0
  25. package/docs/R59-version.mjs +6 -0
  26. package/docs/R60-TASK-CONTRACT.md +22 -0
  27. package/package.json +7 -3
  28. package/proof/r59-full-regression-current.json +13 -0
  29. package/proof/r59-full-regression-current.stderr.log +0 -0
  30. package/proof/r59-full-regression-current.stdout.log +1268 -0
  31. package/scripts/full-regression-r60.mjs +32 -0
  32. package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
  33. package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
  34. package/scripts/r59-full-regression-capture.mjs +35 -0
  35. package/src/capability-resolver.mjs +25 -7
  36. package/src/capability-snapshot.mjs +56 -0
  37. package/src/compact-gateway-mcp-tools.mjs +10 -11
  38. package/src/deadbyte-cli.mjs +80 -34
  39. package/src/mcp-server.mjs +179 -118
  40. package/src/r40-compact-disarmed-probe.mjs +1 -1
  41. package/src/r44-compact-disarmed-probe.mjs +1 -1
  42. package/src/r45-compact-disarmed-probe.mjs +1 -1
  43. package/src/remote-authority.mjs +56 -0
  44. package/src/remote-enforcement-probe-r60.mjs +17 -0
  45. package/src/remote-live-log.mjs +19 -2
  46. package/src/remote-log-decode-r60.mjs +32 -0
  47. package/src/remote-mcp-machine-smoke-client.mjs +19 -1
  48. package/src/remote-mcp-r40-parity-client.mjs +1 -1
  49. package/src/remote-mcp-r44-parity-client.mjs +1 -1
  50. package/src/remote-mcp-r45-parity-client.mjs +1 -1
  51. package/src/remote-mcp-smoke-client.mjs +1 -1
  52. package/src/remote-session-guardian.mjs +20 -3
  53. package/src/runtime-plane-registry.mjs +74 -0
  54. package/src/tool-audit.mjs +5 -0
  55. package/src/version.mjs +1 -1
  56. package/test/bridge-runtime-r59.test.mjs +60 -0
  57. package/test/capability-resolver.test.mjs +16 -0
  58. package/test/capability-snapshot-r59.test.mjs +39 -0
  59. package/test/compact-gateway-r36.test.mjs +22 -2
  60. package/test/compact-max-catalog-r44.test.mjs +1 -1
  61. package/test/compact-max-catalog-r45.test.mjs +1 -1
  62. package/test/contained.test.mjs +2 -1
  63. package/test/controller-policy-preflight-r59.test.mjs +39 -0
  64. package/test/fixtures/r59-compact-surface-v0.15.35.json +222 -0
  65. package/test/observation-integration.test.mjs +2 -3
  66. package/test/optional-plane-degraded-r59.test.mjs +44 -0
  67. package/test/r48-release-contract.test.mjs +1 -1
  68. package/test/r49-windows-gate.test.mjs +1 -1
  69. package/test/r51-runtime-coherence.test.mjs +2 -2
  70. package/test/r58-bundled-bridge.test.mjs +3 -1
  71. package/test/r58-cleanup-retention.test.mjs +9 -3
  72. package/test/r58-concurrent-recovery.test.mjs +9 -3
  73. package/test/r58-controller-live-local.test.mjs +36 -4
  74. package/test/r58-controller-state-coherence.test.mjs +1 -1
  75. package/test/r58-release-contract.test.mjs +3 -2
  76. package/test/r58-stale-owner.test.mjs +2 -2
  77. package/test/r58-windows-gate.test.mjs +2 -1
  78. package/test/r59-release-contract.test.mjs +61 -0
  79. package/test/r59-tool-surface-compat.test.mjs +90 -0
  80. package/test/r60-authority-coherence.test.mjs +36 -0
  81. package/test/r60-authority-enforcement.test.mjs +28 -0
  82. package/test/r60-bridge-dispatch.test.mjs +119 -0
  83. package/test/r60-clock-discipline.test.mjs +15 -0
  84. package/test/r60-live-timeline.test.mjs +29 -0
  85. package/test/r60-reconnect-errors.test.mjs +21 -0
  86. package/test/r60-reconnect-jitter.test.mjs +18 -0
  87. package/test/r60-release-contract.test.mjs +81 -0
  88. package/test/r60-remote-call-recovery.test.mjs +170 -0
  89. package/test/r60-windows-log-decode.test.mjs +25 -0
  90. package/test/remote-live-log-r37.test.mjs +1 -1
  91. package/test/remote-session-cli.test.mjs +200 -43
  92. package/test/runtime-plane-registry-r59.test.mjs +40 -0
@@ -0,0 +1,32 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const here=path.dirname(fileURLToPath(import.meta.url));
7
+ const root=path.resolve(here,'..');
8
+ const testDir=path.join(root,'test');
9
+ const excluded=new Set(['full-max-catalog-r44.test.mjs','full-max-catalog-r45.test.mjs','full-max-catalog-r46.test.mjs']);
10
+ const files=(await readdir(testDir,{withFileTypes:true}))
11
+ .filter(entry=>entry.isFile()&&entry.name.endsWith('.test.mjs')&&!excluded.has(entry.name));
12
+ const weighted=[];
13
+ for(const entry of files) weighted.push({path:path.join('test',entry.name),size:(await stat(path.join(testDir,entry.name))).size});
14
+ weighted.sort((a,b)=>b.size-a.size||a.path.localeCompare(b.path,'en'));
15
+ const batchCount=16;
16
+ const batches=Array.from({length:batchCount},()=>[]);
17
+ weighted.forEach((entry,index)=>batches[index%batchCount].push(entry.path));
18
+ const match=process.argv.slice(2).map(String).find(arg=>/^--batch=\d+$/.test(arg));
19
+ const selected=match?Number(match.slice('--batch='.length)):null;
20
+ if(selected!==null&&(!Number.isInteger(selected)||selected<1||selected>batchCount))throw new Error(`R60 regression batch must be 1..${batchCount}`);
21
+ const indexes=selected===null?[...Array(batchCount).keys()]:[selected-1];
22
+ let total=0;
23
+ for(const index of indexes){
24
+ const batch=batches[index].sort((a,b)=>a.localeCompare(b,'en'));
25
+ console.log(`R60-BOUNDED-REGRESSION batch=${index+1}/${batchCount} files=${batch.length}`);
26
+ const result=spawnSync(process.execPath,['--test','--test-concurrency=1',...batch],{cwd:root,stdio:'inherit',windowsHide:true,shell:false});
27
+ if(result.error)throw result.error;
28
+ if(result.signal)throw new Error(`R60 regression batch ${index+1} terminated by ${result.signal}`);
29
+ if(result.status!==0)throw new Error(`R60 regression batch ${index+1} failed with exit code ${result.status}`);
30
+ total+=batch.length;
31
+ }
32
+ console.log(`R60-BOUNDED-REGRESSION PASS files=${total} batches=${indexes.length}`);
@@ -0,0 +1,41 @@
1
+ $ErrorActionPreference='Stop'
2
+ Set-StrictMode -Version 3.0
3
+ $root=Split-Path $PSScriptRoot -Parent
4
+ Set-Location -LiteralPath $root
5
+
6
+ function Invoke-NodeTest([string]$Name,[string[]]$Files){
7
+ Write-Host "==== $Name ===="
8
+ & node.exe --test --test-concurrency=1 @Files
9
+ if($LASTEXITCODE -ne 0){ throw "R59 prepublish gate failed at $Name exit=$LASTEXITCODE" }
10
+ }
11
+ function Invoke-Npm([string]$Name,[string[]]$NpmArgs){
12
+ Write-Host "==== $Name ===="
13
+ & npm.cmd @NpmArgs
14
+ if($LASTEXITCODE -ne 0){ throw "R59 prepublish gate failed at $Name exit=$LASTEXITCODE" }
15
+ }
16
+
17
+ $active=Get-Content -LiteralPath (Join-Path $env:USERPROFILE '.deadbyte-mcp\active-release.json') -Raw | ConvertFrom-Json
18
+ if($active.release_id -ne 'v0.15.35-1b5715b276facbc0'){ throw "R59 predecessor release mismatch: $($active.release_id)" }
19
+ if($active.manifest_sha256 -ne '1b5715b276facbc00f8e341a554472cb4829e5af60e221c357c41b66053af9d4'){ throw 'R59 predecessor manifest mismatch' }
20
+ if($active.generation_manifest_sha256 -ne '047e73b62d7b7819b84201a221a09b7e647cacf21f82101ebed4fbd7e23fc430'){ throw 'R59 predecessor generation mismatch' }
21
+
22
+ Invoke-NodeTest 'r59-contracts' @(
23
+ 'test/capability-snapshot-r59.test.mjs',
24
+ 'test/capability-resolver.test.mjs',
25
+ 'test/runtime-plane-registry-r59.test.mjs',
26
+ 'test/optional-plane-degraded-r59.test.mjs',
27
+ 'test/bridge-runtime-r59.test.mjs',
28
+ 'test/controller-policy-preflight-r59.test.mjs',
29
+ 'test/r59-tool-surface-compat.test.mjs',
30
+ 'test/r58-controller-state-coherence.test.mjs'
31
+ )
32
+
33
+ $proof=Get-Content -LiteralPath (Join-Path $root 'proof\r59-full-regression-current.json') -Raw | ConvertFrom-Json
34
+ if($proof.schema -ne 'deadbyte.r59-full-regression-capture.v1' -or -not $proof.pass -or $proof.exit_code -ne 0){ throw 'R59 full regression proof is not green' }
35
+ if($proof.stderr_bytes -ne 0){ throw 'R59 full regression stderr is non-empty' }
36
+
37
+ Invoke-Npm 'manifest-write' @('run','manifest:write')
38
+ Invoke-Npm 'manifest-verify' @('run','manifest:verify')
39
+ Invoke-Npm 'doctor' @('run','doctor')
40
+
41
+ Write-Host '==== R59 PREPUBLISH WINDOWS GATE: PASS ===='
@@ -0,0 +1,74 @@
1
+ # R60 staged gate revision 3: final pre-package metadata seal identity
2
+ param(
3
+ [ValidateSet('all','preflight','focused','full-batch','manifest','doctor')]
4
+ [string]$Stage='all',
5
+ [ValidateRange(1,16)]
6
+ [int]$Batch=1
7
+ )
8
+
9
+ $ErrorActionPreference='Stop'
10
+ Set-StrictMode -Version 3.0
11
+ $root=Split-Path $PSScriptRoot -Parent
12
+ Set-Location -LiteralPath $root
13
+
14
+ function Invoke-NodeTest([string]$Name,[string[]]$Files){
15
+ Write-Host "==== $Name ===="
16
+ & node.exe --test --test-concurrency=1 @Files
17
+ if($LASTEXITCODE -ne 0){ throw "R60 prepublish gate failed at $Name exit=$LASTEXITCODE" }
18
+ }
19
+ function Invoke-Npm([string]$Name,[string[]]$NpmArgs){
20
+ Write-Host "==== $Name ===="
21
+ & npm.cmd @NpmArgs
22
+ if($LASTEXITCODE -ne 0){ throw "R60 prepublish gate failed at $Name exit=$LASTEXITCODE" }
23
+ }
24
+ function Assert-R60Predecessor {
25
+ $active=Get-Content -LiteralPath (Join-Path $env:USERPROFILE '.deadbyte-mcp\active-release.json') -Raw | ConvertFrom-Json
26
+ if($active.release_id -ne 'v0.15.36-090d9db3e0679c0d'){ throw "R60 predecessor release mismatch: $($active.release_id)" }
27
+ if($active.manifest_sha256 -ne '090d9db3e0679c0d4943f3229ecab7b9fcccbe4f767348a568c9f3bb60d1b431'){ throw 'R60 predecessor manifest mismatch' }
28
+ if($active.generation_manifest_sha256 -ne '6134f9f26b680e13ed545da4ab5f3020f411ea2569062fb9c154c8a7e036c9dc'){ throw 'R60 predecessor generation mismatch' }
29
+ }
30
+
31
+ Assert-R60Predecessor
32
+ if($Stage -eq 'preflight'){
33
+ Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS preflight ===='
34
+ return
35
+ }
36
+
37
+ if($Stage -eq 'all' -or $Stage -eq 'focused'){
38
+ Invoke-Npm 'r60-focused' @('run','r60:focused:test')
39
+ if($Stage -eq 'focused'){
40
+ Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS focused ===='
41
+ return
42
+ }
43
+ }
44
+
45
+ if($Stage -eq 'full-batch'){
46
+ Write-Host "==== r60-full-regression-batch-$Batch ===="
47
+ & node.exe scripts/full-regression-r60.mjs "--batch=$Batch"
48
+ if($LASTEXITCODE -ne 0){ throw "R60 prepublish gate failed at full-batch-$Batch exit=$LASTEXITCODE" }
49
+ Write-Host "==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS full-batch-$Batch ===="
50
+ return
51
+ }
52
+
53
+ if($Stage -eq 'all'){
54
+ Invoke-Npm 'r60-full-regression' @('run','r60:full:test')
55
+ }
56
+
57
+ if($Stage -eq 'all' -or $Stage -eq 'manifest'){
58
+ Invoke-Npm 'manifest-write' @('run','manifest:write')
59
+ Invoke-Npm 'manifest-verify' @('run','manifest:verify')
60
+ if($Stage -eq 'manifest'){
61
+ Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS manifest ===='
62
+ return
63
+ }
64
+ }
65
+
66
+ if($Stage -eq 'all' -or $Stage -eq 'doctor'){
67
+ Invoke-Npm 'doctor' @('run','doctor')
68
+ if($Stage -eq 'doctor'){
69
+ Write-Host '==== R60 PREPUBLISH WINDOWS GATE STAGE: PASS doctor ===='
70
+ return
71
+ }
72
+ }
73
+
74
+ Write-Host '==== R60 PREPUBLISH WINDOWS GATE: PASS ===='
@@ -0,0 +1,35 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ const here=path.dirname(fileURLToPath(import.meta.url));
8
+ const root=path.resolve(here,'..');
9
+ const proofDir=path.join(root,'proof');
10
+ await mkdir(proofDir,{recursive:true});
11
+ const started=new Date().toISOString();
12
+ const child=spawnSync(process.execPath,[path.join(root,'scripts','full-regression-r48.mjs')],{
13
+ cwd:root,encoding:'utf8',windowsHide:true,shell:false,maxBuffer:64*1024*1024
14
+ });
15
+ const stdout=child.stdout??'';
16
+ const stderr=child.stderr??'';
17
+ const sha=s=>createHash('sha256').update(s,'utf8').digest('hex');
18
+ const result={
19
+ schema:'deadbyte.r59-full-regression-capture.v1',
20
+ started_at_utc:started,
21
+ finished_at_utc:new Date().toISOString(),
22
+ exit_code:child.status,
23
+ signal:child.signal??null,
24
+ error:child.error?String(child.error.message??child.error):null,
25
+ stdout_sha256:sha(stdout),
26
+ stderr_sha256:sha(stderr),
27
+ stdout_bytes:Buffer.byteLength(stdout),
28
+ stderr_bytes:Buffer.byteLength(stderr),
29
+ pass:child.status===0&&!child.signal&&!child.error
30
+ };
31
+ await writeFile(path.join(proofDir,'r59-full-regression-current.stdout.log'),stdout,'utf8');
32
+ await writeFile(path.join(proofDir,'r59-full-regression-current.stderr.log'),stderr,'utf8');
33
+ await writeFile(path.join(proofDir,'r59-full-regression-current.json'),JSON.stringify(result,null,2)+'\n','utf8');
34
+ console.log(JSON.stringify(result));
35
+ if(!result.pass) process.exitCode=1;
@@ -146,7 +146,8 @@ export async function resolveCapabilityStates({
146
146
  desktopPolicy = null,
147
147
  autonomousRuntime = null,
148
148
  skillRuntime = null,
149
- hostPolicy = null
149
+ hostPolicy = null,
150
+ runtimePlanes = null
150
151
  } = {}) {
151
152
  const capabilities = ledger?.entries ?? [];
152
153
  const resolved = [];
@@ -201,9 +202,26 @@ export async function resolveCapabilityStates({
201
202
  let state = CAPABILITY_STATES.AVAILABLE;
202
203
  let ready = true;
203
204
  let diagnostic = null;
205
+ const livePlane = cap.runtime_flag && runtimePlanes && typeof runtimePlanes === 'object'
206
+ ? runtimePlanes[cap.runtime_flag] ?? null
207
+ : null;
204
208
 
205
- // 1. Runtime Flag Disabled check
206
- if (cap.runtime_flag && env[cap.runtime_flag] === '0') {
209
+ // 1. A live plane overlay may only reduce availability declared by the
210
+ // signed ledger. It cannot create a capability or bypass later authority
211
+ // checks.
212
+ if (livePlane && livePlane.ready !== true) {
213
+ const observed=String(livePlane.state??'UNAVAILABLE');
214
+ state=observed==='DEGRADED'?CAPABILITY_STATES.DEGRADED:
215
+ observed==='DISABLED'?CAPABILITY_STATES.DISABLED:CAPABILITY_STATES.UNAVAILABLE;
216
+ ready=false;
217
+ diagnostic={
218
+ code:String(livePlane.diagnostic?.code??`RUNTIME_PLANE_${observed}`).slice(0,128),
219
+ reason:String(livePlane.diagnostic?.reason??`Runtime plane ${cap.runtime_flag} is ${observed}`).slice(0,1024),
220
+ recoverable:livePlane.diagnostic?.recoverable===true
221
+ };
222
+ }
223
+ // 2. Runtime Flag Disabled check
224
+ else if (cap.runtime_flag && env[cap.runtime_flag] === '0') {
207
225
  state = CAPABILITY_STATES.DISABLED;
208
226
  ready = false;
209
227
  diagnostic = {
@@ -212,7 +230,7 @@ export async function resolveCapabilityStates({
212
230
  recoverable: false
213
231
  };
214
232
  }
215
- // 2. Tool presence check
233
+ // 3. Tool presence check
216
234
  else if (!catalogSet.has(activeTool)) {
217
235
  state = CAPABILITY_STATES.UNAVAILABLE;
218
236
  ready = false;
@@ -222,7 +240,7 @@ export async function resolveCapabilityStates({
222
240
  recoverable: false
223
241
  };
224
242
  }
225
- // 3. Subsystem runtime instantiation check
243
+ // 4. Subsystem runtime instantiation check
226
244
  else if (cap.id.startsWith('machine_fs.') && !machineFsRuntime) {
227
245
  state = CAPABILITY_STATES.UNAVAILABLE;
228
246
  ready = false;
@@ -256,7 +274,7 @@ export async function resolveCapabilityStates({
256
274
  recoverable: true
257
275
  };
258
276
  }
259
- // 4. Session Authority Checks
277
+ // 5. Session Authority Checks
260
278
  else if (requires.includes('coding_session.armed') && !codingStatus?.armed) {
261
279
  state = CAPABILITY_STATES.DENIED;
262
280
  ready = false;
@@ -298,7 +316,7 @@ export async function resolveCapabilityStates({
298
316
  recoverable: true
299
317
  };
300
318
  }
301
- // 5. Subsystem Health Probes (e.g. Skill Plane envelope verification)
319
+ // 6. Subsystem Health Probes (e.g. Skill Plane envelope verification)
302
320
  else if (cap.id === 'skill.catalog') {
303
321
  if (skillListResult?.invalid_skills && skillListResult.invalid_skills.length > 0) {
304
322
  state = CAPABILITY_STATES.DEGRADED;
@@ -0,0 +1,56 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ const HEX64=/^[0-9a-f]{64}$/;
4
+ function canonicalJson(value){
5
+ if(value===null||typeof value!=='object') return JSON.stringify(value);
6
+ if(Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
7
+ return `{${Object.keys(value).sort((a,b)=>a.localeCompare(b,'en')).map(key=>`${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
8
+ }
9
+ const sha=value=>createHash('sha256').update(canonicalJson(value),'utf8').digest('hex');
10
+ function iso(ms){return new Date(ms).toISOString();}
11
+ function boundedPlaneState(raw){
12
+ const diagnostic=raw?.diagnostic&&typeof raw.diagnostic==='object'?{
13
+ code:String(raw.diagnostic.code??'UNKNOWN').slice(0,128),
14
+ reason:String(raw.diagnostic.reason??'').replace(/[\r\n\t]+/g,' ').slice(0,512),
15
+ recoverable:raw.diagnostic.recoverable===true
16
+ }:null;
17
+ return Object.freeze({state:String(raw?.state??'UNAVAILABLE'),ready:raw?.ready===true,
18
+ policy_sha256:HEX64.test(String(raw?.policy_sha256??''))?raw.policy_sha256:null,diagnostic});
19
+ }
20
+
21
+ export function createCapabilitySnapshotTracker({clock=Date.now,ttlMs=5000}={}){
22
+ if(typeof clock!=='function'||!Number.isInteger(ttlMs)||ttlMs<1000||ttlMs>15000) throw new Error('capability snapshot tracker options invalid');
23
+ let lastFingerprint=null,stateEpoch=0,lastSnapshotId=null;
24
+ function snapshot({profile,ledger,toolCatalog,catalogSha256,resolution,bindings={},planes={}}){
25
+ if(!ledger||!HEX64.test(String(ledger.ledger_sha256??''))||!Array.isArray(ledger.entries)) throw new Error('capability snapshot ledger invalid');
26
+ if(!Array.isArray(toolCatalog)||toolCatalog.some(name=>typeof name!=='string')||new Set(toolCatalog).size!==toolCatalog.length) throw new Error('capability snapshot catalog invalid');
27
+ if(!resolution||!Array.isArray(resolution.capabilities)) throw new Error('capability snapshot resolution invalid');
28
+ const signedIds=new Set(ledger.entries.map(entry=>entry.id));
29
+ for(const capability of resolution.capabilities) if(!signedIds.has(capability.id)) throw new Error(`capability '${capability.id}' is outside signed ledger`);
30
+ const safePlanes=Object.freeze(Object.fromEntries(Object.entries(planes).sort(([a],[b])=>a.localeCompare(b,'en')).map(([id,value])=>[id,boundedPlaneState(value)])));
31
+ const capabilities=resolution.capabilities.map(capability=>Object.freeze({...capability,
32
+ selected_route:Object.freeze({tool:capability.tool,operations:[...(capability.operations??[])]})
33
+ }));
34
+ const safeBindings={
35
+ release_id:typeof bindings.release_id==='string'?bindings.release_id:null,
36
+ release_manifest_sha256:HEX64.test(String(bindings.release_manifest_sha256??''))?bindings.release_manifest_sha256:null,
37
+ generation_manifest_sha256:HEX64.test(String(bindings.generation_manifest_sha256??''))?bindings.generation_manifest_sha256:null
38
+ };
39
+ const body={profile,ledger_sha256:ledger.ledger_sha256,tool_catalog:[...toolCatalog],tool_count:toolCatalog.length,
40
+ catalog_sha256:catalogSha256,capabilities,effective_capabilities:[...(resolution.effective_capabilities??[])],
41
+ unavailable_capabilities:[...(resolution.unavailable_capabilities??[])],degraded_capabilities:[...(resolution.degraded_capabilities??[])],
42
+ bindings:safeBindings,planes:safePlanes,
43
+ // Informational transport contract, not an authority-bearing ledger entry.
44
+ remote_dispatch_contract:Object.freeze({scope:'bridge_ingress_only',schema:'deadbyte.remote-dispatch.v1',
45
+ keyed_mutation:'same stable key and canonical payload: no duplicate dispatch; unknown post-dispatch outcome is not retried',
46
+ read_only_recovery:'a proven read-only interrupted call may re-execute under its original deadline',
47
+ keyless_mutation:'cross-reconnect logical retry safety unavailable; ambiguous outcome is not auto-replayed'})};
48
+ const fingerprint=sha(body);
49
+ if(fingerprint!==lastFingerprint){stateEpoch++;lastFingerprint=fingerprint;lastSnapshotId=sha({fingerprint,state_epoch:stateEpoch});}
50
+ const observed=Number(clock());
51
+ if(!Number.isFinite(observed)) throw new Error('capability snapshot clock invalid');
52
+ return Object.freeze({schema:'deadbyte.capability-manifest.v3',...body,snapshot_id:lastSnapshotId,state_epoch:stateEpoch,
53
+ observed_at_utc:iso(observed),valid_until_utc:iso(observed+ttlMs)});
54
+ }
55
+ return Object.freeze({snapshot});
56
+ }
@@ -3,6 +3,7 @@ import * as z from 'zod/v4';
3
3
  import { compactGatewayOutputSchema, compactGatewayResult } from './compact-gateway-output-contracts.mjs';
4
4
  import { desktopControlInputSchema,invokeDesktopOperation } from './desktop-mcp-tools-r40.mjs';
5
5
  import { resolveCapabilityStates } from './capability-resolver.mjs';
6
+ import { createCapabilitySnapshotTracker } from './capability-snapshot.mjs';
6
7
 
7
8
  const id=z.string().regex(/^[A-Za-z0-9._-]{1,64}$/);
8
9
  const rel=z.string().min(1).max(2048);
@@ -71,7 +72,7 @@ function unavailable(op,name){return compactGatewayResult({status:'unavailable',
71
72
  async function invoke(op,runtime,name,call){
72
73
  if(!runtime) return unavailable(op,name);
73
74
  try{return compactGatewayResult({status:'ok',op,result:await call(runtime)},false);}
74
- catch(error){return compactGatewayResult({status:'denied',op,error:String(error)},true);}
75
+ catch(error){return compactGatewayResult({status:error?.code==='DEADBYTE_RUNTIME_UNAVAILABLE'?'unavailable':'denied',op,error:String(error)},true);}
75
76
  }
76
77
 
77
78
  async function codingInspect(args,codingRuntime,semanticRuntime){
@@ -138,27 +139,25 @@ async function machineObserve(args,machineRuntime,observationRuntime){
138
139
  return invoke(args.op,observationRuntime,'observation',map[args.op]);
139
140
  }
140
141
 
141
- export function registerCompactGatewayTools(server,{profile,ledger,codingRuntime,semanticRuntime,machineFsRuntime,processRuntime,observationRuntime,machineRuntime,desktopRuntime,skillRuntime,autonomousRuntime,hostPolicy,env=process.env,catalogProvider}){
142
+ export function registerCompactGatewayTools(server,{profile,ledger,codingRuntime,semanticRuntime,machineFsRuntime,processRuntime,observationRuntime,machineRuntime,desktopRuntime,skillRuntime,autonomousRuntime,hostPolicy,env=process.env,catalogProvider,runtimePlanes=null,releaseBindingProvider=null}){
142
143
  if(!server) throw new Error('compact gateway registration requires server');
143
144
  if(profile!=='compact') return;
144
145
  if(typeof catalogProvider!=='function') throw new Error('compact gateway requires catalogProvider');
146
+ const capabilitySnapshots=createCapabilitySnapshotTracker({ttlMs:5000});
145
147
  server.registerTool('capability_manifest',{description:'Read the canonical capability ledger and exact active compact tool catalog with runtime-aware resolution.',inputSchema:capabilityManifestInputSchema,outputSchema:compactGatewayOutputSchema},async()=>{
146
148
  const toolCatalog=[...catalogProvider()].map(String).sort();
147
149
  if(toolCatalog.length<1||new Set(toolCatalog).size!==toolCatalog.length) throw new Error('compact catalog provider returned invalid catalog');
148
150
  const catalogSha256=createHash('sha256').update(JSON.stringify(toolCatalog),'utf8').digest('hex');
151
+ const livePlanes=typeof runtimePlanes==='function'?await runtimePlanes():runtimePlanes;
149
152
  const dynamicResolution = await resolveCapabilityStates({
150
153
  ledger, toolCatalog, profile, env,
151
154
  codingRuntime, processRuntime, machineFsRuntime, observationRuntime, desktopRuntime,
152
- autonomousRuntime, skillRuntime, hostPolicy
155
+ autonomousRuntime, skillRuntime, hostPolicy, runtimePlanes:livePlanes
153
156
  });
154
- return compactGatewayResult({status:'ok',op:'manifest',result:{
155
- schema:'deadbyte.capability-manifest.v2',profile,ledger_sha256:ledger?.ledger_sha256??null,
156
- tool_catalog:toolCatalog,tool_count:toolCatalog.length,catalog_sha256:catalogSha256,
157
- capabilities:dynamicResolution.capabilities,
158
- effective_capabilities:dynamicResolution.effective_capabilities,
159
- unavailable_capabilities:dynamicResolution.unavailable_capabilities,
160
- degraded_capabilities:dynamicResolution.degraded_capabilities
161
- }},false);
157
+ const bindings=typeof releaseBindingProvider==='function'?await releaseBindingProvider():{};
158
+ return compactGatewayResult({status:'ok',op:'manifest',result:capabilitySnapshots.snapshot({
159
+ profile,ledger,toolCatalog,catalogSha256,resolution:dynamicResolution,bindings,planes:livePlanes??{}
160
+ })},false);
162
161
  });
163
162
  server.registerTool('coding_inspect',{description:'Typed compact coding inspection and semantic navigation gateway. No arbitrary tool dispatch.',inputSchema:codingInspectInputSchema,outputSchema:compactGatewayOutputSchema},args=>codingInspect(args,codingRuntime,semanticRuntime));
164
163
  server.registerTool('machine_fs',{description:'Typed compact machine filesystem gateway preserving root policy and mutation preconditions.',inputSchema:machineFsGatewayInputSchema,outputSchema:compactGatewayOutputSchema},args=>machineFs(args,machineFsRuntime));
@@ -20,6 +20,8 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
20
20
  import { resolveStateRoot } from './state-root.mjs';
21
21
  import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
22
22
  import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
23
+ import { AUTHORITY_PLANES, authorityVector, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
24
+ import { decodeRemoteLogBytes, boundedRemoteDiagnostic, safeRemoteLogPrefixLength } from './remote-log-decode-r60.mjs';
23
25
 
24
26
  const REMOTE_COMMANDS=new Set([
25
27
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
@@ -27,17 +29,9 @@ const REMOTE_COMMANDS=new Set([
27
29
  'armdesktop','disarmdesktop','desktopstatus',
28
30
  'armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
29
31
  ]);
30
- const SESSION_ARM=['armprocess','armcoding','armautonomous'];
31
32
  const REMOTE_STATUS_TIMEOUT_MS=30000;
32
33
  const REMOTE_START_TIMEOUT_MS=150000;
33
- const SESSION_STATUS=[
34
- ['processstatus',/^Process plane\s*:\s*ARMED\b/m],
35
- ['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
36
- ['autonomousstatus',/^Autonomous\s*:\s*ARMED\b/m],
37
- ['desktopstatus',/^Desktop plane\s*:\s*DISARMED\b/m],
38
- ['hoststatus',/^Host plane\s*:\s*DISARMED\b/m]
39
- ];
40
- const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
34
+ const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
41
35
  const SUPERVISOR_COMMANDS=Object.freeze({
42
36
  start:'supervisorstart',stop:'supervisorstop',status:'supervisorstatus',recover:'supervisorrecover'
43
37
  });
@@ -55,6 +49,9 @@ function printHelp(){
55
49
  ` npx -y ${exact} doctor # verify current package/release truth`,
56
50
  ` npx -y ${exact} mcp # stdio MCP server for local clients`,
57
51
  ` npx -y ${exact} remote # unified foreground device session + live timeline`,
52
+ ` npx -y ${exact} remote --desktop # session with Desktop authority; Host remains disarmed`,
53
+ ` npx -y ${exact} remote --host # session with Host authority; Desktop remains disarmed`,
54
+ ` npx -y ${exact} remote --full # explicit five-plane foreground session`,
58
55
  ` npx -y ${exact} remote --resume <cursor> # replay missed results, then reconnect`,
59
56
  ` npx -y ${exact} remote --trace # show trace/span identity without payload detail`,
60
57
  ` npx -y ${exact} remote --filter <expr> # presentation filter: tool/category/status/trace`,
@@ -75,8 +72,7 @@ async function remoteContext(){
75
72
  return {root,launcher,powershell};
76
73
  }
77
74
  function boundedSingleLine(value,maxChars=2048){
78
- const line=String(value??'').replace(/[\r\n\t]+/g,' ').replace(/\s{2,}/g,' ').trim();
79
- return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
75
+ return boundedRemoteDiagnostic(value,maxChars);
80
76
  }
81
77
  async function readActiveRuntimeBinding(root){
82
78
  const file=path.join(root,'active-release.json');
@@ -158,11 +154,16 @@ async function readTailBounded(file,maxBytes=4096){
158
154
  if(length===0) return '';
159
155
  const buffer=Buffer.alloc(length);
160
156
  const handle=await open(file,'r');
161
- try{await handle.read(buffer,0,length,info.size-length);}finally{await handle.close();}
162
- return boundedSingleLine(buffer.toString('utf8'),maxBytes);
157
+ let encoding=null;
158
+ try{
159
+ const header=Buffer.alloc(2);await handle.read(header,0,2,0);
160
+ if(header[0]===0xff&&header[1]===0xfe)encoding='utf16le';
161
+ await handle.read(buffer,0,length,info.size-length);
162
+ }finally{await handle.close();}
163
+ return boundedSingleLine(decodeRemoteLogBytes(file,buffer,{encoding}),maxBytes);
163
164
  }catch(error){if(error?.code==='ENOENT') return '';throw error;}
164
165
  }
165
- function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null}={}){
166
+ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null,json=false,probeMode=null}={}){
166
167
  if(capture&&discardOutput) throw new Error('invokeController cannot capture and discard output together');
167
168
  if(capture&&redirectOutput) throw new Error('invokeController cannot capture and redirect output together');
168
169
  if(discardOutput&&redirectOutput) throw new Error('invokeController cannot discard and redirect output together');
@@ -175,9 +176,10 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
175
176
  stderrFd=openSync(redirectOutput.stderr_path,'w');
176
177
  stdio=['ignore',stdoutFd,stderrFd];
177
178
  }else stdio=capture?['ignore','pipe','pipe']:'inherit';
178
- const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,env:windowsPowerShellEnvironment()};
179
+ const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,
180
+ env:windowsPowerShellEnvironment(probeMode?{...process.env,DEADBYTE_REMOTE_PROBE_MODE:probeMode}:process.env)};
179
181
  if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
180
- result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
182
+ result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command,...(json?['-Json']:[])],options);
181
183
  }finally{
182
184
  if(stdoutFd!==null) closeSync(stdoutFd);
183
185
  if(stderrFd!==null) closeSync(stderrFd);
@@ -193,6 +195,10 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
193
195
  if(status!==0&&!allowFailure) throw new Error(`remote controller '${command}' failed exit=${status}`);
194
196
  return {status,stdout,stderr};
195
197
  }
198
+ function observedAuthority(ctx){
199
+ const result=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS,json:true});
200
+ return parseAuthorityObservation(result.stdout);
201
+ }
196
202
  function controllerRuntimeReady(text){
197
203
  const value=String(text||'');
198
204
  return /^DEADBYTE MCP\s*=\s*ONLINE\s*$/m.test(value)&&
@@ -278,13 +284,23 @@ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
278
284
  async function readAppend(file,offset){
279
285
  let info;try{info=await stat(file);}catch(error){if(error?.code==='ENOENT') return {offset:0,text:''};throw error;}
280
286
  let start=offset;
281
- if(info.size<start) start=0;
287
+ const reset=info.size<start;
288
+ if(reset) start=0;
282
289
  if(info.size===start) return {offset:start,text:''};
283
- const length=info.size-start;
284
- const buffer=Buffer.alloc(length);
285
290
  const handle=await open(file,'r');
286
- try{await handle.read(buffer,0,length,start);}finally{await handle.close();}
287
- return {offset:info.size,text:buffer.toString('utf8')};
291
+ let encoding=null,buffer,bytesRead=0;
292
+ try{
293
+ const header=Buffer.alloc(2);await handle.read(header,0,2,0);
294
+ if(header[0]===0xff&&header[1]===0xfe&&/\.log$/i.test(file)){
295
+ encoding='utf16le';if(start%2!==0)start=Math.max(0,start-1);
296
+ }
297
+ const length=Math.min(1024*1024,info.size-start);
298
+ if(length===0)return {offset:start,text:''};
299
+ buffer=Buffer.alloc(length);
300
+ ({bytesRead}=await handle.read(buffer,0,length,start));
301
+ }finally{await handle.close();}
302
+ const complete=safeRemoteLogPrefixLength(buffer.subarray(0,bytesRead),encoding??'utf8');
303
+ return {offset:start+complete,text:decodeRemoteLogBytes(file,buffer.subarray(0,complete),{encoding}),reset};
288
304
  }
289
305
  async function snapshotRemoteLogOffsets(root){
290
306
  const offsets=new Map();
@@ -314,6 +330,7 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
314
330
  ? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
315
331
  : '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
316
332
  const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
333
+ const partialLines=new Map();
317
334
  let liveFiles=[...activeOffsets.keys()];
318
335
  let nextDiscoveryAt=0;
319
336
  while(!signal.aborted){
@@ -326,7 +343,13 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
326
343
  const prior=activeOffsets.has(file)?activeOffsets.get(file):0;
327
344
  const next=await readAppend(file,prior);
328
345
  activeOffsets.set(file,next.offset);
329
- for(const line of next.text.split(/\r?\n/)){
346
+ if(next.reset)partialLines.delete(file);
347
+ if(!next.text)continue;
348
+ const lines=((partialLines.get(file)??'')+next.text).split(/\r?\n/);
349
+ partialLines.set(file,lines.pop()??'');
350
+ if(Buffer.byteLength(partialLines.get(file),'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
351
+ for(const line of lines){
352
+ if(Buffer.byteLength(line,'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
330
353
  const auditEvent=parseAuditLogLine(file,line);
331
354
  const timeline=auditTimelineRecord(file,line,auditEvent);
332
355
  let recorded=null;
@@ -363,6 +386,15 @@ async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
363
386
  catch(error){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
364
387
  }
365
388
  if(failed.length) throw new Error(`remote session cleanup failed; ownership marker retained: ${failed.join(',')}`);
389
+ let snapshot;
390
+ try{snapshot=assertAuthorityVector(observedAuthority(ctx),Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,false])));}
391
+ catch(error){throw new Error(`remote session cleanup verification failed; ownership marker retained: ${String(error?.message||error)}`);}
392
+ if(snapshot.state==='ONLINE'){
393
+ try{invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:'revoked'});}
394
+ catch(error){throw new Error(`remote session enforcement cleanup verification failed; ownership marker retained: ${String(error?.message||error)}`);}
395
+ }
396
+ const parked=invokeController(ctx,'park',{allowFailure:true,capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
397
+ if(parked.status!==0) throw new Error(`remote session park failed exit=${parked.status}; ownership marker retained`);
366
398
  }
367
399
  const GUARDIAN_SCRIPT=fileURLToPath(new URL('./remote-session-guardian.mjs',import.meta.url));
368
400
  function remoteSessionMarker(root){return path.join(root,'runtime','remote-session.json');}
@@ -454,7 +486,7 @@ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
454
486
  const same=current?.schema==='deadbyte.remote-session.v2'&&current.owner_pid===marker.owner_pid&&current.session_nonce===marker.session_nonce;
455
487
  if(same) await rm(remoteSessionMarker(ctx.root),{force:true});
456
488
  }
457
- async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression=''}={}){
489
+ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default'}={}){
458
490
  const display=prepareRemoteConsole();
459
491
  const displayMode=display.mode;
460
492
  const ctx=await remoteContext();
@@ -524,21 +556,31 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
524
556
  }
525
557
  process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
526
558
  process.stdout.write(`${formatRemoteLifecycle('cloud_connected',{displayMode})}\n`);
527
- for(const command of SESSION_ARM){
559
+ const desired=authorityVector(mode);
560
+ const priorAuthority=observedAuthority(ctx);
561
+ for(const plane of AUTHORITY_PLANES.filter(name=>!desired[name]&&priorAuthority.authority[name].armed)){
562
+ const command=`disarm${plane}`;
563
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Revoking unwanted ${plane} authority...\n`,displayMode);
564
+ invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
565
+ }
566
+ for(const plane of AUTHORITY_PLANES.filter(name=>desired[name])){
567
+ const command=`arm${plane}`;
528
568
  writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Arming ${command.slice(3)}...\n`,displayMode);
529
569
  invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
530
570
  }
531
571
  const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
572
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying exact ${mode} authority vector...\n`,displayMode);
573
+ const current=await readActiveRuntimeBinding(ctx.root);
574
+ const authority=assertAuthorityVector(assertRuntimeReadyObservation(observedAuthority(ctx),{
575
+ coreRoot:current?.release_root??null
576
+ }),desired);
577
+ process.stdout.write('[BOOT] Verifying public signed MCP execution path...\n');
578
+ invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:mode});
579
+ ready=true;
580
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ${authorityBanner(authority)}\n`,displayMode);
532
581
  process.stdout.write('[OK] ONLINE\n');
533
582
  process.stdout.write(`${formatRemoteLifecycle('ready',{displayMode})}\n`);
534
583
  if(url) writeRemote(process.stdout,`URL: ${url}\n`,displayMode);
535
- writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying process, coding, autonomous; desktop + host must stay DISARMED...\n`,displayMode);
536
- for(const [command,pattern] of SESSION_STATUS){
537
- const result=invokeController(ctx,command,{capture:true,echoCaptured:false});
538
- if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} state mismatch`);
539
- }
540
- ready=true;
541
- writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ARMED: process, coding, autonomous | desktop, host DISARMED\n`,displayMode);
542
584
  await logTask;
543
585
  }finally{
544
586
  const wasAborted=aborter.signal.aborted;
@@ -556,12 +598,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
556
598
  }
557
599
  }
558
600
  async function runRemote(args){
559
- let verbose=false,trace=false,filterExpression='',resumeCursor=null;
601
+ let verbose=false,trace=false,filterExpression='',resumeCursor=null,mode='default';
560
602
  const positional=[];
561
603
  for(let index=0;index<args.length;index++){
562
604
  const arg=args[index];
563
605
  if(arg==='--verbose'||arg==='-v'){verbose=true;continue;}
564
606
  if(arg==='--trace'){trace=true;continue;}
607
+ if(['--desktop','--host','--full'].includes(arg)){
608
+ if(mode!=='default') throw new Error('remote authority modes are mutually exclusive');
609
+ mode=arg.slice(2);continue;
610
+ }
565
611
  if(arg==='--filter'){
566
612
  if(filterExpression) throw new Error('remote --filter may be specified once');
567
613
  if(index+1>=args.length) throw new Error('remote --filter requires an expression');
@@ -574,8 +620,8 @@ async function runRemote(args){
574
620
  }
575
621
  positional.push(arg);
576
622
  }
577
- const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null;
578
- if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression});return;}
623
+ const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null||mode!=='default';
624
+ if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression,mode});return;}
579
625
  if(presentation) throw new Error('remote presentation flags are valid only for foreground session');
580
626
  if(positional.length>1) throw new Error('remote accepts one controller command');
581
627
  const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);