deadbyte-mcp 0.13.0 → 0.15.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.
Files changed (153) hide show
  1. package/.gitattributes +7 -0
  2. package/AGENTS.md +18 -0
  3. package/CONTEXT.md +3 -3
  4. package/MANIFEST.SHA256 +152 -75
  5. package/R42-PARENT.json +19 -0
  6. package/R43-PARENT.json +20 -0
  7. package/R44-DONORS.json +99 -0
  8. package/R44-PARENT.json +18 -0
  9. package/README.txt +7 -2
  10. package/bin/appcontainer-stage.obj +0 -0
  11. package/bin/bootstrap-advapi32.obj +0 -0
  12. package/bin/bootstrap-exitcode.obj +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.obj +0 -0
  15. package/bin/child-control-stage.obj +0 -0
  16. package/bin/contained-reverse-worker.obj +0 -0
  17. package/bin/contained-transform-worker.obj +0 -0
  18. package/bin/containment-probe.obj +0 -0
  19. package/bin/deadbyte-contain.obj +0 -0
  20. package/bin/deadbyte-exec.obj +0 -0
  21. package/bin/deadbyte-process-host.obj +0 -0
  22. package/bin/deadbyte-tunnel-host.obj +0 -0
  23. package/controller/README.TXT +4 -5
  24. package/controller/deadbyte-controller.ps1 +1 -1
  25. package/controller/deadbyte-desktop-policy.json +5 -5
  26. package/controller/deadbyte-process-policy.json +4 -4
  27. package/docs/ARCHITECTURE.md +8 -1
  28. package/docs/R44-ARCHITECTURE.md +37 -0
  29. package/harness/r42-agent-evals.json +48 -0
  30. package/harness/task-contract.schema.json +33 -0
  31. package/package.json +15 -8
  32. package/proof/CONTAINMENT-BUILD.txt +6 -6
  33. package/scripts/agent-eval-r42.mjs +46 -0
  34. package/scripts/deferred-slot-operation-r42.mjs +126 -0
  35. package/scripts/deferred-slot-operation-r43.mjs +126 -0
  36. package/scripts/deferred-slot-operation-r44.mjs +126 -0
  37. package/scripts/final-closure-r42.mjs +116 -0
  38. package/scripts/final-closure-r43.mjs +115 -0
  39. package/scripts/final-closure-r44.mjs +121 -0
  40. package/scripts/final-closure-verify-r42.mjs +223 -0
  41. package/scripts/final-closure-verify-r43.mjs +213 -0
  42. package/scripts/final-closure-verify-r44.mjs +220 -0
  43. package/scripts/finalize-r43.mjs +217 -0
  44. package/scripts/finalize-r44.mjs +217 -0
  45. package/scripts/full-regression-r43.mjs +20 -0
  46. package/scripts/full-regression-r44.mjs +20 -0
  47. package/scripts/gate-windows-r42.ps1 +54 -0
  48. package/scripts/gate-windows-r43.ps1 +36 -0
  49. package/scripts/gate-windows-r44.ps1 +36 -0
  50. package/scripts/release-parity-r42.mjs +235 -0
  51. package/scripts/release-parity-r43.mjs +218 -0
  52. package/scripts/release-parity-r44.mjs +242 -0
  53. package/scripts/release-parity-tests-r42.ps1 +6 -0
  54. package/scripts/release-parity-tests-r43.ps1 +8 -0
  55. package/scripts/release-parity-tests-r44.ps1 +8 -0
  56. package/scripts/verify-r42-parent.mjs +101 -0
  57. package/scripts/verify-r43-parent.mjs +126 -0
  58. package/scripts/verify-r44-parent.mjs +142 -0
  59. package/scripts/windows-gate-evidence-r42.mjs +64 -0
  60. package/scripts/windows-gate-evidence-r43.mjs +55 -0
  61. package/scripts/windows-gate-evidence-r44.mjs +55 -0
  62. package/src/agent-backend-r44.mjs +254 -0
  63. package/src/agent-eval-r42.mjs +73 -0
  64. package/src/agent-eval-runner-r42.mjs +138 -0
  65. package/src/agent-governance-r42.mjs +142 -0
  66. package/src/autonomous-context-engine.mjs +2 -0
  67. package/src/autonomous-loop-state.mjs +10 -0
  68. package/src/autonomous-mcp-tools.mjs +21 -6
  69. package/src/autonomous-output-contracts.mjs +18 -2
  70. package/src/autonomous-planner-contracts.mjs +3 -0
  71. package/src/autonomous-planner.mjs +46 -6
  72. package/src/autonomous-policy.mjs +50 -5
  73. package/src/autonomous-runtime.mjs +138 -25
  74. package/src/autonomous-supervisor-host.mjs +12 -2
  75. package/src/autonomous-supervisor.mjs +1 -1
  76. package/src/capability-ledger.mjs +2 -1
  77. package/src/codex-app-server-r44.mjs +154 -0
  78. package/src/coding-plane.mjs +66 -19
  79. package/src/durable-job-r44.mjs +145 -0
  80. package/src/evidence-publisher-r42.mjs +76 -0
  81. package/src/external-job-provider-r44.mjs +126 -0
  82. package/src/host-plane.mjs +27 -6
  83. package/src/job-supervisor-host-r44.mjs +112 -0
  84. package/src/machine-fs-smoke-client.mjs +1 -1
  85. package/src/machine-fs.mjs +21 -10
  86. package/src/mcp-host-smoke-client.mjs +1 -1
  87. package/src/mcp-server.mjs +20 -3
  88. package/src/mcp-smoke-client.mjs +1 -1
  89. package/src/native-diff-v2.mjs +3 -3
  90. package/src/native-diff.mjs +3 -2
  91. package/src/observation-smoke-client.mjs +1 -1
  92. package/src/operator-surface.mjs +2 -2
  93. package/src/process-policy.mjs +19 -0
  94. package/src/process-runtime.mjs +5 -2
  95. package/src/process-smoke-client.mjs +2 -2
  96. package/src/provider-circuit.mjs +10 -4
  97. package/src/r44-compact-disarmed-probe.mjs +74 -0
  98. package/src/r44-full-surface-probe.mjs +34 -0
  99. package/src/remote-device-r44.mjs +205 -0
  100. package/src/remote-mcp-autonomous-smoke-client.mjs +4 -4
  101. package/src/remote-mcp-r44-parity-client.mjs +203 -0
  102. package/src/remote-mcp-smoke-client.mjs +7 -4
  103. package/src/runtime-update.mjs +64 -31
  104. package/src/skill-mcp-tools-r44.mjs +37 -0
  105. package/src/skill-plane-r44.mjs +203 -0
  106. package/src/tunnel-stdio-compat-smoke.mjs +1 -1
  107. package/src/version.mjs +1 -1
  108. package/test/agent-backend-r44.test.mjs +115 -0
  109. package/test/agent-eval-r42.test.mjs +39 -0
  110. package/test/agent-eval-runner-r42.test.mjs +61 -0
  111. package/test/agent-governance-r42.test.mjs +77 -0
  112. package/test/agent-runtime.test.mjs +3 -3
  113. package/test/autonomous-loop.test.mjs +97 -11
  114. package/test/autonomous-r34-e2e.test.mjs +2 -1
  115. package/test/autonomous-runtime.test.mjs +2 -2
  116. package/test/capability-ledger-r36.test.mjs +3 -3
  117. package/test/cli-entrypoint.test.mjs +1 -1
  118. package/test/codex-app-server-r44.test.mjs +115 -0
  119. package/test/codex-autonomous-r44.test.mjs +87 -0
  120. package/test/coding-plane.test.mjs +2 -2
  121. package/test/coding-runtime.test.mjs +36 -1
  122. package/test/compact-catalog-r36.test.mjs +3 -3
  123. package/test/compact-max-catalog-r40.test.mjs +4 -2
  124. package/test/compact-max-catalog-r44.test.mjs +97 -0
  125. package/test/core.test.mjs +1 -1
  126. package/test/durable-job-r44.test.mjs +75 -0
  127. package/test/evidence-publisher-r42.test.mjs +53 -0
  128. package/test/external-job-provider-r44.test.mjs +46 -0
  129. package/test/full-max-catalog-r40.test.mjs +5 -3
  130. package/test/full-max-catalog-r44.test.mjs +91 -0
  131. package/test/host-plane.test.mjs +15 -1
  132. package/test/human-clarification-r42.test.mjs +53 -0
  133. package/test/native-diff.test.mjs +28 -0
  134. package/test/output-schema.test.mjs +2 -2
  135. package/test/process-policy.test.mjs +22 -1
  136. package/test/prompts-resources.test.mjs +1 -1
  137. package/test/provider-responses-r42.test.mjs +90 -0
  138. package/test/r33-closeout-regression.test.mjs +14 -16
  139. package/test/r33-finalization.test.mjs +7 -7
  140. package/test/r42-finalization.test.mjs +38 -0
  141. package/test/r43-finalization.test.mjs +31 -0
  142. package/test/r43-finalizer.test.mjs +34 -0
  143. package/test/r43-parent.test.mjs +29 -0
  144. package/test/r44-finalization.test.mjs +58 -0
  145. package/test/r44-finalizer.test.mjs +48 -0
  146. package/test/r44-parent.test.mjs +25 -0
  147. package/test/release-version.test.mjs +53 -19
  148. package/test/remote-device-r44.test.mjs +104 -0
  149. package/test/runtime-self-update.test.mjs +42 -0
  150. package/test/skill-mcp-r44.test.mjs +38 -0
  151. package/test/skill-plane-r44.test.mjs +93 -0
  152. package/test/standing-grants.test.mjs +5 -1
  153. package/test/task-contract-r42.test.mjs +41 -0
