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,115 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createAgentBackendR44 } from '../src/agent-backend-r44.mjs';
|
|
8
|
+
|
|
9
|
+
async function waitFor(predicate,timeoutMs=2000){
|
|
10
|
+
const deadline=Date.now()+timeoutMs;
|
|
11
|
+
while(Date.now()<deadline){if(predicate())return;await new Promise(resolve=>setTimeout(resolve,5));}
|
|
12
|
+
throw new Error('waitFor timeout');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
class FakeTransport extends EventEmitter {
|
|
16
|
+
constructor(){super();this.sent=[];this.started=false;this.stopped=false;}
|
|
17
|
+
async start(){this.started=true;}
|
|
18
|
+
async send(value){this.sent.push(structuredClone(value));}
|
|
19
|
+
async stop(){this.stopped=true;}
|
|
20
|
+
reply(id,result){this.emit('message',{jsonrpc:'2.0',id,result});}
|
|
21
|
+
serverRequest(id,method,params={}){this.emit('message',{jsonrpc:'2.0',id,method,params});}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('R44 agent backend persists thread identity and request lifecycle atomically',async()=>{
|
|
25
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-backend-'));
|
|
26
|
+
const transport=new FakeTransport();
|
|
27
|
+
try{
|
|
28
|
+
const backend=await createAgentBackendR44({backendId:'codex',stateDir:root,transportFactory:async()=>transport});
|
|
29
|
+
await backend.start();
|
|
30
|
+
await backend.bindThread({logicalId:'main',threadId:'thread-abc'});
|
|
31
|
+
assert.equal((await backend.status()).threads.main,'thread-abc');
|
|
32
|
+
const pending=backend.request({method:'thread/read',params:{threadId:'thread-abc'},timeoutMs:2000});
|
|
33
|
+
await waitFor(()=>transport.sent.length===1);
|
|
34
|
+
assert.equal(transport.sent.length,1);
|
|
35
|
+
const call=transport.sent[0];
|
|
36
|
+
assert.equal(call.jsonrpc,'2.0');assert.equal(call.method,'thread/read');assert.match(call.id,/^[0-9a-f]{64}$/);
|
|
37
|
+
transport.reply(call.id,{ok:true});
|
|
38
|
+
assert.deepEqual(await pending,{ok:true});
|
|
39
|
+
const state=JSON.parse(await readFile(path.join(root,'codex.json'),'utf8'));
|
|
40
|
+
assert.equal(state.schema,'deadbyte.agent-backend-state.v1');
|
|
41
|
+
assert.equal(state.threads.main,'thread-abc');
|
|
42
|
+
assert.equal(state.requests[call.id].state,'completed');
|
|
43
|
+
assert.deepEqual(state.requests[call.id].result,{ok:true});
|
|
44
|
+
assert.equal('token' in state,false);assert.equal('api_key' in state,false);
|
|
45
|
+
await backend.close();
|
|
46
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('R44 agent backend denies server approval by default and never auto-approves',async()=>{
|
|
50
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-backend-deny-'));
|
|
51
|
+
const transport=new FakeTransport();
|
|
52
|
+
try{
|
|
53
|
+
const backend=await createAgentBackendR44({backendId:'codex',stateDir:root,transportFactory:async()=>transport});
|
|
54
|
+
await backend.start();
|
|
55
|
+
transport.serverRequest('srv-1','approval/request',{kind:'shell'});
|
|
56
|
+
await waitFor(()=>transport.sent.some(x=>x.id==='srv-1'));
|
|
57
|
+
const response=transport.sent.find(x=>x.id==='srv-1');
|
|
58
|
+
assert.ok(response);assert.equal(response.error.code,-32001);assert.match(response.error.message,/denied/i);
|
|
59
|
+
assert.equal(response.result,undefined);
|
|
60
|
+
await backend.close();
|
|
61
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('R44 backend restart classifies unresolved requests interrupted and preserves threads',async()=>{
|
|
65
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-backend-restart-'));
|
|
66
|
+
const firstTransport=new FakeTransport();
|
|
67
|
+
try{
|
|
68
|
+
const first=await createAgentBackendR44({backendId:'codex',stateDir:root,transportFactory:async()=>firstTransport});
|
|
69
|
+
await first.start();await first.bindThread({logicalId:'main',threadId:'thread-persist'});
|
|
70
|
+
const promise=first.request({method:'turn/start',params:{threadId:'thread-persist'},timeoutMs:10000});
|
|
71
|
+
promise.catch(()=>{});
|
|
72
|
+
await waitFor(()=>firstTransport.sent.length===1);
|
|
73
|
+
const id=firstTransport.sent[0].id;
|
|
74
|
+
await first.simulateCrashForTest();
|
|
75
|
+
const secondTransport=new FakeTransport();
|
|
76
|
+
const second=await createAgentBackendR44({backendId:'codex',stateDir:root,transportFactory:async()=>secondTransport});
|
|
77
|
+
const status=await second.status();
|
|
78
|
+
assert.equal(status.threads.main,'thread-persist');
|
|
79
|
+
assert.equal(status.requests[id].state,'interrupted');
|
|
80
|
+
assert.equal(secondTransport.sent.length,0,'interrupted request must not auto-replay');
|
|
81
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('R44 backend fails closed on truncated state and binds production executable hash',async()=>{
|
|
85
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-backend-corrupt-'));
|
|
86
|
+
try{
|
|
87
|
+
await writeFile(path.join(root,'codex.json'),'{"schema":','utf8');
|
|
88
|
+
await assert.rejects(createAgentBackendR44({backendId:'codex',stateDir:root,transportFactory:async()=>new FakeTransport()}),/state JSON invalid/);
|
|
89
|
+
await rm(path.join(root,'codex.json'));
|
|
90
|
+
await assert.rejects(createAgentBackendR44({backendId:'codex',stateDir:root,executable:process.execPath,executableSha256:'0'.repeat(64),args:[]}),/executable hash mismatch/);
|
|
91
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
test('R44 agent backend supports Codex wire mode notifications without jsonrpc envelope',async()=>{
|
|
96
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-backend-codex-wire-'));
|
|
97
|
+
const transport=new FakeTransport();const seen=[];
|
|
98
|
+
try{
|
|
99
|
+
const backend=await createAgentBackendR44({backendId:'codex-wire',stateDir:root,transportFactory:async()=>transport,
|
|
100
|
+
wireMode:'codex-app-server',notificationHandler:async event=>seen.push(event)});
|
|
101
|
+
await backend.start();
|
|
102
|
+
const pending=backend.request({method:'initialize',params:{clientInfo:{name:'deadbyte',version:'0.15.0'}},timeoutMs:2000});
|
|
103
|
+
await waitFor(()=>transport.sent.length===1);
|
|
104
|
+
const request=transport.sent[0];
|
|
105
|
+
assert.equal('jsonrpc' in request,false);assert.equal(request.method,'initialize');
|
|
106
|
+
transport.reply(request.id,{userAgent:'codex-test'});
|
|
107
|
+
assert.deepEqual(await pending,{userAgent:'codex-test'});
|
|
108
|
+
await backend.notify({method:'initialized',params:{}});
|
|
109
|
+
assert.equal(transport.sent[1].method,'initialized');assert.equal('id' in transport.sent[1],false);assert.equal('jsonrpc' in transport.sent[1],false);
|
|
110
|
+
transport.emit('message',{method:'turn/completed',params:{threadId:'t',turn:{id:'turn-1',status:'completed'}}});
|
|
111
|
+
await waitFor(()=>seen.length===1);
|
|
112
|
+
assert.deepEqual(seen[0],{method:'turn/completed',params:{threadId:'t',turn:{id:'turn-1',status:'completed'}},backendId:'codex-wire'});
|
|
113
|
+
await backend.close();
|
|
114
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
115
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import os from 'node:os';
|
|
@@ -20,7 +20,7 @@ const AGENT_TOOLS = [
|
|
|
20
20
|
'agent_task_submit',
|
|
21
21
|
'agent_task_verify'
|
|
22
22
|
];
|
|
23
|
-
const BASE_TOOLS = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
|
|
23
|
+
const BASE_TOOLS = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
|
|
24
24
|
|
|
25
25
|
function structured(result, label) {
|
|
26
26
|
if (result.isError) throw new Error(`${label}: ${JSON.stringify(result.structuredContent ?? result.content)}`);
|
|
@@ -54,6 +54,7 @@ async function connectAgent(f) {
|
|
|
54
54
|
DEADBYTE_WORKSPACE_ROOT: f.root,
|
|
55
55
|
DEADBYTE_SIGNING_KEY: f.keys.privateKeyPath,
|
|
56
56
|
DEADBYTE_VERIFY_KEY: f.keys.publicKeyPath,
|
|
57
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
57
58
|
DEADBYTE_AGENT_RUNTIME: '1'
|
|
58
59
|
},
|
|
59
60
|
stderr: 'pipe'
|
|
@@ -68,7 +69,7 @@ async function cleanup(f, client) {
|
|
|
68
69
|
await rm(f.trust, { recursive: true, force: true }).catch(() => {});
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
test('agent runtime
|
|
72
|
+
test('R44 agent runtime exposes base skill catalog plus durable task tools without host authority', async () => {
|
|
72
73
|
const f = await fixture();
|
|
73
74
|
let client;
|
|
74
75
|
try {
|
|
@@ -32,7 +32,7 @@ async function startPlanner(decisions) {
|
|
|
32
32
|
await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
|
|
33
33
|
return { server, prompts, endpoint:`http://127.0.0.1:${server.address().port}/v1/chat/completions` };
|
|
34
34
|
}
|
|
35
|
-
async function fixture(decisionPrefix = []) {
|
|
35
|
+
async function fixture(decisionPrefix = [], options = {}) {
|
|
36
36
|
const root = await mkdtemp(path.join(os.tmpdir(),'deadbyte-autonomous-loop-'));
|
|
37
37
|
const project = path.join(root,'project'); const trust = path.join(root,'trust');
|
|
38
38
|
await mkdir(project); await mkdir(trust);
|
|
@@ -101,14 +101,17 @@ async function fixture(decisionPrefix = []) {
|
|
|
101
101
|
const policy = await loadAutonomousPolicy(autonomousPolicyPath,codingPolicy);
|
|
102
102
|
await writeFile(policy.lease_file,JSON.stringify({ schema:'deadbyte.autonomous-lease.v1',
|
|
103
103
|
policy_sha256:policy.policy_sha256, instance_nonce:randomBytes(32).toString('hex'), root_ids:['project'],
|
|
104
|
-
armed_at_utc:new Date().toISOString(), expires_at_utc:new Date(Date.now()+1800000).toISOString() }));
|
|
104
|
+
armed_at_utc:new Date().toISOString(), expires_at_utc:new Date(Date.now()+(options.autonomousLeaseMs??1800000)).toISOString() }));
|
|
105
105
|
const codingRuntime = createCodingRuntime({ policy:codingPolicy,
|
|
106
106
|
signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath });
|
|
107
|
+
const autonomousCodingRuntime = options.codingRuntimeFactory
|
|
108
|
+
? options.codingRuntimeFactory(codingRuntime,{policy})
|
|
109
|
+
: codingRuntime;
|
|
107
110
|
const nativeDiff = createNativeDiffRuntime({ policy,codingRuntime,
|
|
108
111
|
signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath });
|
|
109
|
-
const runtime = createAutonomousRuntime({ policy,codingRuntime,nativeDiffRuntime:nativeDiff,
|
|
112
|
+
const runtime = createAutonomousRuntime({ policy,codingRuntime:autonomousCodingRuntime,nativeDiffRuntime:nativeDiff,
|
|
110
113
|
signingKeyPath:keys.privateKeyPath, verifyKeyPath:keys.publicKeyPath });
|
|
111
|
-
return { root,project,program,keys,policy,codingRuntime,nativeDiff,runtime,planner,
|
|
114
|
+
return { root,project,program,keys,policy,codingRuntime,autonomousCodingRuntime,nativeDiff,runtime,planner,
|
|
112
115
|
initialHash,almostHash,fixedHash };
|
|
113
116
|
}
|
|
114
117
|
|
|
@@ -144,6 +147,107 @@ test('R24 autonomous goal diagnoses a failing test, replans with native diff, be
|
|
|
144
147
|
}
|
|
145
148
|
});
|
|
146
149
|
|
|
150
|
+
test('historical autonomous release verification survives source drift and disarmed mutation authority', async () => {
|
|
151
|
+
const f = await fixture();
|
|
152
|
+
try {
|
|
153
|
+
const submitted = await f.runtime.submit({ submissionId:'historical-release-verify', rootId:'project',
|
|
154
|
+
title:'repair program', goal:'make exported value equal fixed', acceptance:'release evidence must remain verifiable',
|
|
155
|
+
maxIterations:10 });
|
|
156
|
+
const result = await f.runtime.run({ goalId:submitted.goal_id, maxCycles:16 });
|
|
157
|
+
assert.equal(result.status,'completed');
|
|
158
|
+
assert.equal((await f.runtime.verify({ goalId:submitted.goal_id })).ok,true);
|
|
159
|
+
|
|
160
|
+
await writeFile(f.program,"export const value = 'rolled-back';\n");
|
|
161
|
+
await rm(f.codingRuntime.policy.lease_file);
|
|
162
|
+
await rm(f.policy.lease_file);
|
|
163
|
+
|
|
164
|
+
const verified = await f.runtime.verify({ goalId:submitted.goal_id });
|
|
165
|
+
assert.equal(verified.ok,true);
|
|
166
|
+
assert.equal(verified.native_diffs_verified,2);
|
|
167
|
+
assert.equal(verified.required_profiles_verified,3);
|
|
168
|
+
} finally {
|
|
169
|
+
await new Promise(resolve => f.planner.server.close(resolve));
|
|
170
|
+
await rm(f.root,{recursive:true,force:true});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('autonomous profile execution is capped by the absolute autonomous lease without changing coding request identity', async () => {
|
|
175
|
+
const calls=[];
|
|
176
|
+
const f=await fixture([],{
|
|
177
|
+
autonomousLeaseMs:30000,
|
|
178
|
+
codingRuntimeFactory:base=>Object.freeze({
|
|
179
|
+
...base,
|
|
180
|
+
commandRun:async args=>{
|
|
181
|
+
const result=await base.commandRun(args);
|
|
182
|
+
calls.push({kind:'command',deadline:args.executionDeadlineUtc,has_precheck:typeof args.preExecutionCheck==='function',
|
|
183
|
+
timeout_ms:result.timeout_ms,request_sha256:result.request_sha256});
|
|
184
|
+
return result;
|
|
185
|
+
},
|
|
186
|
+
benchmarkRun:async args=>{
|
|
187
|
+
const result=await base.benchmarkRun(args);
|
|
188
|
+
calls.push({kind:'benchmark',deadline:args.executionDeadlineUtc,has_precheck:typeof args.preExecutionCheck==='function',
|
|
189
|
+
request_sha256:result.request_sha256});
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
const submitted=await f.runtime.submit({submissionId:'profile-deadline',rootId:'project',
|
|
196
|
+
title:'deadline-bound profiles',goal:'make exported value equal fixed',
|
|
197
|
+
acceptance:'build, test, and benchmark must pass',maxIterations:10});
|
|
198
|
+
const result=await f.runtime.run({goalId:submitted.goal_id,maxCycles:16});
|
|
199
|
+
assert.equal(result.status,'completed');
|
|
200
|
+
assert.ok(calls.length>=3);
|
|
201
|
+
assert.ok(calls.every(call=>typeof call.deadline==='string'&&Number.isFinite(Date.parse(call.deadline))));
|
|
202
|
+
assert.ok(calls.every(call=>call.has_precheck===true));
|
|
203
|
+
const command=calls.find(call=>call.kind==='command');
|
|
204
|
+
assert.ok(command.timeout_ms>=100&&command.timeout_ms<30000);
|
|
205
|
+
const build=f.policy.profiles.build;
|
|
206
|
+
assert.equal(command.request_sha256,f.codingRuntime.requestSha('coding_command_run',{
|
|
207
|
+
root_id:build.root_id,command_id:build.command_id,script_path:build.script_path,script_sha256:build.script_sha256,
|
|
208
|
+
args:build.args,cwd:build.cwd,timeout_ms:build.timeout_ms
|
|
209
|
+
}));
|
|
210
|
+
} finally {
|
|
211
|
+
await new Promise(resolve=>f.planner.server.close(resolve));
|
|
212
|
+
await rm(f.root,{recursive:true,force:true});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('autonomous profile nonce rotation immediately before spawn fails closed without a coding receipt', async () => {
|
|
217
|
+
let rotated=false;
|
|
218
|
+
const f=await fixture([],{
|
|
219
|
+
codingRuntimeFactory:(base,{policy})=>Object.freeze({
|
|
220
|
+
...base,
|
|
221
|
+
commandRun:async args=>{
|
|
222
|
+
if(!rotated){
|
|
223
|
+
rotated=true;
|
|
224
|
+
const lease=JSON.parse(await readFile(policy.lease_file,'utf8'));
|
|
225
|
+
await writeFile(policy.lease_file,JSON.stringify({...lease,instance_nonce:'f'.repeat(64)}));
|
|
226
|
+
}
|
|
227
|
+
return base.commandRun(args);
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
});
|
|
231
|
+
try {
|
|
232
|
+
const submitted=await f.runtime.submit({submissionId:'profile-revoke-before-spawn',rootId:'project',
|
|
233
|
+
title:'profile revoke',goal:'make exported value equal fixed',
|
|
234
|
+
acceptance:'profile dispatch must fail closed after authority rotation',maxIterations:10});
|
|
235
|
+
const result=await f.runtime.run({goalId:submitted.goal_id,maxCycles:16});
|
|
236
|
+
assert.equal(rotated,true);
|
|
237
|
+
assert.equal(result.status,'paused');
|
|
238
|
+
const journal=await f.runtime.events({goalId:submitted.goal_id,fromSeq:1,limit:200});
|
|
239
|
+
const started=journal.events.find(event=>event.type==='profile_started'&&event.payload?.profile_id==='build');
|
|
240
|
+
assert.ok(started);
|
|
241
|
+
assert.match(started.payload.authority_instance_nonce,/^[0-9a-f]{64}$/);
|
|
242
|
+
assert.ok(journal.events.some(event=>event.type==='goal_paused'&&event.payload?.reason==='profile_execution_without_receipt'));
|
|
243
|
+
assert.equal(journal.events.some(event=>event.type==='profile_completed'&&event.payload?.profile_id==='build'),false);
|
|
244
|
+
assert.equal((await f.codingRuntime.verifyReceiptDetailed(started.payload.receipt_id)).ok,false);
|
|
245
|
+
} finally {
|
|
246
|
+
await new Promise(resolve=>f.planner.server.close(resolve));
|
|
247
|
+
await rm(f.root,{recursive:true,force:true});
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
147
251
|
test('provider planner repairs an invalid structured decision inside the same run', async () => {
|
|
148
252
|
const f = await fixture(['not-json']);
|
|
149
253
|
try {
|
|
@@ -166,7 +270,7 @@ test('provider planner repairs an invalid structured decision inside the same ru
|
|
|
166
270
|
}
|
|
167
271
|
});
|
|
168
272
|
|
|
169
|
-
test('provider circuit survives runtime restart
|
|
273
|
+
test('provider circuit survives runtime restart and permits exactly one probe at the HALF_OPEN boundary', async () => {
|
|
170
274
|
const failures=[{__http_status:503},{__http_status:503},{__http_status:503}];
|
|
171
275
|
const f=await fixture(failures);
|
|
172
276
|
try {
|
|
@@ -178,17 +282,23 @@ test('provider circuit survives runtime restart, suppresses OPEN calls, then per
|
|
|
178
282
|
const opened=await f.runtime.run({goalId:submitted.goal_id,maxCycles:2});
|
|
179
283
|
assert.equal(opened.status,'paused');
|
|
180
284
|
assert.equal(opened.reason,'provider_circuit_open');
|
|
285
|
+
const openedJournal=await f.runtime.events({goalId:submitted.goal_id,fromSeq:1,limit:200});
|
|
286
|
+
const openPause=[...openedJournal.events].reverse().find(event=>event.type==='goal_paused'&&event.payload?.reason==='provider_circuit_open');
|
|
287
|
+
assert.equal(openPause?.payload?.circuit_state,'OPEN',JSON.stringify(openPause));
|
|
181
288
|
assert.equal(f.planner.prompts.length,3);
|
|
182
289
|
|
|
183
|
-
const immediate=await f.runtime.run({goalId:submitted.goal_id,maxCycles:2});
|
|
184
|
-
assert.equal(immediate.status,'paused');
|
|
185
|
-
assert.equal(f.planner.prompts.length,3,'OPEN circuit must suppress provider invocation');
|
|
186
|
-
|
|
187
290
|
const restarted=createAutonomousRuntime({policy:f.policy,codingRuntime:f.codingRuntime,nativeDiffRuntime:f.nativeDiff,
|
|
188
291
|
signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
|
|
189
|
-
|
|
190
|
-
|
|
292
|
+
const deadline=Date.now()+2_000;
|
|
293
|
+
let circuitBeforeResume;
|
|
294
|
+
do {
|
|
295
|
+
circuitBeforeResume=await restarted.providerCircuitStatus({goalId:submitted.goal_id});
|
|
296
|
+
if(circuitBeforeResume.state==='HALF_OPEN') break;
|
|
297
|
+
assert.equal(circuitBeforeResume.state,'OPEN',JSON.stringify(circuitBeforeResume));
|
|
298
|
+
await new Promise(resolve=>setTimeout(resolve,10));
|
|
299
|
+
} while(Date.now()<deadline);
|
|
191
300
|
assert.equal(circuitBeforeResume.state,'HALF_OPEN',JSON.stringify(circuitBeforeResume));
|
|
301
|
+
|
|
192
302
|
const resumed=await restarted.run({goalId:submitted.goal_id,maxCycles:1});
|
|
193
303
|
assert.notEqual(resumed.status,'paused');
|
|
194
304
|
assert.equal(f.planner.prompts.length,4,'HALF_OPEN must permit exactly one provider probe');
|
|
@@ -20,7 +20,7 @@ const r24Tools = [
|
|
|
20
20
|
'native_diff_apply', 'native_diff_preview', 'native_diff_verify'
|
|
21
21
|
];
|
|
22
22
|
const r23Tools = [
|
|
23
|
-
'contained_transform','evidence_verify','file_read','proof_execute','workspace_open',
|
|
23
|
+
'contained_transform','evidence_verify','file_read','proof_execute','skill_catalog','workspace_open',
|
|
24
24
|
'agent_task_approve','agent_task_cancel','agent_task_events','agent_task_run',
|
|
25
25
|
'agent_task_status','agent_task_submit','agent_task_verify',
|
|
26
26
|
'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read',
|
|
@@ -68,6 +68,7 @@ async function fixture() {
|
|
|
68
68
|
command:process.execPath, args:[serverPath], cwd:repoRoot,
|
|
69
69
|
env:{ ...getDefaultEnvironment(), DEADBYTE_WORKSPACE_ROOT:workspaceRoot,
|
|
70
70
|
DEADBYTE_SIGNING_KEY:keys.privateKeyPath, DEADBYTE_VERIFY_KEY:keys.publicKeyPath,
|
|
71
|
+
DEADBYTE_PRIVILEGED_POLICY:'',
|
|
71
72
|
DEADBYTE_AGENT_RUNTIME:'1', DEADBYTE_CODING_RUNTIME:'1',
|
|
72
73
|
DEADBYTE_CODING_POLICY:codingPolicyPath, DEADBYTE_AUTONOMOUS_RUNTIME:'1',
|
|
73
74
|
DEADBYTE_AUTONOMOUS_POLICY:autonomousPolicyPath }, stderr:'pipe'
|
|
@@ -75,7 +76,7 @@ async function fixture() {
|
|
|
75
76
|
return { root, client, transport };
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
test('
|
|
79
|
+
test('R44 hermetic autonomous fixture exposes the exact 50-tool no-host catalog', async () => {
|
|
79
80
|
const f = await fixture();
|
|
80
81
|
try {
|
|
81
82
|
await f.client.connect(f.transport);
|
|
@@ -92,12 +92,12 @@ test('R36 closure fails when a production route is lost or advertised tool is ab
|
|
|
92
92
|
}), /compact tool/i);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
|
-
test('
|
|
95
|
+
test('R44 compact surface hard-fails above the observed 43-tool ceiling', () => {
|
|
96
96
|
const ledger=createCapabilityLedger([entry({})]);
|
|
97
|
-
const tooMany=Array.from({length:
|
|
97
|
+
const tooMany=Array.from({length:44},(_,i)=>i===0?'coding_inspect':`extra_${i}`);
|
|
98
98
|
assert.throws(() => assertCapabilityClosure({
|
|
99
99
|
ledger,
|
|
100
100
|
fullToolSnapshot:snapshot(['coding_search']),
|
|
101
101
|
compactToolSnapshot:snapshot(tooMany)
|
|
102
|
-
}), /
|
|
102
|
+
}), /43/i);
|
|
103
103
|
});
|
|
@@ -21,7 +21,7 @@ test('npx CLI reports the canonical package version through --version, -v and ve
|
|
|
21
21
|
for(const arg of ['--version','-v','version']){
|
|
22
22
|
const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
|
|
23
23
|
assert.equal(result.status,0,result.stderr);
|
|
24
|
-
assert.equal(result.stdout.trim(),'0.
|
|
24
|
+
assert.equal(result.stdout.trim(),'0.15.1');
|
|
25
25
|
}
|
|
26
26
|
});
|
|
27
27
|
|
|
@@ -45,11 +45,11 @@ test('npx CLI exposes local supervisor lifecycle without any supervisor arm comm
|
|
|
45
45
|
assert.match(source,/supervisorrecover/);
|
|
46
46
|
const mapping=source.match(/const SUPERVISOR_COMMANDS=.*?;\n/s)?.[0]??'';
|
|
47
47
|
assert.doesNotMatch(mapping,/arm(?:coding|autonomous|process|host)/i);
|
|
48
|
-
assert.match(source,/npx
|
|
48
|
+
assert.match(source,/npx -y \$\{exact\} supervisor status/);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
test('README makes npx the primary operator UX',async()=>{
|
|
52
52
|
const readme=await readFile(path.join(root,'README.txt'),'utf8');
|
|
53
|
-
assert.match(readme,/npx deadbyte-mcp@
|
|
54
|
-
assert.match(readme,/npx deadbyte-mcp@
|
|
53
|
+
assert.match(readme,/npx -y deadbyte-mcp@0\.15\.1 remote/);
|
|
54
|
+
assert.match(readme,/npx -y deadbyte-mcp@0\.15\.1 audit verify/);
|
|
55
55
|
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { EventEmitter } from 'node:events';
|
|
4
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { createCodexAppServerR44 } from '../src/codex-app-server-r44.mjs';
|
|
8
|
+
|
|
9
|
+
async function waitFor(predicate,timeoutMs=2000){
|
|
10
|
+
const deadline=Date.now()+timeoutMs;
|
|
11
|
+
while(Date.now()<deadline){const value=predicate();if(value)return value;await new Promise(resolve=>setTimeout(resolve,5));}
|
|
12
|
+
throw new Error('waitFor timeout');
|
|
13
|
+
}
|
|
14
|
+
class FakeTransport extends EventEmitter{
|
|
15
|
+
constructor(){super();this.sent=[];}
|
|
16
|
+
async start(){}
|
|
17
|
+
async send(value){this.sent.push(structuredClone(value));}
|
|
18
|
+
async stop(){}
|
|
19
|
+
request(method){return this.sent.findLast?.(x=>x.method===method&&Object.hasOwn(x,'id'))??[...this.sent].reverse().find(x=>x.method===method&&Object.hasOwn(x,'id'));}
|
|
20
|
+
reply(id,result){this.emit('message',{id,result});}
|
|
21
|
+
notify(method,params){this.emit('message',{method,params});}
|
|
22
|
+
serverRequest(id,method,params){this.emit('message',{id,method,params});}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('R44 Codex adapter initializes no-jsonrpc wire and starts safe persistent planner thread',async()=>{
|
|
26
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-'));const transport=new FakeTransport();
|
|
27
|
+
try{
|
|
28
|
+
const codex=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>transport,
|
|
29
|
+
model:'gpt-test',cwd:root,clientVersion:'0.15.0'});
|
|
30
|
+
const start=codex.start();
|
|
31
|
+
const init=await waitFor(()=>transport.request('initialize'));
|
|
32
|
+
assert.equal('jsonrpc' in init,false);
|
|
33
|
+
assert.deepEqual(init.params.clientInfo,{name:'deadbyte-mcp',version:'0.15.0'});
|
|
34
|
+
assert.equal(init.params.capabilities.experimentalApi,false);
|
|
35
|
+
transport.reply(init.id,{userAgent:'codex-test'});
|
|
36
|
+
await start;
|
|
37
|
+
const initialized=transport.sent.find(x=>x.method==='initialized'&&!Object.hasOwn(x,'id'));
|
|
38
|
+
assert.ok(initialized);assert.equal('jsonrpc' in initialized,false);
|
|
39
|
+
|
|
40
|
+
const ensure=codex.ensureThread({logicalId:'planner'});
|
|
41
|
+
const req=await waitFor(()=>transport.request('thread/start'));
|
|
42
|
+
assert.equal(req.params.model,'gpt-test');assert.equal(req.params.cwd,root);
|
|
43
|
+
assert.equal(req.params.approvalPolicy,'never');assert.equal(req.params.sandbox,'read-only');
|
|
44
|
+
assert.equal(req.params.ephemeral,false);assert.equal(req.params.approvalsReviewer,'user');
|
|
45
|
+
transport.reply(req.id,{thread:{id:'thread-1'},model:'gpt-test',modelProvider:'openai',cwd:root,
|
|
46
|
+
approvalPolicy:'never',approvalsReviewer:'user',sandbox:{type:'readOnly'}});
|
|
47
|
+
const thread=await ensure;assert.equal(thread.thread_id,'thread-1');assert.equal(thread.resumed,false);
|
|
48
|
+
await codex.close();
|
|
49
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('R44 Codex planner routes final answer by turn id and declines provider approvals',async()=>{
|
|
53
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-turn-'));const transport=new FakeTransport();
|
|
54
|
+
try{
|
|
55
|
+
const codex=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>transport,model:'gpt-test',cwd:root});
|
|
56
|
+
const start=codex.start();const init=await waitFor(()=>transport.request('initialize'));transport.reply(init.id,{});await start;
|
|
57
|
+
const ensure=codex.ensureThread({logicalId:'planner'});const ts=await waitFor(()=>transport.request('thread/start'));
|
|
58
|
+
transport.reply(ts.id,{thread:{id:'thread-1'},model:'gpt-test'});await ensure;
|
|
59
|
+
|
|
60
|
+
const planned=codex.runPlanner({logicalId:'planner',prompt:'Return JSON',timeoutMs:2000});
|
|
61
|
+
const turn=await waitFor(()=>transport.request('turn/start'));
|
|
62
|
+
assert.equal(turn.params.threadId,'thread-1');assert.deepEqual(turn.params.input,[{type:'text',text:'Return JSON'}]);
|
|
63
|
+
assert.equal(turn.params.approvalPolicy,'never');
|
|
64
|
+
transport.reply(turn.id,{turn:{id:'turn-1',status:'inProgress'}});
|
|
65
|
+
transport.notify('item/completed',{threadId:'thread-1',turnId:'other',item:{id:'x',type:'agentMessage',text:'wrong'}});
|
|
66
|
+
transport.notify('item/completed',{threadId:'thread-1',turnId:'turn-1',item:{id:'m1',type:'agentMessage',text:'working',phase:'commentary'}});
|
|
67
|
+
transport.notify('item/completed',{threadId:'thread-1',turnId:'turn-1',item:{id:'m2',type:'agentMessage',text:'{"schema":"ok"}',phase:'final_answer'}});
|
|
68
|
+
transport.notify('turn/completed',{threadId:'thread-1',turn:{id:'turn-1',status:'completed'}});
|
|
69
|
+
const result=await planned;
|
|
70
|
+
assert.equal(result.backend,'codex_app_server');assert.equal(result.raw_text,'{"schema":"ok"}');assert.equal(result.response_id,'turn-1');
|
|
71
|
+
|
|
72
|
+
transport.serverRequest('approval-1','item/commandExecution/requestApproval',{threadId:'thread-1',turnId:'turn-1'});
|
|
73
|
+
const denial=await waitFor(()=>transport.sent.find(x=>x.id==='approval-1'));
|
|
74
|
+
assert.deepEqual(denial.result,{decision:'decline'});assert.equal(denial.error,undefined);
|
|
75
|
+
await codex.close();
|
|
76
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('R44 Codex steer and interrupt bind exact active turn identity',async()=>{
|
|
80
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-control-'));const transport=new FakeTransport();
|
|
81
|
+
try{
|
|
82
|
+
const codex=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>transport,model:'gpt-test',cwd:root});
|
|
83
|
+
const start=codex.start();const init=await waitFor(()=>transport.request('initialize'));transport.reply(init.id,{});await start;
|
|
84
|
+
const steerP=codex.steer({threadId:'thread-1',turnId:'turn-1',text:'new direction',timeoutMs:2000});
|
|
85
|
+
const steer=await waitFor(()=>transport.request('turn/steer'));
|
|
86
|
+
assert.deepEqual(steer.params,{threadId:'thread-1',expectedTurnId:'turn-1',input:[{type:'text',text:'new direction'}]});
|
|
87
|
+
transport.reply(steer.id,{turnId:'turn-1'});await steerP;
|
|
88
|
+
const interruptP=codex.interrupt({threadId:'thread-1',turnId:'turn-1',timeoutMs:2000});
|
|
89
|
+
const interrupt=await waitFor(()=>transport.request('turn/interrupt'));
|
|
90
|
+
assert.deepEqual(interrupt.params,{threadId:'thread-1',turnId:'turn-1'});
|
|
91
|
+
transport.reply(interrupt.id,{});await interruptP;
|
|
92
|
+
await codex.close();
|
|
93
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('R44 Codex adapter resumes durable thread after backend process recreation',async()=>{
|
|
97
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-resume-'));
|
|
98
|
+
try{
|
|
99
|
+
const firstTransport=new FakeTransport();
|
|
100
|
+
const first=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>firstTransport,model:'gpt-test',cwd:root});
|
|
101
|
+
const s1=first.start();const i1=await waitFor(()=>firstTransport.request('initialize'));firstTransport.reply(i1.id,{});await s1;
|
|
102
|
+
const e1=first.ensureThread({logicalId:'planner'});const t1=await waitFor(()=>firstTransport.request('thread/start'));
|
|
103
|
+
firstTransport.reply(t1.id,{thread:{id:'thread-persist'},model:'gpt-test'});await e1;await first.close();
|
|
104
|
+
|
|
105
|
+
const secondTransport=new FakeTransport();
|
|
106
|
+
const second=await createCodexAppServerR44({stateDir:root,transportFactory:async()=>secondTransport,model:'gpt-test',cwd:root});
|
|
107
|
+
const s2=second.start();const i2=await waitFor(()=>secondTransport.request('initialize'));secondTransport.reply(i2.id,{});await s2;
|
|
108
|
+
const e2=second.ensureThread({logicalId:'planner'});const resume=await waitFor(()=>secondTransport.request('thread/resume'));
|
|
109
|
+
assert.equal(resume.params.threadId,'thread-persist');assert.equal(resume.params.approvalPolicy,'never');assert.equal(resume.params.sandbox,'read-only');
|
|
110
|
+
secondTransport.reply(resume.id,{thread:{id:'thread-persist'},model:'gpt-test'});
|
|
111
|
+
const result=await e2;assert.equal(result.thread_id,'thread-persist');assert.equal(result.resumed,true);
|
|
112
|
+
assert.equal(secondTransport.sent.some(x=>x.method==='thread/start'),false);
|
|
113
|
+
await second.close();
|
|
114
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
115
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
4
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
8
|
+
import { createCodingRuntime, loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
9
|
+
import { loadAutonomousPolicy } from '../src/autonomous-policy.mjs';
|
|
10
|
+
import { createNativeDiffRuntime } from '../src/native-diff.mjs';
|
|
11
|
+
import { createAutonomousRuntime } from '../src/autonomous-runtime.mjs';
|
|
12
|
+
import { providerCircuitId } from '../src/provider-circuit.mjs';
|
|
13
|
+
|
|
14
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
15
|
+
const nonce=()=>randomBytes(32).toString('hex');
|
|
16
|
+
|
|
17
|
+
async function fixture(){
|
|
18
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r44-codex-auto-'));
|
|
19
|
+
const project=path.join(root,'project'),trust=path.join(root,'trust');
|
|
20
|
+
await mkdir(project);await mkdir(trust);
|
|
21
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
|
|
22
|
+
const codingPath=path.join(root,'coding.json');
|
|
23
|
+
await writeFile(codingPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
24
|
+
lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'coding-evidence'),
|
|
25
|
+
roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:[]}},commands:{}},null,2));
|
|
26
|
+
const codingPolicy=await loadCodingPolicy(codingPath);
|
|
27
|
+
const nodeSha=sha(await readFile(process.execPath));
|
|
28
|
+
const autonomousPath=path.join(root,'autonomous.json');
|
|
29
|
+
await writeFile(autonomousPath,JSON.stringify({schema:'deadbyte.autonomous-policy.v1',enabled:true,
|
|
30
|
+
lease_file:path.join(root,'autonomous-grant.json'),evidence_dir:path.join(root,'autonomous-evidence'),
|
|
31
|
+
coding_policy_sha256:codingPolicy.policy_sha256,root_ids:['project'],
|
|
32
|
+
planner:{backend:'codex_app_server',model:'gpt-test',root_id:'project',executable:process.execPath,
|
|
33
|
+
executable_sha256:nodeSha,args:['app-server'],max_tokens:4096,timeout_ms:30000,
|
|
34
|
+
circuit:{failure_threshold:2,open_ms:1000,half_open_probe_limit:1}},
|
|
35
|
+
profiles:{},limits:{max_iterations:8,max_mutations:4,max_wall_ms:600000,max_context_bytes:262144,
|
|
36
|
+
max_diff_bytes:262144,max_files_per_diff:4,max_stalled_cycles:2,max_post_replan_stalls:2,max_replans:4}},null,2));
|
|
37
|
+
const policy=await loadAutonomousPolicy(autonomousPath,codingPolicy);
|
|
38
|
+
await writeFile(codingPolicy.lease_file,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:codingPolicy.policy_sha256,
|
|
39
|
+
instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()}));
|
|
40
|
+
await writeFile(policy.lease_file,JSON.stringify({schema:'deadbyte.autonomous-grant.v2',policy_sha256:policy.policy_sha256,
|
|
41
|
+
instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()}));
|
|
42
|
+
const codingRuntime=createCodingRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
43
|
+
const nativeDiffRuntime=createNativeDiffRuntime({policy,codingRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
44
|
+
return {root,project,keys,codingPolicy,policy,codingRuntime,nativeDiffRuntime,cleanup:()=>rm(root,{recursive:true,force:true})};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test('R44 policy binds Codex executable, model, root and circuit identity',async()=>{
|
|
48
|
+
const f=await fixture();
|
|
49
|
+
try{
|
|
50
|
+
assert.equal(f.policy.planner.backend,'codex_app_server');
|
|
51
|
+
assert.equal(f.policy.planner.root_id,'project');
|
|
52
|
+
assert.equal(f.policy.planner.executable,process.execPath);
|
|
53
|
+
assert.match(f.policy.planner.executable_sha256,/^[0-9a-f]{64}$/);
|
|
54
|
+
assert.deepEqual(f.policy.planner.args,['app-server']);
|
|
55
|
+
assert.match(providerCircuitId(f.policy.planner),/^codex_app_server:[0-9a-f]{64}$/);
|
|
56
|
+
}finally{await f.cleanup();}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('R44 autonomous runtime consumes injected Codex planner and does not ask client sampling',async()=>{
|
|
60
|
+
const f=await fixture();const calls=[];
|
|
61
|
+
try{
|
|
62
|
+
const plannerProviderRuntime={runPlanner:async({logicalId,prompt,timeoutMs})=>{
|
|
63
|
+
calls.push({logicalId,prompt,timeoutMs});
|
|
64
|
+
return {backend:'codex_app_server',model:'gpt-test',raw_text:JSON.stringify({
|
|
65
|
+
schema:'deadbyte.autonomous-decision.v2',kind:'plan_create',title:'Provider path',
|
|
66
|
+
reason:'prove Codex planner integration',steps:[{step_id:'prove',intent:'observe provider call'}]
|
|
67
|
+
}),response_id:'turn-test-1'};
|
|
68
|
+
}};
|
|
69
|
+
const runtime=createAutonomousRuntime({policy:f.policy,codingRuntime:f.codingRuntime,nativeDiffRuntime:f.nativeDiffRuntime,
|
|
70
|
+
plannerProviderRuntime,signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
|
|
71
|
+
const submitted=await runtime.submit({submissionId:'codex-plan',rootId:'project',title:'codex',goal:'prove provider path',acceptance:'pause'});
|
|
72
|
+
const result=await runtime.run({goalId:submitted.goal_id,maxCycles:1});
|
|
73
|
+
assert.notEqual(result.status,'input_required');
|
|
74
|
+
assert.equal(result.status,'yielded');
|
|
75
|
+
assert.equal(calls.length,1);
|
|
76
|
+
assert.equal(calls[0].logicalId,`goal-${submitted.goal_id}`);
|
|
77
|
+
assert.match(calls[0].prompt,/DEADBYTE/);
|
|
78
|
+
}finally{await f.cleanup();}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('R44 autonomous Codex policy fails closed without injected provider runtime',async()=>{
|
|
82
|
+
const f=await fixture();
|
|
83
|
+
try{
|
|
84
|
+
assert.throws(()=>createAutonomousRuntime({policy:f.policy,codingRuntime:f.codingRuntime,nativeDiffRuntime:f.nativeDiffRuntime,
|
|
85
|
+
signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath}),/codex.*provider/i);
|
|
86
|
+
}finally{await f.cleanup();}
|
|
87
|
+
});
|
|
@@ -19,7 +19,7 @@ const codingTools = [
|
|
|
19
19
|
'coding_task_events', 'coding_task_run', 'coding_task_status', 'coding_task_submit',
|
|
20
20
|
'coding_task_verify', 'coding_text_replace', 'coding_tree'
|
|
21
21
|
];
|
|
22
|
-
const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'workspace_open'];
|
|
22
|
+
const baseTools = ['contained_transform', 'evidence_verify', 'file_read', 'proof_execute', 'skill_catalog', 'workspace_open'];
|
|
23
23
|
const agentTools = [
|
|
24
24
|
'agent_task_approve', 'agent_task_cancel', 'agent_task_events', 'agent_task_run',
|
|
25
25
|
'agent_task_status', 'agent_task_submit', 'agent_task_verify'
|
|
@@ -60,6 +60,7 @@ async function fixture() {
|
|
|
60
60
|
DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
|
|
61
61
|
DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
|
|
62
62
|
DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
|
|
63
|
+
DEADBYTE_PRIVILEGED_POLICY: '',
|
|
63
64
|
DEADBYTE_AGENT_RUNTIME: '1',
|
|
64
65
|
DEADBYTE_CODING_RUNTIME: '1',
|
|
65
66
|
DEADBYTE_CODING_POLICY: policyPath
|
|
@@ -69,7 +70,7 @@ async function fixture() {
|
|
|
69
70
|
return { root, client, transport };
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
test('
|
|
73
|
+
test('R44 hermetic coding fixture exposes the exact 38-tool no-host catalog', async () => {
|
|
73
74
|
const f = await fixture();
|
|
74
75
|
try {
|
|
75
76
|
await f.client.connect(f.transport);
|