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.
- package/MANIFEST.SHA256 +101 -39
- package/README.txt +10 -9
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +10 -8
- package/scripts/build-desktop-r40.ps1 +106 -0
- package/scripts/deferred-slot-operation-r39.mjs +124 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/final-closure-r39.mjs +113 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-verify-r39.mjs +208 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/gate-windows-r39.ps1 +42 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/init-desktop-policy-r40.ps1 +79 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r39.mjs +200 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-tests-r39.ps1 +9 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/windows-gate-evidence-r39.mjs +52 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +22 -0
- package/src/autonomous-runtime.mjs +144 -24
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +79 -47
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +74 -19
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +215 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +85 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-closeout-regression.test.mjs +8 -8
- package/test/r33-finalization.test.mjs +14 -14
- package/test/r37-remote-tool-call-timeline.test.mjs +7 -6
- package/test/r39-finalization.test.mjs +74 -0
- package/test/r40-finalization.test.mjs +118 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +19 -58
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +112 -55
- package/test/runtime-self-update.test.mjs +44 -0
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -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:
|
|
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
|
|
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')
|
|
116
|
-
|
|
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
|
|
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
|
|
135
|
-
|
|
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(
|
|
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
|
-
|
|
177
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'&¤t.owner_pid===marker.owner_pid&¤t.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(
|
|
268
|
-
await
|
|
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
|
-
|
|
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
|
|
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
|
|
307
|
-
process.stdout
|
|
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
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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]); }
|