deadbyte-mcp 0.14.0 → 0.15.1
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/AGENTS.md +11 -18
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +156 -70
- package/PROVENANCE.json +10 -0
- package/R43-PARENT.json +20 -0
- package/R44-DONORS.json +99 -0
- package/R44-PARENT.json +18 -0
- package/R45-PARENT.json +18 -0
- package/README.txt +19 -14
- package/RELEASE.json +37 -0
- package/SBOM.cdx.json +46 -0
- package/TEST-MATRIX.json +31 -0
- package/bin/deadbyte-hardware-observer-r45.exe +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/controller/README.TXT +5 -6
- package/controller/deadbyte-controller.ps1 +2 -2
- package/docs/ARCHITECTURE.md +8 -1
- package/docs/R44-ARCHITECTURE.md +37 -0
- package/native/deadbyte-hardware-observer.cpp +308 -0
- package/package.json +18 -8
- package/scripts/autonomous-tests.ps1 +2 -2
- package/scripts/build-hardware-r45.ps1 +71 -0
- package/scripts/deferred-slot-operation-r43.mjs +126 -0
- package/scripts/deferred-slot-operation-r44.mjs +126 -0
- package/scripts/deferred-slot-operation-r45.mjs +126 -0
- package/scripts/derive-r45-parent.mjs +24 -0
- package/scripts/derive-r45-release.mjs +72 -0
- package/scripts/derive-r45-tests.mjs +20 -0
- package/scripts/final-closure-r43.mjs +115 -0
- package/scripts/final-closure-r44.mjs +121 -0
- package/scripts/final-closure-r45.mjs +121 -0
- package/scripts/final-closure-verify-r43.mjs +213 -0
- package/scripts/final-closure-verify-r44.mjs +220 -0
- package/scripts/final-closure-verify-r45.mjs +220 -0
- package/scripts/finalize-r43.mjs +217 -0
- package/scripts/finalize-r44.mjs +217 -0
- package/scripts/finalize-r45.mjs +219 -0
- package/scripts/full-regression-r43.mjs +20 -0
- package/scripts/full-regression-r44.mjs +20 -0
- package/scripts/full-regression-r45.mjs +20 -0
- package/scripts/full-regression-summary-r45.mjs +14 -0
- package/scripts/gate-windows-r43.ps1 +36 -0
- package/scripts/gate-windows-r44.ps1 +36 -0
- package/scripts/gate-windows-r45.ps1 +44 -0
- package/scripts/init-host-policy.ps1 +39 -1
- package/scripts/inspect-active-release-r45.mjs +20 -0
- package/scripts/release-manifest.mjs +3 -1
- package/scripts/release-parity-r43.mjs +218 -0
- package/scripts/release-parity-r44.mjs +242 -0
- package/scripts/release-parity-r45.mjs +253 -0
- package/scripts/release-parity-tests-r43.ps1 +8 -0
- package/scripts/release-parity-tests-r44.ps1 +8 -0
- package/scripts/release-parity-tests-r45.ps1 +8 -0
- package/scripts/verify-r43-parent.mjs +126 -0
- package/scripts/verify-r44-parent.mjs +142 -0
- package/scripts/verify-r45-parent.mjs +142 -0
- package/scripts/windows-gate-evidence-r43.mjs +55 -0
- package/scripts/windows-gate-evidence-r44.mjs +55 -0
- package/scripts/windows-gate-evidence-r45.mjs +60 -0
- package/src/agent-backend-r44.mjs +254 -0
- package/src/autonomous-output-contracts.mjs +1 -1
- package/src/autonomous-policy.mjs +41 -4
- package/src/autonomous-runtime.mjs +37 -19
- package/src/autonomous-supervisor-host.mjs +12 -2
- package/src/capability-ledger.mjs +3 -1
- package/src/codex-app-server-r44.mjs +154 -0
- package/src/coding-plane.mjs +66 -19
- package/src/compact-gateway-mcp-tools.mjs +4 -2
- package/src/deadbyte-cli.mjs +17 -9
- package/src/doctor-r45.mjs +65 -0
- package/src/durable-job-r44.mjs +145 -0
- package/src/external-job-provider-r44.mjs +126 -0
- package/src/host-plane.mjs +156 -87
- package/src/job-supervisor-host-r44.mjs +112 -0
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +21 -10
- package/src/mcp-host-smoke-client.mjs +1 -1
- package/src/mcp-server.mjs +84 -26
- package/src/mcp-smoke-client.mjs +1 -1
- package/src/native-diff-v2.mjs +12 -7
- package/src/native-diff.mjs +32 -9
- package/src/observation-mcp-tools.mjs +5 -1
- package/src/observation-output-contracts.mjs +8 -0
- package/src/observation-runtime.mjs +33 -2
- package/src/observation-smoke-client.mjs +7 -3
- package/src/operator-surface.mjs +2 -2
- package/src/output-contracts.mjs +27 -11
- package/src/process-policy.mjs +19 -0
- package/src/process-runtime.mjs +5 -2
- package/src/process-smoke-client.mjs +2 -2
- package/src/provider-circuit.mjs +8 -2
- package/src/r44-compact-disarmed-probe.mjs +74 -0
- package/src/r44-full-surface-probe.mjs +34 -0
- package/src/r45-compact-disarmed-probe.mjs +74 -0
- package/src/r45-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +1 -0
- package/src/remote-device-r44.mjs +205 -0
- package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
- package/src/remote-mcp-r44-parity-client.mjs +203 -0
- package/src/remote-mcp-r45-parity-client.mjs +206 -0
- package/src/remote-mcp-smoke-client.mjs +7 -4
- package/src/runtime-update.mjs +100 -39
- package/src/skill-mcp-tools-r44.mjs +37 -0
- package/src/skill-plane-r44.mjs +203 -0
- package/src/tunnel-stdio-compat-smoke.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-backend-r44.test.mjs +115 -0
- package/test/agent-runtime.test.mjs +4 -3
- package/test/autonomous-loop.test.mjs +121 -11
- package/test/autonomous-runtime.test.mjs +3 -2
- package/test/capability-ledger-r36.test.mjs +3 -3
- package/test/cli-entrypoint.test.mjs +4 -4
- package/test/codex-app-server-r44.test.mjs +115 -0
- package/test/codex-autonomous-r44.test.mjs +87 -0
- package/test/coding-plane.test.mjs +3 -2
- package/test/coding-runtime.test.mjs +36 -1
- package/test/compact-catalog-r36.test.mjs +3 -3
- package/test/compact-max-catalog-r40.test.mjs +4 -2
- package/test/compact-max-catalog-r44.test.mjs +97 -0
- package/test/compact-max-catalog-r45.test.mjs +99 -0
- package/test/contained.test.mjs +23 -11
- package/test/controller.test.mjs +5 -1
- package/test/core.test.mjs +1 -1
- package/test/durable-job-r44.test.mjs +75 -0
- package/test/external-job-provider-r44.test.mjs +46 -0
- package/test/full-max-catalog-r40.test.mjs +5 -3
- package/test/full-max-catalog-r44.test.mjs +91 -0
- package/test/full-max-catalog-r45.test.mjs +91 -0
- package/test/hardware-observation-r45.test.mjs +44 -0
- package/test/host-inline-r45.test.mjs +67 -0
- package/test/host-plane.test.mjs +15 -1
- package/test/machine-mcp-integration.test.mjs +1 -0
- package/test/native-diff.test.mjs +28 -0
- package/test/observation-mcp-tools.test.mjs +4 -3
- package/test/output-schema.test.mjs +3 -2
- package/test/package-boundary.test.mjs +1 -1
- package/test/process-policy.test.mjs +22 -1
- package/test/production-docs.test.mjs +3 -3
- package/test/prompts-resources.test.mjs +1 -1
- package/test/r33-closeout-regression.test.mjs +20 -36
- package/test/r33-finalization.test.mjs +7 -7
- package/test/r43-finalization.test.mjs +31 -0
- package/test/r43-finalizer.test.mjs +34 -0
- package/test/r43-parent.test.mjs +29 -0
- package/test/r44-finalization.test.mjs +58 -0
- package/test/r44-finalizer.test.mjs +48 -0
- package/test/r44-parent.test.mjs +25 -0
- package/test/r45-autonomous-quick-test.test.mjs +10 -0
- package/test/r45-finalization.test.mjs +84 -0
- package/test/release-generation.test.mjs +1 -0
- package/test/release-version.test.mjs +65 -20
- package/test/remote-device-r44.test.mjs +104 -0
- package/test/runtime-host-bootstrap-r45.test.mjs +46 -0
- package/test/runtime-self-update.test.mjs +42 -0
- package/test/skill-mcp-r44.test.mjs +38 -0
- package/test/skill-plane-r44.test.mjs +93 -0
- package/test/standing-grants.test.mjs +5 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { createHash, createPublicKey, verify as cryptoVerify } from 'node:crypto';
|
|
2
|
+
import { lstat, readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const CLOSURE_DOMAIN=Buffer.from('DEADBYTE-R44-FINAL-CLOSURE-V1\0','utf8');
|
|
8
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
9
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
11
|
+
?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
|
|
12
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
13
|
+
const same=(a,b)=>canonicalJson(a)===canonicalJson(b);
|
|
14
|
+
const samePath=(a,b)=>path.resolve(String(a)).toLowerCase()===path.resolve(String(b)).toLowerCase();
|
|
15
|
+
function insist(ok,message){if(!ok) throw new Error(message);}
|
|
16
|
+
|
|
17
|
+
function parseManifest(text){
|
|
18
|
+
const out=new Map();
|
|
19
|
+
for(const [i,line] of text.split(/\r?\n/).entries()){
|
|
20
|
+
if(!line) continue;
|
|
21
|
+
const m=/^([0-9a-f]{64}) ([^\0]+)$/.exec(line);
|
|
22
|
+
insist(m,`manifest line ${i+1} malformed`);
|
|
23
|
+
const rel=m[2];
|
|
24
|
+
insist(!path.posix.isAbsolute(rel)&&!rel.split('/').includes('..')&&!out.has(rel),`unsafe/duplicate manifest path ${rel}`);
|
|
25
|
+
out.set(rel,m[1]);
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
async function walk(root,rel=''){
|
|
30
|
+
const dir=path.join(root,...(rel?rel.split('/'):[]));
|
|
31
|
+
const names=(await readdir(dir)).sort((a,b)=>a.localeCompare(b,'en'));
|
|
32
|
+
const files=[];
|
|
33
|
+
for(const name of names){
|
|
34
|
+
const childRel=rel?`${rel}/${name}`:name;
|
|
35
|
+
const full=path.join(root,...childRel.split('/'));
|
|
36
|
+
const st=await lstat(full);
|
|
37
|
+
insist(!st.isSymbolicLink(),`symlink forbidden: ${childRel}`);
|
|
38
|
+
if(st.isDirectory()) files.push(...await walk(root,childRel));
|
|
39
|
+
else { insist(st.isFile(),`non-file forbidden: ${childRel}`); files.push(childRel); }
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
async function verifyTree(root,manifestName,expectedDigest,expectedCount){
|
|
44
|
+
const manifestBytes=await readFile(path.join(root,manifestName));
|
|
45
|
+
insist(sha256(manifestBytes)===expectedDigest,`${manifestName} digest mismatch`);
|
|
46
|
+
const entries=parseManifest(manifestBytes.toString('utf8'));
|
|
47
|
+
insist(entries.size===expectedCount,`${manifestName} file count mismatch`);
|
|
48
|
+
const actual=(await walk(root)).filter(rel=>rel!==manifestName).sort((a,b)=>a.localeCompare(b,'en'));
|
|
49
|
+
const expected=[...entries.keys()].sort((a,b)=>a.localeCompare(b,'en'));
|
|
50
|
+
insist(same(actual,expected),`${manifestName} exact tree mismatch`);
|
|
51
|
+
for(const [rel,digest] of entries){
|
|
52
|
+
insist(sha256(await readFile(path.join(root,...rel.split('/'))))===digest,`${manifestName} file hash mismatch: ${rel}`);
|
|
53
|
+
}
|
|
54
|
+
return entries.size;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function verifyR45Parent(parentFile,{stateRoot=path.join(os.homedir(),'.deadbyte-mcp')}={}){
|
|
58
|
+
const parentPath=path.resolve(parentFile);
|
|
59
|
+
const parent=JSON.parse(await readFile(parentPath,'utf8'));
|
|
60
|
+
insist(parent?.schema==='deadbyte.r45-parent.v1','R45 parent schema mismatch');
|
|
61
|
+
insist(parent.parent_release_id==='v0.15.0-c15fa41719be61b3','R45 parent release id mismatch');
|
|
62
|
+
insist(parent.parent_version==='0.15.0','R45 parent version mismatch');
|
|
63
|
+
insist(parent.parent_manifest_sha256==='c15fa41719be61b3186e49b2367a5f4c057365cfb465102dbefb7d7355f45483','R45 parent manifest mismatch');
|
|
64
|
+
insist(parent.parent_manifest_file_count===1871,'R45 parent file count mismatch');
|
|
65
|
+
insist(parent.parent_generation_manifest_sha256==='6d7429c050b7fe893492e102f00282019116e0c9e54e4f25a5791defa1661d30','R45 parent generation mismatch');
|
|
66
|
+
insist(parent.parent_generation_file_count===7,'R45 parent generation file count mismatch');
|
|
67
|
+
insist(parent.parent_final_closure_file_sha256==='8aa9c31ab18dfde67275d7cd59295ba08099839d35d6c97ee277fb184c05cae0','R45 parent closure file hash mismatch');
|
|
68
|
+
insist(parent.parent_final_closure_sha256==='533b7e0ff9ded04086dd90ed34e3b87bc427acc3c55c25bb9f4ec66cb448e667','R45 parent closure hash mismatch');
|
|
69
|
+
insist(parent.parent_final_closure_signer_key_id==='c0727a7911e29a0f1edd3cd910e47f89f43edb484743988c89cd82bfd5e37936','R45 parent closure signer mismatch');
|
|
70
|
+
insist(parent.parent_proof==='immutable-slot+generation+signed-final-closure','R45 parent proof class mismatch');
|
|
71
|
+
|
|
72
|
+
const closureBytes=await readFile(path.resolve(parent.parent_final_closure_path));
|
|
73
|
+
insist(sha256(closureBytes)===parent.parent_final_closure_file_sha256,'R45 parent closure file bytes mismatch');
|
|
74
|
+
const envelope=JSON.parse(closureBytes.toString('utf8'));
|
|
75
|
+
insist(envelope?.body?.schema==='deadbyte.r44-final-closure.v1','R45 parent closure schema mismatch');
|
|
76
|
+
insist(envelope.body.version===parent.parent_version,'R45 parent closure version mismatch');
|
|
77
|
+
insist(envelope.body.release_label==='R44 REMOTE AGENT SKILL BASELINE','R45 parent closure label mismatch');
|
|
78
|
+
|
|
79
|
+
const publicKey=createPublicKey(await readFile(path.join(path.resolve(stateRoot),'trust','ed25519-public.pem')));
|
|
80
|
+
insist(publicKey.asymmetricKeyType==='ed25519','R45 parent trust key must be Ed25519');
|
|
81
|
+
const keyId=sha256(Buffer.from(publicKey.export({type:'spki',format:'der'})));
|
|
82
|
+
insist(keyId===parent.parent_final_closure_signer_key_id,'R45 parent trust key id mismatch');
|
|
83
|
+
const bodyBytes=Buffer.from(canonicalJson(envelope.body),'utf8');
|
|
84
|
+
insist(sha256(bodyBytes)===envelope.body_sha256,'R45 parent closure body hash mismatch');
|
|
85
|
+
const {closure_sha256,...unsigned}=envelope;
|
|
86
|
+
insist(sha256(Buffer.from(canonicalJson(unsigned),'utf8'))===closure_sha256,'R45 parent closure envelope hash mismatch');
|
|
87
|
+
insist(closure_sha256===parent.parent_final_closure_sha256,'R45 parent closure content address mismatch');
|
|
88
|
+
insist(envelope.attestation?.algorithm==='ed25519'&&envelope.attestation?.domain==='DEADBYTE-R44-FINAL-CLOSURE-V1','R45 parent closure attestation contract mismatch');
|
|
89
|
+
insist(envelope.attestation.key_id===keyId,'R45 parent closure attestation signer mismatch');
|
|
90
|
+
insist(cryptoVerify(null,Buffer.concat([CLOSURE_DOMAIN,bodyBytes]),publicKey,Buffer.from(envelope.attestation.signature_base64,'base64')),'R45 parent closure signature invalid');
|
|
91
|
+
|
|
92
|
+
const pointer=envelope.body.active_pointer;
|
|
93
|
+
insist(pointer?.release_id===parent.parent_release_id&&pointer?.version===parent.parent_version&&pointer?.manifest_sha256===parent.parent_manifest_sha256,'R45 parent closure release binding mismatch');
|
|
94
|
+
insist(pointer.generation_manifest_sha256===parent.parent_generation_manifest_sha256,'R45 parent closure generation binding mismatch');
|
|
95
|
+
insist(pointer.launcher_sha256===parent.parent_launcher_sha256,'R45 parent launcher binding mismatch');
|
|
96
|
+
insist(samePath(pointer.release_root,parent.parent_release_root),'R45 parent release root mismatch');
|
|
97
|
+
insist(samePath(pointer.generation_root,parent.parent_generation_root),'R45 parent generation root mismatch');
|
|
98
|
+
insist(envelope.body.immutable_slot?.protected===true,'R45 parent immutable slot proof missing');
|
|
99
|
+
insist(samePath(envelope.body.immutable_slot.release_root,parent.parent_release_root),'R45 parent immutable release root mismatch');
|
|
100
|
+
insist(!samePath(envelope.body.immutable_slot.development_root,parent.parent_release_root),'R45 parent development root aliases release slot');
|
|
101
|
+
insist(samePath(envelope.body.immutable_slot.development_root,parent.development_root),'R45 parent development root mismatch');
|
|
102
|
+
insist(envelope.body.authority?.all_disarmed===true,'R45 parent closure did not record all-disarmed state');
|
|
103
|
+
for(const name of ['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json']){
|
|
104
|
+
insist(envelope.body.authority.grants_absent?.[name]===true,`R45 parent closure grant absence missing: ${name}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const evidence=envelope.body.evidence;
|
|
108
|
+
insist(evidence&&evidence.parity&&evidence.activation&&evidence.rollback&&evidence.restore&&evidence.windows_gate,'R45 parent closure evidence set incomplete');
|
|
109
|
+
for(const [name,item] of Object.entries(evidence)){
|
|
110
|
+
insist(typeof item?.path==='string'&&HEX64.test(item?.sha256??''),`R45 parent ${name} evidence identity malformed`);
|
|
111
|
+
insist(sha256(await readFile(item.path))===item.sha256,`R45 parent ${name} evidence hash mismatch`);
|
|
112
|
+
}
|
|
113
|
+
const parity=JSON.parse(await readFile(evidence.parity.path,'utf8'));
|
|
114
|
+
insist(parity?.schema==='deadbyte.r44-parity.v1'&&parity.status==='passed','R45 parent parity status mismatch');
|
|
115
|
+
insist(parity.candidate?.release_id===parent.parent_release_id&&parity.candidate?.manifest_sha256===parent.parent_manifest_sha256,'R45 parent parity candidate mismatch');
|
|
116
|
+
insist(parity.successor?.release_id===parent.parent_release_id&&parity.successor?.manifest_sha256===parent.parent_manifest_sha256,'R45 parent parity successor mismatch');
|
|
117
|
+
insist(parity.successor_disarmed?.status==='passed','R45 parent successor-disarmed proof missing');
|
|
118
|
+
|
|
119
|
+
const releaseFiles=await verifyTree(parent.parent_release_root,'MANIFEST.SHA256',parent.parent_manifest_sha256,parent.parent_manifest_file_count);
|
|
120
|
+
const generationFiles=await verifyTree(parent.parent_generation_root,'GENERATION.SHA256',parent.parent_generation_manifest_sha256,parent.parent_generation_file_count);
|
|
121
|
+
return {
|
|
122
|
+
ok:true,
|
|
123
|
+
status:'passed',
|
|
124
|
+
proof_class:parent.parent_proof,
|
|
125
|
+
release_id:parent.parent_release_id,
|
|
126
|
+
manifest_sha256:parent.parent_manifest_sha256,
|
|
127
|
+
generation_manifest_sha256:parent.parent_generation_manifest_sha256,
|
|
128
|
+
release_files:releaseFiles,
|
|
129
|
+
generation_files:generationFiles,
|
|
130
|
+
closure_sha256:parent.parent_final_closure_sha256,
|
|
131
|
+
closure_file_sha256:parent.parent_final_closure_file_sha256,
|
|
132
|
+
signer_key_id:keyId,
|
|
133
|
+
recorded_all_disarmed:true
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
138
|
+
if(invoked){
|
|
139
|
+
const file=process.argv[2]??path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..','R45-PARENT.json');
|
|
140
|
+
verifyR45Parent(file).then(result=>console.log(JSON.stringify(result,null,2)))
|
|
141
|
+
.catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
142
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { verifyR43Parent } from './verify-r43-parent.mjs';
|
|
7
|
+
|
|
8
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root=path.resolve(here,'..');
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const marker='DEADBYTE V0.14.1 / R43 AUTHORITY HARDENING WINDOWS BATCH GATE: PASS';
|
|
12
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
13
|
+
|
|
14
|
+
export async function writeWindowsGateEvidence({
|
|
15
|
+
candidateRoot=root,
|
|
16
|
+
stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
|
|
17
|
+
clock=()=>new Date()
|
|
18
|
+
}={}){
|
|
19
|
+
const candidate=path.resolve(candidateRoot);
|
|
20
|
+
const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
21
|
+
const manifestSha256=sha256(manifestBytes);
|
|
22
|
+
const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
|
|
23
|
+
insist(pkg.version==='0.14.1',`Windows gate evidence requires V0.14.1, found ${pkg.version}`);
|
|
24
|
+
const parent=await verifyR43Parent(path.join(candidate,'R43-PARENT.json'));
|
|
25
|
+
const completed=clock();
|
|
26
|
+
insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
|
|
27
|
+
const evidence={
|
|
28
|
+
schema:'deadbyte.r43-windows-gate.v1',status:'passed',exit_code:0,version:'0.14.1',
|
|
29
|
+
marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256,
|
|
30
|
+
parent:{release_id:parent.parent_release_id,manifest_sha256:parent.parent_manifest_sha256,
|
|
31
|
+
generation_manifest_sha256:parent.parent_generation_manifest_sha256,
|
|
32
|
+
activation_receipt_sha256:parent.parent_activation_receipt_sha256},
|
|
33
|
+
hardening:{authority_toctou:true,absolute_execution_deadline:true,pre_spawn_authority_recheck:true,
|
|
34
|
+
full_regression_required:true,model_path_changed:false}
|
|
35
|
+
};
|
|
36
|
+
const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
|
|
37
|
+
const dir=path.join(path.resolve(stateRoot),'evidence','release');
|
|
38
|
+
await mkdir(dir,{recursive:true});
|
|
39
|
+
const file=path.join(dir,`r43-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
40
|
+
try{
|
|
41
|
+
const handle=await open(file,'wx',0o600);
|
|
42
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
43
|
+
return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256,parent:evidence.parent};
|
|
44
|
+
}catch(error){
|
|
45
|
+
if(error?.code!=='EEXIST')throw error;
|
|
46
|
+
const existingBytes=await readFile(file);
|
|
47
|
+
const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
48
|
+
insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing R43 Windows gate evidence invalid');
|
|
49
|
+
insist(existing.version==='0.14.1'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R43 Windows gate identity mismatch');
|
|
50
|
+
insist(existing.parent?.release_id===parent.parent_release_id&&existing.parent?.activation_receipt_sha256===parent.parent_activation_receipt_sha256,'existing R43 parent evidence mismatch');
|
|
51
|
+
return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256,parent:existing.parent};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
55
|
+
if(invoked)writeWindowsGateEvidence().then(value=>console.log(JSON.stringify(value))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { verifyR44Parent } from './verify-r44-parent.mjs';
|
|
7
|
+
|
|
8
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const root=path.resolve(here,'..');
|
|
10
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const marker='DEADBYTE V0.15.0 / R44 REMOTE AGENT SKILL WINDOWS BATCH GATE: PASS';
|
|
12
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
13
|
+
|
|
14
|
+
export async function writeWindowsGateEvidence({
|
|
15
|
+
candidateRoot=root,
|
|
16
|
+
stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
|
|
17
|
+
clock=()=>new Date()
|
|
18
|
+
}={}){
|
|
19
|
+
const candidate=path.resolve(candidateRoot);
|
|
20
|
+
const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
21
|
+
const manifestSha256=sha256(manifestBytes);
|
|
22
|
+
const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
|
|
23
|
+
insist(pkg.version==='0.15.0',`Windows gate evidence requires V0.15.0, found ${pkg.version}`);
|
|
24
|
+
const parent=await verifyR44Parent(path.join(candidate,'R44-PARENT.json'));
|
|
25
|
+
const completed=clock();
|
|
26
|
+
insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
|
|
27
|
+
const evidence={
|
|
28
|
+
schema:'deadbyte.r44-windows-gate.v1',status:'passed',exit_code:0,version:'0.15.0',
|
|
29
|
+
marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256,
|
|
30
|
+
parent:{release_id:parent.release_id,manifest_sha256:parent.manifest_sha256,
|
|
31
|
+
generation_manifest_sha256:parent.generation_manifest_sha256,
|
|
32
|
+
final_closure_sha256:parent.closure_sha256,final_closure_file_sha256:parent.closure_file_sha256,
|
|
33
|
+
signer_key_id:parent.signer_key_id},
|
|
34
|
+
features:{skill_integrity:true,agent_backend_persistence:true,codex_protocol_contract:true,
|
|
35
|
+
remote_device_identity:true,durable_job_supervisor:true,real_external_supervisor_smoke:true,
|
|
36
|
+
full_regression_required:true,codex_live_binary_required:false}
|
|
37
|
+
};
|
|
38
|
+
const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
|
|
39
|
+
const dir=path.join(path.resolve(stateRoot),'evidence','release');await mkdir(dir,{recursive:true});
|
|
40
|
+
const file=path.join(dir,`r44-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
41
|
+
try{
|
|
42
|
+
const handle=await open(file,'wx',0o600);try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
43
|
+
return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256,parent:evidence.parent};
|
|
44
|
+
}catch(error){
|
|
45
|
+
if(error?.code!=='EEXIST')throw error;
|
|
46
|
+
const existingBytes=await readFile(file);
|
|
47
|
+
const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
48
|
+
insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing R44 Windows gate evidence invalid');
|
|
49
|
+
insist(existing.version==='0.15.0'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R44 Windows gate identity mismatch');
|
|
50
|
+
insist(existing.parent?.release_id===parent.release_id&&existing.parent?.final_closure_sha256===parent.closure_sha256,'existing R44 parent evidence mismatch');
|
|
51
|
+
return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256,parent:existing.parent};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
55
|
+
if(invoked)writeWindowsGateEvidence().then(value=>console.log(JSON.stringify(value))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { verifyR45Parent } from './verify-r45-parent.mjs';
|
|
7
|
+
import { doctor } from '../src/doctor-r45.mjs';
|
|
8
|
+
|
|
9
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const root=path.resolve(here,'..');
|
|
11
|
+
const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
const marker='DEADBYTE V0.15.1 / R45 MACHINE FREEDOM WINDOWS BATCH GATE: PASS';
|
|
13
|
+
function insist(ok,message){if(!ok)throw new Error(message);}
|
|
14
|
+
|
|
15
|
+
export async function writeWindowsGateEvidence({
|
|
16
|
+
candidateRoot=root,
|
|
17
|
+
stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
|
|
18
|
+
clock=()=>new Date()
|
|
19
|
+
}={}){
|
|
20
|
+
const candidate=path.resolve(candidateRoot);
|
|
21
|
+
const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
22
|
+
const manifestSha256=sha256(manifestBytes);
|
|
23
|
+
const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
|
|
24
|
+
insist(pkg.version==='0.15.1',`Windows gate evidence requires V0.15.1, found ${pkg.version}`);
|
|
25
|
+
const parent=await verifyR45Parent(path.join(candidate,'R45-PARENT.json'));
|
|
26
|
+
const doctorResult=await doctor();
|
|
27
|
+
insist(doctorResult?.status==='ok'&&doctorResult?.package_version==='0.15.1'&&doctorResult?.manifest?.verified===true,'R45 doctor evidence invalid');
|
|
28
|
+
insist(doctorResult?.policy?.host_v2_ready===true,'R45 doctor did not verify Host policy v2');
|
|
29
|
+
const completed=clock();
|
|
30
|
+
insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
|
|
31
|
+
const evidence={
|
|
32
|
+
schema:'deadbyte.r45-windows-gate.v1',status:'passed',exit_code:0,version:'0.15.1',
|
|
33
|
+
marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256,
|
|
34
|
+
doctor:{status:doctorResult.status,manifest_sha256:doctorResult.manifest.sha256,verified_entries:doctorResult.manifest.verified_entries,host_v2_ready:doctorResult.policy.host_v2_ready},
|
|
35
|
+
parent:{release_id:parent.release_id,manifest_sha256:parent.manifest_sha256,
|
|
36
|
+
generation_manifest_sha256:parent.generation_manifest_sha256,
|
|
37
|
+
final_closure_sha256:parent.closure_sha256,final_closure_file_sha256:parent.closure_file_sha256,
|
|
38
|
+
signer_key_id:parent.signer_key_id},
|
|
39
|
+
features:{skill_integrity:true,agent_backend_persistence:true,codex_protocol_contract:true,
|
|
40
|
+
remote_device_identity:true,durable_job_supervisor:true,real_external_supervisor_smoke:true,
|
|
41
|
+
full_regression_required:true,codex_live_binary_required:false,host_inline_native:true,native_hardware_observation:true,doctor_release_truth:true}
|
|
42
|
+
};
|
|
43
|
+
const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
|
|
44
|
+
const dir=path.join(path.resolve(stateRoot),'evidence','release');await mkdir(dir,{recursive:true});
|
|
45
|
+
const file=path.join(dir,`r45-windows-gate-${manifestSha256.slice(0,16)}.json`);
|
|
46
|
+
try{
|
|
47
|
+
const handle=await open(file,'wx',0o600);try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
48
|
+
return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256,parent:evidence.parent};
|
|
49
|
+
}catch(error){
|
|
50
|
+
if(error?.code!=='EEXIST')throw error;
|
|
51
|
+
const existingBytes=await readFile(file);
|
|
52
|
+
const existing=JSON.parse(existingBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
53
|
+
insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing R45 Windows gate evidence invalid');
|
|
54
|
+
insist(existing.version==='0.15.1'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing R45 Windows gate identity mismatch');
|
|
55
|
+
insist(existing.parent?.release_id===parent.release_id&&existing.parent?.final_closure_sha256===parent.closure_sha256,'existing R45 parent evidence mismatch');
|
|
56
|
+
return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256,parent:existing.parent};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
|
|
60
|
+
if(invoked)writeWindowsGateEvidence().then(value=>console.log(JSON.stringify(value))).catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { lstat, mkdir, open, readFile, realpath, rename } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const STATE_SCHEMA='deadbyte.agent-backend-state.v1';
|
|
8
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
9
|
+
const ID=/^[A-Za-z0-9._-]{1,64}$/;
|
|
10
|
+
const METHOD=/^[A-Za-z0-9._\/-]{1,128}$/;
|
|
11
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
13
|
+
?Object.fromEntries(Object.keys(value).sort((a,b)=>a.localeCompare(b,'en')).map(key=>[key,canonical(value[key])])):value;
|
|
14
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
15
|
+
function insist(ok,message){if(!ok) throw new Error(message);}
|
|
16
|
+
function now(){return new Date().toISOString();}
|
|
17
|
+
function isJson(value){try{JSON.stringify(value);return true;}catch{return false;}}
|
|
18
|
+
|
|
19
|
+
async function atomicJson(file,value){
|
|
20
|
+
await mkdir(path.dirname(file),{recursive:true});
|
|
21
|
+
const bytes=Buffer.from(`${JSON.stringify(value,null,2)}\n`,'utf8');
|
|
22
|
+
const temp=`${file}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
23
|
+
const handle=await open(temp,'wx');
|
|
24
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
25
|
+
await rename(temp,file);
|
|
26
|
+
return sha(bytes);
|
|
27
|
+
}
|
|
28
|
+
function validateState(state,backendId){
|
|
29
|
+
insist(state&&typeof state==='object'&&!Array.isArray(state),'agent backend state must be object');
|
|
30
|
+
const allowed=['schema','backend_id','state_epoch','next_sequence','threads','requests','updated_at_utc'];
|
|
31
|
+
insist(Object.keys(state).every(k=>allowed.includes(k))&&allowed.every(k=>Object.hasOwn(state,k)),'agent backend state fields mismatch');
|
|
32
|
+
insist(state.schema===STATE_SCHEMA,'agent backend state schema mismatch');
|
|
33
|
+
insist(state.backend_id===backendId,'agent backend state backend mismatch');
|
|
34
|
+
insist(HEX64.test(state.state_epoch??''),'agent backend state epoch invalid');
|
|
35
|
+
insist(Number.isInteger(state.next_sequence)&&state.next_sequence>=0,'agent backend sequence invalid');
|
|
36
|
+
insist(state.threads&&typeof state.threads==='object'&&!Array.isArray(state.threads),'agent backend threads invalid');
|
|
37
|
+
for(const [logicalId,threadId] of Object.entries(state.threads)){
|
|
38
|
+
insist(ID.test(logicalId),'agent backend logical thread id invalid');
|
|
39
|
+
insist(typeof threadId==='string'&&threadId.length>=1&&threadId.length<=512,'agent backend thread id invalid');
|
|
40
|
+
}
|
|
41
|
+
insist(state.requests&&typeof state.requests==='object'&&!Array.isArray(state.requests),'agent backend requests invalid');
|
|
42
|
+
for(const [id,item] of Object.entries(state.requests)){
|
|
43
|
+
insist(HEX64.test(id),'agent backend request id invalid');
|
|
44
|
+
insist(item&&typeof item==='object'&&!Array.isArray(item),'agent backend request record invalid');
|
|
45
|
+
insist(['pending','completed','failed','interrupted'].includes(item.state),'agent backend request state invalid');
|
|
46
|
+
insist(typeof item.method==='string'&&METHOD.test(item.method),'agent backend request method invalid');
|
|
47
|
+
insist(HEX64.test(item.params_sha256??''),'agent backend params hash invalid');
|
|
48
|
+
insist(typeof item.created_at_utc==='string'&&typeof item.updated_at_utc==='string','agent backend request timestamp invalid');
|
|
49
|
+
}
|
|
50
|
+
insist(typeof state.updated_at_utc==='string','agent backend state timestamp invalid');
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
async function loadState(file,backendId){
|
|
54
|
+
try{
|
|
55
|
+
const st=await lstat(file);
|
|
56
|
+
insist(st.isFile()&&!st.isSymbolicLink(),'agent backend state must be real file');
|
|
57
|
+
let parsed;
|
|
58
|
+
try{parsed=JSON.parse(await readFile(file,'utf8'));}catch(error){throw new Error(`agent backend state JSON invalid: ${error.message}`);}
|
|
59
|
+
return validateState(parsed,backendId);
|
|
60
|
+
}catch(error){
|
|
61
|
+
if(error?.code!=='ENOENT') throw error;
|
|
62
|
+
return {schema:STATE_SCHEMA,backend_id:backendId,state_epoch:randomBytes(32).toString('hex'),next_sequence:0,threads:{},requests:{},updated_at_utc:now()};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async function fileSha256(file){
|
|
66
|
+
const st=await lstat(file);
|
|
67
|
+
insist(st.isFile()&&!st.isSymbolicLink(),'agent backend executable must be real file');
|
|
68
|
+
return sha(await readFile(file));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class JsonRpcLineTransport extends EventEmitter {
|
|
72
|
+
constructor({executable,args=[],cwd=null,env=null,maxLineBytes=1024*1024}){
|
|
73
|
+
super();this.executable=executable;this.args=args;this.cwd=cwd;this.env=env;this.maxLineBytes=maxLineBytes;this.child=null;this.buffer='';
|
|
74
|
+
}
|
|
75
|
+
async start(){
|
|
76
|
+
if(this.child) throw new Error('agent backend transport already started');
|
|
77
|
+
this.child=spawn(this.executable,this.args,{cwd:this.cwd??undefined,env:this.env??process.env,windowsHide:true,shell:false,stdio:['pipe','pipe','pipe']});
|
|
78
|
+
this.child.stdout.setEncoding('utf8');
|
|
79
|
+
this.child.stdout.on('data',chunk=>{
|
|
80
|
+
this.buffer+=chunk;
|
|
81
|
+
if(Buffer.byteLength(this.buffer,'utf8')>this.maxLineBytes*2){this.emit('protocol_error',new Error('agent backend JSON-RPC buffer limit exceeded'));return;}
|
|
82
|
+
for(;;){
|
|
83
|
+
const nl=this.buffer.indexOf('\n');if(nl<0)break;
|
|
84
|
+
const line=this.buffer.slice(0,nl).trim();this.buffer=this.buffer.slice(nl+1);
|
|
85
|
+
if(!line)continue;
|
|
86
|
+
if(Buffer.byteLength(line,'utf8')>this.maxLineBytes){this.emit('protocol_error',new Error('agent backend JSON-RPC line limit exceeded'));continue;}
|
|
87
|
+
try{this.emit('message',JSON.parse(line));}catch(error){this.emit('protocol_error',new Error(`agent backend JSON-RPC invalid: ${error.message}`));}
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
let stderr='';
|
|
91
|
+
this.child.stderr.setEncoding('utf8');
|
|
92
|
+
this.child.stderr.on('data',chunk=>{stderr=(stderr+chunk).slice(-16384);});
|
|
93
|
+
this.child.once('error',error=>this.emit('transport_error',error));
|
|
94
|
+
this.child.once('exit',(code,signal)=>{this.emit('exit',{code,signal,stderr});this.child=null;});
|
|
95
|
+
await new Promise((resolve,reject)=>{
|
|
96
|
+
if(this.child.pid){resolve();return;}
|
|
97
|
+
this.child.once('spawn',resolve);this.child.once('error',reject);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async send(value){
|
|
101
|
+
insist(this.child?.stdin?.writable,'agent backend transport is not writable');
|
|
102
|
+
const line=`${JSON.stringify(value)}\n`;
|
|
103
|
+
insist(Buffer.byteLength(line,'utf8')<=this.maxLineBytes,'agent backend outbound JSON-RPC line limit exceeded');
|
|
104
|
+
await new Promise((resolve,reject)=>this.child.stdin.write(line,error=>error?reject(error):resolve()));
|
|
105
|
+
}
|
|
106
|
+
async stop(){
|
|
107
|
+
if(!this.child)return;
|
|
108
|
+
const child=this.child;
|
|
109
|
+
const done=new Promise(resolve=>child.once('exit',resolve));
|
|
110
|
+
child.kill();
|
|
111
|
+
await Promise.race([done,new Promise(resolve=>setTimeout(resolve,3000))]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function createAgentBackendR44({
|
|
116
|
+
backendId,stateDir,transportFactory=null,serverRequestHandler=null,notificationHandler=null,wireMode='jsonrpc2',
|
|
117
|
+
executable=null,executableSha256=null,args=[],cwd=null,env=null
|
|
118
|
+
}={}){
|
|
119
|
+
insist(typeof backendId==='string'&&ID.test(backendId),'agent backend id invalid');
|
|
120
|
+
insist(typeof stateDir==='string'&&path.isAbsolute(stateDir),'agent backend stateDir must be absolute');
|
|
121
|
+
await mkdir(stateDir,{recursive:true});
|
|
122
|
+
const root=await realpath(stateDir);
|
|
123
|
+
const stateFile=path.join(root,`${backendId}.json`);
|
|
124
|
+
if(transportFactory===null){
|
|
125
|
+
insist(typeof executable==='string'&&path.isAbsolute(executable),'agent backend executable must be absolute');
|
|
126
|
+
insist(HEX64.test(executableSha256??''),'agent backend executable SHA-256 invalid');
|
|
127
|
+
insist(await fileSha256(executable)===executableSha256,'agent backend executable hash mismatch');
|
|
128
|
+
insist(Array.isArray(args)&&args.length<=64&&args.every(x=>typeof x==='string'&&x.length<=4096),'agent backend args invalid');
|
|
129
|
+
transportFactory=async()=>new JsonRpcLineTransport({executable,args,cwd,env});
|
|
130
|
+
}else insist(typeof transportFactory==='function','agent backend transportFactory invalid');
|
|
131
|
+
insist(serverRequestHandler===null||typeof serverRequestHandler==='function','agent backend serverRequestHandler invalid');
|
|
132
|
+
insist(notificationHandler===null||typeof notificationHandler==='function','agent backend notificationHandler invalid');
|
|
133
|
+
insist(['jsonrpc2','codex-app-server'].includes(wireMode),'agent backend wireMode invalid');
|
|
134
|
+
const wire=value=>wireMode==='jsonrpc2'?{jsonrpc:'2.0',...value}:value;
|
|
135
|
+
|
|
136
|
+
let state=await loadState(stateFile,backendId);
|
|
137
|
+
let changed=false;
|
|
138
|
+
for(const item of Object.values(state.requests)){
|
|
139
|
+
if(item.state==='pending'){item.state='interrupted';item.updated_at_utc=now();item.error='backend restarted before terminal response';changed=true;}
|
|
140
|
+
}
|
|
141
|
+
if(changed){state.updated_at_utc=now();await atomicJson(stateFile,state);}
|
|
142
|
+
else{
|
|
143
|
+
try{await lstat(stateFile);}catch(error){if(error?.code==='ENOENT')await atomicJson(stateFile,state);else throw error;}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let transport=null;
|
|
147
|
+
let started=false;
|
|
148
|
+
const pending=new Map();
|
|
149
|
+
let writeChain=Promise.resolve();
|
|
150
|
+
const persist=async()=>{
|
|
151
|
+
state.updated_at_utc=now();
|
|
152
|
+
writeChain=writeChain.then(()=>atomicJson(stateFile,state));
|
|
153
|
+
return writeChain;
|
|
154
|
+
};
|
|
155
|
+
const publicState=()=>({
|
|
156
|
+
status:'ok',backend_id:backendId,running:started,state_epoch:state.state_epoch,
|
|
157
|
+
threads:structuredClone(state.threads),requests:structuredClone(state.requests)
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
async function terminal(id,{stateName,result=null,error=null}){
|
|
161
|
+
const record=state.requests[id];if(!record||record.state!=='pending')return;
|
|
162
|
+
record.state=stateName;record.updated_at_utc=now();
|
|
163
|
+
if(result!==null)record.result=result;
|
|
164
|
+
if(error!==null)record.error=String(error);
|
|
165
|
+
await persist();
|
|
166
|
+
const waiter=pending.get(id);pending.delete(id);
|
|
167
|
+
if(!waiter)return;
|
|
168
|
+
if(stateName==='completed')waiter.resolve(result);
|
|
169
|
+
else waiter.reject(new Error(record.error??stateName));
|
|
170
|
+
}
|
|
171
|
+
async function handleMessage(message){
|
|
172
|
+
if(!message||typeof message!=='object'||Array.isArray(message))return;
|
|
173
|
+
if(typeof message.id==='string'&&Object.hasOwn(message,'method')){
|
|
174
|
+
const method=String(message.method);
|
|
175
|
+
try{
|
|
176
|
+
if(!serverRequestHandler) throw Object.assign(new Error('server request denied by DEADBYTE policy'),{code:-32001});
|
|
177
|
+
const result=await serverRequestHandler({method,params:message.params??{},backendId});
|
|
178
|
+
await transport?.send(wire({id:message.id,result}));
|
|
179
|
+
}catch(error){
|
|
180
|
+
await transport?.send(wire({id:message.id,error:{code:Number.isInteger(error?.code)?error.code:-32001,message:String(error?.message??error)}}));
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if(!Object.hasOwn(message,'id')&&typeof message.method==='string'){
|
|
185
|
+
if(notificationHandler) await notificationHandler({method:String(message.method),params:message.params??{},backendId});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if(typeof message.id!=='string'||!state.requests[message.id])return;
|
|
189
|
+
if(Object.hasOwn(message,'result'))await terminal(message.id,{stateName:'completed',result:message.result});
|
|
190
|
+
else if(Object.hasOwn(message,'error'))await terminal(message.id,{stateName:'failed',error:message.error?.message??'JSON-RPC error'});
|
|
191
|
+
}
|
|
192
|
+
async function start(){
|
|
193
|
+
if(started)return publicState();
|
|
194
|
+
transport=await transportFactory();
|
|
195
|
+
insist(transport&&typeof transport.start==='function'&&typeof transport.send==='function'&&typeof transport.on==='function','agent backend transport contract invalid');
|
|
196
|
+
transport.on('message',message=>{void handleMessage(message);});
|
|
197
|
+
transport.on('protocol_error',error=>{
|
|
198
|
+
for(const id of pending.keys())void terminal(id,{stateName:'failed',error:String(error?.message??error)});
|
|
199
|
+
});
|
|
200
|
+
transport.on('transport_error',error=>{
|
|
201
|
+
for(const id of pending.keys())void terminal(id,{stateName:'interrupted',error:String(error?.message??error)});
|
|
202
|
+
started=false;
|
|
203
|
+
});
|
|
204
|
+
transport.on('exit',()=>{
|
|
205
|
+
for(const id of pending.keys())void terminal(id,{stateName:'interrupted',error:'agent backend transport exited'});
|
|
206
|
+
started=false;
|
|
207
|
+
});
|
|
208
|
+
await transport.start();started=true;return publicState();
|
|
209
|
+
}
|
|
210
|
+
async function bindThread({logicalId,threadId}={}){
|
|
211
|
+
insist(typeof logicalId==='string'&&ID.test(logicalId),'logical thread id invalid');
|
|
212
|
+
insist(typeof threadId==='string'&&threadId.length>=1&&threadId.length<=512,'provider thread id invalid');
|
|
213
|
+
const existing=state.threads[logicalId];
|
|
214
|
+
if(existing!==undefined&&existing!==threadId) throw new Error('logical thread already bound to different provider thread');
|
|
215
|
+
state.threads[logicalId]=threadId;await persist();
|
|
216
|
+
return {status:'ok',logical_id:logicalId,thread_id:threadId};
|
|
217
|
+
}
|
|
218
|
+
async function request({method,params={},timeoutMs=60000}={}){
|
|
219
|
+
insist(started&&transport,'agent backend is not started');
|
|
220
|
+
insist(typeof method==='string'&&METHOD.test(method),'agent backend method invalid');
|
|
221
|
+
insist(isJson(params)&¶ms&&typeof params==='object','agent backend params must be JSON');
|
|
222
|
+
insist(Number.isInteger(timeoutMs)&&timeoutMs>=100&&timeoutMs<=900000,'agent backend timeout invalid');
|
|
223
|
+
const sequence=++state.next_sequence;
|
|
224
|
+
const paramsSha=sha(Buffer.from(canonicalJson(params),'utf8'));
|
|
225
|
+
const id=sha(Buffer.from(canonicalJson({backend_id:backendId,state_epoch:state.state_epoch,sequence,method,params_sha256:paramsSha}),'utf8'));
|
|
226
|
+
state.requests[id]={state:'pending',method,params_sha256:paramsSha,created_at_utc:now(),updated_at_utc:now()};
|
|
227
|
+
await persist();
|
|
228
|
+
let timer;
|
|
229
|
+
const promise=new Promise((resolve,reject)=>{
|
|
230
|
+
pending.set(id,{resolve,reject});
|
|
231
|
+
timer=setTimeout(()=>{void terminal(id,{stateName:'failed',error:'agent backend request timeout'});},timeoutMs);
|
|
232
|
+
}).finally(()=>clearTimeout(timer));
|
|
233
|
+
try{await transport.send(wire({id,method,params}));}
|
|
234
|
+
catch(error){await terminal(id,{stateName:'failed',error:String(error?.message??error)});}
|
|
235
|
+
return promise;
|
|
236
|
+
}
|
|
237
|
+
async function notify({method,params={}}={}){
|
|
238
|
+
insist(started&&transport,'agent backend is not started');
|
|
239
|
+
insist(typeof method==='string'&&METHOD.test(method),'agent backend notification method invalid');
|
|
240
|
+
insist(isJson(params)&¶ms&&typeof params==='object','agent backend notification params must be JSON');
|
|
241
|
+
await transport.send(wire({method,params}));
|
|
242
|
+
return {status:'sent',method};
|
|
243
|
+
}
|
|
244
|
+
async function status(){return publicState();}
|
|
245
|
+
async function close(){
|
|
246
|
+
for(const id of [...pending.keys()])await terminal(id,{stateName:'interrupted',error:'agent backend closed before response'});
|
|
247
|
+
if(transport?.stop)await transport.stop();transport=null;started=false;return publicState();
|
|
248
|
+
}
|
|
249
|
+
async function simulateCrashForTest(){
|
|
250
|
+
for(const [id,waiter] of pending){waiter.reject(new Error('simulated backend crash'));pending.delete(id);}
|
|
251
|
+
if(transport?.stop)await transport.stop();transport=null;started=false;
|
|
252
|
+
}
|
|
253
|
+
return Object.freeze({start,bindThread,request,notify,status,close,simulateCrashForTest,state_file:stateFile,wire_mode:wireMode});
|
|
254
|
+
}
|
|
@@ -68,7 +68,7 @@ export const autonomousSessionStatusOutputSchema = statusContract({
|
|
|
68
68
|
shape:{ configured:z.literal(true).optional(), armed:z.boolean().optional(), autonomous_armed:z.boolean().optional(),
|
|
69
69
|
autonomous_reason:z.string().optional(), autonomous_expires_at_utc:z.string().nullable().optional(),
|
|
70
70
|
coding_armed:z.boolean().optional(), coding_reason:z.string().optional(), root_ids:z.array(z.string()).max(64).optional(),
|
|
71
|
-
planner_backend:z.enum(['client_sampling','openai_compatible','openai_responses']).optional(), profile_ids:z.array(z.string()).max(128).optional(),
|
|
71
|
+
planner_backend:z.enum(['client_sampling','openai_compatible','openai_responses','codex_app_server']).optional(), profile_ids:z.array(z.string()).max(128).optional(),
|
|
72
72
|
policy_sha256:hex64.optional(), coding_policy_sha256:hex64.optional() }
|
|
73
73
|
});
|
|
74
74
|
|