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
@@ -11,6 +11,7 @@ import { verifyAuditChain } from './audit-verify.mjs';
11
11
  import { openRemoteResultJournal, parseRemoteResultCursor } from './remote-result-journal.mjs';
12
12
  import { DEADBYTE_VERSION } from './version.mjs';
13
13
  import { createLiveTimelineTracker, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
14
+ import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
14
15
 
15
16
  const REMOTE_COMMANDS=new Set([
16
17
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
@@ -55,9 +56,11 @@ async function remoteContext(){
55
56
  const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
56
57
  return {root,launcher,powershell};
57
58
  }
58
- function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null}={}){
59
+ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false}={}){
60
+ if(capture&&discardOutput) throw new Error('invokeController cannot capture and discard output together');
59
61
  const options={
60
- stdio:capture?['ignore','pipe','pipe']:'inherit',encoding:capture?'utf8':undefined,windowsHide:false,shell:false
62
+ stdio:discardOutput?['ignore','ignore','ignore']:(capture?['ignore','pipe','pipe']:'inherit'),
63
+ encoding:capture?'utf8':undefined,windowsHide:false,shell:false
61
64
  };
62
65
  if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
63
66
  const result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
@@ -93,30 +96,38 @@ async function listLogFiles(root){
93
96
  }
94
97
  return files;
95
98
  }
96
- function formatLogLine(file,line,{activityTracker=null}={}){
99
+ function formatLogLine(file,line,{activityTracker=null,sequence=null,verbose=false,displayMode='unicode'}={}){
97
100
  if(!line) return null;
98
101
  if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))){
99
102
  try{
100
103
  const event=JSON.parse(line);
101
- return activityTracker?activityTracker.observe(event):formatCompactAuditEvent(event);
104
+ return activityTracker?activityTracker.observe(event,{sequence}):formatCompactAuditEvent(event);
102
105
  }catch{return null;}
103
106
  }
104
- return formatRuntimeDiagnosticLine(file,line);
107
+ return formatRuntimeDiagnosticLine(file,line,{verbose,displayMode});
105
108
  }
106
109
  function remoteResultJournalPath(root,sessionId){return path.join(root,'runtime','remote-results',`${sessionId}.json`);}
107
110
  function auditTimelineRecord(file,line){
108
111
  if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
109
112
  let event;try{event=JSON.parse(line);}catch{return null;}
110
113
  if(typeof event?.request_id!=='string'||!event.request_id||typeof event.tool!=='string'||!event.tool) return null;
111
- const observed=Number.isFinite(Date.parse(event.created_at_utc))?new Date(Date.parse(event.created_at_utc)).toISOString():new Date().toISOString();
114
+ const stamp=[event.ts,event.created_at_utc,event.observed_at_utc].find(value=>typeof value==='string'&&Number.isFinite(Date.parse(value)));
115
+ const observed=stamp?new Date(Date.parse(stamp)).toISOString():new Date().toISOString();
112
116
  const payload_sha256=createHash('sha256').update(Buffer.from(line,'utf8')).digest('hex');
113
117
  if(event.event_type==='tool.request') return {event_type:'tool.call',request_id:event.request_id,tool:event.tool,
114
118
  observed_at_utc:observed,arguments:event.args??{},payload_sha256};
115
- if(event.event_type==='tool.result') return {event_type:'tool.result',request_id:event.request_id,tool:event.tool,
116
- status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,payload_sha256};
119
+ if(event.event_type==='tool.result'){
120
+ const unknown=event.outcome?.side_effect_state==='unknown';
121
+ const recoveryReason=unknown&&typeof event.outcome?.recovery?.reason==='string'?event.outcome.recovery.reason:null;
122
+ return {event_type:'tool.result',request_id:event.request_id,tool:event.tool,
123
+ status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,payload_sha256,
124
+ ...(Number.isFinite(Number(event.outcome?.duration_ms))?{duration_ms:Number(event.outcome.duration_ms)}:{}),
125
+ ...(unknown?{side_effect_state:'unknown'}:{}),
126
+ ...(typeof event.outcome?.reason==='string'&&event.outcome.reason?{reason:event.outcome.reason}:(recoveryReason?{reason:recoveryReason}:{}))};
127
+ }
117
128
  return null;
118
129
  }
