deadbyte-mcp 0.15.24 → 0.15.26
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 +47 -37
- package/PROVENANCE.json +11 -11
- package/R49-PARENT.json +11 -0
- package/README.txt +16 -7
- package/RELEASE.json +15 -12
- package/SBOM.cdx.json +3 -3
- package/TEST-MATRIX.json +15 -12
- package/docs/R49-FULL-TOOL-CATALOG.json +13 -0
- package/docs/R49-REMOTE-CONTROL-DONORS.json +75 -0
- package/package.json +2 -2
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +17 -6
- package/scripts/commander-hardening-gate-r47.mjs +10 -1
- package/scripts/deferred-slot-operation-r46.mjs +44 -2
- package/scripts/gate-windows-r48.ps1 +45 -0
- package/scripts/gate-windows-r49.ps1 +54 -0
- package/scripts/init-host-policy.ps1 +9 -1
- package/scripts/npm-release-r48.mjs +2 -1
- package/scripts/windows-gate-evidence-r48.mjs +27 -0
- package/scripts/windows-gate-evidence-r49.mjs +27 -0
- package/src/autonomous-planner-contracts.mjs +1 -0
- package/src/autonomous-planner.mjs +11 -5
- package/src/autonomous-runtime.mjs +11 -1
- package/src/capability-ledger.mjs +1 -0
- package/src/commander-wire-adapter-r47.mjs +16 -0
- package/src/desktop-mcp-tools-r40.mjs +23 -1
- package/src/desktop-runtime-r40.mjs +46 -2
- package/src/machine-fs-smoke-client.mjs +6 -17
- package/src/mcp-host-smoke-client.mjs +3 -2
- package/src/mcp-smoke-client.mjs +4 -9
- package/src/observation-smoke-client.mjs +5 -10
- package/src/process-smoke-client.mjs +3 -3
- package/src/remote-mcp-smoke-client.mjs +7 -1
- package/src/runtime-catalog-expectation.mjs +45 -0
- package/src/tunnel-stdio-compat-smoke.mjs +5 -9
- package/src/version.mjs +1 -1
- package/test/autonomous-desktop-r40.test.mjs +27 -1
- package/test/coding-catalog-consistency.test.mjs +9 -1
- package/test/commander-real-parity-r47.test.mjs +8 -4
- package/test/contained.test.mjs +4 -6
- package/test/controller.test.mjs +2 -1
- package/test/desktop-mcp-tools-r40.test.mjs +19 -4
- package/test/desktop-runtime-r40.test.mjs +43 -2
- package/test/full-max-catalog-r48.test.mjs +5 -3
- package/test/r48-release-contract.test.mjs +14 -20
- package/test/r49-remote-control-donors.test.mjs +24 -0
- package/test/r49-windows-gate.test.mjs +33 -0
- package/test/remote-r24-catalog.test.mjs +9 -0
- package/test/tunnel-stdio-compat.test.mjs +4 -6
|
@@ -33,6 +33,19 @@ const explicitTargetFields={
|
|
|
33
33
|
confidence:z.number().min(0).max(1),semantic_label_sha256:hex64.nullable().default(null)
|
|
34
34
|
};
|
|
35
35
|
const uiaTargetFields={frame_id:hex64,uia_query:uiaQuery};
|
|
36
|
+
const waitFields={
|
|
37
|
+
baseline_frame_id:hex64,
|
|
38
|
+
until:z.enum(['frame_changed','topology_changed','foreground_changed','uia_present','uia_absent']),
|
|
39
|
+
uia_query:uiaQuery.nullable().default(null),timeout_ms:z.number().int().min(100).max(30000).default(5000),
|
|
40
|
+
poll_ms:z.number().int().min(50).max(1000).default(100),preview:z.boolean().default(false)
|
|
41
|
+
};
|
|
42
|
+
function validateWait(value,ctx){
|
|
43
|
+
const needsQuery=value.until==='uia_present'||value.until==='uia_absent';
|
|
44
|
+
if(needsQuery&&value.uia_query===null)ctx.addIssue({code:'custom',path:['uia_query'],message:'UIA wait requires uia_query'});
|
|
45
|
+
if(!needsQuery&&value.uia_query!==null)ctx.addIssue({code:'custom',path:['uia_query'],message:'non-UIA wait cannot carry uia_query'});
|
|
46
|
+
if(value.poll_ms>value.timeout_ms)ctx.addIssue({code:'custom',path:['poll_ms'],message:'poll_ms cannot exceed timeout_ms'});
|
|
47
|
+
}
|
|
48
|
+
export const desktopWaitInputSchema=z.strictObject(waitFields).superRefine(validateWait);
|
|
36
49
|
export const desktopTargetInputSchema=z.union([
|
|
37
50
|
z.strictObject(explicitTargetFields),z.strictObject(uiaTargetFields)
|
|
38
51
|
]);
|
|
@@ -61,12 +74,13 @@ export const desktopActionInputSchema=z.strictObject(actionFields).superRefine(v
|
|
|
61
74
|
|
|
62
75
|
const statusOp=z.strictObject({op:z.literal('status')});
|
|
63
76
|
const observeOp=z.strictObject({op:z.literal('observe'),preview:z.boolean().default(false)});
|
|
77
|
+
const waitOp=z.strictObject({op:z.literal('wait'),...waitFields}).superRefine(validateWait);
|
|
64
78
|
const targetOp=z.union([
|
|
65
79
|
z.strictObject({op:z.literal('target'),...explicitTargetFields}),
|
|
66
80
|
z.strictObject({op:z.literal('target'),...uiaTargetFields})
|
|
67
81
|
]);
|
|
68
82
|
const actionOp=z.strictObject({op:z.literal('action'),...actionFields}).superRefine(validateAction);
|
|
69
|
-
export const desktopControlInputSchema=z.union([statusOp,observeOp,targetOp,actionOp]);
|
|
83
|
+
export const desktopControlInputSchema=z.union([statusOp,observeOp,waitOp,targetOp,actionOp]);
|
|
70
84
|
|
|
71
85
|
const sha256=value=>createHash('sha256').update(value).digest('hex');
|
|
72
86
|
function publicResult(value){
|
|
@@ -89,6 +103,10 @@ export async function invokeDesktopOperation(args,runtime){
|
|
|
89
103
|
let value;
|
|
90
104
|
if(op==='status') value=await runtime.status();
|
|
91
105
|
else if(op==='observe') value=await runtime.observe({preview:args.preview??false});
|
|
106
|
+
else if(op==='wait') value=await runtime.wait({
|
|
107
|
+
baselineFrameId:args.baseline_frame_id,until:args.until,query:args.uia_query,
|
|
108
|
+
timeoutMs:args.timeout_ms,pollMs:args.poll_ms,preview:args.preview
|
|
109
|
+
});
|
|
92
110
|
else if(op==='target'&&args.uia_query) value=await runtime.ground({frameId:args.frame_id,query:args.uia_query});
|
|
93
111
|
else if(op==='target') value=await runtime.target({
|
|
94
112
|
frameId:args.frame_id,rect:args.rect,source:args.source,confidence:args.confidence,
|
|
@@ -115,6 +133,10 @@ export function registerDesktopTools(server,{runtime}={}){
|
|
|
115
133
|
description:'Capture one bounded Windows desktop frame and signed observation evidence. Observation does not require Desktop mutation authority.',
|
|
116
134
|
inputSchema:desktopObserveInputSchema,outputSchema:compactGatewayOutputSchema
|
|
117
135
|
},args=>invokeDesktopOperation({op:'observe',...args},runtime));
|
|
136
|
+
server.registerTool('desktop_wait',{
|
|
137
|
+
description:'Wait for one bounded desktop or local UIA condition using fresh observations. This is read-only, returns signed final evidence, and never dispatches or retries an action.',
|
|
138
|
+
inputSchema:desktopWaitInputSchema,outputSchema:compactGatewayOutputSchema
|
|
139
|
+
},args=>invokeDesktopOperation({op:'wait',...args},runtime));
|
|
118
140
|
server.registerTool('desktop_target',{
|
|
119
141
|
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
142
|
inputSchema:desktopTargetInputSchema,outputSchema:compactGatewayOutputSchema
|
|
@@ -71,13 +71,14 @@ export function verifyDesktopReceiptObject(receipt,{policySha256,publicKey,keyId
|
|
|
71
71
|
|
|
72
72
|
export function createDesktopRuntime({
|
|
73
73
|
policy,observerRuntime,inputProvider,grounderProvider=null,privateKey,publicKey=null,keyId,evidenceDir=policy?.evidence_dir,
|
|
74
|
-
now=()=>new Date().toISOString(),maxTargets=64
|
|
74
|
+
now=()=>new Date().toISOString(),clockMs=()=>Date.now(),sleeper=ms=>new Promise(resolve=>setTimeout(resolve,ms)),maxTargets=64
|
|
75
75
|
}={}){
|
|
76
76
|
insist(policy?.schema===DESKTOP_POLICY_SCHEMA,'desktop runtime policy invalid');
|
|
77
77
|
insist(observerRuntime?.capture&&observerRuntime?.frameStatus,'desktop runtime observer invalid');
|
|
78
78
|
insist(typeof inputProvider==='function','desktop runtime inputProvider required');
|
|
79
79
|
insist(privateKey,'desktop runtime privateKey required');
|
|
80
80
|
insist(typeof keyId==='string'&&keyId.length>=1,'desktop runtime keyId invalid');
|
|
81
|
+
insist(typeof clockMs==='function'&&typeof sleeper==='function','desktop wait clock invalid');
|
|
81
82
|
insist(Number.isInteger(maxTargets)&&maxTargets>=1&&maxTargets<=1024,'maxTargets invalid');
|
|
82
83
|
const receiptPublicKey=publicKey??createPublicKey(privateKey);
|
|
83
84
|
const targets=new Map();
|
|
@@ -118,6 +119,49 @@ export function createDesktopRuntime({
|
|
|
118
119
|
},observed.frame.observed_at_utc);
|
|
119
120
|
return Object.freeze({...observed,...(previewValue?{preview:previewValue}:{}),receipt_id:evidence.receipt_id,receipt_sha256:evidence.receipt_sha256});
|
|
120
121
|
}
|
|
122
|
+
async function wait({baselineFrameId,until,query=null,timeoutMs=5000,pollMs=100,preview=false}={}){
|
|
123
|
+
insist(HEX64.test(baselineFrameId??''),'desktop wait baseline frame invalid');
|
|
124
|
+
insist(['frame_changed','topology_changed','foreground_changed','uia_present','uia_absent'].includes(until),'desktop wait condition invalid');
|
|
125
|
+
insist(Number.isInteger(timeoutMs)&&timeoutMs>=100&&timeoutMs<=30000,'desktop wait timeout invalid');
|
|
126
|
+
insist(Number.isInteger(pollMs)&&pollMs>=50&&pollMs<=1000&&pollMs<=timeoutMs,'desktop wait poll invalid');
|
|
127
|
+
insist(typeof preview==='boolean','desktop wait preview invalid');
|
|
128
|
+
const needsQuery=until==='uia_present'||until==='uia_absent';
|
|
129
|
+
insist(needsQuery?query&&typeof grounderProvider==='function':query===null,'desktop wait query invalid');
|
|
130
|
+
const baseline=observerRuntime.frameStatus(baselineFrameId).frame;
|
|
131
|
+
const started=clockMs();
|
|
132
|
+
insist(Number.isFinite(started),'desktop wait clock result invalid');
|
|
133
|
+
let attempts=0,observed=null,match=null,satisfied=false;
|
|
134
|
+
while(true){
|
|
135
|
+
observed=await observerRuntime.capture();attempts++;
|
|
136
|
+
if(until==='frame_changed')satisfied=observed.frame.image_sha256!==baseline.image_sha256;
|
|
137
|
+
else if(until==='topology_changed')satisfied=observed.frame.topology_sha256!==baseline.topology_sha256;
|
|
138
|
+
else if(until==='foreground_changed')satisfied=!sameWindow(observed.frame.active_window,baseline.active_window);
|
|
139
|
+
else{
|
|
140
|
+
const grounded=await grounderProvider({frame:observed.frame,query});
|
|
141
|
+
insist(grounded&&grounded.status==='ok'&&HEX64.test(grounded.query_sha256??''),'desktop wait UIA result invalid');
|
|
142
|
+
const candidate=grounded.matches?.[grounded.ordinal??0]??null;
|
|
143
|
+
const actionable=Boolean(candidate?.enabled===true&&!candidate?.offscreen);
|
|
144
|
+
match=Object.freeze({query_sha256:grounded.query_sha256,query_kind:grounded.query_kind,
|
|
145
|
+
match_count:Array.isArray(grounded.matches)?grounded.matches.length:0,actionable,
|
|
146
|
+
visited:grounded.visited,truncated:grounded.truncated,provider_receipt_sha256:grounded.provider_receipt_sha256});
|
|
147
|
+
satisfied=until==='uia_present'?actionable:!actionable;
|
|
148
|
+
}
|
|
149
|
+
const elapsed=Math.max(0,clockMs()-started);
|
|
150
|
+
if(satisfied||elapsed>=timeoutMs)break;
|
|
151
|
+
await sleeper(Math.min(pollMs,timeoutMs-elapsed));
|
|
152
|
+
}
|
|
153
|
+
const elapsedMs=Math.max(0,clockMs()-started);
|
|
154
|
+
const previewValue=preview?observerRuntime.framePreview?.(observed.frame.frame_id,{maxBytes:policy.limits.max_preview_bytes}):null;
|
|
155
|
+
if(preview)insist(previewValue?.schema==='deadbyte.desktop-preview.v1','desktop preview unavailable');
|
|
156
|
+
const payload={condition:until,satisfied,attempts,elapsed_ms:elapsedMs,baseline_frame_id:baseline.frame_id,
|
|
157
|
+
frame_id:observed.frame.frame_id,desktop_epoch:observed.frame.desktop_epoch,topology_sha256:observed.frame.topology_sha256,
|
|
158
|
+
image_sha256:observed.frame.image_sha256,observed_at_utc:observed.frame.observed_at_utc,active_window:observed.frame.active_window,
|
|
159
|
+
match,preview:previewValue?{sha256:previewValue.sha256,byte_length:previewValue.byte_length,width:previewValue.width,height:previewValue.height,encoding:previewValue.encoding}:null};
|
|
160
|
+
const evidence=await receipt('desktop_wait_observation',payload,observed.frame.observed_at_utc);
|
|
161
|
+
return Object.freeze({status:'ok',condition:until,satisfied,timed_out:!satisfied,attempts,elapsed_ms:elapsedMs,
|
|
162
|
+
baseline_frame_id:baseline.frame_id,frame:observed.frame,match,...(previewValue?{preview:previewValue}:{}),
|
|
163
|
+
receipt_id:evidence.receipt_id,receipt_sha256:evidence.receipt_sha256});
|
|
164
|
+
}
|
|
121
165
|
function targetFor(targetId){
|
|
122
166
|
insist(HEX64.test(targetId??''),'targetId invalid');
|
|
123
167
|
const found=targets.get(targetId);if(!found)throw new Error('desktop target expired or unknown');return found;
|
|
@@ -314,5 +358,5 @@ export function createDesktopRuntime({
|
|
|
314
358
|
throw wrapped;
|
|
315
359
|
}
|
|
316
360
|
}
|
|
317
|
-
return Object.freeze({status,observe,target,ground,action,verifyReceipt});
|
|
361
|
+
return Object.freeze({status,observe,wait,target,ground,action,verifyReceipt});
|
|
318
362
|
}
|
|
@@ -8,6 +8,7 @@ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotoc
|
|
|
8
8
|
import { CODING_GRANT_SCHEMA, loadCodingPolicy } from './coding-plane.mjs';
|
|
9
9
|
import { verifyMachineFsReceiptFile } from './machine-fs.mjs';
|
|
10
10
|
import { generateTrustKeypair } from './trust.mjs';
|
|
11
|
+
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
11
12
|
|
|
12
13
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const repoRoot = path.resolve(here, '..');
|
|
@@ -51,24 +52,13 @@ const client = new Client({ name: 'deadbyte-machine-fs-smoke', version: '0.8.0'
|
|
|
51
52
|
const transport = new StdioClientTransport({
|
|
52
53
|
command: process.execPath, args: [serverPath], cwd: repoRoot,
|
|
53
54
|
env: { ...getDefaultEnvironment(), DEADBYTE_CODING_RUNTIME: '1', DEADBYTE_MACHINE_FS_RUNTIME: '1',
|
|
55
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
54
56
|
DEADBYTE_CODING_POLICY: policyPath, DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
55
57
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath }, stderr: 'pipe'
|
|
56
58
|
});
|
|
57
59
|
const stderrChunks = [];
|
|
58
60
|
transport.stderr?.on('data', chunk => stderrChunks.push(Buffer.from(chunk)));
|
|
59
|
-
const
|
|
60
|
-
const codingTools = [
|
|
61
|
-
'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read',
|
|
62
|
-
'coding_receipt_verify','coding_search','coding_session_status','coding_stat',
|
|
63
|
-
'coding_symbol_definition','coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace',
|
|
64
|
-
'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run',
|
|
65
|
-
'coding_task_status','coding_task_submit','coding_task_verify','coding_text_replace','coding_tree'
|
|
66
|
-
];
|
|
67
|
-
const machineFsTools = [
|
|
68
|
-
'machine_fs_delete','machine_fs_edit_exact','machine_fs_list','machine_fs_mkdir',
|
|
69
|
-
'machine_fs_move','machine_fs_read','machine_fs_stat','machine_fs_write'
|
|
70
|
-
];
|
|
71
|
-
const expectedCatalog = [...baseTools, ...codingTools, ...machineFsTools].sort();
|
|
61
|
+
const expectedCatalog = await expectedFullCatalogForEnv({DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_MACHINE_FS_RUNTIME:'1'});
|
|
72
62
|
const receipts = [];
|
|
73
63
|
const remember = (operation, result) => {
|
|
74
64
|
if (result.status !== 'committed' || !/^[0-9a-f]{24}$/.test(result.receipt_id ?? '')) throw new Error(`${operation} missing committed receipt`);
|
|
@@ -79,10 +69,9 @@ try {
|
|
|
79
69
|
await client.connect(transport);
|
|
80
70
|
const listed = await client.listTools();
|
|
81
71
|
const names = listed.tools.map(tool => tool.name).sort();
|
|
82
|
-
|
|
83
|
-
for (const tool of listed.tools)
|
|
84
|
-
|
|
85
|
-
}
|
|
72
|
+
assertExactCatalog(names,expectedCatalog,'machine fs smoke');
|
|
73
|
+
for (const tool of listed.tools) if (tool.outputSchema !== undefined &&
|
|
74
|
+
(tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false)) throw new Error(`tool ${tool.name} has non-strict output schema`);
|
|
86
75
|
const rootStat = structured(await client.callTool({ name:'machine_fs_stat', arguments:{ root_id:'core', path:'.', hash:false } }), 'machine_fs_stat(root)');
|
|
87
76
|
if (rootStat.status !== 'ok' || rootStat.kind !== 'directory') throw new Error('machine fs root stat malformed');
|
|
88
77
|
const made = remember('machine_fs_mkdir', structured(await client.callTool({
|
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { Client } from '@modelcontextprotocol/client';
|
|
7
7
|
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
8
8
|
import { generateTrustKeypair } from './trust.mjs';
|
|
9
|
+
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
9
10
|
|
|
10
11
|
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
11
12
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -84,8 +85,8 @@ function structured(result, label) {
|
|
|
84
85
|
try {
|
|
85
86
|
await client.connect(transport);
|
|
86
87
|
const names = (await client.listTools()).tools.map(t => t.name).sort();
|
|
87
|
-
const expected =
|
|
88
|
-
|
|
88
|
+
const expected = await expectedFullCatalogForEnv({DEADBYTE_PRIVILEGED_POLICY:policyPath});
|
|
89
|
+
assertExactCatalog(names,expected,'privileged smoke');
|
|
89
90
|
|
|
90
91
|
const writeText = structured(await client.callTool({
|
|
91
92
|
name: 'host_file_write',
|
package/src/mcp-smoke-client.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { Client } from '@modelcontextprotocol/client';
|
|
7
7
|
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
8
8
|
import { generateTrustKeypair } from './trust.mjs';
|
|
9
|
+
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
9
10
|
|
|
10
11
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const repoRoot = path.resolve(here, '..');
|
|
@@ -39,6 +40,7 @@ const transport = new StdioClientTransport({
|
|
|
39
40
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
40
41
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
41
42
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
|
|
43
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
42
44
|
DEADBYTE_STDIO_CONSOLE_GUARD_PROBE: '1'
|
|
43
45
|
},
|
|
44
46
|
stderr: 'pipe'
|
|
@@ -67,15 +69,8 @@ try {
|
|
|
67
69
|
const names = listed.tools.map(tool => tool.name).sort();
|
|
68
70
|
const guardStderr = Buffer.concat(serverStderr).toString('utf8');
|
|
69
71
|
if (!guardStderr.includes('DEADBYTE MCP LOG DEADBYTE_STDIO_CONSOLE_GUARD_PROBE')) throw new Error('stdio console guard probe did not reach stderr');
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
const allowedCatalogs = [
|
|
73
|
-
baseTools,
|
|
74
|
-
[...baseTools, ...privilegedTools]
|
|
75
|
-
].map(items => [...items].sort());
|
|
76
|
-
if (!allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected))) {
|
|
77
|
-
throw new Error(`tool catalog mismatch: ${JSON.stringify(names)}`);
|
|
78
|
-
}
|
|
72
|
+
const expected=await expectedFullCatalogForEnv({});
|
|
73
|
+
assertExactCatalog(names,expected,'MCP smoke');
|
|
79
74
|
|
|
80
75
|
const opened = structured(await client.callTool({
|
|
81
76
|
name: 'workspace_open',
|
|
@@ -8,6 +8,7 @@ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotoc
|
|
|
8
8
|
import { loadCodingPolicy } from './coding-plane.mjs';
|
|
9
9
|
import { verifyObservationReceiptFile } from './observation-runtime.mjs';
|
|
10
10
|
import { generateTrustKeypair } from './trust.mjs';
|
|
11
|
+
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
11
12
|
|
|
12
13
|
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const repoRoot=path.resolve(here,'..');
|
|
@@ -49,21 +50,15 @@ const client=new Client({name:'deadbyte-observation-smoke',version:'0.8.0'},
|
|
|
49
50
|
{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
50
51
|
const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,
|
|
51
52
|
env:{...getDefaultEnvironment(),DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_OBSERVATION_RUNTIME:'1',
|
|
53
|
+
DEADBYTE_PRIVILEGED_POLICY:'',
|
|
52
54
|
DEADBYTE_CODING_POLICY:policyPath,DEADBYTE_STATE_ROOT:state,DEADBYTE_SIGNING_KEY:keys.privateKeyPath,DEADBYTE_VERIFY_KEY:keys.publicKeyPath},stderr:'pipe'});
|
|
53
55
|
const stderr=[]; transport.stderr?.on('data',chunk=>stderr.push(Buffer.from(chunk)));
|
|
54
|
-
const
|
|
55
|
-
const machine=['machine_list','machine_ping','machine_revoke','machine_status'];
|
|
56
|
-
const coding=['coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read','coding_receipt_verify','coding_search',
|
|
57
|
-
'coding_session_status','coding_stat','coding_symbol_definition','coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace',
|
|
58
|
-
'coding_task_approve','coding_task_cancel','coding_task_events','coding_task_run','coding_task_status',
|
|
59
|
-
'coding_task_submit','coding_task_verify','coding_text_replace','coding_tree'];
|
|
60
|
-
const observation=['machine_hardware_query','machine_process_list','machine_search_cancel','machine_search_page','machine_search_start','machine_search_status','machine_system_status'];
|
|
61
|
-
const expected=[...base,...machine,...coding,...observation].sort();
|
|
56
|
+
const expected=await expectedFullCatalogForEnv({DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_CODING_RUNTIME:'1',DEADBYTE_OBSERVATION_RUNTIME:'1'});
|
|
62
57
|
try{
|
|
63
58
|
await client.connect(transport);
|
|
64
59
|
const listed=await client.listTools(); const names=listed.tools.map(tool=>tool.name).sort();
|
|
65
|
-
|
|
66
|
-
for(const tool of listed.tools) if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false) throw new Error(`tool ${tool.name}
|
|
60
|
+
assertExactCatalog(names,expected,'observation smoke');
|
|
61
|
+
for(const tool of listed.tools) if(tool.outputSchema!==undefined&&(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false)) throw new Error(`tool ${tool.name} has non-strict output schema`);
|
|
67
62
|
const codingStatus=structured(await client.callTool({name:'coding_session_status',arguments:{}}),'coding_session_status');
|
|
68
63
|
if(codingStatus.status!=='ok'||codingStatus.armed!==false) throw new Error('observation smoke requires coding mutation authority disarmed');
|
|
69
64
|
const system=structured(await client.callTool({name:'machine_system_status',arguments:{}}),'machine_system_status');
|
|
@@ -8,6 +8,7 @@ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotoc
|
|
|
8
8
|
import { canonicalJson } from './canonical-json.mjs';
|
|
9
9
|
import { loadProcessPolicy, PROCESS_GRANT_SCHEMA } from './process-policy.mjs';
|
|
10
10
|
import { generateTrustKeypair, loadPublicVerifyKey } from './trust.mjs';
|
|
11
|
+
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
11
12
|
|
|
12
13
|
const EVENT_DOMAIN = Buffer.from('DEADBYTE-PROCESS-EVENT-V1\0', 'utf8');
|
|
13
14
|
const ZERO_SHA256 = '0'.repeat(64);
|
|
@@ -58,9 +59,8 @@ try {
|
|
|
58
59
|
await client.connect(transport);
|
|
59
60
|
const listed = await client.listTools();
|
|
60
61
|
const names = listed.tools.map(tool => tool.name).sort();
|
|
61
|
-
const expected =
|
|
62
|
-
|
|
63
|
-
if (JSON.stringify(names) !== JSON.stringify(expected)) throw new Error(`process smoke catalog mismatch: ${JSON.stringify(names)}`);
|
|
62
|
+
const expected = await expectedFullCatalogForEnv({DEADBYTE_PROCESS_RUNTIME:'1'});
|
|
63
|
+
assertExactCatalog(names,expected,'process smoke');
|
|
64
64
|
const started = structured(await client.callTool({ name: 'process_session_start', arguments: {
|
|
65
65
|
profile_id: 'ping_hold', root_id: 'core', cwd: '.', args: []
|
|
66
66
|
} }), 'process_session_start');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
3
|
+
import { verifyMachinePingPublic } from './machine-identity.mjs';
|
|
3
4
|
|
|
4
5
|
const endpointText = process.argv[2];
|
|
5
6
|
const mode = process.argv[3] ?? 'smoke';
|
|
@@ -151,6 +152,11 @@ try {
|
|
|
151
152
|
ping.release_id !== machine.release_id || ping.release_manifest_sha256 !== machine.release_manifest_sha256) {
|
|
152
153
|
throw new Error('compact machine ping binding mismatch');
|
|
153
154
|
}
|
|
155
|
+
const pingSignatureValid = await verifyMachinePingPublic({
|
|
156
|
+
ping,
|
|
157
|
+
publicKeySpkiBase64: machine.public_key_spki_base64
|
|
158
|
+
});
|
|
159
|
+
if (!pingSignatureValid) throw new Error('machine ping public Ed25519 verification failed');
|
|
154
160
|
compact = {
|
|
155
161
|
profile: 'compact',
|
|
156
162
|
ledger_sha256: manifest.ledger_sha256,
|
|
@@ -160,7 +166,7 @@ try {
|
|
|
160
166
|
machine_id: machine.machine_id,
|
|
161
167
|
release_id: machine.release_id,
|
|
162
168
|
release_manifest_sha256: machine.release_manifest_sha256,
|
|
163
|
-
ping_signature_valid:
|
|
169
|
+
ping_signature_valid: true
|
|
164
170
|
};
|
|
165
171
|
} else {
|
|
166
172
|
const opened = structured(await client.callTool({ name: 'workspace_open', arguments: { workspace_id: 'demo' } }), 'workspace_open');
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { R36_CAPABILITY_LEDGER } from './capability-ledger.mjs';
|
|
6
|
+
|
|
7
|
+
const packageRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
8
|
+
const catalogPath=path.join(packageRoot,'docs','R48-FULL-TOOL-CATALOG.json');
|
|
9
|
+
const overlayPath=path.join(packageRoot,'docs','R49-FULL-TOOL-CATALOG.json');
|
|
10
|
+
const AGENT_TOOLS=new Set(['agent_task_approve','agent_task_cancel','agent_task_events','agent_task_run','agent_task_status','agent_task_submit','agent_task_verify']);
|
|
11
|
+
|
|
12
|
+
export async function expectedFullCatalogForEnv(env){
|
|
13
|
+
const canonical=JSON.parse(await readFile(catalogPath,'utf8'));
|
|
14
|
+
if(canonical.schema!=='deadbyte.r48-full-catalog.v1'||canonical.tool_count!==canonical.tools?.length) throw new Error('canonical R48 full catalog is invalid');
|
|
15
|
+
const overlay=JSON.parse(await readFile(overlayPath,'utf8'));
|
|
16
|
+
const baseSha=createHash('sha256').update(await readFile(catalogPath)).digest('hex');
|
|
17
|
+
if(overlay.schema!=='deadbyte.r49-full-catalog-overlay.v1'||overlay.base?.sha256!==baseSha||overlay.base?.tool_count!==canonical.tool_count||
|
|
18
|
+
overlay.tool_count!==canonical.tool_count+overlay.additions.length-overlay.removals.length) throw new Error('canonical R49 full catalog overlay is invalid');
|
|
19
|
+
const full=[...new Set([...canonical.tools.filter(name=>!overlay.removals.includes(name)),...overlay.additions])].sort((a,b)=>a.localeCompare(b,'en'));
|
|
20
|
+
if(full.length!==overlay.tool_count) throw new Error('canonical R49 full catalog count mismatch');
|
|
21
|
+
const disabled=new Set();
|
|
22
|
+
for(const capability of R36_CAPABILITY_LEDGER.entries){
|
|
23
|
+
const flag=capability.runtime_flag;
|
|
24
|
+
const enabled=flag===null||(flag==='DEADBYTE_PRIVILEGED_POLICY'?Boolean(env[flag]):env[flag]==='1');
|
|
25
|
+
if(!enabled) disabled.add(capability.full_tool);
|
|
26
|
+
}
|
|
27
|
+
// The rename preview tool is registered by the coding surface even when its
|
|
28
|
+
// optional Native Diff backend is absent; only the apply path needs Autonomous.
|
|
29
|
+
if(env.DEADBYTE_CODING_RUNTIME==='1') disabled.delete('coding_symbol_rename_preview');
|
|
30
|
+
if(env.DEADBYTE_AGENT_RUNTIME!=='1') for(const name of AGENT_TOOLS) disabled.add(name);
|
|
31
|
+
if(env.DEADBYTE_PROCESS_RUNTIME!=='1') disabled.add('process_session_cancel');
|
|
32
|
+
return full.filter(name=>!disabled.has(name));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function assertExactCatalog(actual,expected,label='MCP'){
|
|
36
|
+
const names=[...actual].sort((a,b)=>a.localeCompare(b,'en'));
|
|
37
|
+
const wanted=[...expected].sort((a,b)=>a.localeCompare(b,'en'));
|
|
38
|
+
if(JSON.stringify(names)!==JSON.stringify(wanted)){
|
|
39
|
+
const actualSet=new Set(names); const expectedSet=new Set(wanted);
|
|
40
|
+
const missing=wanted.filter(name=>!actualSet.has(name));
|
|
41
|
+
const unexpected=names.filter(name=>!expectedSet.has(name));
|
|
42
|
+
throw new Error(`${label} catalog mismatch: missing=${JSON.stringify(missing)} unexpected=${JSON.stringify(unexpected)}`);
|
|
43
|
+
}
|
|
44
|
+
return names;
|
|
45
|
+
}
|
|
@@ -3,6 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { Client } from '@modelcontextprotocol/client';
|
|
5
5
|
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
6
|
+
import { assertExactCatalog, expectedFullCatalogForEnv } from './runtime-catalog-expectation.mjs';
|
|
6
7
|
|
|
7
8
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const repoRoot = path.resolve(here, '..');
|
|
@@ -11,6 +12,7 @@ const workspaceRoot = path.join(repoRoot, 'workspaces');
|
|
|
11
12
|
const baseEnv = {
|
|
12
13
|
...process.env,
|
|
13
14
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
15
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
14
16
|
DEADBYTE_TUNNEL_STDIO_PROBE_COMPAT: '1'
|
|
15
17
|
};
|
|
16
18
|
|
|
@@ -100,6 +102,7 @@ async function modernPassThrough() {
|
|
|
100
102
|
env: {
|
|
101
103
|
...getDefaultEnvironment(),
|
|
102
104
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
105
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
103
106
|
DEADBYTE_TUNNEL_STDIO_PROBE_COMPAT: '1'
|
|
104
107
|
},
|
|
105
108
|
stderr: 'pipe'
|
|
@@ -112,15 +115,8 @@ async function modernPassThrough() {
|
|
|
112
115
|
if (era !== 'modern') throw new Error(`expected modern era through compat prefilter, got ${String(era)}`);
|
|
113
116
|
const listed = await client.listTools();
|
|
114
117
|
const names = listed.tools.map(tool => tool.name).sort();
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
const allowedCatalogs = [
|
|
118
|
-
baseTools,
|
|
119
|
-
[...baseTools, ...privilegedTools]
|
|
120
|
-
].map(items => [...items].sort());
|
|
121
|
-
if (!allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected))) {
|
|
122
|
-
throw new Error(`modern catalog changed through compat prefilter: ${JSON.stringify(names)}`);
|
|
123
|
-
}
|
|
118
|
+
const expected=await expectedFullCatalogForEnv({});
|
|
119
|
+
assertExactCatalog(names,expected,'modern tunnel compatibility');
|
|
124
120
|
return { protocol_revision: '2026-07-28', protocol_era: era, tools: names };
|
|
125
121
|
} catch (error) {
|
|
126
122
|
const stderr = Buffer.concat(serverStderr).toString('utf8').trim();
|
package/src/version.mjs
CHANGED
|
@@ -11,7 +11,7 @@ const POLICY=H('desktop-policy');
|
|
|
11
11
|
const WIN={hwnd:'0x1234',pid:77,process_image_sha256:H('desktop-app'),title_sha256:H('desktop-title')};
|
|
12
12
|
|
|
13
13
|
function fakeDesktopRuntime({armed=false,unknown=false}={}){
|
|
14
|
-
const calls={status:0,observe:0,ground:0,action:0,verify:0};
|
|
14
|
+
const calls={status:0,observe:0,wait:0,ground:0,action:0,verify:0};
|
|
15
15
|
const receipts=new Map();
|
|
16
16
|
let seq=0;
|
|
17
17
|
const issue=kind=>{
|
|
@@ -28,6 +28,9 @@ function fakeDesktopRuntime({armed=false,unknown=false}={}){
|
|
|
28
28
|
async status(){calls.status++;return {status:'ok',configured:true,armed,reason:armed?'standing-grant':'disarmed',
|
|
29
29
|
action_kinds:armed?['click_primary']:[],policy_sha256:POLICY,observer_backend:'fake'};},
|
|
30
30
|
async observe(){calls.observe++;const r=issue('desktop_observation');return {status:'ok',frame:frame(FRAME),backend:'fake',byte_length:4,...r};},
|
|
31
|
+
async wait({baselineFrameId,until}){calls.wait++;assert.equal(baselineFrameId,FRAME);assert.equal(until,'frame_changed');
|
|
32
|
+
const r=issue('desktop_wait_observation');return {status:'ok',condition:until,satisfied:true,timed_out:false,attempts:2,elapsed_ms:100,
|
|
33
|
+
baseline_frame_id:FRAME,frame:frame(GROUNDED_FRAME),match:null,...r};},
|
|
31
34
|
async ground({frameId,query}){calls.ground++;assert.equal(frameId,FRAME);assert.equal(query.kind,'root');
|
|
32
35
|
const r=issue('desktop_grounding');return {status:'ok',frame:frame(GROUNDED_FRAME),
|
|
33
36
|
target:{target_id:TARGET,frame_id:GROUNDED_FRAME,rect:{left:100,top:100,width:200,height:80},source:'uia'},
|
|
@@ -99,6 +102,29 @@ test('T7 read-only Desktop observe works while Desktop mutation authority is DIS
|
|
|
99
102
|
}finally{await f.cleanup();}
|
|
100
103
|
});
|
|
101
104
|
|
|
105
|
+
test('successor autonomous Desktop wait replaces fixed sleep and verifies signed observation evidence',async()=>{
|
|
106
|
+
const d=fakeDesktopRuntime({armed:false});
|
|
107
|
+
const f=await createR34Fixture({desktopRuntime:d});
|
|
108
|
+
try{
|
|
109
|
+
const goal=await f.runtime.submit({submissionId:'desktop-bounded-wait',rootId:'project',title:'desktop wait',goal:'wait for visual change',acceptance:'signed bounded wait'});
|
|
110
|
+
let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
|
|
111
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe','wait']));
|
|
112
|
+
result=await toAct(f,goal,result);
|
|
113
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
|
|
114
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('observe','wait-observe-proof'));
|
|
115
|
+
result=await toAct(f,goal,result);
|
|
116
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
|
|
117
|
+
kind:'desktop_wait',baseline_frame_id:FRAME,until:'frame_changed',uia_query:null,timeout_ms:1000,poll_ms:100
|
|
118
|
+
}});
|
|
119
|
+
assert.equal(result.loop.phase,'VERIFYING');
|
|
120
|
+
assert.equal(d.calls.wait,1);assert.equal(d.calls.action,0);assert.equal(d.calls.verify,2);
|
|
121
|
+
const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
|
|
122
|
+
const completed=journal.events.find(e=>e.type==='delegated_action_completed'&&e.payload?.operation?.kind==='desktop_wait');
|
|
123
|
+
assert.equal(completed.payload.capability_id,'desktop.wait');
|
|
124
|
+
assert.equal(completed.payload.evidence.receipts[0].kind,'desktop_wait_observation');
|
|
125
|
+
}finally{await f.cleanup();}
|
|
126
|
+
});
|
|
127
|
+
|
|
102
128
|
test('T7 Autonomous authority cannot substitute for missing Desktop mutation grant',async()=>{
|
|
103
129
|
const d=fakeDesktopRuntime({armed:false});
|
|
104
130
|
const f=await createR34Fixture({desktopRuntime:d});
|
|
@@ -16,9 +16,17 @@ function extract(source){
|
|
|
16
16
|
assert.ok(m,'coding catalog array missing');
|
|
17
17
|
return [...m[1].matchAll(/'([^']+)'/g)].map(x=>x[1]).sort();
|
|
18
18
|
}
|
|
19
|
-
for(const rel of ['src/
|
|
19
|
+
for(const rel of ['src/remote-mcp-coding-smoke-client.mjs','src/remote-mcp-smoke-client.mjs']){
|
|
20
20
|
test(`${rel} uses the exact R36 25-tool coding catalog`,async()=>{
|
|
21
21
|
const source=await readFile(path.join(root,...rel.split('/')),'utf8');
|
|
22
22
|
assert.deepEqual(extract(source),expected);
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
|
+
for(const rel of ['src/machine-fs-smoke-client.mjs','src/observation-smoke-client.mjs']){
|
|
26
|
+
test(`${rel} derives its exact R48 catalog from the canonical runtime catalog`,async()=>{
|
|
27
|
+
const source=await readFile(path.join(root,...rel.split('/')),'utf8');
|
|
28
|
+
assert.match(source,/expectedFullCatalogForEnv/);
|
|
29
|
+
assert.match(source,/assertExactCatalog/);
|
|
30
|
+
assert.doesNotMatch(source,/const (?:codingTools|coding)\s*=\s*\[/);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -73,13 +73,14 @@ test('real Commander-parity filesystem lifecycle is transactional and signed',as
|
|
|
73
73
|
|
|
74
74
|
test('real Commander adapter preserves PowerShell command bytes through the bounded REPL', {skip:process.platform!=='win32'}, async()=>{
|
|
75
75
|
const f=await processFixture();
|
|
76
|
+
let adapter=null,pid=null;
|
|
76
77
|
try{
|
|
77
|
-
|
|
78
|
+
adapter=createCommanderWireAdapter({processRuntime:f.runtime});
|
|
78
79
|
const command=`[Console]::OutputEncoding=[Text.UTF8Encoding]::new(); $x='a b'; "DB:$x"; 'pipe' | ForEach-Object { "P:$_" }; "U:สวัสดี"`;
|
|
79
80
|
const started=await adapter.handlers.start_process({command,timeout_ms:10000});
|
|
80
|
-
|
|
81
|
+
pid=started.structuredContent.pid;
|
|
81
82
|
let output='';
|
|
82
|
-
for(let i=0;i<50&&!output.includes('P:pipe');i++){
|
|
83
|
+
for(let i=0;i<50&&!(output.includes('DB:a b')&&output.includes('P:pipe')&&output.includes('U:สวัสดี'));i++){
|
|
83
84
|
const page=await adapter.handlers.read_process_output({pid,offset:0,length:100,timeout_ms:100});
|
|
84
85
|
output+=page.content[0].text;
|
|
85
86
|
}
|
|
@@ -94,7 +95,10 @@ test('real Commander adapter preserves PowerShell command bytes through the boun
|
|
|
94
95
|
assert.match(largeText,/END/);
|
|
95
96
|
const ended=await adapter.handlers.force_terminate({pid});
|
|
96
97
|
assert.equal(ended.structuredContent.termination.verified,true);
|
|
97
|
-
}finally{
|
|
98
|
+
}finally{
|
|
99
|
+
if(adapter&&pid!==null) await adapter.handlers.force_terminate({pid}).catch(()=>{});
|
|
100
|
+
await rm(f.root,{recursive:true,force:true});
|
|
101
|
+
}
|
|
98
102
|
});
|
|
99
103
|
|
|
100
104
|
test('real stale mutation is rejected before commit and leaves source intact',async()=>{
|
package/test/contained.test.mjs
CHANGED
|
@@ -456,13 +456,11 @@ test('receipt and verifier bind contained worker, launcher and independent trans
|
|
|
456
456
|
assert.match(verifier, /contained transform semantic mismatch/);
|
|
457
457
|
});
|
|
458
458
|
|
|
459
|
-
test('MCP protocol smoke
|
|
459
|
+
test('MCP protocol smoke derives the exact full catalog and explicitly disables Host', async () => {
|
|
460
460
|
const smoke = await readFile(fileURLToPath(new URL('../src/mcp-smoke-client.mjs', import.meta.url)), 'utf8');
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
assert.match(smoke, /
|
|
464
|
-
assert.match(smoke, /baseTools/);
|
|
465
|
-
assert.match(smoke, /privilegedTools/);
|
|
461
|
+
assert.match(smoke, /expectedFullCatalogForEnv/);
|
|
462
|
+
assert.match(smoke, /assertExactCatalog/);
|
|
463
|
+
assert.match(smoke, /DEADBYTE_PRIVILEGED_POLICY: ''/);
|
|
466
464
|
assert.doesNotMatch(smoke, /agent_task_/);
|
|
467
465
|
});
|
|
468
466
|
|
package/test/controller.test.mjs
CHANGED
|
@@ -83,7 +83,8 @@ test('controller keeps privileged policy optional and propagates it only to the
|
|
|
83
83
|
test('Windows host policy bootstrap pins executables and creates only explicit local roots', async () => {
|
|
84
84
|
const init = await readFile(fileURLToPath(new URL('../scripts/init-host-policy.ps1', import.meta.url)), 'utf8');
|
|
85
85
|
assert.match(init, /deadbyte\.host-policy\.v2/);
|
|
86
|
-
assert.match(init, /
|
|
86
|
+
assert.match(init, /System\.Security\.Cryptography\.SHA256/);
|
|
87
|
+
assert.match(init, /System\.IO\.File.*OpenRead/);
|
|
87
88
|
assert.match(init, /powershell_script/);
|
|
88
89
|
assert.match(init, /cmd_script/);
|
|
89
90
|
assert.match(init, /node_script/);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import {
|
|
4
|
-
desktopActionInputSchema,desktopControlInputSchema,desktopTargetInputSchema,registerDesktopTools
|
|
4
|
+
desktopActionInputSchema,desktopControlInputSchema,desktopTargetInputSchema,desktopWaitInputSchema,registerDesktopTools
|
|
5
5
|
} from '../src/desktop-mcp-tools-r40.mjs';
|
|
6
6
|
|
|
7
7
|
const H=c=>c.repeat(64);
|
|
@@ -30,11 +30,26 @@ test('R40 T5 target schema supports local UIA query without arbitrary dispatch',
|
|
|
30
30
|
assert.equal(desktopControlInputSchema.safeParse({op:'target',frame_id:frame,uia_query:{kind:'control_type',value:'button',max_results:4,ordinal:0}}).success,true);
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
test('
|
|
33
|
+
test('successor desktop wait schema is bounded, semantic, and rejects action-shaped fields',()=>{
|
|
34
|
+
assert.equal(desktopWaitInputSchema.safeParse({
|
|
35
|
+
baseline_frame_id:frame,until:'frame_changed',timeout_ms:5000,poll_ms:100,preview:false
|
|
36
|
+
}).success,true);
|
|
37
|
+
assert.equal(desktopWaitInputSchema.safeParse({
|
|
38
|
+
baseline_frame_id:frame,until:'uia_present',uia_query:{kind:'name_exact',value:'Ready',max_results:8,ordinal:0},timeout_ms:5000,poll_ms:100,preview:false
|
|
39
|
+
}).success,true);
|
|
40
|
+
assert.equal(desktopWaitInputSchema.safeParse({
|
|
41
|
+
baseline_frame_id:frame,until:'uia_present',timeout_ms:5000,poll_ms:100,preview:false
|
|
42
|
+
}).success,false);
|
|
43
|
+
assert.equal(desktopControlInputSchema.safeParse({
|
|
44
|
+
op:'wait',baseline_frame_id:frame,until:'frame_changed',timeout_ms:5000,poll_ms:100,preview:false,point:{x:1,y:2}
|
|
45
|
+
}).success,false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('successor full desktop surface registers bounded semantic wait without mutation authority',()=>{
|
|
34
49
|
const server=fakeServer();
|
|
35
|
-
const runtime={status(){},observe(){},target(){},action(){}};
|
|
50
|
+
const runtime={status(){},observe(){},wait(){},target(){},action(){}};
|
|
36
51
|
registerDesktopTools(server,{runtime});
|
|
37
|
-
assert.deepEqual([...server.tools.keys()].sort(),['desktop_action','desktop_observe','desktop_status','desktop_target']);
|
|
52
|
+
assert.deepEqual([...server.tools.keys()].sort(),['desktop_action','desktop_observe','desktop_status','desktop_target','desktop_wait']);
|
|
38
53
|
});
|
|
39
54
|
|
|
40
55
|
test('R40 public desktop action output removes raw text while preserving content and action hashes',async()=>{
|