@@ -146,6 +146,7 @@ export function deriveLoopState(goal,events) {
146
146
  replan_count:0,
147
147
  pending_action:null,
148
148
  pending_approval:false,
149
+ clarification:null,
149
150
  progress:null,
150
151
  critic:null,
151
152
  repair:null,
@@ -223,6 +224,10 @@ export function deriveLoopState(goal,events) {
223
224
  state.phase='WAITING';
224
225
  state.wait_reason=requireText(payload.reason,'loop wait reason');
225
226
  state.pending_approval=payload.requires_approval===true;
227
+ state.clarification=payload.reason==='human_input_required' ? {
228
+ question:requireText(payload.question,'loop clarification question',1000),
229
+ choices:Array.isArray(payload.choices) ? payload.choices.map((choice,index)=>requireText(choice,`loop clarification choice ${index+1}`,256)) : []
230
+ } : null;
226
231
  break;
227
232
  case 'waiting_resumed': {
228
233
  if (state.phase !== 'WAITING') throw new Error('loop waiting_resumed outside WAITING phase');
@@ -231,6 +236,7 @@ export function deriveLoopState(goal,events) {
231
236
  state.phase=resumed;
232
237
  state.wait_reason=null;
233
238
  state.pending_approval=false;
239
+ state.clarification=null;
234
240
  break;
235
241
  }
236
242
  case 'planner_requested':
@@ -255,24 +261,28 @@ export function deriveLoopState(goal,events) {
255
261
  state.phase='PLAN';
256
262
  state.wait_reason=null;
257
263
  state.pending_approval=false;
264
+ state.clarification=null;
258
265
  }
259
266
  break;
260
267
  case 'goal_cancelled':
261
268
  state.phase='FAILED';
262
269
  state.wait_reason=typeof payload.reason==='string'&&payload.reason.length>0?payload.reason:'cancelled';
263
270
  state.pending_approval=false;
271
+ state.clarification=null;
264
272
  state.pending_action=null;
265
273
  break;
266
274
  case 'goal_failed':
267
275
  state.phase='FAILED';
268
276
  state.wait_reason=typeof payload.reason==='string'&&payload.reason.length>0?payload.reason:'failed';
269
277
  state.pending_approval=false;
278
+ state.clarification=null;
270
279
  state.pending_action=null;
271
280
  break;
272
281
  case 'release_completed':
273
282
  state.phase='SUCCEEDED';
274
283
  state.wait_reason=null;
275
284
  state.pending_approval=false;
285
+ state.clarification=null;
276
286
  state.pending_action=null;
277
287
  break;
278
288
  default:
@@ -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','codex_app_server']).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 !== 'openai_compatible') throw new Error('provider planner backend is not openai_compatible');
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({ model:policy.planner.model, temperature:0,
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,
@@ -30,12 +30,31 @@ function normalizeProviderCircuit(raw) {
30
30
  half_open_probe_limit:integer(value.half_open_probe_limit,'planner.circuit.half_open_probe_limit',1,8,1)
31
31
  });
32
32
  }
33
- function normalizePlanner(raw) {
33
+ function normalizePlanner(raw,codingPolicy,rootIds) {
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', 'codex_app_server'].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
+ if (typeof raw.model !== 'string' || raw.model.length < 1 || raw.model.length > 128) throw new Error('planner.model invalid');
40
+ if (backend === 'codex_app_server') {
41
+ if (!ID_RE.test(raw.root_id ?? '') || !rootIds.includes(raw.root_id) || !codingPolicy.roots[raw.root_id]) {
42
+ throw new Error('planner.root_id invalid');
43
+ }
44
+ if (typeof raw.executable !== 'string' || !path.isAbsolute(raw.executable)) throw new Error('planner.executable must be absolute');
45
+ if (!SHA256_RE.test(raw.executable_sha256 ?? '')) throw new Error('planner.executable_sha256 invalid');
46
+ const args=raw.args ?? ['app-server'];
47
+ if (!Array.isArray(args) || args.length < 1 || args.length > 16 ||
48
+ args.some(value=>typeof value!=='string'||value.length<1||value.length>2048||value.includes('\0'))) {
49
+ throw new Error('planner.args invalid');
50
+ }
51
+ return {
52
+ ...common, model:raw.model, root_id:raw.root_id, executable:path.resolve(raw.executable),
53
+ executable_sha256:raw.executable_sha256, args:[...args],
54
+ timeout_ms:integer(raw.timeout_ms,'planner.timeout_ms',1000,300000,60000),
55
+ circuit:normalizeProviderCircuit(raw.circuit)
56
+ };
57
+ }
39
58
  if (typeof raw.endpoint !== 'string') throw new Error('planner.endpoint required');
40
59
  const endpoint = new URL(raw.endpoint);
41
60
  const loopback = ['127.0.0.1', 'localhost', '::1'].includes(endpoint.hostname);
@@ -43,15 +62,22 @@ function normalizePlanner(raw) {
43
62
  throw new Error('planner endpoint must be HTTPS or loopback HTTP');
44
63
  }
45
64
  if (endpoint.username || endpoint.password || endpoint.hash) throw new Error('planner endpoint credentials/fragments forbidden');
46
- if (typeof raw.model !== 'string' || raw.model.length < 1 || raw.model.length > 128) throw new Error('planner.model invalid');
47
65
  const apiKeyEnv = raw.api_key_env ?? null;
48
66
  if (apiKeyEnv !== null && !ENV_RE.test(apiKeyEnv)) throw new Error('planner.api_key_env invalid');
49
- return {
67
+ const provider = {
50
68
  ...common, endpoint: endpoint.toString(), model: raw.model, api_key_env: apiKeyEnv,
51
69
  timeout_ms: integer(raw.timeout_ms, 'planner.timeout_ms', 1000, 300000, 60000),
52
70
  max_response_bytes: integer(raw.max_response_bytes, 'planner.max_response_bytes', 4096, 2 * 1024 * 1024, 512 * 1024),
53
71
  circuit:normalizeProviderCircuit(raw.circuit)
54
72
  };
73
+ if (backend === 'openai_responses') {
74
+ const effort = raw.reasoning_effort ?? null;
75
+ if (effort !== null && !['minimal','low','medium','high','xhigh'].includes(effort)) {
76
+ throw new Error('planner.reasoning_effort invalid');
77
+ }
78
+ return { ...provider, reasoning_effort:effort, store:false };
79
+ }
80
+ return provider;
55
81
  }
56
82
  function normalizeProfile(id, raw, codingPolicy) {
57
83
  if (!ID_RE.test(id)) throw new Error(`invalid autonomous profile '${id}'`);
@@ -109,7 +135,7 @@ export async function loadAutonomousPolicy(policyPath, codingPolicy) {
109
135
  schema: AUTONOMOUS_POLICY_SCHEMA, enabled: true, policy_path: absolute,
110
136
  lease_file: path.resolve(raw.lease_file), evidence_dir: path.resolve(raw.evidence_dir),
111
137
  coding_policy_sha256: codingPolicy.policy_sha256, root_ids: rootIds,
112
- planner: normalizePlanner(raw.planner), profiles,
138
+ planner: normalizePlanner(raw.planner,codingPolicy,rootIds), profiles,
113
139
  limits: {
114
140
  max_iterations: integer(limits.max_iterations, 'limits.max_iterations', 1, 64, 12),
115
141
  max_mutations: integer(limits.max_mutations, 'limits.max_mutations', 1, 128, 24),
@@ -157,3 +183,22 @@ export async function assertAutonomousArmed(policy, rootId = null, now = new Dat
157
183
  if (rootId !== null && !status.root_ids.includes(rootId)) throw new Error(`autonomous root '${rootId}' is outside active lease`);
158
184
  return status;
159
185
  }
186
+
187
+ export async function recheckAutonomousAuthority(policy, initial, rootId = null, now = new Date()) {
188
+ if (!initial?.armed || typeof initial.instance_nonce !== 'string' || !Array.isArray(initial.root_ids)) {
189
+ throw new Error('autonomous authority recheck requires an armed initial snapshot');
190
+ }
191
+ let current;
192
+ try { current = await assertAutonomousArmed(policy, rootId, now); }
193
+ catch (error) { throw new Error(`autonomous authority changed during operation: ${String(error?.message ?? error)}`); }
194
+ const initialRoots = [...initial.root_ids].sort();
195
+ const currentRoots = [...current.root_ids].sort();
196
+ const changed =
197
+ current.instance_nonce !== initial.instance_nonce ||
198
+ current.standing !== initial.standing ||
199
+ current.expires_at_utc !== initial.expires_at_utc ||
200
+ initialRoots.length !== currentRoots.length ||
201
+ initialRoots.some((id, index) => id !== currentRoots[index]);
202
+ if (changed) throw new Error('autonomous authority changed during operation');
203
+ return current;
204
+ }