119
- async function replayRemoteResults(root,cursor){
130
+ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
120
131
  const parsed=parseRemoteResultCursor(cursor);
121
132
  const file=remoteResultJournalPath(root,parsed.session_id);
122
133
  await requireRegular(file,'remote result journal');
@@ -127,12 +138,15 @@ async function replayRemoteResults(root,cursor){
127
138
  for(const event of page.events){
128
139
  if(event.event_type==='tool.call'){
129
140
  const line=formatRemoteToolCall({event_type:'tool.request',tool:event.tool,request_id:event.request_id,
130
- observed_at_utc:event.observed_at_utc,args:event.arguments});
131
- process.stdout.write(`${line} | REPLAY cursor r1:${event.session_id}:${event.seq}\n`);
141
+ observed_at_utc:event.observed_at_utc,args:event.arguments},{displayMode});
142
+ process.stdout.write(remoteDisplayText(`${line} | REPLAY #${event.seq}\n`,displayMode));
132
143
  continue;
133
144
  }
134
- const marker=event.status==='error'?'[ERR]':'[OK]';
135
- process.stdout.write(`[REPLAY] ${marker} ${event.tool} [cursor r1:${event.session_id}:${event.seq}]\n`);
145
+ const unknown=event.side_effect_state==='unknown';
146
+ const marker=unknown?'OUTCOME_UNKNOWN':event.status==='error'?(displayMode==='ascii'?'ERR':'✗'):(displayMode==='ascii'?'OK':'✓');
147
+ const duration=Number.isFinite(Number(event.duration_ms))?` ${event.duration_ms<1000?Math.round(event.duration_ms)+'ms':(event.duration_ms/1000).toFixed(2)+'s'}`:'';
148
+ const reason=(event.status==='error'||unknown)&&typeof event.reason==='string'&&event.reason?` — ${event.reason}`:'';
149
+ process.stdout.write(remoteDisplayText(`[REPLAY] #${event.seq} ${event.tool}${duration} ${marker}${reason}\n`,displayMode));
136
150
  }
137
151
  next=page.next_cursor;
138
152
  if(!page.has_more) return next;
@@ -156,8 +170,10 @@ async function snapshotRemoteLogOffsets(root){
156
170
  }
157
171
  return offsets;
158
172
  }
159
- async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null}={}){
160
- process.stdout.write('[LOG] Unified live timeline. Ctrl+C disconnects and revokes authority.\n');
173
+ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null,verbose=false,displayMode='unicode'}={}){
174
+ process.stdout.write(verbose
175
+ ? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
176
+ : '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
161
177
  const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
162
178
  while(!signal.aborted){
163
179
  for(const file of await listLogFiles(root)){
@@ -171,22 +187,28 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
171
187
  ? await journal.recordCall(timeline)
172
188
  : await journal.recordTerminal(timeline);
173
189
  if(recorded?.duplicate) continue;
174
- const formatted=formatLogLine(file,line,{activityTracker});
190
+ const formatted=formatLogLine(file,line,{activityTracker,sequence:recorded?.event?.seq??null,verbose,displayMode});
175
191
  if(!formatted) continue;
176
- if(recorded) process.stdout.write(`${formatted} [cursor ${recorded.cursor}]\n`);
177
- else process.stdout.write(`${formatted}\n`);
192
+ const output=Array.isArray(formatted)?formatted:[formatted];
193
+ for(const item of output) if(item) process.stdout.write(`${item}\n`);
178
194
  }
179
195
  }
180
196
  try{await sleep(250,null,{signal});}catch(error){if(error?.name!=='AbortError') throw error;}
181
197
  }
182
198
  }
