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
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export const DESKTOP_NATIVE_INPUT_SCHEMA='deadbyte.desktop-native-input.v1';
|
|
7
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
8
|
+
const HWND=/^0x[0-9a-f]{1,16}$/;
|
|
9
|
+
const KINDS=new Set(['pointer_move','click_primary','click_secondary','double_click_primary','scroll','key_chord','text_input']);
|
|
10
|
+
const sha256Bytes=value=>createHash('sha256').update(value).digest('hex');
|
|
11
|
+
|
|
12
|
+
function insist(condition,message){if(!condition)throw new Error(message);}
|
|
13
|
+
function sideEffectError(error,state){
|
|
14
|
+
const e=error instanceof Error?error:new Error(String(error));
|
|
15
|
+
if(e.side_effect_state!=='not_started'&&e.side_effect_state!=='unknown') e.side_effect_state=state;
|
|
16
|
+
return e;
|
|
17
|
+
}
|
|
18
|
+
function integer(value,label,{min=Number.MIN_SAFE_INTEGER,max=Number.MAX_SAFE_INTEGER}={}){
|
|
19
|
+
insist(Number.isSafeInteger(value)&&value>=min&&value<=max,`${label} invalid`);return value;
|
|
20
|
+
}
|
|
21
|
+
function hwnd(value){
|
|
22
|
+
const text=String(value??'').toLowerCase();
|
|
23
|
+
insist(HWND.test(text),'desktop native input hwnd invalid');
|
|
24
|
+
return text;
|
|
25
|
+
}
|
|
26
|
+
function lineValue(value,label){
|
|
27
|
+
const text=String(value??'');
|
|
28
|
+
insist(text.length>=1&&Buffer.byteLength(text,'utf8')<=512&&!/[\r\n\0]/.test(text),`${label} invalid`);
|
|
29
|
+
return text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function serializeNativeDesktopInputRequest(action,{inputDesktop,expectedBounds,expectedWindow}={}){
|
|
33
|
+
insist(action&&typeof action==='object'&&action.schema==='deadbyte.desktop-action.v1','desktop native input action invalid');
|
|
34
|
+
insist(HEX64.test(action.action_sha256??''),'desktop native input action hash invalid');
|
|
35
|
+
insist(KINDS.has(action.kind),'desktop native input kind invalid');
|
|
36
|
+
insist(expectedBounds&&typeof expectedBounds==='object','desktop native input bounds missing');
|
|
37
|
+
const left=integer(expectedBounds.left,'desktop native input left',{min:-131072,max:131072});
|
|
38
|
+
const top=integer(expectedBounds.top,'desktop native input top',{min:-131072,max:131072});
|
|
39
|
+
const width=integer(expectedBounds.width,'desktop native input width',{min:1,max:131072});
|
|
40
|
+
const height=integer(expectedBounds.height,'desktop native input height',{min:1,max:131072});
|
|
41
|
+
insist(left+width<=262144&&top+height<=262144,'desktop native input bounds extent invalid');
|
|
42
|
+
const desktop=lineValue(inputDesktop,'desktop native input desktop');
|
|
43
|
+
const window=expectedWindow??null;
|
|
44
|
+
const expectedHwnd=window===null?'0x0':hwnd(window.hwnd);
|
|
45
|
+
const expectedPid=window===null?0:integer(window.pid,'desktop native input pid',{min:1,max:0x7fffffff});
|
|
46
|
+
const pointer=['pointer_move','click_primary','click_secondary','double_click_primary','scroll'].includes(action.kind);
|
|
47
|
+
const x=pointer?integer(action.point?.x,'desktop native input x',{min:-131072,max:131072}):0;
|
|
48
|
+
const y=pointer?integer(action.point?.y,'desktop native input y',{min:-131072,max:131072}):0;
|
|
49
|
+
if(pointer) insist(x>=left&&y>=top&&x<left+width&&y<top+height,'desktop native input point outside bounds');
|
|
50
|
+
const delta=action.kind==='scroll'?integer(action.scroll_delta,'desktop native input scroll',{min:-12000,max:12000}):0;
|
|
51
|
+
if(action.kind==='scroll') insist(delta!==0,'desktop native input scroll cannot be zero');
|
|
52
|
+
const keys=action.kind==='key_chord'?[...(action.key_chord??[])]:[];
|
|
53
|
+
if(action.kind==='key_chord') insist(keys.length>=1&&keys.length<=5,'desktop native input key chord invalid');
|
|
54
|
+
else insist(action.key_chord===null,'desktop native input unexpected key chord');
|
|
55
|
+
const text=action.kind==='text_input'?String(action.text??''):'';
|
|
56
|
+
const textBytes=Buffer.from(text,'utf8');
|
|
57
|
+
if(action.kind==='text_input') insist(textBytes.length>=1&&textBytes.length<=64*1024&&!text.includes('\0'),'desktop native input text invalid');
|
|
58
|
+
else insist(action.text===null,'desktop native input unexpected text');
|
|
59
|
+
|
|
60
|
+
const lines=[
|
|
61
|
+
'DEADBYTE_DESKTOP_INPUT_V1',
|
|
62
|
+
`action_sha256=${action.action_sha256}`,
|
|
63
|
+
`kind=${action.kind}`,
|
|
64
|
+
`expected_desktop=${desktop}`,
|
|
65
|
+
`expected_hwnd=${expectedHwnd}`,
|
|
66
|
+
`expected_pid=${expectedPid}`,
|
|
67
|
+
`left=${left}`,`top=${top}`,`width=${width}`,`height=${height}`,
|
|
68
|
+
`x=${x}`,`y=${y}`,`scroll_delta=${delta}`,
|
|
69
|
+
`key_count=${keys.length}`,
|
|
70
|
+
...keys.map(key=>`key=${lineValue(key,'desktop native input key')}`),
|
|
71
|
+
`text_bytes=${textBytes.length}`
|
|
72
|
+
];
|
|
73
|
+
const header=Buffer.from(lines.join('\n')+'\n','utf8');
|
|
74
|
+
const request=Buffer.concat([header,textBytes]);
|
|
75
|
+
insist(request.length<=128*1024,'desktop native input request exceeds bound');
|
|
76
|
+
return request;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function parseNativeDesktopInputResponse(bytes,{actionSha256,kind,inputDesktop,expectedWindow}={}){
|
|
80
|
+
insist(Buffer.isBuffer(bytes)&&bytes.length>=2&&bytes.length<=64*1024,'desktop native input response invalid');
|
|
81
|
+
let raw;
|
|
82
|
+
try{raw=JSON.parse(bytes.toString('utf8').trim());}catch{throw new Error('desktop native input response JSON invalid');}
|
|
83
|
+
insist(raw?.schema===DESKTOP_NATIVE_INPUT_SCHEMA,'desktop native input response schema mismatch');
|
|
84
|
+
insist(raw.injected===true&&raw.backend==='win32-sendinput','desktop native input response did not confirm injection');
|
|
85
|
+
insist(raw.action_sha256===actionSha256&&raw.kind===kind,'desktop native input response action mismatch');
|
|
86
|
+
insist(raw.input_desktop===inputDesktop,'desktop native input response desktop mismatch');
|
|
87
|
+
const expected=expectedWindow??null;
|
|
88
|
+
insist(hwnd(raw.foreground_hwnd)===(expected===null?'0x0':hwnd(expected.hwnd)),'desktop native input response foreground hwnd mismatch');
|
|
89
|
+
insist(integer(raw.foreground_pid,'desktop native input response pid',{min:0,max:0x7fffffff})===(expected===null?0:expected.pid),'desktop native input response foreground pid mismatch');
|
|
90
|
+
integer(raw.event_count,'desktop native input event_count',{min:1,max:65536});
|
|
91
|
+
return Object.freeze({
|
|
92
|
+
injected:true,backend:raw.backend,event_count:raw.event_count,
|
|
93
|
+
provider_receipt_sha256:sha256Bytes(bytes)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function invokeNativeDesktopInput(action,{
|
|
98
|
+
inputPath,expectedInputSha256,timeoutMs=5000,inputDesktop,expectedBounds,expectedWindow
|
|
99
|
+
}={}){
|
|
100
|
+
let request;
|
|
101
|
+
try{
|
|
102
|
+
insist(typeof inputPath==='string'&&path.isAbsolute(inputPath),'desktop inputPath must be absolute');
|
|
103
|
+
insist(HEX64.test(expectedInputSha256??''),'desktop expectedInputSha256 invalid');
|
|
104
|
+
integer(timeoutMs,'desktop input timeoutMs',{min:100,max:30000});
|
|
105
|
+
const exe=await readFile(inputPath);
|
|
106
|
+
insist(exe.length>0&&exe.length<=4*1024*1024,'desktop input binary size invalid');
|
|
107
|
+
insist(sha256Bytes(exe)===expectedInputSha256,'desktop input executable hash mismatch');
|
|
108
|
+
request=serializeNativeDesktopInputRequest(action,{inputDesktop,expectedBounds,expectedWindow});
|
|
109
|
+
}catch(error){throw sideEffectError(error,'not_started');}
|
|
110
|
+
return await new Promise((resolve,reject)=>{
|
|
111
|
+
let child;
|
|
112
|
+
try{child=spawn(inputPath,[],{shell:false,windowsHide:true,stdio:['pipe','pipe','pipe']});}
|
|
113
|
+
catch(error){reject(sideEffectError(error,'not_started'));return;}
|
|
114
|
+
const stdout=[];let stdoutBytes=0,stderr='',done=false,timer=null;
|
|
115
|
+
const finish=(error,value,state='unknown')=>{
|
|
116
|
+
if(done)return;done=true;if(timer!==null)clearTimeout(timer);
|
|
117
|
+
error?reject(sideEffectError(error,state)):resolve(value);
|
|
118
|
+
};
|
|
119
|
+
child.stdout.on('data',chunk=>{
|
|
120
|
+
stdoutBytes+=chunk.length;
|
|
121
|
+
if(stdoutBytes>64*1024){try{child.kill('SIGKILL');}catch{};return finish(new Error('desktop input stdout exceeds bound'));}
|
|
122
|
+
stdout.push(Buffer.from(chunk));
|
|
123
|
+
});
|
|
124
|
+
child.stderr.on('data',chunk=>{
|
|
125
|
+
stderr+=chunk.toString('utf8');
|
|
126
|
+
if(Buffer.byteLength(stderr,'utf8')>64*1024){try{child.kill('SIGKILL');}catch{};finish(new Error('desktop input stderr exceeds bound'));}
|
|
127
|
+
});
|
|
128
|
+
child.once('error',error=>finish(error,null,'not_started'));
|
|
129
|
+
child.once('exit',(code,signal)=>{
|
|
130
|
+
if(code!==0)return finish(new Error(`desktop input exit=${code} signal=${signal??'none'} stderr=${stderr.slice(-8192)}`));
|
|
131
|
+
try{
|
|
132
|
+
finish(null,parseNativeDesktopInputResponse(Buffer.concat(stdout),{
|
|
133
|
+
actionSha256:action.action_sha256,kind:action.kind,inputDesktop,expectedWindow
|
|
134
|
+
}));
|
|
135
|
+
}catch(error){finish(error);}
|
|
136
|
+
});
|
|
137
|
+
child.stdin.on('error',error=>finish(error));
|
|
138
|
+
child.stdin.end(request);
|
|
139
|
+
timer=setTimeout(()=>{try{child.kill('SIGKILL');}catch{};finish(new Error(`desktop input timed out after ${timeoutMs}ms`));},timeoutMs);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function createNativeDesktopInputProvider({inputPath,expectedInputSha256,timeoutMs=5000}={}){
|
|
144
|
+
return async(action,context={})=>invokeNativeDesktopInput(action,{
|
|
145
|
+
inputPath,expectedInputSha256,timeoutMs:context.timeoutMs??timeoutMs,
|
|
146
|
+
inputDesktop:context.inputDesktop,expectedBounds:context.expectedBounds,expectedWindow:context.expectedWindow
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import * as z from 'zod/v4';
|
|
3
|
+
import { compactGatewayOutputSchema,compactGatewayResult } from './compact-gateway-output-contracts.mjs';
|
|
4
|
+
|
|
5
|
+
const hex64=z.string().regex(/^[0-9a-f]{64}$/);
|
|
6
|
+
const point=z.strictObject({
|
|
7
|
+
x:z.number().int().min(-131072).max(131072),
|
|
8
|
+
y:z.number().int().min(-131072).max(131072)
|
|
9
|
+
});
|
|
10
|
+
const rect=z.strictObject({
|
|
11
|
+
left:z.number().int().min(-131072).max(131072),
|
|
12
|
+
top:z.number().int().min(-131072).max(131072),
|
|
13
|
+
width:z.number().int().min(1).max(131072),
|
|
14
|
+
height:z.number().int().min(1).max(131072)
|
|
15
|
+
});
|
|
16
|
+
const key=z.string().regex(/^(?:[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)$/);
|
|
17
|
+
const actionKinds=z.enum(['pointer_move','click_primary','click_secondary','double_click_primary','scroll','key_chord','text_input']);
|
|
18
|
+
const pointerKinds=new Set(['pointer_move','click_primary','click_secondary','double_click_primary','scroll']);
|
|
19
|
+
|
|
20
|
+
export const desktopStatusInputSchema=z.strictObject({});
|
|
21
|
+
export const desktopObserveInputSchema=z.strictObject({preview:z.boolean().default(false)});
|
|
22
|
+
const uiaQuery=z.strictObject({
|
|
23
|
+
kind:z.enum(['root','name_exact','name_contains','automation_id_exact','control_type']),
|
|
24
|
+
value:z.string().max(512).default(''),max_results:z.number().int().min(1).max(16).default(8),
|
|
25
|
+
ordinal:z.number().int().min(0).max(15).default(0)
|
|
26
|
+
}).superRefine((value,ctx)=>{
|
|
27
|
+
if(value.ordinal>=value.max_results)ctx.addIssue({code:'custom',path:['ordinal'],message:'ordinal must be below max_results'});
|
|
28
|
+
if(value.kind==='root'&&value.value!=='')ctx.addIssue({code:'custom',path:['value'],message:'root query cannot carry value'});
|
|
29
|
+
if(value.kind!=='root'&&value.value.length<1)ctx.addIssue({code:'custom',path:['value'],message:'UIA query value required'});
|
|
30
|
+
});
|
|
31
|
+
const explicitTargetFields={
|
|
32
|
+
frame_id:hex64,rect,source:z.enum(['visual','uia','accessibility','geometry']),
|
|
33
|
+
confidence:z.number().min(0).max(1),semantic_label_sha256:hex64.nullable().default(null)
|
|
34
|
+
};
|
|
35
|
+
const uiaTargetFields={frame_id:hex64,uia_query:uiaQuery};
|
|
36
|
+
export const desktopTargetInputSchema=z.union([
|
|
37
|
+
z.strictObject(explicitTargetFields),z.strictObject(uiaTargetFields)
|
|
38
|
+
]);
|
|
39
|
+
const actionFields={
|
|
40
|
+
intent_id:hex64,kind:actionKinds,expected_frame_id:hex64,target_id:hex64.nullable().default(null),
|
|
41
|
+
point:point.nullable().default(null),scroll_delta:z.number().int().min(-12000).max(12000).nullable().default(null),
|
|
42
|
+
key_chord:z.array(key).min(1).max(5).nullable().default(null),
|
|
43
|
+
text:z.string().min(1).max(4096).refine(value=>!value.includes('\u0000'),'text contains NUL').nullable().default(null)
|
|
44
|
+
};
|
|
45
|
+
function validateAction(value,ctx){
|
|
46
|
+
const pointer=pointerKinds.has(value.kind);
|
|
47
|
+
if(pointer){
|
|
48
|
+
if((value.target_id===null)===(value.point===null)) ctx.addIssue({code:'custom',message:'pointer action requires exactly one of target_id or point'});
|
|
49
|
+
}else if(value.target_id!==null||value.point!==null) ctx.addIssue({code:'custom',message:'non-pointer action cannot carry target_id or point'});
|
|
50
|
+
if(value.kind==='scroll'){
|
|
51
|
+
if(value.scroll_delta===null||value.scroll_delta===0) ctx.addIssue({code:'custom',path:['scroll_delta'],message:'scroll requires non-zero scroll_delta'});
|
|
52
|
+
}else if(value.scroll_delta!==null) ctx.addIssue({code:'custom',path:['scroll_delta'],message:'non-scroll action cannot carry scroll_delta'});
|
|
53
|
+
if(value.kind==='key_chord'){
|
|
54
|
+
if(value.key_chord===null||new Set(value.key_chord).size!==value.key_chord.length) ctx.addIssue({code:'custom',path:['key_chord'],message:'key_chord missing or duplicate'});
|
|
55
|
+
}else if(value.key_chord!==null) ctx.addIssue({code:'custom',path:['key_chord'],message:'non-key action cannot carry key_chord'});
|
|
56
|
+
if(value.kind==='text_input'){
|
|
57
|
+
if(value.text===null) ctx.addIssue({code:'custom',path:['text'],message:'text_input requires text'});
|
|
58
|
+
}else if(value.text!==null) ctx.addIssue({code:'custom',path:['text'],message:'non-text action cannot carry text'});
|
|
59
|
+
}
|
|
60
|
+
export const desktopActionInputSchema=z.strictObject(actionFields).superRefine(validateAction);
|
|
61
|
+
|
|
62
|
+
const statusOp=z.strictObject({op:z.literal('status')});
|
|
63
|
+
const observeOp=z.strictObject({op:z.literal('observe'),preview:z.boolean().default(false)});
|
|
64
|
+
const targetOp=z.union([
|
|
65
|
+
z.strictObject({op:z.literal('target'),...explicitTargetFields}),
|
|
66
|
+
z.strictObject({op:z.literal('target'),...uiaTargetFields})
|
|
67
|
+
]);
|
|
68
|
+
const actionOp=z.strictObject({op:z.literal('action'),...actionFields}).superRefine(validateAction);
|
|
69
|
+
export const desktopControlInputSchema=z.union([statusOp,observeOp,targetOp,actionOp]);
|
|
70
|
+
|
|
71
|
+
const sha256=value=>createHash('sha256').update(value).digest('hex');
|
|
72
|
+
function publicResult(value){
|
|
73
|
+
if(!value||typeof value!=='object'||!value.action) return value;
|
|
74
|
+
const {action,...rest}=value;
|
|
75
|
+
const summary={
|
|
76
|
+
schema:'deadbyte.desktop-action-summary.v1',intent_id:action.intent_id,kind:action.kind,frame_id:action.frame_id,
|
|
77
|
+
desktop_epoch:action.desktop_epoch,topology_sha256:action.topology_sha256,expected_window:action.expected_window,
|
|
78
|
+
target_id:action.target_id,point:action.point,scroll_delta:action.scroll_delta,key_chord:action.key_chord,
|
|
79
|
+
text_sha256:action.text_sha256,action_sha256:action.action_sha256,
|
|
80
|
+
...(action.kind==='text_input'?{text_bytes:Buffer.byteLength(action.text??'','utf8')}: {})
|
|
81
|
+
};
|
|
82
|
+
return Object.freeze({...rest,action_summary:Object.freeze(summary)});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function invokeDesktopOperation(args,runtime){
|
|
86
|
+
const op=args.op;
|
|
87
|
+
if(!runtime) return compactGatewayResult({status:'unavailable',op,error:'desktop runtime unavailable'},true);
|
|
88
|
+
try{
|
|
89
|
+
let value;
|
|
90
|
+
if(op==='status') value=await runtime.status();
|
|
91
|
+
else if(op==='observe') value=await runtime.observe({preview:args.preview??false});
|
|
92
|
+
else if(op==='target'&&args.uia_query) value=await runtime.ground({frameId:args.frame_id,query:args.uia_query});
|
|
93
|
+
else if(op==='target') value=await runtime.target({
|
|
94
|
+
frameId:args.frame_id,rect:args.rect,source:args.source,confidence:args.confidence,
|
|
95
|
+
semanticLabelSha256:args.semantic_label_sha256
|
|
96
|
+
});
|
|
97
|
+
else if(op==='action') value=await runtime.action({
|
|
98
|
+
intentId:args.intent_id,kind:args.kind,expectedFrameId:args.expected_frame_id,targetId:args.target_id,
|
|
99
|
+
point:args.point,scrollDelta:args.scroll_delta,keyChord:args.key_chord,text:args.text
|
|
100
|
+
});
|
|
101
|
+
else throw new Error('desktop operation unsupported');
|
|
102
|
+
return compactGatewayResult({status:'ok',op,result:publicResult(value)},false);
|
|
103
|
+
}catch(error){
|
|
104
|
+
return compactGatewayResult({status:'denied',op,error:String(error?.message??error)},true);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function registerDesktopTools(server,{runtime}={}){
|
|
109
|
+
if(!server||!runtime) throw new Error('desktop tool registration requires server/runtime');
|
|
110
|
+
server.registerTool('desktop_status',{
|
|
111
|
+
description:'Read Desktop runtime configuration and standing-grant state. This never arms Desktop authority.',
|
|
112
|
+
inputSchema:desktopStatusInputSchema,outputSchema:compactGatewayOutputSchema
|
|
113
|
+
},()=>invokeDesktopOperation({op:'status'},runtime));
|
|
114
|
+
server.registerTool('desktop_observe',{
|
|
115
|
+
description:'Capture one bounded Windows desktop frame and signed observation evidence. Observation does not require Desktop mutation authority.',
|
|
116
|
+
inputSchema:desktopObserveInputSchema,outputSchema:compactGatewayOutputSchema
|
|
117
|
+
},args=>invokeDesktopOperation({op:'observe',...args},runtime));
|
|
118
|
+
server.registerTool('desktop_target',{
|
|
119
|
+
description:'Bind one explicit rectangle or one local Windows UIA query to fresh desktop evidence. UIA query text is matched locally and is not returned.',
|
|
120
|
+
inputSchema:desktopTargetInputSchema,outputSchema:compactGatewayOutputSchema
|
|
121
|
+
},args=>invokeDesktopOperation({op:'target',...args},runtime));
|
|
122
|
+
server.registerTool('desktop_action',{
|
|
123
|
+
description:'Perform one guarded Desktop effect against a fresh observed frame. Requires separate Desktop standing authority and signed before/after evidence.',
|
|
124
|
+
inputSchema:desktopActionInputSchema,outputSchema:compactGatewayOutputSchema
|
|
125
|
+
},args=>invokeDesktopOperation({op:'action',...args},runtime));
|
|
126
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { createHash, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, opendir, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
5
|
+
import { verifyDesktopReceiptObject } from './desktop-runtime-r40.mjs';
|
|
6
|
+
|
|
7
|
+
export const DESKTOP_MEMORY_SCHEMA='deadbyte.desktop-memory.v1';
|
|
8
|
+
export const DESKTOP_MEMORY_SNAPSHOT_SCHEMA='deadbyte.desktop-memory-snapshot.v1';
|
|
9
|
+
const DOMAIN=Buffer.from('DEADBYTE-DESKTOP-MEMORY-V1\0','utf8');
|
|
10
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
11
|
+
const RECEIPT_FILE=/^[0-9a-f]{24}\.json$/;
|
|
12
|
+
const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
|
|
13
|
+
const uniq=values=>[...new Set(values.filter(v=>typeof v==='string'&&v.length>0))];
|
|
14
|
+
|
|
15
|
+
function insist(c,m){if(!c)throw new Error(m);}
|
|
16
|
+
function safeWindow(value){
|
|
17
|
+
if(value===null||value===undefined)return null;
|
|
18
|
+
if(typeof value!=='object'||Array.isArray(value))return null;
|
|
19
|
+
const hwnd=typeof value.hwnd==='string'?value.hwnd:null;
|
|
20
|
+
const pid=Number.isInteger(value.pid)?value.pid:null;
|
|
21
|
+
if(!hwnd||pid===null)return null;
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
hwnd,pid,
|
|
24
|
+
process_image_sha256:HEX64.test(value.process_image_sha256??'')?value.process_image_sha256:null,
|
|
25
|
+
title_sha256:HEX64.test(value.title_sha256??'')?value.title_sha256:null
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function windowKey(w){return w?sha({schema:'deadbyte.desktop-window-memory-key.v1',...w}):null;}
|
|
29
|
+
function boundedPush(array,value,max){array.push(value);while(array.length>max)array.shift();}
|
|
30
|
+
function receiptSort(a,b){
|
|
31
|
+
const at=Date.parse(a?.observed_at_utc??''),bt=Date.parse(b?.observed_at_utc??'');
|
|
32
|
+
if(Number.isFinite(at)&&Number.isFinite(bt)&&at!==bt)return at-bt;
|
|
33
|
+
return String(a?.receipt_id??'').localeCompare(String(b?.receipt_id??''),'en');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function deriveDesktopMemory(receipts,{policySha256,publicKey,keyId,maxTrajectory=32,maxWindows=32}={}){
|
|
37
|
+
insist(Array.isArray(receipts),'desktop memory receipts must be array');
|
|
38
|
+
insist(HEX64.test(policySha256??''),'desktop memory policy sha invalid');
|
|
39
|
+
insist(publicKey,'desktop memory publicKey missing');
|
|
40
|
+
insist(typeof keyId==='string'&&keyId.length>=1,'desktop memory keyId invalid');
|
|
41
|
+
insist(Number.isInteger(maxTrajectory)&&maxTrajectory>=1&&maxTrajectory<=256,'desktop memory maxTrajectory invalid');
|
|
42
|
+
insist(Number.isInteger(maxWindows)&&maxWindows>=1&&maxWindows<=128,'desktop memory maxWindows invalid');
|
|
43
|
+
|
|
44
|
+
const verified=[...receipts].sort(receiptSort);
|
|
45
|
+
for(const receipt of verified){
|
|
46
|
+
const proof=verifyDesktopReceiptObject(receipt,{policySha256,publicKey,keyId});
|
|
47
|
+
insist(proof.ok,'desktop memory rejected unauthenticated receipt '+String(receipt?.receipt_id??'unknown')+': '+String(proof.reason??'invalid'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const windows=new Map();
|
|
51
|
+
const started=new Map();
|
|
52
|
+
const trajectory=[];
|
|
53
|
+
const groundedTargets=[];
|
|
54
|
+
const rejectedTargetFingerprints=[];
|
|
55
|
+
const unknownEffects=new Map();
|
|
56
|
+
let lastVerifiedDesktop=null;
|
|
57
|
+
|
|
58
|
+
const rememberWindow=value=>{
|
|
59
|
+
const w=safeWindow(value);if(!w)return;
|
|
60
|
+
const key=windowKey(w);windows.set(key,{window_id:key,...w});
|
|
61
|
+
while(windows.size>maxWindows)windows.delete(windows.keys().next().value);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for(const receipt of verified){
|
|
65
|
+
const p=receipt.payload&&typeof receipt.payload==='object'?receipt.payload:{};
|
|
66
|
+
if(receipt.kind==='desktop_observation'){
|
|
67
|
+
rememberWindow(p.active_window);
|
|
68
|
+
if(HEX64.test(p.frame_id??'')&&HEX64.test(p.desktop_epoch??'')&&HEX64.test(p.topology_sha256??'')){
|
|
69
|
+
lastVerifiedDesktop={
|
|
70
|
+
frame_id:p.frame_id,desktop_epoch:p.desktop_epoch,topology_sha256:p.topology_sha256,
|
|
71
|
+
window_id:windowKey(safeWindow(p.active_window)),observed_at_utc:receipt.observed_at_utc,
|
|
72
|
+
receipt_sha256:receipt.receipt_sha256
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}else if(receipt.kind==='desktop_grounding'){
|
|
76
|
+
if(HEX64.test(p.target_id??'')&&HEX64.test(p.query_sha256??'')){
|
|
77
|
+
boundedPush(groundedTargets,{
|
|
78
|
+
target_id:p.target_id,query_sha256:p.query_sha256,query_kind:String(p.query_kind??''),
|
|
79
|
+
frame_id:HEX64.test(p.grounded_frame_id??'')?p.grounded_frame_id:null,
|
|
80
|
+
control_type:Number.isInteger(p.control_type)?p.control_type:null,
|
|
81
|
+
receipt_sha256:receipt.receipt_sha256
|
|
82
|
+
},maxTrajectory);
|
|
83
|
+
}
|
|
84
|
+
}else if(receipt.kind==='desktop_action_started'){
|
|
85
|
+
if(HEX64.test(p.effect_id??'')) started.set(p.effect_id,{
|
|
86
|
+
effect_id:p.effect_id,effect_fingerprint:HEX64.test(p.effect_fingerprint??'')?p.effect_fingerprint:null,
|
|
87
|
+
action_sha256:HEX64.test(p.action_sha256??'')?p.action_sha256:null,
|
|
88
|
+
target_id:HEX64.test(p.target_id??'')?p.target_id:null,kind:String(p.kind??''),
|
|
89
|
+
frame_id:HEX64.test(p.pre_frame_id??'')?p.pre_frame_id:null,
|
|
90
|
+
receipt_sha256:receipt.receipt_sha256
|
|
91
|
+
});
|
|
92
|
+
}else if(receipt.kind==='desktop_post_observation'){
|
|
93
|
+
rememberWindow(p.active_window);
|
|
94
|
+
if(HEX64.test(p.frame_id??'')&&HEX64.test(p.desktop_epoch??'')&&HEX64.test(p.topology_sha256??'')){
|
|
95
|
+
lastVerifiedDesktop={
|
|
96
|
+
frame_id:p.frame_id,desktop_epoch:p.desktop_epoch,topology_sha256:p.topology_sha256,
|
|
97
|
+
window_id:windowKey(safeWindow(p.active_window)),observed_at_utc:receipt.observed_at_utc,
|
|
98
|
+
receipt_sha256:receipt.receipt_sha256
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}else if(receipt.kind==='desktop_action_completed'){
|
|
102
|
+
const effectId=HEX64.test(p.effect_id??'')?p.effect_id:null;
|
|
103
|
+
const s=effectId?started.get(effectId):null;
|
|
104
|
+
if(effectId&&HEX64.test(p.effect_fingerprint??'')&&p.effect?.side_effect_state==='confirmed_injected'){
|
|
105
|
+
boundedPush(trajectory,{
|
|
106
|
+
effect_id:effectId,effect_fingerprint:p.effect_fingerprint,
|
|
107
|
+
action_sha256:HEX64.test(p.action_sha256??'')?p.action_sha256:s?.action_sha256??null,
|
|
108
|
+
target_id:s?.target_id??null,kind:s?.kind??null,side_effect_state:'confirmed_injected',
|
|
109
|
+
chain_sha256:HEX64.test(p.action_chain?.chain_sha256??'')?p.action_chain.chain_sha256:null,
|
|
110
|
+
post_observation_sha256:HEX64.test(p.post_observation?.post_observation_sha256??'')?p.post_observation.post_observation_sha256:null,
|
|
111
|
+
window_transition:p.post_observation?.foreground_transition?.changed===true,
|
|
112
|
+
receipt_sha256:receipt.receipt_sha256
|
|
113
|
+
},maxTrajectory);
|
|
114
|
+
unknownEffects.delete(effectId);
|
|
115
|
+
}
|
|
116
|
+
}else if(receipt.kind==='desktop_action_effect_failed'){
|
|
117
|
+
const effectId=HEX64.test(p.effect_id??'')?p.effect_id:null;
|
|
118
|
+
const s=effectId?started.get(effectId):null;
|
|
119
|
+
if(p.side_effect_state==='not_started'&&s?.target_id){
|
|
120
|
+
boundedPush(rejectedTargetFingerprints,sha({
|
|
121
|
+
schema:'deadbyte.desktop-rejected-target.v1',target_id:s.target_id,
|
|
122
|
+
effect_fingerprint:s.effect_fingerprint,action_sha256:s.action_sha256
|
|
123
|
+
}),maxTrajectory);
|
|
124
|
+
}
|
|
125
|
+
}else if(receipt.kind==='desktop_action_outcome_unknown'){
|
|
126
|
+
const effectId=HEX64.test(p.effect_id??'')?p.effect_id:null;
|
|
127
|
+
if(effectId) unknownEffects.set(effectId,{
|
|
128
|
+
effect_id:effectId,effect_fingerprint:HEX64.test(p.effect_fingerprint??'')?p.effect_fingerprint:null,
|
|
129
|
+
action_sha256:HEX64.test(p.action_sha256??'')?p.action_sha256:null,
|
|
130
|
+
receipt_sha256:receipt.receipt_sha256
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const body={
|
|
136
|
+
schema:DESKTOP_MEMORY_SCHEMA,policy_sha256:policySha256,
|
|
137
|
+
source_receipt_sha256:verified.map(r=>r.receipt_sha256),
|
|
138
|
+
last_verified_desktop:lastVerifiedDesktop,
|
|
139
|
+
windows:[...windows.values()].sort((a,b)=>a.window_id.localeCompare(b.window_id,'en')),
|
|
140
|
+
grounded_targets:groundedTargets,
|
|
141
|
+
rejected_target_fingerprints:uniq(rejectedTargetFingerprints),
|
|
142
|
+
recent_effects:trajectory,
|
|
143
|
+
unresolved_unknown_effects:[...unknownEffects.values()].sort((a,b)=>a.effect_id.localeCompare(b.effect_id,'en'))
|
|
144
|
+
};
|
|
145
|
+
return Object.freeze({...body,memory_sha256:sha(body)});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function signDesktopMemory(memory,{privateKey,keyId}={}){
|
|
149
|
+
insist(memory?.schema===DESKTOP_MEMORY_SCHEMA&&HEX64.test(memory.memory_sha256??''),'desktop memory invalid');
|
|
150
|
+
insist(privateKey,'desktop memory privateKey missing');
|
|
151
|
+
insist(typeof keyId==='string'&&keyId.length>=1&&keyId.length<=128,'desktop memory signing key id invalid');
|
|
152
|
+
const body={schema:DESKTOP_MEMORY_SNAPSHOT_SCHEMA,memory};
|
|
153
|
+
const snapshotSha256=sha(body);
|
|
154
|
+
const signed={...body,snapshot_sha256:snapshotSha256};
|
|
155
|
+
const signature=cryptoSign(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),privateKey).toString('base64');
|
|
156
|
+
return Object.freeze({...signed,attestation:Object.freeze({algorithm:'ed25519',key_id:keyId,signature_base64:signature})});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function verifyDesktopMemorySnapshot(snapshot,{publicKey,keyId,policySha256}={}){
|
|
160
|
+
try{
|
|
161
|
+
insist(snapshot?.schema===DESKTOP_MEMORY_SNAPSHOT_SCHEMA,'desktop memory snapshot schema mismatch');
|
|
162
|
+
insist(snapshot.memory?.schema===DESKTOP_MEMORY_SCHEMA,'desktop memory payload schema mismatch');
|
|
163
|
+
insist(snapshot.memory.policy_sha256===policySha256,'desktop memory policy mismatch');
|
|
164
|
+
const {attestation,snapshot_sha256:claimed,...body}=snapshot;
|
|
165
|
+
insist(HEX64.test(claimed??'')&&sha(body)===claimed,'desktop memory snapshot hash mismatch');
|
|
166
|
+
const {memory_sha256,...memoryBody}=snapshot.memory;
|
|
167
|
+
insist(HEX64.test(memory_sha256??'')&&sha(memoryBody)===memory_sha256,'desktop memory hash mismatch');
|
|
168
|
+
insist(attestation?.algorithm==='ed25519'&&attestation.key_id===keyId,'desktop memory attestation mismatch');
|
|
169
|
+
const signature=Buffer.from(attestation.signature_base64??'','base64');
|
|
170
|
+
insist(signature.length===64,'desktop memory signature length invalid');
|
|
171
|
+
insist(cryptoVerify(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson({...body,snapshot_sha256:claimed}),'utf8')]),publicKey,signature),'desktop memory signature invalid');
|
|
172
|
+
return {ok:true,snapshot_sha256:claimed,memory_sha256};
|
|
173
|
+
}catch(error){return {ok:false,reason:String(error?.message??error)};}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function writeDesktopMemorySnapshot(file,snapshot){
|
|
177
|
+
insist(typeof file==='string'&&path.isAbsolute(file),'desktop memory path must be absolute');
|
|
178
|
+
insist(snapshot?.schema===DESKTOP_MEMORY_SNAPSHOT_SCHEMA,'desktop memory snapshot invalid');
|
|
179
|
+
await mkdir(path.dirname(file),{recursive:true});
|
|
180
|
+
const tmp=file+'.tmp';
|
|
181
|
+
await rm(tmp,{force:true});
|
|
182
|
+
const handle=await open(tmp,'wx',0o600);
|
|
183
|
+
try{await handle.writeFile(canonicalJson(snapshot)+'\n','utf8');await handle.sync();}
|
|
184
|
+
finally{await handle.close();}
|
|
185
|
+
await rename(tmp,file);
|
|
186
|
+
return Object.freeze({path:file,snapshot_sha256:snapshot.snapshot_sha256,memory_sha256:snapshot.memory.memory_sha256});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
export async function loadDesktopEvidenceReceipts(evidenceDir,{
|
|
191
|
+
policySha256=null,maxReceipts=2048,maxDirectoryEntries=4096,maxReceiptBytes=512*1024,maxTotalBytes=32*1024*1024
|
|
192
|
+
}={}){
|
|
193
|
+
if(policySha256!==null) insist(HEX64.test(policySha256??''),'desktop evidence policy sha invalid');
|
|
194
|
+
insist(typeof evidenceDir==='string'&&path.isAbsolute(evidenceDir),'desktop evidence directory must be absolute');
|
|
195
|
+
for(const [name,value,min,max] of [
|
|
196
|
+
['maxReceipts',maxReceipts,1,8192],['maxDirectoryEntries',maxDirectoryEntries,1,16384],
|
|
197
|
+
['maxReceiptBytes',maxReceiptBytes,1024,4*1024*1024],['maxTotalBytes',maxTotalBytes,1024,128*1024*1024]
|
|
198
|
+
]) insist(Number.isInteger(value)&&value>=min&&value<=max,'desktop memory '+name+' invalid');
|
|
199
|
+
const names=[];let directoryEntries=0;
|
|
200
|
+
const dir=await opendir(evidenceDir);
|
|
201
|
+
for await(const entry of dir){
|
|
202
|
+
directoryEntries++;
|
|
203
|
+
insist(directoryEntries<=maxDirectoryEntries,'desktop evidence directory entry limit exceeded');
|
|
204
|
+
if(!entry.isFile()||!RECEIPT_FILE.test(entry.name))continue;
|
|
205
|
+
names.push(entry.name);
|
|
206
|
+
if(policySha256===null) insist(names.length<=maxReceipts,'desktop evidence receipt limit exceeded; compact/rotate evidence before continuing');
|
|
207
|
+
}
|
|
208
|
+
names.sort((a,b)=>a.localeCompare(b,'en'));
|
|
209
|
+
const receipts=[];let totalBytes=0;
|
|
210
|
+
for(const name of names){
|
|
211
|
+
const file=path.join(evidenceDir,name);
|
|
212
|
+
const handle=await open(file,'r');
|
|
213
|
+
try{
|
|
214
|
+
const st=await handle.stat();
|
|
215
|
+
insist(st.isFile()&&st.size>=2&&st.size<=maxReceiptBytes,'desktop evidence receipt size invalid: '+name);
|
|
216
|
+
totalBytes+=st.size;
|
|
217
|
+
insist(totalBytes<=maxTotalBytes,'desktop evidence total byte limit exceeded; compact/rotate evidence before continuing');
|
|
218
|
+
const bytes=await handle.readFile();
|
|
219
|
+
insist(bytes.length===st.size,'desktop evidence receipt changed while reading: '+name);
|
|
220
|
+
let parsed;try{parsed=JSON.parse(bytes.toString('utf8'));}catch{throw new Error('desktop evidence receipt JSON invalid: '+name);}
|
|
221
|
+
insist(parsed?.receipt_id===name.slice(0,24),'desktop evidence receipt filename/id mismatch: '+name);
|
|
222
|
+
if(policySha256!==null&&parsed?.policy_sha256!==policySha256) continue;
|
|
223
|
+
receipts.push(parsed);
|
|
224
|
+
insist(receipts.length<=maxReceipts,'desktop evidence receipt limit exceeded; compact/rotate evidence before continuing');
|
|
225
|
+
}finally{await handle.close();}
|
|
226
|
+
}
|
|
227
|
+
return Object.freeze(receipts);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function desktopMemoryPlannerProjection(snapshot,{maxWindows=8,maxTargets=16,maxEffects=16,maxUnknown=16}={}){
|
|
231
|
+
insist(snapshot?.schema===DESKTOP_MEMORY_SNAPSHOT_SCHEMA,'desktop memory snapshot invalid');
|
|
232
|
+
const m=snapshot.memory;
|
|
233
|
+
insist(m?.schema===DESKTOP_MEMORY_SCHEMA&&HEX64.test(m.memory_sha256??''),'desktop memory payload invalid');
|
|
234
|
+
const body={
|
|
235
|
+
schema:'deadbyte.desktop-memory-planner.v1',
|
|
236
|
+
snapshot_sha256:snapshot.snapshot_sha256,memory_sha256:m.memory_sha256,policy_sha256:m.policy_sha256,
|
|
237
|
+
source_receipt_count:m.source_receipt_sha256.length,
|
|
238
|
+
last_verified_desktop:m.last_verified_desktop,
|
|
239
|
+
windows:m.windows.slice(-maxWindows),
|
|
240
|
+
grounded_targets:m.grounded_targets.slice(-maxTargets),
|
|
241
|
+
rejected_target_fingerprints:m.rejected_target_fingerprints.slice(-maxTargets),
|
|
242
|
+
recent_effects:m.recent_effects.slice(-maxEffects),
|
|
243
|
+
unresolved_unknown_effects:m.unresolved_unknown_effects.slice(-maxUnknown)
|
|
244
|
+
};
|
|
245
|
+
return Object.freeze(body);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function createDesktopMemoryProvider({
|
|
249
|
+
evidenceDir,snapshotPath,policySha256,privateKey,publicKey,keyId,
|
|
250
|
+
maxReceipts=2048,maxDirectoryEntries=4096,maxReceiptBytes=512*1024,maxTotalBytes=32*1024*1024
|
|
251
|
+
}={}){
|
|
252
|
+
insist(typeof evidenceDir==='string'&&path.isAbsolute(evidenceDir),'desktop memory evidenceDir invalid');
|
|
253
|
+
insist(typeof snapshotPath==='string'&&path.isAbsolute(snapshotPath),'desktop memory snapshotPath invalid');
|
|
254
|
+
insist(HEX64.test(policySha256??''),'desktop memory provider policy sha invalid');
|
|
255
|
+
insist(privateKey&&publicKey,'desktop memory provider trust keys missing');
|
|
256
|
+
insist(typeof keyId==='string'&&keyId.length>=1&&keyId.length<=128,'desktop memory provider key id invalid');
|
|
257
|
+
async function refresh(){
|
|
258
|
+
const receipts=await loadDesktopEvidenceReceipts(evidenceDir,{policySha256,maxReceipts,maxDirectoryEntries,maxReceiptBytes,maxTotalBytes});
|
|
259
|
+
const memory=deriveDesktopMemory(receipts,{policySha256,publicKey,keyId});
|
|
260
|
+
const snapshot=signDesktopMemory(memory,{privateKey,keyId});
|
|
261
|
+
const proof=verifyDesktopMemorySnapshot(snapshot,{publicKey,keyId,policySha256});
|
|
262
|
+
insist(proof.ok,'desktop memory self-verification failed: '+String(proof.reason??'unknown'));
|
|
263
|
+
const written=await writeDesktopMemorySnapshot(snapshotPath,snapshot);
|
|
264
|
+
return Object.freeze({
|
|
265
|
+
status:'ok',source_receipt_count:receipts.length,
|
|
266
|
+
snapshot_sha256:written.snapshot_sha256,memory_sha256:written.memory_sha256,
|
|
267
|
+
planner_context:desktopMemoryPlannerProjection(snapshot)
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async function plannerContext(){return (await refresh()).planner_context;}
|
|
271
|
+
return Object.freeze({refresh,plannerContext});
|
|
272
|
+
}
|