deadbyte-mcp 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +7 -0
- package/AGENTS.md +25 -0
- package/CONTEXT.md +3 -3
- package/MANIFEST.SHA256 +66 -40
- package/R42-PARENT.json +19 -0
- package/README.txt +2 -2
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/controller/README.TXT +1 -1
- package/controller/deadbyte-controller.ps1 +1 -1
- package/controller/deadbyte-desktop-policy.json +5 -5
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/ARCHITECTURE.md +1 -1
- package/harness/r42-agent-evals.json +48 -0
- package/harness/task-contract.schema.json +33 -0
- package/package.json +9 -7
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/agent-eval-r42.mjs +46 -0
- package/scripts/deferred-slot-operation-r42.mjs +126 -0
- package/scripts/final-closure-r42.mjs +116 -0
- package/scripts/final-closure-verify-r42.mjs +223 -0
- package/scripts/gate-windows-r42.ps1 +54 -0
- package/scripts/release-parity-r42.mjs +235 -0
- package/scripts/release-parity-tests-r42.ps1 +6 -0
- package/scripts/verify-r42-parent.mjs +101 -0
- package/scripts/windows-gate-evidence-r42.mjs +64 -0
- package/src/agent-eval-r42.mjs +73 -0
- package/src/agent-eval-runner-r42.mjs +138 -0
- package/src/agent-governance-r42.mjs +142 -0
- package/src/autonomous-context-engine.mjs +2 -0
- package/src/autonomous-loop-state.mjs +10 -0
- package/src/autonomous-mcp-tools.mjs +21 -6
- package/src/autonomous-output-contracts.mjs +18 -2
- package/src/autonomous-planner-contracts.mjs +3 -0
- package/src/autonomous-planner.mjs +46 -6
- package/src/autonomous-policy.mjs +10 -2
- package/src/autonomous-runtime.mjs +116 -16
- package/src/autonomous-supervisor.mjs +1 -1
- package/src/evidence-publisher-r42.mjs +76 -0
- package/src/mcp-server.mjs +1 -1
- package/src/provider-circuit.mjs +4 -4
- package/src/version.mjs +1 -1
- package/test/agent-eval-r42.test.mjs +39 -0
- package/test/agent-eval-runner-r42.test.mjs +61 -0
- package/test/agent-governance-r42.test.mjs +77 -0
- package/test/autonomous-r34-e2e.test.mjs +2 -1
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/core.test.mjs +1 -1
- package/test/evidence-publisher-r42.test.mjs +53 -0
- package/test/human-clarification-r42.test.mjs +53 -0
- package/test/provider-responses-r42.test.mjs +90 -0
- package/test/r33-closeout-regression.test.mjs +27 -12
- package/test/r33-finalization.test.mjs +3 -3
- package/test/r42-finalization.test.mjs +38 -0
- package/test/release-version.test.mjs +13 -13
- package/test/task-contract-r42.test.mjs +41 -0
|
@@ -25,6 +25,17 @@ const rmdirOperation = z.strictObject({ kind:z.literal('rmdir'), path:relPath, e
|
|
|
25
25
|
const nativeOperation = z.union([writeOperation,replaceOperation,deleteOperation,mkdirOperation,moveOperation,rmdirOperation]);
|
|
26
26
|
const plannerResponse = z.strictObject({ request_id:hex64, raw_text:z.string().min(2).max(8*1024*1024),
|
|
27
27
|
model:z.string().min(1).max(128).default('client-model') });
|
|
28
|
+
const taskContract = z.strictObject({
|
|
29
|
+
schema:z.literal('deadbyte.task-contract.v1'),
|
|
30
|
+
scope:z.array(z.string().min(1).max(512)).max(32).default([]),
|
|
31
|
+
non_goals:z.array(z.string().min(1).max(512)).max(24).default([]),
|
|
32
|
+
constraints:z.array(z.string().min(1).max(512)).max(32).default([]),
|
|
33
|
+
evidence_requirements:z.array(z.string().min(1).max(512)).max(32).default([]),
|
|
34
|
+
risk_level:z.enum(['low','medium','high']).default('medium'),
|
|
35
|
+
clarification_policy:z.enum(['ask_when_blocked','fail_closed']).default('ask_when_blocked'),
|
|
36
|
+
project_default:z.enum(['lightweight','structured','agentic']).default('structured'),
|
|
37
|
+
explicit_override:z.enum(['lightweight','structured','agentic']).nullable().default(null)
|
|
38
|
+
});
|
|
28
39
|
function result(schema,value,isError=false) { return autonomousStructuredResult(schema,value,isError); }
|
|
29
40
|
function fail(schema,error,status='failed') { return result(schema,{ status, error:String(error) },true); }
|
|
30
41
|
|
|
@@ -60,11 +71,12 @@ export function registerAutonomousTools(server,{ autonomousRuntime, nativeDiffRu
|
|
|
60
71
|
description:'Submit one bounded R24 production goal. Execution requires separate live coding and autonomous leases.',
|
|
61
72
|
inputSchema:z.strictObject({ submission_id:id, root_id:id, title:z.string().max(160).default(''),
|
|
62
73
|
goal:z.string().min(1).max(8192), acceptance:z.string().max(4096).default(''),
|
|
74
|
+
task_contract:taskContract.nullable().default(null),
|
|
63
75
|
max_iterations:z.number().int().min(1).max(64).nullable().default(null) }),
|
|
64
76
|
outputSchema:autonomousGoalSubmitOutputSchema
|
|
65
|
-
},async({submission_id,root_id,title,goal,acceptance,max_iterations})=>{ try {
|
|
77
|
+
},async({submission_id,root_id,title,goal,acceptance,task_contract,max_iterations})=>{ try {
|
|
66
78
|
return result(autonomousGoalSubmitOutputSchema,await autonomousRuntime.submit({ submissionId:submission_id,
|
|
67
|
-
rootId:root_id,title,goal,acceptance,maxIterations:max_iterations })); }
|
|
79
|
+
rootId:root_id,title,goal,acceptance,taskContract:task_contract,maxIterations:max_iterations })); }
|
|
68
80
|
catch(error){ return fail(autonomousGoalSubmitOutputSchema,error); } });
|
|
69
81
|
server.registerTool('autonomous_goal_run',{
|
|
70
82
|
description:'Advance one autonomous production goal through inspect/native-diff/build/test/benchmark/replan cycles. Client-planner mode returns a typed input_required sentinel for the Bridge MRTR adapter.',
|
|
@@ -100,11 +112,14 @@ export function registerAutonomousTools(server,{ autonomousRuntime, nativeDiffRu
|
|
|
100
112
|
catch(error){ return fail(autonomousGoalCancelOutputSchema,error); } });
|
|
101
113
|
|
|
102
114
|
server.registerTool('autonomous_goal_approve',{
|
|
103
|
-
description:'Approve or reject resuming a paused autonomous goal. This never arms or renews any lease and cannot bypass release evidence.',
|
|
104
|
-
inputSchema:z.strictObject({ goal_id:hex64, decision:z.enum(['approve','reject']).default('approve')
|
|
115
|
+
description:'Approve or reject resuming a paused autonomous goal, optionally supplying the bounded answer required by a typed human clarification. This never arms or renews any lease and cannot bypass release evidence.',
|
|
116
|
+
inputSchema:z.strictObject({ goal_id:hex64, decision:z.enum(['approve','reject']).default('approve'),
|
|
117
|
+
answer:z.string().min(1).max(4096).nullable().default(null),
|
|
118
|
+
approval_subject_sha256:hex64.nullable().default(null),
|
|
119
|
+
expires_in_ms:z.number().int().min(1000).max(3600000).default(300000) }),
|
|
105
120
|
outputSchema:autonomousGoalApproveOutputSchema
|
|
106
|
-
},async({goal_id,decision})=>{ try { return result(autonomousGoalApproveOutputSchema,
|
|
107
|
-
await autonomousRuntime.approve({goalId:goal_id,decision})); }
|
|
121
|
+
},async({goal_id,decision,answer,approval_subject_sha256,expires_in_ms})=>{ try { return result(autonomousGoalApproveOutputSchema,
|
|
122
|
+
await autonomousRuntime.approve({goalId:goal_id,decision,answer,approvalSubjectSha256:approval_subject_sha256,expiresInMs:expires_in_ms})); }
|
|
108
123
|
catch(error){ return fail(autonomousGoalApproveOutputSchema,error); } });
|
|
109
124
|
|
|
110
125
|
server.registerTool('autonomous_goal_verify',{
|
|
@@ -15,12 +15,28 @@ const loopProgress = z.strictObject({
|
|
|
15
15
|
const loopPendingAction = z.strictObject({
|
|
16
16
|
action_id:hex64, request_sha256:hex64, kind:z.string().regex(/^[A-Za-z0-9._-]{1,64}$/)
|
|
17
17
|
});
|
|
18
|
+
const loopClarification = z.strictObject({
|
|
19
|
+
question:z.string().min(1).max(1000), choices:z.array(z.string().min(1).max(256)).max(5)
|
|
20
|
+
});
|
|
21
|
+
const loopApprovalSubject = z.strictObject({
|
|
22
|
+
goal_id:hex64,reviewed_event_seq:z.number().int().min(1),reviewed_event_sha256:hex64,
|
|
23
|
+
journal_head_sha256:hex64,scope:z.enum(['resume_goal','human_clarification','desktop_resume','interrupted_effect_resume']),
|
|
24
|
+
subject:z.strictObject({reason:z.string().min(1).max(1024),waiting_payload_sha256:hex64}),approval_subject_sha256:hex64
|
|
25
|
+
});
|
|
26
|
+
const taskDagTask=z.strictObject({task_id:z.string().regex(/^[A-Za-z0-9._-]{1,64}$/),depends_on:z.array(z.string()).max(256),
|
|
27
|
+
input_artifacts:z.array(z.string()).max(128),output_artifacts:z.array(z.string()).max(128),
|
|
28
|
+
state:z.enum(['PENDING','RUNNING','COMPLETE','FAILED','STALE'])});
|
|
29
|
+
const taskDag=z.strictObject({schema:z.literal('deadbyte.task-dag.v1'),run_id:z.string().regex(/^[A-Za-z0-9._-]{1,96}$/),
|
|
30
|
+
goal_id:hex64,tasks:z.array(taskDagTask).min(1).max(256),dag_sha256:hex64});
|
|
18
31
|
const publicLoopSchema = z.strictObject({
|
|
19
32
|
schema:z.literal('deadbyte.autonomous-loop-state.v1'), phase:loopPhase,
|
|
20
33
|
plan_id:hex64.nullable(), plan_version:z.number().int().min(1).nullable(),
|
|
21
34
|
current_step:loopStep.nullable(), replan_count:z.number().int().min(0),
|
|
22
35
|
progress:loopProgress.nullable(), pending_action:loopPendingAction.nullable(),
|
|
23
|
-
pending_approval:z.boolean(), wait_reason:z.string().min(1).max(1024).nullable()
|
|
36
|
+
pending_approval:z.boolean(), wait_reason:z.string().min(1).max(1024).nullable(),
|
|
37
|
+
clarification:loopClarification.nullable().default(null),
|
|
38
|
+
approval_subject:loopApprovalSubject.nullable().default(null),
|
|
39
|
+
task_dag:taskDag.nullable().default(null)
|
|
24
40
|
});
|
|
25
41
|
function requireFields(value,ctx,fields) {
|
|
26
42
|
for (const field of fields) if (value[field] === undefined) ctx.addIssue({ code:'custom', path:[field], message:`${field} is required` });
|
|
@@ -52,7 +68,7 @@ export const autonomousSessionStatusOutputSchema = statusContract({
|
|
|
52
68
|
shape:{ configured:z.literal(true).optional(), armed:z.boolean().optional(), autonomous_armed:z.boolean().optional(),
|
|
53
69
|
autonomous_reason:z.string().optional(), autonomous_expires_at_utc:z.string().nullable().optional(),
|
|
54
70
|
coding_armed:z.boolean().optional(), coding_reason:z.string().optional(), root_ids:z.array(z.string()).max(64).optional(),
|
|
55
|
-
planner_backend:z.enum(['client_sampling','openai_compatible']).optional(), profile_ids:z.array(z.string()).max(128).optional(),
|
|
71
|
+
planner_backend:z.enum(['client_sampling','openai_compatible','openai_responses']).optional(), profile_ids:z.array(z.string()).max(128).optional(),
|
|
56
72
|
policy_sha256:hex64.optional(), coding_policy_sha256:hex64.optional() }
|
|
57
73
|
});
|
|
58
74
|
|
|
@@ -163,6 +163,9 @@ export const r34PlannerDecisionSchema = z.discriminatedUnion('kind',[
|
|
|
163
163
|
step_id:z.string().regex(ID),evidence:z.array(r34EvidenceSchema).min(1).max(32)}),
|
|
164
164
|
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('replan'),
|
|
165
165
|
reason:z.string().min(1).max(512),steps:z.array(r34PlanStepSchema).min(1).max(24)}),
|
|
166
|
+
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('clarify'),
|
|
167
|
+
question:z.string().min(1).max(1000),
|
|
168
|
+
choices:z.array(z.string().min(1).max(256)).min(2).max(5).default([])}),
|
|
166
169
|
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('pause'),
|
|
167
170
|
reason:z.string().min(1).max(1000)}),
|
|
168
171
|
z.strictObject({schema:z.literal('deadbyte.autonomous-decision.v2'),kind:z.literal('finish'),
|
|
@@ -276,7 +276,7 @@ export function parseR34PlannerDecision(rawText, policy) {
|
|
|
276
276
|
assertR34UniqueSteps(decision.steps,'R34 replan');
|
|
277
277
|
return decision;
|
|
278
278
|
}
|
|
279
|
-
if (decision.kind === 'pause' || decision.kind === 'finish') return decision;
|
|
279
|
+
if (decision.kind === 'clarify' || decision.kind === 'pause' || decision.kind === 'finish') return decision;
|
|
280
280
|
throw new Error('R34 planner decision kind unsupported');
|
|
281
281
|
}
|
|
282
282
|
|
|
@@ -363,7 +363,7 @@ export function buildPlannerPrompt({ goal, policy, view }) {
|
|
|
363
363
|
'You are the DEADBYTE R24 autonomous coding planner.',
|
|
364
364
|
'Return exactly one JSON object and no Markdown or prose outside JSON.',
|
|
365
365
|
`The object schema is ${AUTONOMOUS_DECISION_SCHEMA}.`,
|
|
366
|
-
'Allowed actions: inspect, patch, delegate, run_profile, finish, pause.',
|
|
366
|
+
'Allowed actions: inspect, patch, delegate, run_profile, clarify, finish, pause.',
|
|
367
367
|
'For nontrivial work, include plan={title,steps:[{step_id,title,status,result?}],reason?} on decisions and keep it authoritative across rounds.',
|
|
368
368
|
'Plan statuses must be a completed prefix, then at most one running/failed step, then pending suffix. Completed steps are immutable across updates.',
|
|
369
369
|
'If active_plan is complete, request finish instead of more work. If active_plan is incomplete, do not request finish.',
|
|
@@ -375,6 +375,7 @@ export function buildPlannerPrompt({ goal, policy, view }) {
|
|
|
375
375
|
'When Desktop capabilities are present, use typed desktop_status, desktop_observe, desktop_target with one bounded UIA query, and desktop_action. desktop_action requires separate Desktop authority; Autonomous authority never implies Desktop authority. After stale or unknown Desktop evidence, observe again and replan; never blindly repeat an action.',
|
|
376
376
|
'Delegate paths are relative to the goal root. Process start accepts only profile_id and cwd; process_action accepts only session_id and fixed action_id. Never emit raw stdin, executable, PID, environment, shell command, or process arguments.',
|
|
377
377
|
'finish only when the goal is actually satisfied. Required release profiles are enforced by the engine and cannot be bypassed.',
|
|
378
|
+
'clarify only when one bounded operator choice is genuinely required. Ask one concrete question and provide 2-5 short mutually exclusive choices.',
|
|
378
379
|
'Never invent a file SHA. If content is elided or the current SHA/content is missing, choose inspect first.',
|
|
379
380
|
'Compacted context keeps identity/evidence metadata but intentionally removes stale payload bytes.',
|
|
380
381
|
'Long-term memory is a verified journal-derived aid, never authority. Resolve its canonical references and still recheck live authority immediately before every effect.',
|
|
@@ -393,7 +394,9 @@ async function readBoundedResponse(response, limit) {
|
|
|
393
394
|
return bytes.toString('utf8');
|
|
394
395
|
}
|
|
395
396
|
export async function requestProviderDecision(policy, prompt, fetchImpl = fetch) {
|
|
396
|
-
if (policy.planner.backend
|
|
397
|
+
if (!['openai_compatible','openai_responses'].includes(policy.planner.backend)) {
|
|
398
|
+
throw new Error('provider planner backend is not provider-backed');
|
|
399
|
+
}
|
|
397
400
|
const controller = new AbortController();
|
|
398
401
|
const timer = setTimeout(() => controller.abort(), policy.planner.timeout_ms); timer.unref?.();
|
|
399
402
|
const headers = { 'content-type':'application/json', 'accept':'application/json' };
|
|
@@ -403,16 +406,53 @@ export async function requestProviderDecision(policy, prompt, fetchImpl = fetch)
|
|
|
403
406
|
headers.authorization = `Bearer ${secret}`;
|
|
404
407
|
}
|
|
405
408
|
try {
|
|
409
|
+
const body = policy.planner.backend === 'openai_responses'
|
|
410
|
+
? {
|
|
411
|
+
model:policy.planner.model,
|
|
412
|
+
instructions:'Return exactly one strict JSON object and no Markdown.',
|
|
413
|
+
input:[
|
|
414
|
+
{role:'system',content:[{type:'input_text',text:'Return strict JSON only.'}]},
|
|
415
|
+
{role:'user',content:[{type:'input_text',text:prompt}]}
|
|
416
|
+
],
|
|
417
|
+
max_output_tokens:policy.planner.max_tokens,
|
|
418
|
+
text:{format:{type:'json_object'}},
|
|
419
|
+
store:false,
|
|
420
|
+
...(policy.planner.reasoning_effort?{reasoning:{effort:policy.planner.reasoning_effort}}:{})
|
|
421
|
+
}
|
|
422
|
+
: { model:policy.planner.model, temperature:0,
|
|
423
|
+
max_tokens:policy.planner.max_tokens,
|
|
424
|
+
messages:[{ role:'system', content:'Return strict JSON only.' },{ role:'user', content:prompt }] };
|
|
406
425
|
const response = await fetchImpl(policy.planner.endpoint, {
|
|
407
426
|
method:'POST', redirect:'error', signal:controller.signal, headers,
|
|
408
|
-
body:JSON.stringify(
|
|
409
|
-
max_tokens:policy.planner.max_tokens,
|
|
410
|
-
messages:[{ role:'system', content:'Return strict JSON only.' },{ role:'user', content:prompt }] })
|
|
427
|
+
body:JSON.stringify(body)
|
|
411
428
|
});
|
|
412
429
|
const rawBody = await readBoundedResponse(response, policy.planner.max_response_bytes);
|
|
413
430
|
if (!response.ok) throw new Error(`planner HTTP ${response.status}: ${rawBody.slice(0,512)}`);
|
|
414
431
|
let parsed;
|
|
415
432
|
try { parsed = JSON.parse(rawBody); } catch { throw new Error('planner HTTP response is not JSON'); }
|
|
433
|
+
if (policy.planner.backend === 'openai_responses') {
|
|
434
|
+
if (parsed?.status !== 'completed') throw new Error(`planner Responses API response not completed: ${String(parsed?.status ?? 'missing')}`);
|
|
435
|
+
const content=[];
|
|
436
|
+
let refused=false;
|
|
437
|
+
for (const item of Array.isArray(parsed?.output) ? parsed.output : []) {
|
|
438
|
+
if (item?.type !== 'message') continue;
|
|
439
|
+
for (const part of Array.isArray(item.content) ? item.content : []) {
|
|
440
|
+
if (part?.type === 'refusal') refused=true;
|
|
441
|
+
if (part?.type === 'output_text' && typeof part.text === 'string') content.push(part.text);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (refused) throw new Error('planner Responses API refusal');
|
|
445
|
+
if (content.length < 1) throw new Error('planner Responses API response missing output_text');
|
|
446
|
+
const usage=parsed?.usage && Number.isInteger(parsed.usage.input_tokens) && Number.isInteger(parsed.usage.output_tokens) && Number.isInteger(parsed.usage.total_tokens)
|
|
447
|
+
? {input_tokens:parsed.usage.input_tokens,output_tokens:parsed.usage.output_tokens,total_tokens:parsed.usage.total_tokens}
|
|
448
|
+
: null;
|
|
449
|
+
return {
|
|
450
|
+
backend:'openai_responses', model:typeof parsed.model === 'string' ? parsed.model.slice(0,128) : policy.planner.model,
|
|
451
|
+
raw_text:content.join(''),
|
|
452
|
+
response_id:typeof parsed.id === 'string' ? parsed.id.slice(0,160) : null,
|
|
453
|
+
usage
|
|
454
|
+
};
|
|
455
|
+
}
|
|
416
456
|
const content = parsed?.choices?.[0]?.message?.content;
|
|
417
457
|
if (typeof content !== 'string') throw new Error('planner HTTP response missing choices[0].message.content');
|
|
418
458
|
return { backend:'openai_compatible', model:typeof parsed.model === 'string' ? parsed.model : policy.planner.model,
|
|
@@ -33,7 +33,7 @@ function normalizeProviderCircuit(raw) {
|
|
|
33
33
|
function normalizePlanner(raw) {
|
|
34
34
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) throw new Error('autonomous planner missing');
|
|
35
35
|
const backend = raw.backend;
|
|
36
|
-
if (!['client_sampling', 'openai_compatible'].includes(backend)) throw new Error('autonomous planner backend invalid');
|
|
36
|
+
if (!['client_sampling', 'openai_compatible', 'openai_responses'].includes(backend)) throw new Error('autonomous planner backend invalid');
|
|
37
37
|
const common = { backend, max_tokens: integer(raw.max_tokens, 'planner.max_tokens', 256, 16384, 4096) };
|
|
38
38
|
if (backend === 'client_sampling') return common;
|
|
39
39
|
if (typeof raw.endpoint !== 'string') throw new Error('planner.endpoint required');
|
|
@@ -46,12 +46,20 @@ function normalizePlanner(raw) {
|
|
|
46
46
|
if (typeof raw.model !== 'string' || raw.model.length < 1 || raw.model.length > 128) throw new Error('planner.model invalid');
|
|
47
47
|
const apiKeyEnv = raw.api_key_env ?? null;
|
|
48
48
|
if (apiKeyEnv !== null && !ENV_RE.test(apiKeyEnv)) throw new Error('planner.api_key_env invalid');
|
|
49
|
-
|
|
49
|
+
const provider = {
|
|
50
50
|
...common, endpoint: endpoint.toString(), model: raw.model, api_key_env: apiKeyEnv,
|
|
51
51
|
timeout_ms: integer(raw.timeout_ms, 'planner.timeout_ms', 1000, 300000, 60000),
|
|
52
52
|
max_response_bytes: integer(raw.max_response_bytes, 'planner.max_response_bytes', 4096, 2 * 1024 * 1024, 512 * 1024),
|
|
53
53
|
circuit:normalizeProviderCircuit(raw.circuit)
|
|
54
54
|
};
|
|
55
|
+
if (backend === 'openai_responses') {
|
|
56
|
+
const effort = raw.reasoning_effort ?? null;
|
|
57
|
+
if (effort !== null && !['minimal','low','medium','high','xhigh'].includes(effort)) {
|
|
58
|
+
throw new Error('planner.reasoning_effort invalid');
|
|
59
|
+
}
|
|
60
|
+
return { ...provider, reasoning_effort:effort, store:false };
|
|
61
|
+
}
|
|
62
|
+
return provider;
|
|
55
63
|
}
|
|
56
64
|
function normalizeProfile(id, raw, codingPolicy) {
|
|
57
65
|
if (!ID_RE.test(id)) throw new Error(`invalid autonomous profile '${id}'`);
|
|
@@ -10,6 +10,7 @@ import { createPlan, replanPlan, completePlanStep, planIsComplete as r34PlanIsCo
|
|
|
10
10
|
import { deriveLoopState, repairFingerprint } from './autonomous-loop-state.mjs';
|
|
11
11
|
import { judgeProgress, stallDisposition } from './autonomous-progress.mjs';
|
|
12
12
|
import { classifyProviderFailure, deriveCircuit, providerCallDisposition, providerCircuitId } from './provider-circuit.mjs';
|
|
13
|
+
import { bindApprovalSubject, deriveEffectiveRigor, taskDagFromPlan, validateBoundApproval } from './agent-governance-r42.mjs';
|
|
13
14
|
import { verifyObservationReceiptFile } from './observation-runtime.mjs';
|
|
14
15
|
import { R36_CAPABILITY_LEDGER } from './capability-ledger.mjs';
|
|
15
16
|
import { deriveAgentMemory } from './agent-memory-plane.mjs';
|
|
@@ -129,6 +130,11 @@ function deriveState(goal, events) {
|
|
|
129
130
|
const binding = { ...p, event_sha256:event.event_sha256 };
|
|
130
131
|
observations.push(binding);
|
|
131
132
|
}
|
|
133
|
+
if (event.type === 'human_input_provided') {
|
|
134
|
+
observations.push({kind:'human_input',question:p.question,answer:p.answer,
|
|
135
|
+
answer_sha256:p.answer_sha256,trust_class:'operator_input',observed_at_utc:event.created_at_utc,
|
|
136
|
+
event_sha256:event.event_sha256});
|
|
137
|
+
}
|
|
132
138
|
if (event.type === 'native_diff_committed') {
|
|
133
139
|
mutationEpoch = Number(p.mutation_epoch ?? mutationEpoch + 1); mutationCount += Number(p.mutation_count ?? 1);
|
|
134
140
|
diffs.push({ ...p, event_sha256:event.event_sha256 });
|
|
@@ -325,7 +331,31 @@ function releaseReadiness(policy, state) {
|
|
|
325
331
|
});
|
|
326
332
|
return { ready:results.every(item => item.passed), mutation_epoch:state.mutation_epoch, required_profiles:results };
|
|
327
333
|
}
|
|
328
|
-
function
|
|
334
|
+
function normalizeTaskContract(raw) {
|
|
335
|
+
const value=raw ?? {schema:'deadbyte.task-contract.v1',scope:[],non_goals:[],constraints:[],
|
|
336
|
+
evidence_requirements:[],risk_level:'medium',clarification_policy:'ask_when_blocked',
|
|
337
|
+
project_default:'structured',explicit_override:null};
|
|
338
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('autonomous task contract invalid');
|
|
339
|
+
const allowed=['schema','scope','non_goals','constraints','evidence_requirements','risk_level','clarification_policy','project_default','explicit_override'];
|
|
340
|
+
const unknown=Object.keys(value).filter(key=>!allowed.includes(key));
|
|
341
|
+
if (unknown.length) throw new Error(`autonomous task contract unknown field '${unknown[0]}'`);
|
|
342
|
+
if (value.schema !== 'deadbyte.task-contract.v1') throw new Error('autonomous task contract schema invalid');
|
|
343
|
+
const list=(field,maxItems,maxLength)=>{
|
|
344
|
+
const items=value[field] ?? [];
|
|
345
|
+
if (!Array.isArray(items) || items.length > maxItems || items.some(item=>typeof item !== 'string' || item.length < 1 || item.length > maxLength || item.includes('\0'))) {
|
|
346
|
+
throw new Error(`autonomous task contract ${field} invalid`);
|
|
347
|
+
}
|
|
348
|
+
return [...items];
|
|
349
|
+
};
|
|
350
|
+
if (!['low','medium','high'].includes(value.risk_level)) throw new Error('autonomous task contract risk_level invalid');
|
|
351
|
+
if (!['ask_when_blocked','fail_closed'].includes(value.clarification_policy)) throw new Error('autonomous task contract clarification_policy invalid');
|
|
352
|
+
const rigor=deriveEffectiveRigor({project_default:value.project_default??'structured',risk_level:value.risk_level,
|
|
353
|
+
explicit_override:value.explicit_override??null});
|
|
354
|
+
return Object.freeze({schema:value.schema,scope:list('scope',32,512),non_goals:list('non_goals',24,512),
|
|
355
|
+
constraints:list('constraints',32,512),evidence_requirements:list('evidence_requirements',32,512),
|
|
356
|
+
risk_level:value.risk_level,clarification_policy:value.clarification_policy,...rigor});
|
|
357
|
+
}
|
|
358
|
+
function normalizeGoal(policy, { submissionId, rootId, title = '', goal, acceptance = '', taskContract = null, maxIterations = null }) {
|
|
329
359
|
if (!ID_RE.test(submissionId ?? '')) throw new Error('autonomous submission_id invalid');
|
|
330
360
|
if (!policy.root_ids.includes(rootId)) throw new Error(`autonomous root '${rootId}' is not allowed`);
|
|
331
361
|
if (typeof title !== 'string' || title.length > 160) throw new Error('autonomous title must be <=160 characters');
|
|
@@ -335,7 +365,7 @@ function normalizeGoal(policy, { submissionId, rootId, title = '', goal, accepta
|
|
|
335
365
|
if (!Number.isInteger(iterations) || iterations < 1 || iterations > policy.limits.max_iterations) throw new Error('autonomous max_iterations exceeds policy');
|
|
336
366
|
const body = { schema:AUTONOMOUS_GOAL_SCHEMA, policy_sha256:policy.policy_sha256,
|
|
337
367
|
coding_policy_sha256:policy.coding_policy_sha256, submission_id:submissionId, root_id:rootId,
|
|
338
|
-
title, goal, acceptance, max_iterations:iterations };
|
|
368
|
+
title, goal, acceptance, task_contract:normalizeTaskContract(taskContract),max_iterations:iterations };
|
|
339
369
|
return Object.freeze({ ...body, goal_sha256:sha256Object(body) });
|
|
340
370
|
}
|
|
341
371
|
|
|
@@ -389,7 +419,8 @@ async function requestPlanner(ctx, goal, events, state, policy, codingRuntime, c
|
|
|
389
419
|
return null;
|
|
390
420
|
}
|
|
391
421
|
const view = await plannerView(goal,state,events,codingRuntime,capabilityManifest,desktopMemoryProvider,memoryProvider);
|
|
392
|
-
let prompt = buildPlannerPrompt({ goal:{ title:goal.title, goal:goal.goal, acceptance:goal.acceptance
|
|
422
|
+
let prompt = buildPlannerPrompt({ goal:{ goal_id:goal.goal_id,title:goal.title, goal:goal.goal, acceptance:goal.acceptance,
|
|
423
|
+
task_contract:goal.task_contract }, policy, view });
|
|
393
424
|
const repair = [...(state.planner_rejections ?? [])].reverse().find(item => item.planner_round === round) ?? null;
|
|
394
425
|
if (repair) {
|
|
395
426
|
const repairState = canonicalJson({ request_id:repair.request_id, response_sha256:repair.response_sha256,
|
|
@@ -452,7 +483,24 @@ function legacyPublicLoopPhase(phase) {
|
|
|
452
483
|
return ({PLAN:'PLANNING',INSPECT:'INSPECTING',ACT:'ACTING',VERIFY:'VERIFYING',CRITIC:'REPLANNING',
|
|
453
484
|
REPAIR:'REPLANNING',RELEASE:'VERIFYING',WAITING:'WAITING',SUCCEEDED:'COMPLETE',FAILED:'WAITING'})[phase] ?? 'WAITING';
|
|
454
485
|
}
|
|
455
|
-
function
|
|
486
|
+
function approvalScopeFor(reason){
|
|
487
|
+
if(reason==='human_input_required') return 'human_clarification';
|
|
488
|
+
if(String(reason??'').startsWith('desktop_')) return 'desktop_resume';
|
|
489
|
+
if(['ambiguous_interrupted_action','profile_execution_without_receipt'].includes(reason)) return 'interrupted_effect_resume';
|
|
490
|
+
return 'resume_goal';
|
|
491
|
+
}
|
|
492
|
+
function pendingApprovalSubject(goal,events,loop){
|
|
493
|
+
if(!loop?.pending_approval) return null;
|
|
494
|
+
const waiting=[...events].reverse().find(event=>event.type==='waiting_entered')??null;
|
|
495
|
+
if(!waiting) return null;
|
|
496
|
+
const payload=waiting.payload??{};
|
|
497
|
+
return bindApprovalSubject({
|
|
498
|
+
goal_id:goal.goal_id,reviewed_event_seq:waiting.seq,reviewed_event_sha256:waiting.event_sha256,
|
|
499
|
+
journal_head_sha256:events.at(-1)?.event_sha256??ZERO_HASH,scope:approvalScopeFor(payload.reason),
|
|
500
|
+
subject:{reason:payload.reason??loop.wait_reason??'paused',waiting_payload_sha256:sha256Object(payload)}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
function publicLoopState(loop,goal=null,events=null) {
|
|
456
504
|
if (!loop) return null;
|
|
457
505
|
const currentStep=loop.current_step ? {
|
|
458
506
|
step_id:loop.current_step.step_id,intent:loop.current_step.intent,status:loop.current_step.status
|
|
@@ -468,15 +516,18 @@ function publicLoopState(loop) {
|
|
|
468
516
|
return {
|
|
469
517
|
schema:'deadbyte.autonomous-loop-state.v1',phase:legacyPublicLoopPhase(loop.phase),plan_id:loop.plan_id,plan_version:loop.plan_version,
|
|
470
518
|
current_step:currentStep,replan_count:loop.replan_count,progress,
|
|
471
|
-
pending_action:pendingAction,pending_approval:loop.pending_approval,wait_reason:loop.wait_reason
|
|
519
|
+
pending_action:pendingAction,pending_approval:loop.pending_approval,wait_reason:loop.wait_reason,
|
|
520
|
+
clarification:loop.clarification ?? null,
|
|
521
|
+
approval_subject:goal&&events?pendingApprovalSubject(goal,events,loop):null,
|
|
522
|
+
task_dag:goal&&loop.plan?taskDagFromPlan({run_id:goal.goal_id,goal_id:goal.goal_id,plan:loop.plan}):null
|
|
472
523
|
};
|
|
473
524
|
}
|
|
474
525
|
function withLoopResult(goal,events,result) {
|
|
475
|
-
const loop=publicLoopState(loopStateOrNull(goal,events));
|
|
526
|
+
const loop=publicLoopState(loopStateOrNull(goal,events),goal,events);
|
|
476
527
|
return loop ? {...result,loop} : result;
|
|
477
528
|
}
|
|
478
529
|
function providerCircuitSnapshot(policy,events,observedAtUtc=new Date().toISOString()) {
|
|
479
|
-
if (policy.planner.backend
|
|
530
|
+
if (!['openai_compatible','openai_responses'].includes(policy.planner.backend)) return null;
|
|
480
531
|
return deriveCircuit(events,providerCircuitId(policy.planner),policy.planner.circuit,observedAtUtc);
|
|
481
532
|
}
|
|
482
533
|
async function enterProviderCircuitWait({ctx,events,goal,policy,signingKeyPath,reason,circuit}) {
|
|
@@ -517,7 +568,9 @@ async function requestProviderWithCircuit({ctx,events,goal,policy,pending,signin
|
|
|
517
568
|
try {
|
|
518
569
|
const response=await requestProviderDecision(policy,pending.prompt);
|
|
519
570
|
await appendEvent(ctx,events,signingKeyPath,'provider_call_succeeded',{
|
|
520
|
-
provider_id:providerId,request_id:pending.request_id,probe:disposition.probe===true
|
|
571
|
+
provider_id:providerId,request_id:pending.request_id,probe:disposition.probe===true,
|
|
572
|
+
...(response.response_id?{response_id:response.response_id}:{}),
|
|
573
|
+
...(response.usage?{usage:response.usage}:{})
|
|
521
574
|
});
|
|
522
575
|
return {response};
|
|
523
576
|
} catch (error) {
|
|
@@ -543,7 +596,7 @@ async function acceptPlannerDecision({ ctx, events, goal, state, policy, codingR
|
|
|
543
596
|
let suppliedResponse = plannerResponse;
|
|
544
597
|
while (pending) {
|
|
545
598
|
let response;
|
|
546
|
-
if (policy.planner.backend
|
|
599
|
+
if (['openai_compatible','openai_responses'].includes(policy.planner.backend)) {
|
|
547
600
|
const provider=await requestProviderWithCircuit({ctx,events,goal,policy,pending,signingKeyPath});
|
|
548
601
|
if (provider.waiting) return {needs_planner:false,rejected:false,paused:true,provider_waiting:true};
|
|
549
602
|
response=provider.response;
|
|
@@ -585,6 +638,8 @@ async function acceptPlannerDecision({ ctx, events, goal, state, policy, codingR
|
|
|
585
638
|
const event = await appendEvent(ctx,events,signingKeyPath,'planner_decision',{
|
|
586
639
|
request_id:pending.request_id, planner_round:pending.planner_round, mutation_epoch:pending.mutation_epoch,
|
|
587
640
|
backend:response.backend, model:response.model ?? 'unknown', response_sha256:sha256Bytes(Buffer.from(response.raw_text,'utf8')),
|
|
641
|
+
...(response.response_id?{response_id:response.response_id}:{}),
|
|
642
|
+
...(response.usage?{usage:response.usage}:{}),
|
|
588
643
|
...(pending.working_memory_sha256?{
|
|
589
644
|
working_memory_sha256:pending.working_memory_sha256,memory_snapshot_sha256:pending.memory_snapshot_sha256,
|
|
590
645
|
memory_index_sha256:pending.memory_index_sha256,
|
|
@@ -1506,6 +1561,20 @@ async function executeR34Decision({ctx,events,goal,state,decisionEvent,decision,
|
|
|
1506
1561
|
return r34EnterWaiting({ctx,events,goal,signingKeyPath,reason:decision.reason,resumePhase:loop.phase});
|
|
1507
1562
|
}
|
|
1508
1563
|
|
|
1564
|
+
if (decision.kind === 'clarify') {
|
|
1565
|
+
await appendEvent(ctx,events,signingKeyPath,'human_input_requested',{
|
|
1566
|
+
decision_event_sha256:decisionEvent.event_sha256,question:decision.question,choices:decision.choices
|
|
1567
|
+
});
|
|
1568
|
+
await appendEvent(ctx,events,signingKeyPath,'waiting_entered',{
|
|
1569
|
+
reason:'human_input_required',requires_approval:true,resume_phase:'PLAN',
|
|
1570
|
+
question:decision.question,choices:decision.choices
|
|
1571
|
+
});
|
|
1572
|
+
await appendEvent(ctx,events,signingKeyPath,'goal_paused',{
|
|
1573
|
+
reason:'human_input_required',decision_event_sha256:decisionEvent.event_sha256
|
|
1574
|
+
});
|
|
1575
|
+
return {status:'paused',reason:'human_input_required'};
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1509
1578
|
if (decision.kind === 'finish') {
|
|
1510
1579
|
if (!r34PlanIsComplete(loop.plan)) {
|
|
1511
1580
|
await appendEvent(ctx,events,signingKeyPath,'plan_guard_blocked',{
|
|
@@ -1676,7 +1745,7 @@ function terminalState(state) {
|
|
|
1676
1745
|
}
|
|
1677
1746
|
function pauseState(state) { return state.state === 'paused'; }
|
|
1678
1747
|
async function resumeProviderWaitIfReady({ctx,journal,goal,state,policy,signingKeyPath}) {
|
|
1679
|
-
if (state.state!=='paused'||state.reason!=='provider_circuit_open'
|
|
1748
|
+
if (state.state!=='paused'||state.reason!=='provider_circuit_open'||!['openai_compatible','openai_responses'].includes(policy.planner.backend)) return state;
|
|
1680
1749
|
const circuit=providerCircuitSnapshot(policy,journal);
|
|
1681
1750
|
const disposition=providerCallDisposition(circuit);
|
|
1682
1751
|
if (!disposition.allowed||circuit.state!=='HALF_OPEN') return state;
|
|
@@ -1715,7 +1784,7 @@ export function createAutonomousRuntime({ policy, codingRuntime, nativeDiffRunti
|
|
|
1715
1784
|
coding_policy_sha256:policy.coding_policy_sha256 };
|
|
1716
1785
|
}
|
|
1717
1786
|
async function providerCircuitStatus({goalId}) {
|
|
1718
|
-
if (policy.planner.backend
|
|
1787
|
+
if (!['openai_compatible','openai_responses'].includes(policy.planner.backend)) return null;
|
|
1719
1788
|
const ctx=goalContext(policy,goalId);
|
|
1720
1789
|
await loadGoal(ctx,policy,verifyKeyPath);
|
|
1721
1790
|
const journal=await readEvents(ctx,verifyKeyPath);
|
|
@@ -1730,9 +1799,9 @@ export function createAutonomousRuntime({ policy, codingRuntime, nativeDiffRunti
|
|
|
1730
1799
|
return { autonomous, coding };
|
|
1731
1800
|
}
|
|
1732
1801
|
|
|
1733
|
-
async function submit({ submissionId, rootId, title = '', goal, acceptance = '', maxIterations = null }) {
|
|
1802
|
+
async function submit({ submissionId, rootId, title = '', goal, acceptance = '', taskContract = null, maxIterations = null }) {
|
|
1734
1803
|
await assertRunAuthority(rootId);
|
|
1735
|
-
const normalized = normalizeGoal(policy,{ submissionId,rootId,title,goal,acceptance,maxIterations });
|
|
1804
|
+
const normalized = normalizeGoal(policy,{ submissionId,rootId,title,goal,acceptance,taskContract,maxIterations });
|
|
1736
1805
|
const goalId = goalIdFor(policy.policy_sha256,submissionId);
|
|
1737
1806
|
const ctx = goalContext(policy,goalId);
|
|
1738
1807
|
if (await exists(ctx.dir)) {
|
|
@@ -1749,7 +1818,8 @@ export function createAutonomousRuntime({ policy, codingRuntime, nativeDiffRunti
|
|
|
1749
1818
|
await writeFile(ctx.goalPath,`${canonicalJson(envelope)}\n`,{flag:'wx'});
|
|
1750
1819
|
const journal = [];
|
|
1751
1820
|
await appendEvent(ctx,journal,signingKeyPath,'goal_created',{
|
|
1752
|
-
submission_id:submissionId, root_id:rootId, title,
|
|
1821
|
+
submission_id:submissionId, root_id:rootId, title, acceptance:normalized.acceptance,
|
|
1822
|
+
task_contract:normalized.task_contract,goal_sha256:normalized.goal_sha256,
|
|
1753
1823
|
max_iterations:normalized.max_iterations,
|
|
1754
1824
|
...(memoryProvider?{source_release_manifest:memoryProvider.source_release_manifest}:{})
|
|
1755
1825
|
});
|
|
@@ -1761,7 +1831,7 @@ export function createAutonomousRuntime({ policy, codingRuntime, nativeDiffRunti
|
|
|
1761
1831
|
const goal = await loadGoal(ctx,policy,verifyKeyPath);
|
|
1762
1832
|
const journal = await readEvents(ctx,verifyKeyPath);
|
|
1763
1833
|
const derived = deriveState(goal,journal);
|
|
1764
|
-
const loop = publicLoopState(loopStateOrNull(goal,journal));
|
|
1834
|
+
const loop = publicLoopState(loopStateOrNull(goal,journal),goal,journal);
|
|
1765
1835
|
const lock=await runLockStatus(ctx);
|
|
1766
1836
|
return { status:'ok', goal_id:goalId, goal_sha256:goal.goal_sha256,
|
|
1767
1837
|
title:goal.title, root_id:goal.root_id, journal_verified:true, ...derived, ...lock,
|
|
@@ -1795,8 +1865,11 @@ export function createAutonomousRuntime({ policy, codingRuntime, nativeDiffRunti
|
|
|
1795
1865
|
return { status:'existing', goal_id:goalId, state:current.state };
|
|
1796
1866
|
}
|
|
1797
1867
|
}
|
|
1798
|
-
async function approve({ goalId, decision = 'approve' }) {
|
|
1868
|
+
async function approve({ goalId, decision = 'approve', answer = null, approvalSubjectSha256 = null, expiresInMs = 300000 }) {
|
|
1799
1869
|
if (!['approve','reject'].includes(decision)) throw new Error('autonomous approval decision invalid');
|
|
1870
|
+
if (answer !== null && (typeof answer !== 'string' || answer.length < 1 || answer.length > 4096 || answer.includes('\0'))) {
|
|
1871
|
+
throw new Error('autonomous approval answer invalid');
|
|
1872
|
+
}
|
|
1800
1873
|
const ctx = goalContext(policy,goalId);
|
|
1801
1874
|
await acquireLock(ctx);
|
|
1802
1875
|
try {
|
|
@@ -1811,7 +1884,34 @@ export function createAutonomousRuntime({ policy, codingRuntime, nativeDiffRunti
|
|
|
1811
1884
|
}
|
|
1812
1885
|
if (hasR34Loop(journal)) {
|
|
1813
1886
|
await assertRunAuthority(goal.root_id);
|
|
1887
|
+
const loopState=loopStateOrNull(goal,journal);
|
|
1888
|
+
const approvalSubject=pendingApprovalSubject(goal,journal,loopState);
|
|
1889
|
+
if(!approvalSubject) throw new Error('autonomous approval subject unavailable');
|
|
1890
|
+
if(typeof approvalSubjectSha256!=='string'||approvalSubjectSha256!==approvalSubject.approval_subject_sha256){
|
|
1891
|
+
throw new Error('autonomous approval subject mismatch');
|
|
1892
|
+
}
|
|
1893
|
+
if(!Number.isInteger(expiresInMs)||expiresInMs<1000||expiresInMs>3_600_000) throw new Error('autonomous approval lifetime invalid');
|
|
1814
1894
|
const waiting=[...journal].reverse().find(event=>event.type==='waiting_entered') ?? null;
|
|
1895
|
+
if (waiting?.payload?.reason === 'human_input_required' && answer === null) {
|
|
1896
|
+
throw new Error('autonomous clarification answer is required');
|
|
1897
|
+
}
|
|
1898
|
+
if (waiting?.payload?.reason !== 'human_input_required' && answer !== null) {
|
|
1899
|
+
throw new Error('autonomous approval answer is only valid for human clarification');
|
|
1900
|
+
}
|
|
1901
|
+
const issuedAt=new Date();
|
|
1902
|
+
const approval={schema:'deadbyte.bound-approval.v1',approval_subject_sha256:approvalSubjectSha256,
|
|
1903
|
+
reviewed_event_seq:approvalSubject.reviewed_event_seq,reviewed_event_sha256:approvalSubject.reviewed_event_sha256,
|
|
1904
|
+
journal_head_sha256:approvalSubject.journal_head_sha256,scope:approvalSubject.scope,
|
|
1905
|
+
issued_at_utc:issuedAt.toISOString(),expires_at_utc:new Date(issuedAt.getTime()+expiresInMs).toISOString(),
|
|
1906
|
+
provenance:'operator_mcp'};
|
|
1907
|
+
validateBoundApproval({approval,subject:approvalSubject,now:issuedAt});
|
|
1908
|
+
await appendEvent(ctx,journal,signingKeyPath,'approval_granted',approval);
|
|
1909
|
+
if (waiting?.payload?.reason === 'human_input_required') {
|
|
1910
|
+
await appendEvent(ctx,journal,signingKeyPath,'human_input_provided',{
|
|
1911
|
+
request_event_sha256:waiting.event_sha256,question:waiting.payload.question,
|
|
1912
|
+
answer,answer_sha256:sha256Bytes(Buffer.from(answer,'utf8'))
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1815
1915
|
const resumePhase=typeof waiting?.payload?.resume_phase === 'string' ? waiting.payload.resume_phase : 'PLAN';
|
|
1816
1916
|
await appendEvent(ctx,journal,signingKeyPath,'waiting_resumed',{
|
|
1817
1917
|
to:resumePhase,reason:waiting?.payload?.reason ?? current.reason ?? 'operator_approved',
|
|
@@ -24,7 +24,7 @@ async function listGoalIds(policy) {
|
|
|
24
24
|
export function startAutonomousSupervisor({ runtime, policy = runtime?.policy,
|
|
25
25
|
idleDelayMs = 750, maxCycles = 32, logger = console, continuous = true } = {}) {
|
|
26
26
|
if (!runtime || !policy) throw new Error('autonomous supervisor requires runtime + policy');
|
|
27
|
-
const providerBacked = policy.planner.backend
|
|
27
|
+
const providerBacked = ['openai_compatible','openai_responses'].includes(policy.planner.backend);
|
|
28
28
|
if (!Number.isInteger(idleDelayMs) || idleDelayMs < 25 || idleDelayMs > 60_000) {
|
|
29
29
|
throw new Error('autonomous supervisor idleDelayMs out of range');
|
|
30
30
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { link, mkdir, open, readFile, realpath, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson } from './canonical-json.mjs';
|
|
5
|
+
|
|
6
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
7
|
+
const ID=/^[A-Za-z0-9._-]{1,96}$/;
|
|
8
|
+
const shaBytes=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
9
|
+
const sha=value=>shaBytes(Buffer.from(canonicalJson(value),'utf8'));
|
|
10
|
+
function object(value,label){if(!value||typeof value!=='object'||Array.isArray(value))throw new Error(`${label} invalid`);return value;}
|
|
11
|
+
function keys(value,allowed,label){const extra=Object.keys(value).filter(key=>!allowed.includes(key));if(extra.length)throw new Error(`${label} unknown field '${extra[0]}'`);}
|
|
12
|
+
function id(value,label){if(typeof value!=='string'||!ID.test(value))throw new Error(`${label} invalid`);return value;}
|
|
13
|
+
function hex(value,label){if(typeof value!=='string'||!HEX64.test(value))throw new Error(`${label} invalid`);return value;}
|
|
14
|
+
function iso(value,label){if(typeof value!=='string'||!Number.isFinite(Date.parse(value)))throw new Error(`${label} invalid`);return value;}
|
|
15
|
+
|
|
16
|
+
export async function publishEvidenceAtomic({root,kind,evidenceId=null,payload}){
|
|
17
|
+
if(typeof root!=='string'||!path.isAbsolute(root)) throw new Error('evidence root must be absolute');
|
|
18
|
+
id(kind,'evidence kind');object(payload,'evidence payload');
|
|
19
|
+
const payloadSha=sha(payload);
|
|
20
|
+
const resolvedId=id(evidenceId??payloadSha,'evidence id');
|
|
21
|
+
const body={schema:'deadbyte.evidence-publication.v1',kind,evidence_id:resolvedId,payload_sha256:payloadSha,payload:structuredClone(payload)};
|
|
22
|
+
const envelope={...body,evidence_sha256:sha(body)};
|
|
23
|
+
const bytes=Buffer.from(`${canonicalJson(envelope)}\n`,'utf8');
|
|
24
|
+
const base=path.resolve(root);await mkdir(base,{recursive:true});
|
|
25
|
+
const canonicalRoot=await realpath(base);
|
|
26
|
+
const pendingDir=path.join(canonicalRoot,'.pending');const finalDir=path.join(canonicalRoot,kind);
|
|
27
|
+
await mkdir(pendingDir,{recursive:true});await mkdir(finalDir,{recursive:true});
|
|
28
|
+
const target=path.join(finalDir,`${resolvedId}.json`);
|
|
29
|
+
const temp=path.join(pendingDir,`${kind}-${resolvedId}-${randomUUID()}.tmp`);
|
|
30
|
+
let handle;
|
|
31
|
+
try{
|
|
32
|
+
handle=await open(temp,'wx',0o600);await handle.writeFile(bytes);await handle.sync();await handle.close();handle=null;
|
|
33
|
+
const staged=await readFile(temp);
|
|
34
|
+
if(shaBytes(staged)!==shaBytes(bytes)) throw new Error('staged evidence hash verification failed');
|
|
35
|
+
try{
|
|
36
|
+
await link(temp,target);
|
|
37
|
+
return Object.freeze({status:'created',kind,evidence_id:resolvedId,evidence_sha256:envelope.evidence_sha256,
|
|
38
|
+
bytes:bytes.length,path:target});
|
|
39
|
+
}catch(error){
|
|
40
|
+
if(error?.code!=='EEXIST') throw error;
|
|
41
|
+
const existing=await readFile(target);
|
|
42
|
+
if(!existing.equals(bytes)){
|
|
43
|
+
const conflict=new Error(`EVIDENCE_ID_CONFLICT: ${kind}/${resolvedId}`);conflict.code='EVIDENCE_ID_CONFLICT';throw conflict;
|
|
44
|
+
}
|
|
45
|
+
return Object.freeze({status:'existing',kind,evidence_id:resolvedId,evidence_sha256:envelope.evidence_sha256,
|
|
46
|
+
bytes:bytes.length,path:target});
|
|
47
|
+
}
|
|
48
|
+
}finally{
|
|
49
|
+
if(handle) await handle.close().catch(()=>{});
|
|
50
|
+
await rm(temp,{force:true}).catch(()=>{});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createExecutionReceipt(raw){
|
|
55
|
+
const value=object(raw,'execution receipt');
|
|
56
|
+
keys(value,['execution_id','subject_sha256','request_sha256','observed_exit_code','started_at_utc','completed_at_utc'],'execution receipt');
|
|
57
|
+
const core={schema:'deadbyte.execution-receipt.v1',execution_id:id(value.execution_id,'execution_id'),
|
|
58
|
+
subject_sha256:hex(value.subject_sha256,'execution subject_sha256'),request_sha256:hex(value.request_sha256,'execution request_sha256'),
|
|
59
|
+
observed_exit_code:value.observed_exit_code,started_at_utc:iso(value.started_at_utc,'execution started_at_utc'),
|
|
60
|
+
completed_at_utc:iso(value.completed_at_utc,'execution completed_at_utc')};
|
|
61
|
+
if(!Number.isInteger(core.observed_exit_code)) throw new Error('execution exit code invalid');
|
|
62
|
+
if(Date.parse(core.completed_at_utc)<Date.parse(core.started_at_utc)) throw new Error('execution time order invalid');
|
|
63
|
+
return Object.freeze({...core,receipt_sha256:sha(core)});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createVerificationResult(raw){
|
|
67
|
+
const value=object(raw,'verification result');
|
|
68
|
+
keys(value,['verification_id','subject_sha256','execution_receipt_sha256','outcome_passed','oracle','evidence_refs'],'verification result');
|
|
69
|
+
if(typeof value.outcome_passed!=='boolean') throw new Error('verification outcome invalid');
|
|
70
|
+
if(!Array.isArray(value.evidence_refs)||value.evidence_refs.length<1||value.evidence_refs.length>64||value.evidence_refs.some(ref=>!HEX64.test(ref))) throw new Error('verification evidence refs invalid');
|
|
71
|
+
const core={schema:'deadbyte.verification-result.v1',verification_id:id(value.verification_id,'verification_id'),
|
|
72
|
+
subject_sha256:hex(value.subject_sha256,'verification subject_sha256'),
|
|
73
|
+
execution_receipt_sha256:hex(value.execution_receipt_sha256,'verification receipt_sha256'),
|
|
74
|
+
outcome_passed:value.outcome_passed,oracle:id(value.oracle,'verification oracle'),evidence_refs:[...value.evidence_refs]};
|
|
75
|
+
return Object.freeze({...core,verification_sha256:sha(core)});
|
|
76
|
+
}
|
package/src/mcp-server.mjs
CHANGED
|
@@ -202,7 +202,7 @@ const memoryProvider=autonomousPolicy?createR41MemoryProvider({
|
|
|
202
202
|
}):null;
|
|
203
203
|
const autonomousRuntime = autonomousPolicy ? createAutonomousRuntime({ policy:autonomousPolicy,codingRuntime,nativeDiffRuntime,
|
|
204
204
|
machineFsRuntime,observationRuntime,processRuntime,semanticRuntime,desktopRuntime,desktopMemoryProvider,memoryProvider,signingKeyPath,verifyKeyPath }) : null;
|
|
205
|
-
const autonomousSupervisor = autonomousRuntime && autonomousPolicy.planner.backend
|
|
205
|
+
const autonomousSupervisor = autonomousRuntime && ['openai_compatible','openai_responses'].includes(autonomousPolicy.planner.backend) &&
|
|
206
206
|
autonomousSupervisorMode === 'in_process'
|
|
207
207
|
? startAutonomousSupervisor({ runtime: autonomousRuntime, policy: autonomousPolicy })
|
|
208
208
|
: null;
|
package/src/provider-circuit.mjs
CHANGED
|
@@ -48,11 +48,11 @@ function openAt(state,ms,policy){
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export function providerCircuitId(planner){
|
|
51
|
-
if(planner?.backend
|
|
51
|
+
if(!['openai_compatible','openai_responses'].includes(planner?.backend)||typeof planner.endpoint!=='string'||typeof planner.model!=='string') {
|
|
52
52
|
throw new Error('provider circuit planner identity invalid');
|
|
53
53
|
}
|
|
54
|
-
const digest=createHash('sha256').update(Buffer.from(`${planner.endpoint}\0${planner.model}`,'utf8')).digest('hex');
|
|
55
|
-
return
|
|
54
|
+
const digest=createHash('sha256').update(Buffer.from(`${planner.backend}\0${planner.endpoint}\0${planner.model}`,'utf8')).digest('hex');
|
|
55
|
+
return `${planner.backend}:${digest}`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
export function classifyProviderFailure(error){
|
|
@@ -61,7 +61,7 @@ export function classifyProviderFailure(error){
|
|
|
61
61
|
if(name==='aborterror'||/\b(?:timed?\s*out|timeout|aborted)\b/.test(text)) return 'timeout';
|
|
62
62
|
if(/(?:http\s*429|rate[ _-]?limit|too many requests)/.test(text)) return 'rate_limit';
|
|
63
63
|
if(/(?:http\s*(?:401|403)|unauthori[sz]ed|forbidden|api key|authentication)/.test(text)) return 'auth';
|
|
64
|
-
if(/(?:not json|invalid json|missing choices|invalid response|response.*(?:schema|contract|content))/.test(text)) return 'invalid_response';
|
|
64
|
+
if(/(?:not json|invalid json|missing choices|missing output_text|refusal|not completed|incomplete response|invalid response|response.*(?:schema|contract|content))/.test(text)) return 'invalid_response';
|
|
65
65
|
if(/(?:econnreset|econnrefused|enotfound|fetch failed|network|socket|transport|connection)/.test(text)) return 'transport';
|
|
66
66
|
return 'other';
|
|
67
67
|
}
|
package/src/version.mjs
CHANGED