183
- async function cleanupRemoteSession(ctx){
199
+ function remoteTag(kind,displayMode){
200
+ const unicode={warn:'⚠️ [WARN]',recovery:'♻️ [RECOVERY]',auth:'🔐 [AUTH]'};
201
+ const ascii={warn:'[WARN]',recovery:'[RECOVERY]',auth:'[AUTH]'};
202
+ return (displayMode==='ascii'?ascii:unicode)[kind];
203
+ }
204
+ function writeRemote(stream,text,displayMode){stream.write(remoteDisplayText(text,displayMode));}
205
+ async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
184
206
  for(const command of SESSION_CLEANUP){
185
207
  try{
186
208
  const result=invokeController(ctx,command,{allowFailure:true,capture:true,echoCaptured:false});
187
- if(result.status!==0) process.stderr.write(`⚠️ [WARN] cleanup ${command} exit=${result.status}\n`);
209
+ if(result.status!==0) writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command} exit=${result.status}\n`,displayMode);
188
210
  }
189
- catch(error){process.stderr.write(`⚠️ [WARN] cleanup ${command}: ${String(error?.message||error)}\n`);}
211
+ catch(error){writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
190
212
  }
191
213
  }
192
214
  const GUARDIAN_SCRIPT=fileURLToPath(new URL('./remote-session-guardian.mjs',import.meta.url));
@@ -213,7 +235,7 @@ async function readRemoteSessionMarker(root){
213
235
  }
214
236
  throw new Error('remote session marker schema invalid');
215
237
  }
216
- async function recoverRemoteSession(ctx){
238
+ async function recoverRemoteSession(ctx,{displayMode='unicode'}={}){
217
239
  let prior=await readRemoteSessionMarker(ctx.root);
218
240
  if(!prior) return;
219
241
  const ownerPid=remoteOwnerPid(prior);
@@ -228,8 +250,8 @@ async function recoverRemoteSession(ctx){
228
250
  }
229
251
  throw new Error(`remote session guardian cleanup still active pid=${prior.guardian_pid}`);
230
252
  }
231
- process.stdout.write(`♻️ [RECOVERY] Stale remote session pid=${ownerPid}; revoking authority before restart.\n`);
232
- await cleanupRemoteSession(ctx);
253
+ writeRemote(process.stdout,`${remoteTag('recovery',displayMode)} Stale remote session pid=${ownerPid}; revoking authority before restart.\n`,displayMode);
254
+ await cleanupRemoteSession(ctx,{displayMode});
233
255
  await rm(remoteSessionMarker(ctx.root),{force:true});
234
256
  }
235
257
  async function claimRemoteSession(ctx){
@@ -257,20 +279,23 @@ async function claimRemoteSession(ctx){
257
279
  throw error;
258
280
  }
259
281
  }
260
- async function releaseRemoteSession(ctx,marker){
261
- let current=null;try{current=await readRemoteSessionMarker(ctx.root);}catch(error){process.stderr.write(`⚠️ [WARN] remote session marker cleanup: ${String(error?.message||error)}\n`);return;}
282
+ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
283
+ let current=null;try{current=await readRemoteSessionMarker(ctx.root);}catch(error){writeRemote(process.stderr,`${remoteTag('warn',displayMode)} remote session marker cleanup: ${String(error?.message||error)}\n`,displayMode);return;}
262
284
  const same=current?.schema==='deadbyte.remote-session.v2'&&current.owner_pid===marker.owner_pid&&current.session_nonce===marker.session_nonce;
263
285
  if(same) await rm(remoteSessionMarker(ctx.root),{force:true});
264
286
  }
265
- async function runRemoteSession({resumeCursor=null}={}){
287
+ async function runRemoteSession({resumeCursor=null,verbose=false}={}){
288
+ const display=prepareRemoteConsole();
289
+ const displayMode=display.mode;
266
290
  const ctx=await remoteContext();
267
- if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor);
268
- await recoverRemoteSession(ctx);
291
+ if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
292
+ if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
293
+ await recoverRemoteSession(ctx,{displayMode});
269
294
  const marker=await claimRemoteSession(ctx);
270
295
  const resultJournal=await openRemoteResultJournal({
271
296
  filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
272
297
  });
273
- const activityTracker=createLiveTimelineTracker();
298
+ const activityTracker=createLiveTimelineTracker({verbose,displayMode});
274
299
  const aborter=new AbortController();
275
300
  const abort=()=>aborter.abort();
276
301
  const watchStdin=!process.stdin.isTTY;
@@ -280,10 +305,10 @@ async function runRemoteSession({resumeCursor=null}={}){
280
305
  let logTask=null;
281
306
  try{
282
307
  process.stdout.write('DEADBYTE MCP\n');
283
- process.stdout.write(`${formatRemoteLifecycle('device_start')}\n`);
284
- process.stdout.write(`${formatRemoteLifecycle(resumeCursor!==null?'session_restored':'session_new')}\n`);
308
+ process.stdout.write(`${formatRemoteLifecycle('device_start',{displayMode})}\n`);
309
+ process.stdout.write(`${formatRemoteLifecycle(resumeCursor!==null?'session_restored':'session_new',{displayMode})}\n`);
285
310
  const logOffsets=await snapshotRemoteLogOffsets(ctx.root);
286
- logTask=tailRemoteLogs(ctx.root,aborter.signal,{journal:resultJournal,offsets:logOffsets,activityTracker});
311
+ logTask=tailRemoteLogs(ctx.root,aborter.signal,{journal:resultJournal,offsets:logOffsets,activityTracker,verbose,displayMode});
287
312
  process.stdout.write('[BOOT] Starting local + remote runtime...\n');
288
313
  const preflight=invokeController(ctx,'status',{capture:true,allowFailure:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
289
314
  let startText=preflight.stdout||'';
@@ -291,45 +316,52 @@ async function runRemoteSession({resumeCursor=null}={}){
291
316
  process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
292
317
  }else{
293
318
  process.stdout.write('[BOOT] Starting bridge/runtime...\n');
294
- const started=invokeController(ctx,'start',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS});
319
+ invokeController(ctx,'start',{discardOutput:true,timeoutMs:REMOTE_START_TIMEOUT_MS});
320
+ const started=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
321
+ if(started.status!==0||!controllerRuntimeReady(started.stdout)) throw new Error('remote controller start returned without a verified ONLINE runtime');
295
322
  startText=started.stdout||'';
296
323
  }
297
- process.stdout.write(`${formatRemoteLifecycle('bridge_connected')}\n`);
298
- process.stdout.write(`${formatRemoteLifecycle('cloud_connected')}\n`);
324
+ process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
325
+ process.stdout.write(`${formatRemoteLifecycle('cloud_connected',{displayMode})}\n`);
299
326
  for(const command of SESSION_ARM){
300
- process.stdout.write(`🔐 [AUTH] Arming ${command.slice(3)}...\n`);
327
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Arming ${command.slice(3)}...\n`,displayMode);
301
328
  invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
302
329
  }
