deadbyte-mcp 0.11.3 → 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 +92 -38
- package/README.txt +5 -4
- 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-r40.mjs +126 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-verify-r40.mjs +212 -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-r40.mjs +212 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -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 +72 -44
- 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 +208 -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/r33-closeout-regression.test.mjs +7 -7
- package/test/r33-finalization.test.mjs +14 -14
- package/test/r37-remote-tool-call-timeline.test.mjs +6 -5
- package/test/r40-finalization.test.mjs +118 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +19 -61
- 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 +69 -46
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
3
3
|
|
|
4
4
|
const endpointText = process.argv[2];
|
|
@@ -51,6 +51,9 @@ const r36CompactTools = [
|
|
|
51
51
|
'process_session'
|
|
52
52
|
].sort();
|
|
53
53
|
if (r36CompactTools.length !== 41) throw new Error(`R36 compact catalog constant drift: ${r36CompactTools.length}`);
|
|
54
|
+
const r40CompactTools = [...r36CompactTools, 'desktop_control'].sort();
|
|
55
|
+
if (r40CompactTools.length !== 42) throw new Error(`R40 compact catalog constant drift: ${r40CompactTools.length}`);
|
|
56
|
+
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
54
57
|
const allowedCatalogs = [];
|
|
55
58
|
for (const machine of [false, true]) for (const host of [false, true]) for (const agent of [false, true]) {
|
|
56
59
|
for (const processPlane of [false, true]) for (const machineFs of [false, true]) for (const observation of [false, true]) for (const codingMode of ['none', 'coding', 'autonomous']) {
|
|
@@ -93,7 +96,9 @@ try {
|
|
|
93
96
|
if (client.getProtocolEra() !== 'modern') throw new Error(`expected modern MCP era, got ${String(client.getProtocolEra())}`);
|
|
94
97
|
const listed = await client.listTools();
|
|
95
98
|
const names = listed.tools.map(t => t.name).sort();
|
|
96
|
-
const
|
|
99
|
+
const exactR36Compact = JSON.stringify(names) === JSON.stringify(r36CompactTools);
|
|
100
|
+
const exactR40Compact = JSON.stringify(names) === JSON.stringify(r40CompactTools);
|
|
101
|
+
const compactProfile = exactR36Compact || exactR40Compact;
|
|
97
102
|
const catalogOk = compactProfile || allowedCatalogs.some(expected => JSON.stringify(names) === JSON.stringify(expected));
|
|
98
103
|
if (!catalogOk) throw new Error(`tool catalog mismatch: ${JSON.stringify(names)}`);
|
|
99
104
|
for (const tool of listed.tools) {
|
|
@@ -117,14 +122,23 @@ try {
|
|
|
117
122
|
let agent = null;
|
|
118
123
|
if (compactProfile) {
|
|
119
124
|
const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
|
|
120
|
-
|
|
125
|
+
const manifestV1 = manifest.schema === 'deadbyte.capability-manifest.v1' && exactR36Compact;
|
|
126
|
+
const manifestV2 = manifest.schema === 'deadbyte.capability-manifest.v2' && exactR40Compact;
|
|
127
|
+
if ((!manifestV1 && !manifestV2) || manifest.profile !== 'compact' ||
|
|
121
128
|
!Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
|
|
122
|
-
throw new Error('
|
|
129
|
+
throw new Error('compact capability manifest malformed');
|
|
130
|
+
}
|
|
131
|
+
if (manifestV2) {
|
|
132
|
+
const expectedCatalogSha = sha256(Buffer.from(JSON.stringify(names), 'utf8'));
|
|
133
|
+
if (manifest.tool_count !== names.length || JSON.stringify(manifest.tool_catalog) !== JSON.stringify(names) ||
|
|
134
|
+
manifest.catalog_sha256 !== expectedCatalogSha) {
|
|
135
|
+
throw new Error('R40 compact capability manifest catalog attestation mismatch');
|
|
136
|
+
}
|
|
123
137
|
}
|
|
124
138
|
const required = manifest.capabilities.filter(item => item?.production === true && item?.host_only !== true);
|
|
125
139
|
for (const item of required) {
|
|
126
140
|
if (typeof item.compact_tool !== 'string' || !names.includes(item.compact_tool)) {
|
|
127
|
-
throw new Error(`
|
|
141
|
+
throw new Error(`compact capability route missing: ${String(item.id)}`);
|
|
128
142
|
}
|
|
129
143
|
}
|
|
130
144
|
const machine = gateway(await client.callTool({ name: 'machine_observe', arguments: { op: 'machine_status' } }), 'machine_status');
|
|
@@ -132,7 +146,7 @@ try {
|
|
|
132
146
|
const ping = gateway(await client.callTool({ name: 'machine_observe', arguments: { op: 'machine_ping', nonce } }), 'machine_ping');
|
|
133
147
|
if (ping.nonce !== nonce || ping.machine_id !== machine.machine_id ||
|
|
134
148
|
ping.release_id !== machine.release_id || ping.release_manifest_sha256 !== machine.release_manifest_sha256) {
|
|
135
|
-
throw new Error('
|
|
149
|
+
throw new Error('compact machine ping binding mismatch');
|
|
136
150
|
}
|
|
137
151
|
compact = {
|
|
138
152
|
profile: 'compact',
|
|
@@ -6,6 +6,7 @@ export const REMOTE_RESULT_JOURNAL_SCHEMA='deadbyte.remote-result-journal.v1';
|
|
|
6
6
|
const SESSION=/^[0-9a-f]{24}$/;
|
|
7
7
|
const HASH=/^[0-9a-f]{64}$/;
|
|
8
8
|
const STATUS=new Set(['ok','error']);
|
|
9
|
+
const SIDE_EFFECT_STATES=new Set(['unknown']);
|
|
9
10
|
const EVENT_TYPES=new Set(['tool.call','tool.result']);
|
|
10
11
|
|
|
11
12
|
function text(value,label,max){
|
|
@@ -40,10 +41,17 @@ function validateEvent(raw,index=null){
|
|
|
40
41
|
observed_at_utc:observed,payload_sha256:raw.payload_sha256,arguments:sanitizeAuditArgs(rawArgs)});
|
|
41
42
|
}
|
|
42
43
|
if(!STATUS.has(raw.status)) throw new Error('remote result status invalid');
|
|
44
|
+
const durationMs=raw.duration_ms===undefined?null:raw.duration_ms;
|
|
45
|
+
if(durationMs!==null&&(!Number.isFinite(durationMs)||durationMs<0||durationMs>86_400_000)) throw new Error('remote result duration_ms invalid');
|
|
46
|
+
const reason=raw.reason===undefined||raw.reason===null?null:text(raw.reason,'remote result reason',512);
|
|
47
|
+
const sideEffectState=raw.side_effect_state===undefined||raw.side_effect_state===null?null:raw.side_effect_state;
|
|
48
|
+
if(sideEffectState!==null&&!SIDE_EFFECT_STATES.has(sideEffectState)) throw new Error('remote result side_effect_state invalid');
|
|
43
49
|
return Object.freeze({session_id:sessionId,seq,event_type:eventType,request_id:requestId,tool,status:raw.status,
|
|
44
|
-
observed_at_utc:observed,payload_sha256:raw.payload_sha256
|
|
50
|
+
observed_at_utc:observed,payload_sha256:raw.payload_sha256,
|
|
51
|
+
...(durationMs===null?{}:{duration_ms:durationMs}),...(reason===null?{}:{reason}),
|
|
52
|
+
...(sideEffectState===null?{}:{side_effect_state:sideEffectState})});
|
|
45
53
|
}
|
|
46
|
-
function identity(item){return `${item.event_type}\0${item.request_id}\0${item.status??''}\0${item.payload_sha256}`;}
|
|
54
|
+
function identity(item){return `${item.event_type}\0${item.request_id}\0${item.status??''}\0${item.side_effect_state??''}\0${item.payload_sha256}`;}
|
|
47
55
|
export function parseRemoteResultCursor(cursor){
|
|
48
56
|
if(typeof cursor!=='string') throw new Error('remote result cursor invalid');
|
|
49
57
|
const match=/^r1:([0-9a-f]{24}):(0|[1-9][0-9]{0,15})$/.exec(cursor);
|
package/src/runtime-update.mjs
CHANGED
|
@@ -45,14 +45,17 @@ function normalizePersistentPublicOrigin(value){
|
|
|
45
45
|
return publicOrigin.origin;
|
|
46
46
|
}
|
|
47
47
|
export function parseUpdateArgs(argv){
|
|
48
|
-
const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
-
const cloud={},bridge={},processPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
|
|
48
|
+
const out={stage:null,developmentRoot:null,expectedVersion:null,expectedManifest:null,sourceRoot:null,restoreStandingGrants:false,recoverOfflinePredecessor:false,enableMachineFs:false,enableObservation:false,enableDesktop:false,enableAutonomousSupervisor:false,cloudTransport:null,bridgeBootstrap:null,processPolicyBootstrap:null,desktopPolicyBootstrap:null,semanticLspBootstrap:null,hostCommandPins:[]};
|
|
49
|
+
const cloud={},bridge={},processPolicy={},desktopPolicy={},semanticLsp={},seen=new Set(),hostPinIds=new Set();
|
|
50
50
|
const bridgeFlags=new Map([
|
|
51
51
|
['--bridge-root','root'],['--bridge-script-sha256','scriptSha256']
|
|
52
52
|
]);
|
|
53
53
|
const processPolicyFlags=new Map([
|
|
54
54
|
['--process-policy','path'],['--process-policy-sha256','sha256']
|
|
55
55
|
]);
|
|
56
|
+
const desktopPolicyFlags=new Map([
|
|
57
|
+
['--desktop-policy','path'],['--desktop-policy-sha256','sha256']
|
|
58
|
+
]);
|
|
56
59
|
const semanticLspFlags=new Map([
|
|
57
60
|
['--semantic-lsp-config','path'],['--semantic-lsp-config-sha256','sha256']
|
|
58
61
|
]);
|
|
@@ -61,16 +64,17 @@ export function parseUpdateArgs(argv){
|
|
|
61
64
|
['--cloud-token-file','tokenFilePath'],['--cloud-token-sha256','tokenFileSha256'],
|
|
62
65
|
['--cloud-child-working-directory','childWorkingDirectory'],['--cloud-public-origin','publicOrigin']
|
|
63
66
|
]);
|
|
64
|
-
const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
|
|
67
|
+
const valued=new Set(['--stage','--development-root','--expected-version','--expected-manifest','--source-root','--host-command-pin',...bridgeFlags.keys(),...processPolicyFlags.keys(),...desktopPolicyFlags.keys(),...semanticLspFlags.keys(),...cloudFlags.keys()]);
|
|
65
68
|
for(let index=0;index<argv.length;index++){
|
|
66
69
|
const arg=argv[index];
|
|
67
|
-
if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-autonomous-supervisor'){
|
|
70
|
+
if(arg==='--restore-standing-grants'||arg==='--recover-offline-predecessor'||arg==='--enable-machine-fs'||arg==='--enable-observation'||arg==='--enable-desktop'||arg==='--enable-autonomous-supervisor'){
|
|
68
71
|
if(seen.has(arg)) throw new Error(`duplicate self-update argument '${arg}'`);
|
|
69
72
|
seen.add(arg);
|
|
70
73
|
if(arg==='--restore-standing-grants') out.restoreStandingGrants=true;
|
|
71
74
|
else if(arg==='--recover-offline-predecessor') out.recoverOfflinePredecessor=true;
|
|
72
75
|
else if(arg==='--enable-machine-fs') out.enableMachineFs=true;
|
|
73
76
|
else if(arg==='--enable-observation') out.enableObservation=true;
|
|
77
|
+
else if(arg==='--enable-desktop') out.enableDesktop=true;
|
|
74
78
|
else out.enableAutonomousSupervisor=true;
|
|
75
79
|
continue;
|
|
76
80
|
}
|
|
@@ -95,6 +99,7 @@ export function parseUpdateArgs(argv){
|
|
|
95
99
|
else if(arg==='--source-root') out.sourceRoot=path.resolve(value);
|
|
96
100
|
else if(bridgeFlags.has(arg)) bridge[bridgeFlags.get(arg)]=value;
|
|
97
101
|
else if(processPolicyFlags.has(arg)) processPolicy[processPolicyFlags.get(arg)]=value;
|
|
102
|
+
else if(desktopPolicyFlags.has(arg)) desktopPolicy[desktopPolicyFlags.get(arg)]=value;
|
|
98
103
|
else if(semanticLspFlags.has(arg)) semanticLsp[semanticLspFlags.get(arg)]=value;
|
|
99
104
|
else cloud[cloudFlags.get(arg)]=value;
|
|
100
105
|
}
|
|
@@ -116,6 +121,13 @@ export function parseUpdateArgs(argv){
|
|
|
116
121
|
if(!HEX64.test(sha256)) throw new Error('process policy SHA-256 must be lowercase hex');
|
|
117
122
|
out.processPolicyBootstrap={path:path.resolve(processPolicy.path),sha256};
|
|
118
123
|
}
|
|
124
|
+
const desktopPolicyFields=[...desktopPolicyFlags.values()],desktopPolicyPresent=desktopPolicyFields.filter(key=>desktopPolicy[key]!==undefined);
|
|
125
|
+
if(desktopPolicyPresent.length!==0&&desktopPolicyPresent.length!==desktopPolicyFields.length) throw new Error('desktop policy bootstrap requires path plus SHA-256 as one pair');
|
|
126
|
+
if(desktopPolicyPresent.length===desktopPolicyFields.length){
|
|
127
|
+
const sha256=String(desktopPolicy.sha256).toLowerCase();
|
|
128
|
+
if(!HEX64.test(sha256)) throw new Error('desktop policy SHA-256 must be lowercase hex');
|
|
129
|
+
out.desktopPolicyBootstrap={path:path.resolve(desktopPolicy.path),sha256};
|
|
130
|
+
}
|
|
119
131
|
const semanticLspFields=[...semanticLspFlags.values()],semanticLspPresent=semanticLspFields.filter(key=>semanticLsp[key]!==undefined);
|
|
120
132
|
if(semanticLspPresent.length!==0&&semanticLspPresent.length!==semanticLspFields.length) throw new Error('semantic LSP config bootstrap requires path plus SHA-256 as one pair');
|
|
121
133
|
if(semanticLspPresent.length===semanticLspFields.length){
|
|
@@ -321,6 +333,21 @@ export async function prepareProcessPolicyBootstrap(bootstrap){
|
|
|
321
333
|
return {...pinned,policy};
|
|
322
334
|
}
|
|
323
335
|
|
|
336
|
+
export async function prepareDesktopPolicyBootstrap(bootstrap){
|
|
337
|
+
if(!bootstrap) return null;
|
|
338
|
+
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'desktop policy');
|
|
339
|
+
const policy=await readJson(pinned.path);
|
|
340
|
+
if(policy?.schema!=='deadbyte.desktop-policy.v1'||policy?.enabled!==true) throw new Error('desktop policy schema/enabled mismatch');
|
|
341
|
+
for(const name of ['observer','input','grounder']){
|
|
342
|
+
const provider=policy[name];
|
|
343
|
+
if(!provider||typeof provider.executable!=='string'||!path.isAbsolute(provider.executable)||!HEX64.test(String(provider.sha256??''))) {
|
|
344
|
+
throw new Error(`desktop policy ${name} pin invalid`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if(!Array.isArray(policy.allowed_actions)||policy.allowed_actions.length<1) throw new Error('desktop policy allowed_actions invalid');
|
|
348
|
+
return {...pinned,policy};
|
|
349
|
+
}
|
|
350
|
+
|
|
324
351
|
export async function prepareSemanticLspConfigBootstrap(bootstrap){
|
|
325
352
|
if(!bootstrap) return null;
|
|
326
353
|
const pinned=await verifyPinnedRegularFile(bootstrap.path,bootstrap.sha256,'semantic LSP config');
|
|
@@ -460,6 +487,7 @@ export function parseControllerStatus(text){
|
|
|
460
487
|
local_ready:/Local ready\s*:\s*True/.test(text),
|
|
461
488
|
coding_armed:/Coding plane\s*:\s*ARMED \[standing-grant\]/.test(text),
|
|
462
489
|
autonomous_armed:/Autonomous\s*:\s*ARMED \[standing-grant\]/.test(text),
|
|
490
|
+
desktop_armed:/Desktop plane\s*:\s*ARMED \[standing-grant\]/.test(text),
|
|
463
491
|
host_armed:/Host plane\s*:\s*ARMED \[standing-grant\]/.test(text)
|
|
464
492
|
};
|
|
465
493
|
}
|
|
@@ -664,13 +692,14 @@ async function loadControlInputs({stateRoot,sourceRoot,pointerInfo}){
|
|
|
664
692
|
ctx=legacyContext(sourceRoot); configPath=path.join(ctx.source_root,'controller','deadbyte-controller.config.psd1');
|
|
665
693
|
}
|
|
666
694
|
let config=await loadPowerShellDataFile(configPath); config=await normalizeComponentIdentities(config);
|
|
667
|
-
const codingPath=String(config.CodingPolicyFile??''); const autonomousPath=String(config.AutonomousPolicyFile??''); const hostPath=String(config.HostPolicyFile??''); const processPath=String(config.ProcessPolicyFile??'');
|
|
695
|
+
const codingPath=String(config.CodingPolicyFile??''); const autonomousPath=String(config.AutonomousPolicyFile??''); const hostPath=String(config.HostPolicyFile??''); const processPath=String(config.ProcessPolicyFile??''); const desktopPath=String(config.DesktopPolicyFile??'');
|
|
668
696
|
if(!path.isAbsolute(codingPath)||!path.isAbsolute(autonomousPath)) throw new Error('coding/autonomous policy paths must be absolute');
|
|
669
697
|
if(processPath&&!path.isAbsolute(processPath)) throw new Error('process policy path must be absolute');
|
|
670
|
-
|
|
698
|
+
if(desktopPath&&!path.isAbsolute(desktopPath)) throw new Error('desktop policy path must be absolute');
|
|
699
|
+
const codingPolicy=await readJson(codingPath); const autonomousPolicy=await readJson(autonomousPath); const hostPolicy=hostPath?await readJson(hostPath):null; const processPolicy=processPath?await readJson(processPath):null; const desktopPolicy=desktopPath?await readJson(desktopPath):null;
|
|
671
700
|
const legacyCore=typeof config.CoreRoot==='string'&&config.CoreRoot?path.resolve(config.CoreRoot):ctx.core_root;
|
|
672
701
|
const workspaceRoot=typeof config.WorkspaceRoot==='string'&&config.WorkspaceRoot?path.resolve(config.WorkspaceRoot):path.join(legacyCore,'workspaces');
|
|
673
|
-
return {ctx,configPath,config,codingPolicy,autonomousPolicy,hostPolicy,processPolicy,workspaceRoot};
|
|
702
|
+
return {ctx,configPath,config,codingPolicy,autonomousPolicy,hostPolicy,processPolicy,desktopPolicy,workspaceRoot};
|
|
674
703
|
}
|
|
675
704
|
export function assertDevelopmentRootSafe(developmentRoot,stateRoot){
|
|
676
705
|
const dev=path.resolve(developmentRoot),state=path.resolve(stateRoot);
|
|
@@ -724,17 +753,18 @@ async function rollbackLaunchers(plan){
|
|
|
724
753
|
}
|
|
725
754
|
async function revokeAuthority(ctx,prior){
|
|
726
755
|
let revoked=false;
|
|
756
|
+
if(prior.desktop_armed){ controllerCaptured(ctx,'disarmdesktop',15000); revoked=true; }
|
|
727
757
|
if(prior.autonomous_armed){ controllerCaptured(ctx,'disarmautonomous',15000); revoked=true; }
|
|
728
758
|
if(prior.coding_armed){ controllerCaptured(ctx,'disarmcoding',15000); revoked=true; }
|
|
729
759
|
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
730
|
-
if(status.coding_armed||status.autonomous_armed) throw new Error('standing authority revoke gate failed');
|
|
760
|
+
if(status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority revoke gate failed');
|
|
731
761
|
if(status.host_armed) throw new Error('host plane became armed during revoke');
|
|
732
762
|
return {revoked,status};
|
|
733
763
|
}
|
|
734
764
|
async function restoreStandingAuthority(ctx,prior,enabled){
|
|
735
765
|
if(!enabled){
|
|
736
766
|
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
737
|
-
if(status.coding_armed||status.autonomous_armed) throw new Error('standing authority unexpectedly survived slot switch');
|
|
767
|
+
if(status.coding_armed||status.autonomous_armed||status.desktop_armed) throw new Error('standing authority unexpectedly survived slot switch');
|
|
738
768
|
if(status.host_armed) throw new Error('host plane became armed during slot switch');
|
|
739
769
|
return status;
|
|
740
770
|
}
|
|
@@ -744,6 +774,7 @@ async function restoreStandingAuthority(ctx,prior,enabled){
|
|
|
744
774
|
const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
|
|
745
775
|
if(status.coding_armed!==prior.coding_armed) throw new Error('coding standing-grant restore gate failed');
|
|
746
776
|
if(status.autonomous_armed!==prior.autonomous_armed) throw new Error('autonomous standing-grant restore gate failed');
|
|
777
|
+
if(status.desktop_armed) throw new Error('desktop plane must remain disarmed after slot switch');
|
|
747
778
|
if(status.host_armed) throw new Error('host plane became armed during slot switch');
|
|
748
779
|
return status;
|
|
749
780
|
}
|
|
@@ -795,7 +826,7 @@ function publicPointer(pointer){
|
|
|
795
826
|
release_root:pointer.release_root,generation_root:pointer.generation_root,generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
796
827
|
launcher_sha256:pointer.launcher_sha256,config_path:pointer.config_path};
|
|
797
828
|
}
|
|
798
|
-
export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
|
|
829
|
+
export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
|
|
799
830
|
if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
|
|
800
831
|
const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
|
|
801
832
|
const key=await loadUpdateKey(state); const updateId=`r27-${expectedManifest.slice(0,16)}-${Date.now().toString(36)}-${randomBytes(4).toString('hex')}`;
|
|
@@ -820,6 +851,8 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
820
851
|
control.processPolicy=processBootstrap.policy;
|
|
821
852
|
control.config.ProcessRuntimeEnabled=true;
|
|
822
853
|
}
|
|
854
|
+
const desktopBootstrap=await prepareDesktopPolicyBootstrap(desktopPolicyBootstrap);
|
|
855
|
+
if(desktopBootstrap) control.desktopPolicy=desktopBootstrap.policy;
|
|
823
856
|
const semanticBootstrap=await prepareSemanticLspConfigBootstrap(semanticLspBootstrap);
|
|
824
857
|
if(semanticBootstrap){
|
|
825
858
|
control.config.SemanticLspConfigFile=semanticBootstrap.path;
|
|
@@ -834,6 +867,10 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
834
867
|
if(control.config.CodingRuntimeEnabled!==true) throw new Error('ObservationRuntimeEnabled requires CodingRuntimeEnabled');
|
|
835
868
|
control.config.ObservationRuntimeEnabled=true;
|
|
836
869
|
}
|
|
870
|
+
if(enableDesktop){
|
|
871
|
+
if(!control.desktopPolicy) throw new Error('DesktopRuntimeEnabled requires pinned desktop policy bootstrap or existing policy');
|
|
872
|
+
control.config.DesktopRuntimeEnabled=true;
|
|
873
|
+
}
|
|
837
874
|
control.config=await prepareBridgeSpec(control.config,bridgeBootstrap);
|
|
838
875
|
control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
|
|
839
876
|
control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
|
|
@@ -860,7 +897,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
860
897
|
launchers=await launcherPlan(state);
|
|
861
898
|
const generation=await createGenerationBundle({stateRoot:state,releaseRoot:slot.release_root,developmentRoot:development,
|
|
862
899
|
workspaceRoot:workspace.target,releaseId:slot.release_id,currentConfig:control.config,codingPolicy:control.codingPolicy,
|
|
863
|
-
autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy});
|
|
900
|
+
autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy,desktopPolicy:control.desktopPolicy});
|
|
864
901
|
newPointer={schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:expectedVersion,manifest_sha256:expectedManifest,
|
|
865
902
|
release_root:slot.release_root,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,
|
|
866
903
|
launcher_sha256:launchers.launcher_sha256,config_path:generation.config_path};
|
|
@@ -875,7 +912,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
875
912
|
coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},new_pointer:publicPointer(newPointer)});
|
|
876
913
|
predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor); oldRuntime=predecessor.runtime; priorStatus=predecessor.status;
|
|
877
914
|
await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
|
|
878
|
-
prior_authority:{coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,host_armed:priorStatus.host_armed}});
|
|
915
|
+
prior_authority:{coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
|
|
879
916
|
const revoke=await revokeAuthority(control.ctx,priorStatus); revoked=revoke.revoked;
|
|
880
917
|
await writeState(statePath,journal,'authority_revoked',{revoked_before_switch:revoked});
|
|
881
918
|
if(oldRuntime){
|
|
@@ -897,7 +934,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
897
934
|
await writeState(statePath,journal,'fresh_runtime_verified',{new_runtime:generationTuple(newRuntime),stabilization});
|
|
898
935
|
const finalAuthority=await restoreStandingAuthority(newCtx,priorStatus,restoreStandingGrants);
|
|
899
936
|
await writeState(statePath,journal,'authority_verified',{final_authority:{coding_armed:finalAuthority.coding_armed,
|
|
900
|
-
autonomous_armed:finalAuthority.autonomous_armed,host_armed:finalAuthority.host_armed}});
|
|
937
|
+
autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}});
|
|
901
938
|
const body={schema:UPDATE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,
|
|
902
939
|
completed_at_utc:new Date().toISOString(),activation_boundary:'active-release-pointer',mechanism:'immutable-release-slot-plus-atomic-pointer-rename',
|
|
903
940
|
predecessor_mode:predecessor.mode,prior_pointer:publicPointer(pointerInfo.pointer),active_pointer:publicPointer(newPointer),release_slot:{release_id:slot.release_id,release_root:slot.release_root,
|
|
@@ -908,8 +945,9 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
908
945
|
host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},
|
|
909
946
|
coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},old_runtime:generationTuple(oldRuntime),new_runtime:generationTuple(newRuntime),stabilization,
|
|
910
947
|
authority:{prior:{coding_armed:priorStatus.coding_armed,
|
|
911
|
-
autonomous_armed:priorStatus.autonomous_armed,host_armed:priorStatus.host_armed},revoked_before_switch:revoked,restore_requested:restoreStandingGrants,
|
|
912
|
-
|
|
948
|
+
autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed},revoked_before_switch:revoked,restore_requested:restoreStandingGrants,
|
|
949
|
+
desktop_restore_policy:'explicit-rearm-required',
|
|
950
|
+
final:{coding_armed:finalAuthority.coding_armed,autonomous_armed:finalAuthority.autonomous_armed,desktop_armed:finalAuthority.desktop_armed,host_armed:finalAuthority.host_armed}},rollback_performed:false};
|
|
913
951
|
const receipt=await writeEnvelope(receiptPath,body,key);
|
|
914
952
|
await writeState(statePath,journal,'completed',{receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256});
|
|
915
953
|
return {ok:true,update_id:updateId,state_path:statePath,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,
|
|
@@ -1042,8 +1080,8 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
|
|
|
1042
1080
|
const args=parseUpdateArgs(argv);
|
|
1043
1081
|
const result=await performSlotUpdate({stage:args.stage,developmentRoot:args.developmentRoot,expectedVersion:args.expectedVersion,
|
|
1044
1082
|
expectedManifest:args.expectedManifest,sourceRoot:args.sourceRoot,stateRoot,restoreStandingGrants:args.restoreStandingGrants,recoverOfflinePredecessor:args.recoverOfflinePredecessor,
|
|
1045
|
-
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
|
|
1046
|
-
enableObservation:args.enableObservation,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
|
|
1083
|
+
cloudTransport:args.cloudTransport,bridgeBootstrap:args.bridgeBootstrap,processPolicyBootstrap:args.processPolicyBootstrap,desktopPolicyBootstrap:args.desktopPolicyBootstrap,semanticLspBootstrap:args.semanticLspBootstrap,enableMachineFs:args.enableMachineFs,
|
|
1084
|
+
enableObservation:args.enableObservation,enableDesktop:args.enableDesktop,enableAutonomousSupervisor:args.enableAutonomousSupervisor,hostCommandPins:args.hostCommandPins});
|
|
1047
1085
|
console.log(`R27_SLOT_UPDATE_PASS version=${args.expectedVersion}`);
|
|
1048
1086
|
console.log(`UPDATE_ID ${result.update_id}`); console.log(`RECEIPT_SHA256 ${result.receipt_sha256}`);
|
|
1049
1087
|
console.log(`STATE_PATH ${result.state_path}`); console.log(`ACTIVE_RELEASE ${result.pointer.release_id}`);
|
package/src/tool-audit.mjs
CHANGED
|
@@ -82,15 +82,42 @@ function outcomeReceiptHashes(result){
|
|
|
82
82
|
visit(result?.structuredContent??result);
|
|
83
83
|
return [...found].sort();
|
|
84
84
|
}
|
|
85
|
+
function safeOutcomeReason(value){
|
|
86
|
+
if(value===null||value===undefined) return null;
|
|
87
|
+
let text=typeof value==='string'?value:(value instanceof Error?String(value.message??value):null);
|
|
88
|
+
if(text===null) return null;
|
|
89
|
+
text=text.replace(/[\\r\\n\\t]+/g,' ').replace(/\\s{2,}/g,' ').trim();
|
|
90
|
+
if(!text) return null;
|
|
91
|
+
const originalBytes=Buffer.from(text,'utf8');
|
|
92
|
+
if(secretValuePattern.test(text)) return `<redacted reason bytes=${originalBytes.length} sha256=${sha256(originalBytes)}>`;
|
|
93
|
+
const max=240;
|
|
94
|
+
if(text.length>max) text=`${text.slice(0,max-1)}…`;
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
function resultOutcomeReason(result){
|
|
98
|
+
const structured=result?.structuredContent;
|
|
99
|
+
const candidates=[
|
|
100
|
+
structured?.error,structured?.message,structured?.reason,structured?.result?.error,structured?.result?.message,
|
|
101
|
+
...(Array.isArray(result?.content)?result.content.filter(item=>item?.type==='text').map(item=>item.text):[])
|
|
102
|
+
];
|
|
103
|
+
for(const candidate of candidates){
|
|
104
|
+
const reason=safeOutcomeReason(candidate);
|
|
105
|
+
if(reason) return reason;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
85
109
|
function summarizeOutcome(result,error,durationMs){
|
|
86
110
|
const duration=Math.max(0,Math.round(Number(durationMs)||0));
|
|
87
111
|
if(error){
|
|
88
112
|
const message=String(error?.message??error);
|
|
89
|
-
return {status:'threw',is_error:true,duration_ms:duration,receipt_sha256s:[],
|
|
113
|
+
return {status:'threw',is_error:true,duration_ms:duration,receipt_sha256s:[],reason:safeOutcomeReason(message),
|
|
114
|
+
error:{name:String(error?.name||'Error').slice(0,128),byte_count:Buffer.byteLength(message,'utf8'),sha256:sha256(Buffer.from(message,'utf8'))}};
|
|
90
115
|
}
|
|
91
116
|
const structured=result?.structuredContent;
|
|
92
117
|
const status=typeof structured?.status==='string'?structured.status:(result?.isError===true?'error':'ok');
|
|
93
|
-
|
|
118
|
+
const isError=result?.isError===true;
|
|
119
|
+
return {status:String(status).slice(0,128),is_error:isError,duration_ms:duration,receipt_sha256s:outcomeReceiptHashes(result),
|
|
120
|
+
...(isError?{reason:resultOutcomeReason(result)}:{})};
|
|
94
121
|
}
|
|
95
122
|
|
|
96
123
|
async function appendSynced(file,line){
|
package/src/version.mjs
CHANGED
|
@@ -32,7 +32,11 @@ const typedCases=[
|
|
|
32
32
|
['machine_fs_delete',{kind:'machine_fs_delete',path:'empty',expected_source_identity:H}],
|
|
33
33
|
['observation_search_start',{kind:'observation_search_start',path:'.',pattern:'FAIL',case_sensitive:false,max_results:100,max_files:1000,max_bytes:1048576,max_file_bytes:262144}],
|
|
34
34
|
['observation_search_page',{kind:'observation_search_page',search_id:H,cursor:null,limit:50}],
|
|
35
|
-
['observation_system_status',{kind:'observation_system_status'}]
|
|
35
|
+
['observation_system_status',{kind:'observation_system_status'}],
|
|
36
|
+
['desktop_status',{kind:'desktop_status'}],
|
|
37
|
+
['desktop_observe',{kind:'desktop_observe'}],
|
|
38
|
+
['desktop_target',{kind:'desktop_target',frame_id:H,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}}],
|
|
39
|
+
['desktop_action',{kind:'desktop_action',action_kind:'click_primary',expected_frame_id:H,target_id:null,point:{x:10,y:20},scroll_delta:null,key_chord:null,text:null}]
|
|
36
40
|
];
|
|
37
41
|
|
|
38
42
|
test('R36 planner parses first-class capability actions without delegate wrapper',()=>{
|
|
@@ -44,6 +48,20 @@ test('R36 planner parses first-class capability actions without delegate wrapper
|
|
|
44
48
|
}
|
|
45
49
|
});
|
|
46
50
|
|
|
51
|
+
test('R40 T7 planner rejects ambiguous Desktop actions before runtime',()=>{
|
|
52
|
+
assert.throws(()=>parseR34PlannerDecision(wrap({
|
|
53
|
+
kind:'desktop_action',action_kind:'click_primary',expected_frame_id:H,
|
|
54
|
+
target_id:H,point:{x:1,y:2},scroll_delta:null,key_chord:null,text:null
|
|
55
|
+
}),policy),/exactly one/);
|
|
56
|
+
assert.throws(()=>parseR34PlannerDecision(wrap({
|
|
57
|
+
kind:'desktop_action',action_kind:'key_chord',expected_frame_id:H,
|
|
58
|
+
target_id:null,point:null,scroll_delta:null,key_chord:['CTRL','CTRL'],text:null
|
|
59
|
+
}),policy),/duplicate/);
|
|
60
|
+
assert.throws(()=>parseR34PlannerDecision(wrap({
|
|
61
|
+
kind:'desktop_target',frame_id:H,uia_query:{kind:'root',value:'not-empty',max_results:1,ordinal:0}
|
|
62
|
+
}),policy),/root query value/);
|
|
63
|
+
});
|
|
64
|
+
|
|
47
65
|
test('R36 legacy delegate is accepted only for a known operation and normalized into typed action',()=>{
|
|
48
66
|
const parsed=parseR34PlannerDecision(wrap({kind:'delegate',operation:{kind:'process_status',session_id:H}}),policy);
|
|
49
67
|
assert.equal(parsed.action.kind,'process_status');
|
|
@@ -62,6 +80,30 @@ test('R36 planner prompt exposes configured capability manifest and never advert
|
|
|
62
80
|
assert.doesNotMatch(prompt,/host\.exec|host_file|host authority/i);
|
|
63
81
|
});
|
|
64
82
|
|
|
83
|
+
test('R40 T6 autonomous planner receives bounded Desktop memory projection as context only',async()=>{
|
|
84
|
+
let calls=0;
|
|
85
|
+
const desktopMemoryProvider={plannerContext:async()=>{
|
|
86
|
+
calls++;
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
schema:'deadbyte.desktop-memory-planner.v1',
|
|
89
|
+
snapshot_sha256:'1'.repeat(64),memory_sha256:'2'.repeat(64),policy_sha256:'3'.repeat(64),
|
|
90
|
+
source_receipt_count:7,last_verified_desktop:{frame_id:'4'.repeat(64),desktop_epoch:'5'.repeat(64),topology_sha256:'6'.repeat(64)},
|
|
91
|
+
windows:[],grounded_targets:[],rejected_target_fingerprints:[],recent_effects:[],unresolved_unknown_effects:[]
|
|
92
|
+
});
|
|
93
|
+
}};
|
|
94
|
+
const f=await createR34Fixture({desktopMemoryProvider});
|
|
95
|
+
try{
|
|
96
|
+
const goal=await f.runtime.submit({submissionId:'r40-t6-memory',rootId:'project',title:'desktop memory context',goal:'inspect project',acceptance:'planner sees authenticated desktop summary'});
|
|
97
|
+
const result=await f.runtime.run({goalId:goal.goal_id,maxCycles:1});
|
|
98
|
+
assert.equal(result.status,'input_required');
|
|
99
|
+
assert.equal(calls,1);
|
|
100
|
+
assert.match(result.planner_request.prompt,/deadbyte\.desktop-memory-planner\.v1/);
|
|
101
|
+
assert.match(result.planner_request.prompt,/"desktop_memory"/);
|
|
102
|
+
assert.match(result.planner_request.prompt,/"source_receipt_count":7/);
|
|
103
|
+
assert.doesNotMatch(result.planner_request.prompt,/data_base64|authority_instance_nonce|SECRET/);
|
|
104
|
+
}finally{await f.cleanup();}
|
|
105
|
+
});
|
|
106
|
+
|
|
65
107
|
test('R36 progress fingerprint ignores rationale but binds capability, evidence, session and cursor identity',()=>{
|
|
66
108
|
const base={before:{mutation_epoch:2},after:{mutation_epoch:2},decision:{kind:'process_read',rationale:'first'},evidence:{capability_id:'process.read',session_id:H,cursor:10,observation_sha256:'b'.repeat(64),receipt_sha256:'c'.repeat(64)}};
|
|
67
109
|
const a=judgeProgress(base);
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { createR34Fixture,sendPlannerDecision,inspectDecision } from './helpers/autonomous-r34-fixture.mjs';
|
|
5
|
+
|
|
6
|
+
const H=v=>createHash('sha256').update(String(v)).digest('hex');
|
|
7
|
+
const FRAME=H('desktop-frame');
|
|
8
|
+
const GROUNDED_FRAME=H('desktop-grounded-frame');
|
|
9
|
+
const TARGET=H('desktop-target');
|
|
10
|
+
const POLICY=H('desktop-policy');
|
|
11
|
+
const WIN={hwnd:'0x1234',pid:77,process_image_sha256:H('desktop-app'),title_sha256:H('desktop-title')};
|
|
12
|
+
|
|
13
|
+
function fakeDesktopRuntime({armed=false,unknown=false}={}){
|
|
14
|
+
const calls={status:0,observe:0,ground:0,action:0,verify:0};
|
|
15
|
+
const receipts=new Map();
|
|
16
|
+
let seq=0;
|
|
17
|
+
const issue=kind=>{
|
|
18
|
+
const receipt_id=(++seq).toString(16).padStart(24,'0');
|
|
19
|
+
const receipt_sha256=H(kind+':'+receipt_id);
|
|
20
|
+
receipts.set(receipt_id,{ok:true,receipt_id,receipt_sha256,kind});
|
|
21
|
+
return {receipt_id,receipt_sha256};
|
|
22
|
+
};
|
|
23
|
+
const frame=id=>({frame_id:id,desktop_epoch:H('epoch'),topology_sha256:H('topology'),
|
|
24
|
+
observed_at_utc:'2026-09-19T10:00:00.000Z',bounds:{left:0,top:0,width:1920,height:1080},
|
|
25
|
+
active_window:WIN,image_sha256:H('image:'+id)});
|
|
26
|
+
return {
|
|
27
|
+
calls,
|
|
28
|
+
async status(){calls.status++;return {status:'ok',configured:true,armed,reason:armed?'standing-grant':'disarmed',
|
|
29
|
+
action_kinds:armed?['click_primary']:[],policy_sha256:POLICY,observer_backend:'fake'};},
|
|
30
|
+
async observe(){calls.observe++;const r=issue('desktop_observation');return {status:'ok',frame:frame(FRAME),backend:'fake',byte_length:4,...r};},
|
|
31
|
+
async ground({frameId,query}){calls.ground++;assert.equal(frameId,FRAME);assert.equal(query.kind,'root');
|
|
32
|
+
const r=issue('desktop_grounding');return {status:'ok',frame:frame(GROUNDED_FRAME),
|
|
33
|
+
target:{target_id:TARGET,frame_id:GROUNDED_FRAME,rect:{left:100,top:100,width:200,height:80},source:'uia'},
|
|
34
|
+
grounding:{query_sha256:H('query'),query_kind:'root',match_count:1,ordinal:0,provider_receipt_sha256:H('provider'),...r}};},
|
|
35
|
+
async action(args){calls.action++;
|
|
36
|
+
assert.equal(args.effectContext.goalId.length,64);
|
|
37
|
+
if(unknown){
|
|
38
|
+
assert.equal(args.expectedFrameId,FRAME);assert.equal(args.effectContext.observationHeadSha256,FRAME);
|
|
39
|
+
const e=new Error('transport lost after SendInput');e.side_effect_state='unknown';const r=issue('desktop_action_outcome_unknown');
|
|
40
|
+
e.receipt_id=r.receipt_id;e.receipt_sha256=r.receipt_sha256;throw e;
|
|
41
|
+
}
|
|
42
|
+
assert.equal(args.expectedFrameId,GROUNDED_FRAME);assert.equal(args.targetId,TARGET);
|
|
43
|
+
assert.equal(args.effectContext.observationHeadSha256,GROUNDED_FRAME);
|
|
44
|
+
const started=issue('desktop_action_started'),post=issue('desktop_post_observation'),done=issue('desktop_action_completed');
|
|
45
|
+
return {status:'ok',injected:true,side_effect_state:'confirmed_injected',
|
|
46
|
+
action:{intent_id:args.intentId,kind:'click_primary',frame_id:GROUNDED_FRAME,desktop_epoch:H('epoch'),
|
|
47
|
+
topology_sha256:H('topology'),expected_window:WIN,target_id:TARGET,point:{x:200,y:140},
|
|
48
|
+
scroll_delta:null,key_chord:null,text:null,text_sha256:null,action_sha256:H('action')},
|
|
49
|
+
guard:{guard_sha256:H('guard')},post_observation:{change_observed:true,window_changed:false,
|
|
50
|
+
post_observation_sha256:H('post-observation')},action_chain:{chain_sha256:H('chain')},
|
|
51
|
+
effect_id:H('effect'),effect_fingerprint:H('effect-fp'),effect_completion:{schema:'deadbyte.effect-completion.v1',result_sha256:H('effect-result')},
|
|
52
|
+
started_receipt_id:started.receipt_id,started_receipt_sha256:started.receipt_sha256,
|
|
53
|
+
post_receipt_id:post.receipt_id,post_receipt_sha256:post.receipt_sha256,
|
|
54
|
+
receipt_id:done.receipt_id,receipt_sha256:done.receipt_sha256};},
|
|
55
|
+
async verifyReceipt({receiptId}){calls.verify++;const r=receipts.get(receiptId);if(!r)throw new Error('unknown fake receipt');return r;}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const plan=steps=>({schema:'deadbyte.autonomous-decision.v2',kind:'plan_create',title:'Desktop flow',
|
|
60
|
+
reason:'prove bounded desktop loop',steps:steps.map(step_id=>({step_id,intent:step_id}))});
|
|
61
|
+
const complete=(step_id,tag)=>({schema:'deadbyte.autonomous-decision.v2',kind:'step_complete',step_id,
|
|
62
|
+
evidence:[{kind:'desktop',id:tag,sha256:H(tag)}]});
|
|
63
|
+
|
|
64
|
+
async function toAct(f,goal,result){
|
|
65
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,inspectDecision());
|
|
66
|
+
assert.equal(result.loop.phase,'ACTING');
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
test('T7 capability manifest exposes Desktop only when runtime exists and never Host',async()=>{
|
|
71
|
+
const d=fakeDesktopRuntime();
|
|
72
|
+
const f=await createR34Fixture({desktopRuntime:d});
|
|
73
|
+
try{
|
|
74
|
+
const goal=await f.runtime.submit({submissionId:'r40-t7-manifest',rootId:'project',title:'desktop manifest',goal:'inspect desktop',acceptance:'desktop capability visible'});
|
|
75
|
+
const result=await f.runtime.run({goalId:goal.goal_id,maxCycles:1});
|
|
76
|
+
assert.equal(result.status,'input_required');
|
|
77
|
+
assert.match(result.planner_request.prompt,/"id":"desktop\.observe"/);
|
|
78
|
+
assert.match(result.planner_request.prompt,/"id":"desktop\.action"/);
|
|
79
|
+
assert.match(result.planner_request.prompt,/"authority":"desktop"/);
|
|
80
|
+
assert.doesNotMatch(result.planner_request.prompt,/host\.exec|host_file/);
|
|
81
|
+
}finally{await f.cleanup();}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('T7 read-only Desktop observe works while Desktop mutation authority is DISARMED',async()=>{
|
|
85
|
+
const d=fakeDesktopRuntime({armed:false});
|
|
86
|
+
const f=await createR34Fixture({desktopRuntime:d});
|
|
87
|
+
try{
|
|
88
|
+
const goal=await f.runtime.submit({submissionId:'r40-t7-observe',rootId:'project',title:'desktop observe',goal:'observe desktop',acceptance:'signed observation'});
|
|
89
|
+
let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
|
|
90
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe']));
|
|
91
|
+
result=await toAct(f,goal,result);
|
|
92
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
|
|
93
|
+
assert.equal(result.loop.phase,'VERIFYING');
|
|
94
|
+
assert.equal(d.calls.observe,1);assert.equal(d.calls.action,0);assert.equal(d.calls.verify,1);
|
|
95
|
+
const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
|
|
96
|
+
const completed=journal.events.find(e=>e.type==='delegated_action_completed'&&e.payload?.operation?.kind==='desktop_observe');
|
|
97
|
+
assert.equal(completed.payload.capability_id,'desktop.observe');
|
|
98
|
+
assert.equal(completed.payload.evidence.kind,'desktop_receipts');
|
|
99
|
+
}finally{await f.cleanup();}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('T7 Autonomous authority cannot substitute for missing Desktop mutation grant',async()=>{
|
|
103
|
+
const d=fakeDesktopRuntime({armed:false});
|
|
104
|
+
const f=await createR34Fixture({desktopRuntime:d});
|
|
105
|
+
try{
|
|
106
|
+
const goal=await f.runtime.submit({submissionId:'r40-t7-disarmed',rootId:'project',title:'desktop disarmed',goal:'click only if separately authorized',acceptance:'no input without Desktop grant'});
|
|
107
|
+
let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
|
|
108
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['act']));
|
|
109
|
+
result=await toAct(f,goal,result);
|
|
110
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
|
|
111
|
+
kind:'desktop_action',action_kind:'click_primary',expected_frame_id:FRAME,target_id:null,point:{x:20,y:20},
|
|
112
|
+
scroll_delta:null,key_chord:null,text:null
|
|
113
|
+
}});
|
|
114
|
+
assert.equal(result.status,'paused');
|
|
115
|
+
assert.equal(result.reason,'desktop_authority_unavailable');
|
|
116
|
+
assert.equal(d.calls.action,0);
|
|
117
|
+
const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
|
|
118
|
+
assert.ok(journal.events.some(e=>e.type==='waiting_entered'&&e.payload?.reason==='desktop_authority_unavailable'));
|
|
119
|
+
assert.equal(journal.events.some(e=>e.type==='delegated_action_started'&&e.payload?.operation?.kind==='desktop_action'),false);
|
|
120
|
+
}finally{await f.cleanup();}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('T7 armed flow binds observe to UIA target to one Desktop action and verifies every Desktop receipt',async()=>{
|
|
124
|
+
const d=fakeDesktopRuntime({armed:true});
|
|
125
|
+
const f=await createR34Fixture({desktopRuntime:d});
|
|
126
|
+
try{
|
|
127
|
+
const goal=await f.runtime.submit({submissionId:'r40-t7-flow',rootId:'project',title:'desktop flow',goal:'observe target click',acceptance:'one receipt-verified action'});
|
|
128
|
+
let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
|
|
129
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe','target','act']));
|
|
130
|
+
|
|
131
|
+
result=await toAct(f,goal,result);
|
|
132
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
|
|
133
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('observe','observe-proof'));
|
|
134
|
+
|
|
135
|
+
result=await toAct(f,goal,result);
|
|
136
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
|
|
137
|
+
kind:'desktop_target',frame_id:FRAME,uia_query:{kind:'root',value:'',max_results:1,ordinal:0}
|
|
138
|
+
}});
|
|
139
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('target','target-proof'));
|
|
140
|
+
|
|
141
|
+
result=await toAct(f,goal,result);
|
|
142
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
|
|
143
|
+
kind:'desktop_action',action_kind:'click_primary',expected_frame_id:GROUNDED_FRAME,target_id:TARGET,point:null,
|
|
144
|
+
scroll_delta:null,key_chord:null,text:null
|
|
145
|
+
}});
|
|
146
|
+
assert.equal(result.loop.phase,'VERIFYING');
|
|
147
|
+
assert.equal(d.calls.observe,1);assert.equal(d.calls.ground,1);assert.equal(d.calls.action,1);
|
|
148
|
+
assert.equal(d.calls.verify,5);
|
|
149
|
+
|
|
150
|
+
const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
|
|
151
|
+
const completed=journal.events.filter(e=>e.type==='delegated_action_completed'&&String(e.payload?.operation?.kind).startsWith('desktop_'));
|
|
152
|
+
assert.deepEqual(completed.map(e=>e.payload.operation.kind),['desktop_observe','desktop_target','desktop_action']);
|
|
153
|
+
const action=completed.at(-1);
|
|
154
|
+
assert.equal(action.payload.capability_id,'desktop.action');
|
|
155
|
+
assert.equal(action.payload.evidence.kind,'desktop_receipts');
|
|
156
|
+
assert.deepEqual(action.payload.evidence.receipts.map(x=>x.kind),[
|
|
157
|
+
'desktop_action_started','desktop_post_observation','desktop_action_completed'
|
|
158
|
+
]);
|
|
159
|
+
assert.equal(Object.hasOwn(action.payload.result.action_summary,'text'),false);
|
|
160
|
+
}finally{await f.cleanup();}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('T7 UNKNOWN Desktop outcome pauses for critic/replan and is never replayed',async()=>{
|
|
164
|
+
const d=fakeDesktopRuntime({armed:true,unknown:true});
|
|
165
|
+
const f=await createR34Fixture({desktopRuntime:d});
|
|
166
|
+
try{
|
|
167
|
+
const goal=await f.runtime.submit({submissionId:'r40-t7-unknown',rootId:'project',title:'desktop unknown',goal:'never replay unknown',acceptance:'pause on uncertain side effect'});
|
|
168
|
+
let result=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
|
|
169
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,plan(['observe','act']));
|
|
170
|
+
result=await toAct(f,goal,result);
|
|
171
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{kind:'desktop_observe'}});
|
|
172
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,complete('observe','observe-proof-unknown'));
|
|
173
|
+
result=await toAct(f,goal,result);
|
|
174
|
+
result=await sendPlannerDecision(f.runtime,goal.goal_id,result,{schema:'deadbyte.autonomous-decision.v2',kind:'act',action:{
|
|
175
|
+
kind:'desktop_action',action_kind:'click_primary',expected_frame_id:FRAME,target_id:null,point:{x:40,y:40},
|
|
176
|
+
scroll_delta:null,key_chord:null,text:null
|
|
177
|
+
}});
|
|
178
|
+
assert.equal(result.status,'paused');
|
|
179
|
+
assert.equal(result.reason,'desktop_outcome_unknown');
|
|
180
|
+
assert.equal(d.calls.action,1);
|
|
181
|
+
const journal=await f.runtime.events({goalId:goal.goal_id,fromSeq:1,limit:200});
|
|
182
|
+
assert.ok(journal.events.some(e=>e.type==='waiting_entered'&&e.payload?.reason==='desktop_outcome_unknown'&&e.payload?.resume_phase==='CRITIC'));
|
|
183
|
+
assert.ok(journal.events.some(e=>e.type==='goal_paused'&&e.payload?.side_effect_state==='unknown'));
|
|
184
|
+
const again=await f.runtime.run({goalId:goal.goal_id,maxCycles:2});
|
|
185
|
+
assert.equal(again.status,'paused');
|
|
186
|
+
assert.equal(d.calls.action,1,'UNKNOWN action was replayed');
|
|
187
|
+
}finally{await f.cleanup();}
|
|
188
|
+
});
|
|
@@ -36,6 +36,7 @@ test('R36 capability ledger rejects duplicate ids', () => {
|
|
|
36
36
|
|
|
37
37
|
test('R36 capability ledger validates authority and canonicalizes identity', () => {
|
|
38
38
|
assert.throws(() => createCapabilityLedger([entry({authority:'root'})]), /authority/i);
|
|
39
|
+
assert.doesNotThrow(()=>createCapabilityLedger([entry({authority:'desktop'})]));
|
|
39
40
|
const a=createCapabilityLedger([
|
|
40
41
|
entry({id:'process.read',full_tool:'process_session_read',compact_tool:'process_session',compact_op:'read',runtime_flag:'DEADBYTE_PROCESS_RUNTIME',authority:'process'}),
|
|
41
42
|
entry({})
|