303
330
  const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
304
331
  process.stdout.write('[OK] ONLINE\n');
305
- process.stdout.write(`${formatRemoteLifecycle('ready')}\n`);
306
- if(url) process.stdout.write(`URL: ${url}\n`);
307
- process.stdout.write('🔐 [AUTH] Verifying process, coding, autonomous; host must stay DISARMED...\n');
332
+ process.stdout.write(`${formatRemoteLifecycle('ready',{displayMode})}\n`);
333
+ if(url) writeRemote(process.stdout,`URL: ${url}\n`,displayMode);
334
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying process, coding, autonomous; host must stay DISARMED...\n`,displayMode);
308
335
  for(const [command,pattern] of SESSION_STATUS){
309
336
  const result=invokeController(ctx,command,{capture:true,echoCaptured:false});
310
337
  if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} state mismatch`);
311
338
  }
312
339
  ready=true;
313
- process.stdout.write('🔐 [AUTH] ARMED: process, coding, autonomous | host DISARMED\n');
340
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ARMED: process, coding, autonomous | host DISARMED\n`,displayMode);
314
341
  await logTask;
315
342
  }finally{
316
343
  const wasAborted=aborter.signal.aborted;
317
344
  aborter.abort();
318
345
  if(logTask) await logTask.catch(()=>{});
319
346
  for(const line of activityTracker.reconcileInterrupted('session_end')) process.stdout.write(`${line}\n`);
347
+ process.stdout.write(`${activityTracker.summary()}\n`);
320
348
  process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
321
349
  if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
322
- process.stdout.write(`${formatRemoteLifecycle('cleanup')}\n`);
350
+ process.stdout.write(`${formatRemoteLifecycle('cleanup',{displayMode})}\n`);
323
351
  if(ready||!wasAborted) process.stdout.write('[INFO] Disconnecting; revoking authority...\n');
324
- await cleanupRemoteSession(ctx);
325
- await releaseRemoteSession(ctx,marker);
352
+ await cleanupRemoteSession(ctx,{displayMode});
353
+ await releaseRemoteSession(ctx,marker,{displayMode});
326
354
  }
327
355
  }
328
356
  async function runRemote(args){
329
357
  if(args.length===0){await runRemoteSession();return;}
330
- if(args.length===2&&args[0]==='--resume'){await runRemoteSession({resumeCursor:args[1]});return;}
331
- if(args.length>1) throw new Error('remote accepts one controller command or --resume <cursor>');
332
- const command=args[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
358
+ const verbose=args.includes('--verbose')||args.includes('-v');
359
+ const filtered=args.filter(arg=>arg!=='--verbose'&&arg!=='-v');
360
+ if(filtered.length===0){await runRemoteSession({verbose});return;}
361
+ if(filtered.length===2&&filtered[0]==='--resume'){await runRemoteSession({resumeCursor:filtered[1],verbose});return;}
362
+ if(verbose) throw new Error('remote --verbose/-v is valid only for foreground session or --resume <cursor>');
363
+ if(filtered.length>1) throw new Error('remote accepts one controller command or --resume <cursor>');
364
+ const command=filtered[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
333
365
  const ctx=await remoteContext();
334
366
  const result=invokeController(ctx,command,{allowFailure:true});
335
367
  process.exitCode=result.status;
@@ -0,0 +1,263 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { canonicalJson } from './canonical-json.mjs';
3
+
4
+ export const DESKTOP_FRAME_SCHEMA='deadbyte.desktop-frame.v1';
5
+ export const DESKTOP_TARGET_SCHEMA='deadbyte.desktop-target.v1';
6
+ export const DESKTOP_ACTION_SCHEMA='deadbyte.desktop-action.v1';
7
+ export const DESKTOP_GUARD_SCHEMA='deadbyte.desktop-action-guard.v1';
8
+ export const DESKTOP_POST_OBSERVATION_SCHEMA='deadbyte.desktop-post-observation.v1';
9
+ export const DESKTOP_ACTION_CHAIN_SCHEMA='deadbyte.desktop-action-chain.v1';
10
+
11
+ const HEX64=/^[0-9a-f]{64}$/;
12
+ const INTENT=/^[0-9a-f]{64}$/;
13
+ const HWND=/^0x[0-9a-f]{1,16}$/;
14
+ const ACTION_KINDS=new Set([
15
+ 'pointer_move','click_primary','click_secondary','double_click_primary',
16
+ 'scroll','key_chord','text_input'
17
+ ]);
18
+ const TARGET_SOURCES=new Set(['visual','uia','accessibility','geometry']);
19
+ const KEY=/^(?:[A-Z0-9]|F(?:[1-9]|1[0-2])|ENTER|ESC|TAB|SPACE|BACKSPACE|DELETE|INSERT|HOME|END|PAGEUP|PAGEDOWN|LEFT|RIGHT|UP|DOWN|CTRL|ALT|SHIFT|WIN)$/;
20
+
21
+ const sha256Bytes=bytes=>createHash('sha256').update(bytes).digest('hex');
22
+ const sha256Object=value=>sha256Bytes(Buffer.from(canonicalJson(value),'utf8'));
23
+ const isObject=value=>value!==null&&typeof value==='object'&&!Array.isArray(value);
24
+
25
+ function insist(condition,message){ if(!condition) throw new Error(message); }
26
+ function hash(value,label){ insist(typeof value==='string'&&HEX64.test(value),`${label} must be sha256`); return value; }
27
+ function parseUtc(value,label){
28
+ insist(typeof value==='string'&&value.length>=20&&value.length<=40,`${label} invalid`);
29
+ const ms=Date.parse(value); insist(Number.isFinite(ms),`${label} invalid`);
30
+ return ms;
31
+ }
32
+ function int(value,label,{min=Number.MIN_SAFE_INTEGER,max=Number.MAX_SAFE_INTEGER}={}){
33
+ insist(Number.isSafeInteger(value)&&value>=min&&value<=max,`${label} invalid`);
34
+ return value;
35
+ }
36
+ function number(value,label,{min=-Infinity,max=Infinity}={}){
37
+ insist(Number.isFinite(value)&&value>=min&&value<=max,`${label} invalid`);
38
+ return value;
39
+ }
40
+ function bounds(value,label='desktop bounds'){
41
+ insist(isObject(value),`${label} invalid`);
42
+ const left=int(value.left,`${label}.left`,{min:-131072,max:131072});
43
+ const top=int(value.top,`${label}.top`,{min:-131072,max:131072});
44
+ const width=int(value.width,`${label}.width`,{min:1,max:131072});
45
+ const height=int(value.height,`${label}.height`,{min:1,max:131072});
46
+ insist(left+width<=262144&&top+height<=262144,`${label} extent invalid`);
47
+ return Object.freeze({left,top,width,height});
48
+ }
49
+ function rect(value,label='target rect'){
50
+ insist(isObject(value),`${label} invalid`);
51
+ return Object.freeze({
52
+ left:int(value.left,`${label}.left`,{min:-131072,max:131072}),
53
+ top:int(value.top,`${label}.top`,{min:-131072,max:131072}),
54
+ width:int(value.width,`${label}.width`,{min:1,max:131072}),
55
+ height:int(value.height,`${label}.height`,{min:1,max:131072})
56
+ });
57
+ }
58
+ function point(value,label='point'){
59
+ insist(isObject(value),`${label} invalid`);
60
+ return Object.freeze({
61
+ x:int(value.x,`${label}.x`,{min:-131072,max:131072}),
62
+ y:int(value.y,`${label}.y`,{min:-131072,max:131072})
63
+ });
64
+ }
65
+ function insidePoint(b,p){ return p.x>=b.left&&p.y>=b.top&&p.x<b.left+b.width&&p.y<b.top+b.height; }
66
+ function insideRect(b,r){
67
+ return r.left>=b.left&&r.top>=b.top&&
68
+ r.left+r.width<=b.left+b.width&&r.top+r.height<=b.top+b.height;
69
+ }
70
+ function windowIdentity(value,{optional=false}={}){
71
+ if(value===null||value===undefined){
72
+ insist(optional,'window identity required'); return null;
73
+ }
74
+ insist(isObject(value),'window identity invalid');
75
+ const hwnd=String(value.hwnd??'').toLowerCase();
76
+ insist(HWND.test(hwnd),'window hwnd invalid');
77
+ const pid=int(value.pid,'window pid',{min:1,max:0x7fffffff});
78
+ const result={hwnd,pid};
79
+ if(value.process_image_sha256!==undefined&&value.process_image_sha256!==null) result.process_image_sha256=hash(value.process_image_sha256,'window process_image_sha256');
80
+ if(value.title_sha256!==undefined&&value.title_sha256!==null) result.title_sha256=hash(value.title_sha256,'window title_sha256');
81
+ return Object.freeze(result);
82
+ }
83
+ function sameWindow(expected,actual){
84
+ if(expected===null) return true;
85
+ if(actual===null) return false;
86
+ if(expected.hwnd!==actual.hwnd||expected.pid!==actual.pid) return false;
87
+ if(expected.process_image_sha256&&expected.process_image_sha256!==actual.process_image_sha256) return false;
88
+ if(expected.title_sha256&&expected.title_sha256!==actual.title_sha256) return false;
89
+ return true;
90
+ }
91
+ function normalizeKeyChord(value){
92
+ insist(Array.isArray(value)&&value.length>=1&&value.length<=5,'key_chord invalid');
93
+ const keys=value.map(item=>String(item).toUpperCase());
94
+ insist(keys.every(item=>KEY.test(item)),'key_chord contains unsupported key');
95
+ insist(new Set(keys).size===keys.length,'key_chord contains duplicate key');
96
+ return Object.freeze(keys);
97
+ }
98
+ function validateFrameObject(frame){
99
+ insist(isObject(frame),'desktop frame invalid');
100
+ insist(frame.schema===DESKTOP_FRAME_SCHEMA,'desktop frame schema mismatch');
101
+ hash(frame.desktop_epoch,'desktop_epoch');
102
+ hash(frame.topology_sha256,'topology_sha256');
103
+ hash(frame.image_sha256,'image_sha256');
104
+ hash(frame.frame_id,'frame_id');
105
+ parseUtc(frame.observed_at_utc,'observed_at_utc');
106
+ const b=bounds(frame.bounds);
107
+ const active=frame.active_window===null?null:windowIdentity(frame.active_window);
108
+ const body={
109
+ schema:DESKTOP_FRAME_SCHEMA,
110
+ desktop_epoch:frame.desktop_epoch,
111
+ topology_sha256:frame.topology_sha256,
112
+ observed_at_utc:frame.observed_at_utc,
113
+ bounds:b,
114
+ active_window:active,
115
+ image_sha256:frame.image_sha256
116
+ };
117
+ insist(sha256Object(body)===frame.frame_id,'desktop frame hash mismatch');
118
+ return Object.freeze({...body,frame_id:frame.frame_id});
119
+ }
120
+
121
+ export function createDesktopFrame({desktopEpoch,topologySha256,observedAtUtc,bounds:inputBounds,activeWindow=null,imageSha256}={}){
122
+ hash(desktopEpoch,'desktopEpoch'); hash(topologySha256,'topologySha256'); hash(imageSha256,'imageSha256');
123
+ parseUtc(observedAtUtc,'observedAtUtc');
124
+ const body={
125
+ schema:DESKTOP_FRAME_SCHEMA,
126
+ desktop_epoch:desktopEpoch,
127
+ topology_sha256:topologySha256,
128
+ observed_at_utc:observedAtUtc,
129
+ bounds:bounds(inputBounds),
130
+ active_window:activeWindow===null?null:windowIdentity(activeWindow),
131
+ image_sha256:imageSha256
132
+ };
133
+ return Object.freeze({...body,frame_id:sha256Object(body)});
134
+ }
135
+
136
+ export function verifyDesktopFrame(frame){
137
+ try { const normalized=validateFrameObject(frame); return {ok:true,frame:normalized}; }
138
+ catch(error){ return {ok:false,reason:String(error?.message??error)}; }
139
+ }
140
+
141
+ export function createDesktopTarget({frame,rect:inputRect,source,confidence,semanticLabelSha256=null,windowBinding=undefined}={}){
142
+ const f=validateFrameObject(frame);
143
+ insist(TARGET_SOURCES.has(source),'target source invalid');
144
+ number(confidence,'target confidence',{min:0,max:1});
145
+ const r=rect(inputRect);
146
+ insist(insideRect(f.bounds,r),'target rect outside desktop bounds');
147
+ const binding=windowBinding===undefined?f.active_window:windowIdentity(windowBinding,{optional:true});
148
+ if(binding!==null) insist(sameWindow(binding,f.active_window),'target window binding does not match frame');
149
+ if(semanticLabelSha256!==null) hash(semanticLabelSha256,'semantic_label_sha256');
150
+ const body={
151
+ schema:DESKTOP_TARGET_SCHEMA,frame_id:f.frame_id,desktop_epoch:f.desktop_epoch,
152
+ topology_sha256:f.topology_sha256,rect:r,source,confidence,
153
+ window_binding:binding,semantic_label_sha256:semanticLabelSha256
154
+ };
155
+ return Object.freeze({...body,target_id:sha256Object(body)});
156
+ }
157
+
158
+ export function createDesktopAction({
159
+ intentId,kind,frameId,desktopEpoch,topologySha256,expectedWindow=null,
160
+ point:inputPoint=null,scrollDelta=null,keyChord=null,text=null,targetId=null
161
+ }={}){
162
+ insist(typeof intentId==='string'&&INTENT.test(intentId),'intentId invalid');
163
+ insist(ACTION_KINDS.has(kind),'desktop action kind invalid');
164
+ hash(frameId,'frameId'); hash(desktopEpoch,'desktopEpoch'); hash(topologySha256,'topologySha256');
165
+ if(targetId!==null) hash(targetId,'targetId');
166
+ const expected=windowIdentity(expectedWindow,{optional:true});
167
+ let p=null,delta=null,keys=null,textValue=null,textSha256=null;
168
+ if(['pointer_move','click_primary','click_secondary','double_click_primary','scroll'].includes(kind)){
169
+ p=point(inputPoint,'action point');
170
+ } else insist(inputPoint===null,'non-pointer action cannot carry point');
171
+ if(kind==='scroll'){
172
+ delta=int(scrollDelta,'scrollDelta',{min:-12000,max:12000});
173
+ insist(delta!==0,'scrollDelta cannot be zero');
174
+ } else insist(scrollDelta===null,'non-scroll action cannot carry scrollDelta');
175
+ if(kind==='key_chord') keys=normalizeKeyChord(keyChord);
176
+ else insist(keyChord===null,'non-key action cannot carry keyChord');
177
+ if(kind==='text_input'){
178
+ insist(typeof text==='string'&&text.length>=1&&text.length<=4096&&!text.includes('\u0000'),'text input invalid');
179
+ textValue=text; textSha256=sha256Bytes(Buffer.from(text,'utf8'));
180
+ } else insist(text===null,'non-text action cannot carry text');
181
+ const body={
182
+ schema:DESKTOP_ACTION_SCHEMA,intent_id:intentId,kind,frame_id:frameId,
183
+ desktop_epoch:desktopEpoch,topology_sha256:topologySha256,
184
+ expected_window:expected,target_id:targetId,point:p,scroll_delta:delta,key_chord:keys,
185
+ text:textValue,text_sha256:textSha256
186
+ };
187
+ return Object.freeze({...body,action_sha256:sha256Object(body)});
188
+ }
189
+
190
+ export function guardDesktopAction({frame,action,nowUtc,maxFrameAgeMs=2000,maxFutureSkewMs=250}={}){
191
+ const f=validateFrameObject(frame);
192
+ insist(isObject(action)&&action.schema===DESKTOP_ACTION_SCHEMA,'desktop action invalid');
193
+ hash(action.action_sha256,'action_sha256');
194
+ const actionBody={...action}; delete actionBody.action_sha256;
195
+ insist(sha256Object(actionBody)===action.action_sha256,'desktop action hash mismatch');
196
+ insist(action.frame_id===f.frame_id,'desktop action stale frame id');
197
+ insist(action.desktop_epoch===f.desktop_epoch,'desktop action desktop epoch mismatch');
198
+ insist(action.topology_sha256===f.topology_sha256,'desktop action topology mismatch');
199
+ insist(sameWindow(action.expected_window,f.active_window),'desktop action foreground window mismatch');
200
+ int(maxFrameAgeMs,'maxFrameAgeMs',{min:1,max:60000});
201
+ int(maxFutureSkewMs,'maxFutureSkewMs',{min:0,max:5000});
202
+ const now=parseUtc(nowUtc,'nowUtc'), observed=parseUtc(f.observed_at_utc,'frame observed_at_utc');
203
+ const age=now-observed;
204
+ insist(age>=-maxFutureSkewMs,'desktop frame is from the future');
205
+ insist(age<=maxFrameAgeMs,'desktop frame is stale');
206
+ if(action.point!==null) insist(insidePoint(f.bounds,action.point),'desktop action point outside frame bounds');
207
+ const guardedAt=new Date(now).toISOString();
208
+ const body={
209
+ schema:DESKTOP_GUARD_SCHEMA,frame_id:f.frame_id,action_sha256:action.action_sha256,
210
+ intent_id:action.intent_id,desktop_epoch:f.desktop_epoch,topology_sha256:f.topology_sha256,
211
+ active_window:f.active_window,frame_age_ms:Math.max(0,age),guarded_at_utc:guardedAt
212
+ };
213
+ return Object.freeze({...body,guard_sha256:sha256Object(body)});
214
+ }
215
+
216
+ export function bindDesktopPostObservation({guard,beforeFrame,action,afterFrame}={}){
217
+ insist(isObject(guard)&&guard.schema===DESKTOP_GUARD_SCHEMA,'desktop guard invalid');
218
+ hash(guard.guard_sha256,'guard_sha256');
219
+ const guardBody={...guard}; delete guardBody.guard_sha256;
220
+ insist(sha256Object(guardBody)===guard.guard_sha256,'desktop guard hash mismatch');
221
+ const before=validateFrameObject(beforeFrame),after=validateFrameObject(afterFrame);
222
+ insist(before.frame_id===guard.frame_id,'post-observation before frame mismatch');
223
+ insist(isObject(action)&&action.action_sha256===guard.action_sha256,'post-observation action mismatch');
224
+ parseUtc(guard.guarded_at_utc,'guarded_at_utc');
225
+ insist(parseUtc(after.observed_at_utc,'after observed_at_utc')>=parseUtc(guard.guarded_at_utc,'guarded_at_utc'),'post-observation predates guarded action');
226
+ const windowChanged=!sameWindow(before.active_window,after.active_window)||!sameWindow(after.active_window,before.active_window);
227
+ const result={
228
+ schema:DESKTOP_POST_OBSERVATION_SCHEMA,
229
+ guard_sha256:guard.guard_sha256,
230
+ action_sha256:action.action_sha256,
231
+ before_frame_id:before.frame_id,
232
+ after_frame_id:after.frame_id,
233
+ change_observed:before.image_sha256!==after.image_sha256,
234
+ window_changed:windowChanged,
235
+ foreground_transition:Object.freeze({
236
+ before:before.active_window,
237
+ after:after.active_window,
238
+ changed:windowChanged
239
+ }),
240
+ topology_changed:before.topology_sha256!==after.topology_sha256,
241
+ desktop_epoch_changed:before.desktop_epoch!==after.desktop_epoch,
242
+ observed_at_utc:after.observed_at_utc,
243
+ success:null
244
+ };
245
+ return Object.freeze({...result,post_observation_sha256:sha256Object(result)});
246
+ }
247
+
248
+ export function bindDesktopActionChain({
249
+ beforeFrameId,actionSha256,afterFrameId,guardSha256,postObservationSha256,
250
+ postReceiptSha256,effectId,effectFingerprint
251
+ }={}){
252
+ hash(beforeFrameId,'beforeFrameId'); hash(actionSha256,'actionSha256'); hash(afterFrameId,'afterFrameId');
253
+ hash(guardSha256,'guardSha256'); hash(postObservationSha256,'postObservationSha256');
254
+ hash(postReceiptSha256,'postReceiptSha256'); hash(effectId,'effectId'); hash(effectFingerprint,'effectFingerprint');
255
+ const body={
256
+ schema:DESKTOP_ACTION_CHAIN_SCHEMA,before_frame_id:beforeFrameId,action_sha256:actionSha256,
257
+ after_frame_id:afterFrameId,guard_sha256:guardSha256,post_observation_sha256:postObservationSha256,
258
+ post_receipt_sha256:postReceiptSha256,effect_id:effectId,effect_fingerprint:effectFingerprint
259
+ };
260
+ return Object.freeze({...body,chain_sha256:sha256Object(body)});
261
+ }
262
+
263
+ export function desktopActionKinds(){ return Object.freeze([...ACTION_KINDS]); }