@fitlab-ai/agent-infra 0.11.2 → 0.11.3

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 (79) hide show
  1. package/dist/bin/internal-cli.js +7 -2
  2. package/dist/lib/agent-clients/adapter.js +3 -0
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  6. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  7. package/dist/lib/internal/manual-validation.js +22 -27
  8. package/dist/lib/internal/sandbox-control.js +1 -43
  9. package/dist/lib/internal/task-lifecycle.js +1 -1
  10. package/dist/lib/internal/task-operation-registry.js +6 -2
  11. package/dist/lib/platform/issue-comments.js +2 -31
  12. package/dist/lib/platform/pr-summary.js +7 -2
  13. package/dist/lib/sandbox/control/client.js +3 -44
  14. package/dist/lib/sandbox/control/executor.js +14 -79
  15. package/dist/lib/sandbox/control/finalization-response.js +3 -5
  16. package/dist/lib/sandbox/control/protocol.js +5 -64
  17. package/dist/lib/sandbox/control/server.js +29 -206
  18. package/dist/lib/sandbox/control/state.js +1 -7
  19. package/dist/lib/sandbox/control/task-view.js +1 -2
  20. package/dist/lib/task/control-authority.js +34 -36
  21. package/dist/lib/task/control-recovery.js +3 -42
  22. package/dist/lib/task/delegation-receipts.js +2 -2
  23. package/dist/lib/task/finalization-artifacts.js +1 -3
  24. package/dist/lib/task/finalization-state.js +1 -1
  25. package/dist/lib/task/finalization.js +53 -126
  26. package/dist/lib/task/lifecycle.js +1 -1
  27. package/dist/lib/task/manual-validation-receipt.js +8 -3
  28. package/dist/lib/task/orchestration.js +12 -6
  29. package/dist/lifecycle-build-manifest.json +3 -3
  30. package/lib/agent-clients/adapter.ts +46 -0
  31. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  32. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  33. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  34. package/lib/agent-clients/adapters/codex.ts +10 -0
  35. package/lib/internal/manual-validation.ts +22 -26
  36. package/lib/internal/sandbox-control.ts +0 -42
  37. package/lib/internal/task-lifecycle.ts +2 -2
  38. package/lib/internal/task-operation-registry.ts +7 -4
  39. package/lib/platform/issue-comments.ts +1 -35
  40. package/lib/platform/pr-summary.ts +10 -2
  41. package/lib/sandbox/control/client.ts +5 -68
  42. package/lib/sandbox/control/executor.ts +14 -81
  43. package/lib/sandbox/control/finalization-response.ts +3 -5
  44. package/lib/sandbox/control/protocol.ts +7 -83
  45. package/lib/sandbox/control/server.ts +30 -220
  46. package/lib/sandbox/control/state.ts +1 -9
  47. package/lib/sandbox/control/task-view.ts +1 -2
  48. package/lib/task/control-authority.ts +38 -47
  49. package/lib/task/control-recovery.ts +3 -43
  50. package/lib/task/delegation-receipts.ts +2 -2
  51. package/lib/task/finalization-artifacts.ts +1 -3
  52. package/lib/task/finalization-state.ts +1 -1
  53. package/lib/task/finalization.ts +57 -121
  54. package/lib/task/lifecycle.ts +2 -2
  55. package/lib/task/manual-validation-receipt.ts +8 -2
  56. package/lib/task/orchestration.ts +30 -19
  57. package/package.json +1 -1
  58. package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
  59. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
  60. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  61. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  62. package/templates/.agents/skills/complete-task/SKILL.en.md +6 -7
  63. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +6 -7
  64. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  65. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  66. package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
  67. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
  68. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  69. package/dist/lib/sandbox/control/workflow-executor.js +0 -142
  70. package/dist/lib/task/lifecycle-recovery.js +0 -549
  71. package/dist/lib/task/recovery-warning.js +0 -19
  72. package/dist/lib/task/workflow-command.js +0 -21
  73. package/dist/lib/task/workflow-dispatch.js +0 -213
  74. package/lib/sandbox/control/task-workflow.ts +0 -95
  75. package/lib/sandbox/control/workflow-executor.ts +0 -156
  76. package/lib/task/lifecycle-recovery.ts +0 -691
  77. package/lib/task/recovery-warning.ts +0 -21
  78. package/lib/task/workflow-command.ts +0 -22
  79. package/lib/task/workflow-dispatch.ts +0 -214
@@ -22,7 +22,7 @@ export type TaskOperationEffect = TaskViewAccessEffect;
22
22
  export type TaskRefSource = 'argv' | 'environment' | 'none' | 'delegated' | 'input';
23
23
 
24
24
  export type SandboxControlTransportDecision = Readonly<{
25
- kind: 'direct-host' | 'broker-client' | 'fail-closed';
25
+ kind: 'direct-host' | 'sandbox-local' | 'broker-client' | 'fail-closed';
26
26
  reasonCode: string | null;
27
27
  }>;
28
28
 
@@ -277,6 +277,7 @@ export function resolveTaskOperation(
277
277
  export function resolveDelegatedTaskOperation(args: readonly string[]): TaskOperationDescriptor | null {
278
278
  const [operation, ...rest] = args;
279
279
  if (operation !== 'client' || !rest[0]) return null;
280
+ if (rest[0] === 'task-orchestration') return null;
280
281
  return resolveTaskOperation('internal', rest[0], rest.slice(1));
281
282
  }
282
283
 
@@ -340,7 +341,7 @@ function taskMarkerState(env: NodeJS.ProcessEnv): TaskMarkerState {
340
341
 
341
342
  export function resolveSandboxControlTransport(
342
343
  env: NodeJS.ProcessEnv = process.env,
343
- options: Readonly<{ statusMountPath?: string }> = {}
344
+ options: Readonly<{ statusMountPath?: string; localWorkflow?: boolean }> = {}
344
345
  ): SandboxControlTransportDecision {
345
346
  const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
346
347
  || Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
@@ -407,13 +408,15 @@ export function resolveSandboxControlTransport(
407
408
  if (!hasCompleteConfig) {
408
409
  return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
409
410
  }
410
- return { kind: 'broker-client', reasonCode: null };
411
+ return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
411
412
  }
412
413
  if (!hasAnyMarker) return { kind: 'direct-host', reasonCode: null };
413
414
  if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
414
415
  return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
415
416
  }
416
- return { kind: 'broker-client', reasonCode: null };
417
+ return options.localWorkflow
418
+ ? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_MISSING' }
419
+ : { kind: 'broker-client', reasonCode: null };
417
420
  }
418
421
 
419
422
  export function hasTaskBoundMarker(env: NodeJS.ProcessEnv = process.env): boolean {
@@ -27,7 +27,6 @@ import {
27
27
  import { projectTaskComment } from './task-comment-projection.ts';
28
28
  import {
29
29
  canonicalizeCommentBody,
30
- canonicalizeSummaryBody,
31
30
  escapeHtmlText,
32
31
  fenceRanges,
33
32
  renderSafeCodeFence
@@ -52,7 +51,6 @@ type SyncOptions = {
52
51
  client?: PlatformClient;
53
52
  runtimeVersion?: string;
54
53
  summaryAuthorization?: { sha256: string };
55
- verifyOnly?: boolean;
56
54
  };
57
55
 
58
56
  function providerCommentId(id: string, provider: { identity?: { comment?: string } }): number | string {
@@ -76,8 +74,6 @@ const ARTIFACT_TITLES: Record<string, string> = {
76
74
  'review-plan': '技术方案审查',
77
75
  code: '实现报告',
78
76
  'review-code': '代码审查',
79
- 'manual-validation': '人工验证报告',
80
- 'validation-run': '验证运行证据',
81
77
  'pr-review': 'PR 审查报告'
82
78
  };
83
79
 
@@ -154,7 +150,7 @@ function taskCommentLanguage(repoRoot: string): string {
154
150
 
155
151
  function artifactIdentity(artifact: string): { stem: string; title: string } {
156
152
  const stem = path.basename(artifact, '.md');
157
- const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r(\d+))?$/);
153
+ const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|pr-review)(?:-r(\d+))?$/);
158
154
  if (!match) throw new Error(`unsupported artifact '${artifact}'`);
159
155
  const base = ARTIFACT_TITLES[match[1]!]!;
160
156
  const round = match[2] ? Number(match[2]) : 1;
@@ -483,15 +479,6 @@ function summaryPosition(comments: RemoteComment[], taskId: string): { ok: boole
483
479
  return { ok: managed.every((comment) => sequence >= comment.createdSequence!), known: true, summary: summary[0]! };
484
480
  }
485
481
 
486
- function summaryDigest(comment: RemoteComment): string {
487
- const body = normalizeCommentContent(comment.body)
488
- .replace(/^<!-- sync-issue:[^\n]+:summary -->\n## [^\n]+\n\n> [^\n]+\n\n/u, '')
489
- .replace(/^<details><summary>恢复元数据<\/summary>[\s\S]*?<\/details>\n\n/u, '')
490
- .replace(/\n---\n\*[^\n]*\*$/u, '');
491
- const canonical = canonicalizeSummaryBody(body);
492
- return canonical.ok ? createHash('sha256').update(canonical.value).digest('hex') : '';
493
- }
494
-
495
482
  async function listedComments(provider: any, loaded: any, parent: ReturnType<typeof taskIssueIdentity>): Promise<any> {
496
483
  return provider.comments?.list
497
484
  ? provider.comments.list({ context: providerOperationContext(loaded), parent }).then((response: any) => response.ok
@@ -609,27 +596,6 @@ async function syncPlatformComment(taskRef: string, options: SyncOptions): Promi
609
596
  }
610
597
  }
611
598
 
612
- if (options.verifyOnly) {
613
- if (options.kind !== 'summary' || !options.summaryAuthorization) {
614
- return platformResult('failed', {
615
- ...contextFields(context), resource: { kind: 'issue', number: issue },
616
- error: { code: 'SUMMARY_VERIFICATION_INVALID', message: 'summary verification requires durable authorization', retryable: false }
617
- });
618
- }
619
- const position = summaryPosition(listed.value, resolved.taskId);
620
- if (!position.ok || !position.summary || summaryDigest(position.summary) !== options.summaryAuthorization.sha256) {
621
- return platformResult('failed', {
622
- ...contextFields(context), resource: { kind: 'issue', number: issue },
623
- error: { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary marker, body digest, or managed comment order is invalid', retryable: true }
624
- });
625
- }
626
- return platformResult('no-op', {
627
- ...contextFields(context), changed: false, resource: { kind: 'issue', number: issue },
628
- comment: { kind: options.kind, marker: desired[0]!.marker, ids: [position.summary.id], parts: 1 }, error: null
629
- });
630
- }
631
-
632
-
633
599
  // Backfill only supplies missing artifact comments; valid existing marker sets stay untouched.
634
600
  if (options.kind === 'artifact' && options.backfill && existing.length > 0) {
635
601
  const operations = existing.map((comment): PlatformOperation => ({
@@ -35,7 +35,7 @@ import type {
35
35
  MechanicalChangeReport,
36
36
  PrChangeReport
37
37
  } from './pr-change-report.ts';
38
- import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.ts';
38
+ import { MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.ts';
39
39
  import { readManualValidationCompletion } from '../task/manual-validation-completion.ts';
40
40
 
41
41
  type SummaryComment = { id: number | string; body: string };
@@ -582,6 +582,7 @@ async function syncPullRequestSummary(
582
582
  const initial = await inspectBoundPullRequest(context, resolved.repoRoot, prIdentity, loaded.value);
583
583
  if (!initial.ok) return fail(initial.status, context, initial.error);
584
584
  const manual = options.manualValidation;
585
+ let manualEvidenceDigest: string | null = null;
585
586
  const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
586
587
  if (hasFinalManualValidation && (!manual || manual.phase !== 'final')) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
587
588
  if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
@@ -599,6 +600,7 @@ async function syncPullRequestSummary(
599
600
  });
600
601
  if (!completion.ok) return fail('failed', context, platformError(completion.error), prNumber);
601
602
  const { receipt, transaction } = completion.value;
603
+ manualEvidenceDigest = receipt.evidenceDigest;
602
604
  if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
603
605
  if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
604
606
  }
@@ -635,7 +637,13 @@ async function syncPullRequestSummary(
635
637
  }
636
638
  const replaced = replaceCanonicalReportPlaceholder(`${prefix.value}${placeholder}${suffix.value}`, report.value);
637
639
  if (!replaced.ok) return fail('failed', context, platformError(replaced.error));
638
- const desired = buildPullRequestSummary(resolved.taskId, replaced.value, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
640
+ const renderedBody = manual?.phase === 'final'
641
+ ? replaced.value.replace(
642
+ MANUAL_VALIDATION_RECEIPT_PLACEHOLDER,
643
+ `<!-- manual-validation-receipt: transaction=${manual.transactionId}; receipt=${manual.receiptDigest}; evidence=${manualEvidenceDigest}; head=${manual.prHeadSha} -->`
644
+ )
645
+ : replaced.value;
646
+ const desired = buildPullRequestSummary(resolved.taskId, renderedBody, initial.value.head.sha, renderHumanOverrideAudit(taskContent));
639
647
  if (!isSafeSummaryEnvelope(desired, resolved.taskId)) return fail('failed', context, {
640
648
  code: 'PR_SUMMARY_RENDER_INVALID',
641
649
  message: 'Summary contains an invalid or duplicated reserved control marker',
@@ -11,10 +11,8 @@ import {
11
11
  type SandboxControlRequest,
12
12
  type SandboxControlResponse,
13
13
  type SandboxTaskCreateRequest,
14
- type SandboxTaskCommandRequest,
15
14
  type SandboxTaskFinalizationRequest,
16
- type SandboxCodexControllerRequest,
17
- type SandboxTaskWorkflowRequest
15
+ type SandboxCodexControllerRequest
18
16
  } from './protocol.ts';
19
17
  import type {
20
18
  CodexControllerLeaseProofV1,
@@ -26,8 +24,6 @@ import { readSandboxControlPayload, readSandboxControlStatus } from './state.ts'
26
24
  import type { TaskCreateCandidateV1 } from '../../task/create.ts';
27
25
  import { accessSandboxTaskView, taskViewFromStatus, type TaskViewAccessEffect } from './task-view.ts';
28
26
  import { readSandboxControlIdentitySentinel } from './identity-sentinel.ts';
29
- import type { TaskWorkflowRequest } from './task-workflow.ts';
30
- import type { LifecycleAuthorityRequestV1 } from '../../task/control-authority.ts';
31
27
  import { configuredShortIdLength, resolveShortIdReadOnly } from '../../task/short-id.ts';
32
28
 
33
29
  const SANDBOX_CONTROL_RESPONSE_SETTLE_MS = 250;
@@ -119,8 +115,6 @@ function preflight(
119
115
 
120
116
  function taskViewEffectForRequest(request: SandboxControlRequest): TaskViewAccessEffect | null {
121
117
  if (request.family === 'task-lifecycle' || request.family === 'task-finalization') return 'progress';
122
- if (request.family === 'task-orchestration') return request.args[1] === 'status' ? 'diagnostic' : 'progress';
123
- if (request.family === 'task-workflow') return request.workflow.operation === 'artifact-inspect' || request.workflow.operation === 'decision-next-id' ? 'diagnostic' : 'progress';
124
118
  return null;
125
119
  }
126
120
 
@@ -194,9 +188,7 @@ export function resolveVisibleActiveShortId(ref: string): string | null {
194
188
  // may be a documented short id. Compare canonical ids so a bound sandbox keeps
195
189
  // refusing other tasks without rejecting its own task's short id.
196
190
  function canonicalRequestTask(request: SandboxControlRequest): CanonicalRequestTask {
197
- const ref = request.family === 'task-workflow'
198
- ? request.workflow.taskId
199
- : 'args' in request ? request.args[0] ?? null : null;
191
+ const ref = 'args' in request ? request.args[0] ?? null : null;
200
192
  if (!ref) return { kind: 'none' };
201
193
  if (CONTROL_TASK_ID_RE.test(ref)) return { kind: 'task', taskId: ref };
202
194
  if (!CONTROL_SHORT_ID_RE.test(ref)) return { kind: 'none' };
@@ -223,7 +215,7 @@ function exchangeSandboxControl(request: SandboxControlRequest, params: Readonly
223
215
  || (requestTask.kind === 'task' && requestTask.taskId !== identity.taskId))) {
224
216
  clientError('SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH', 'request task does not match the sandbox identity', false);
225
217
  }
226
- if (identity.mode === 'branch-only' && (request.family === 'task-finalization' || request.family === 'task-workflow')) {
218
+ if (identity.mode === 'branch-only' && request.family === 'task-finalization') {
227
219
  clientError('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks', false);
228
220
  }
229
221
  }
@@ -384,7 +376,7 @@ export function requestSandboxControl(params: Readonly<{
384
376
  token?: string; generation?: string; timeoutMs?: number;
385
377
  }>): SandboxControlResponse {
386
378
  if (!isSandboxControlFamily(params.family)) clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' is not allowed`, false);
387
- if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization' || params.family === 'task-workflow') {
379
+ if (params.family === 'task-create' || params.family === 'codex-controller' || params.family === 'task-finalization') {
388
380
  clientError('SANDBOX_CONTROL_COMMAND_DENIED', `'${params.family}' requires a typed request`, false);
389
381
  }
390
382
  const auth = authority(params);
@@ -392,41 +384,13 @@ export function requestSandboxControl(params: Readonly<{
392
384
  const request: SandboxControlRequest = {
393
385
  version: 3, id: randomUUID(), ...auth, issuedAt,
394
386
  expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
395
- family: params.family as 'task-lifecycle' | 'task-orchestration', args: params.args,
387
+ family: params.family as 'task-lifecycle', args: params.args,
396
388
  controllerProcess: null,
397
389
  controllerProof: null
398
390
  };
399
391
  return exchangeSandboxControl(request, params);
400
392
  }
401
393
 
402
- export function requestSandboxTaskControl(params: Readonly<{
403
- family: 'task-lifecycle' | 'task-orchestration';
404
- args: string[];
405
- controllerProof: CodexControllerLeaseProofV1 | null;
406
- authority?: LifecycleAuthorityRequestV1;
407
- channelDir?: string;
408
- statusDir?: string;
409
- token?: string;
410
- generation?: string;
411
- timeoutMs?: number;
412
- }>): SandboxControlResponse {
413
- const auth = authority(params);
414
- const issuedAt = Date.now();
415
- const request: SandboxTaskCommandRequest = {
416
- version: 3,
417
- id: params.authority?.requestId ?? randomUUID(),
418
- ...auth,
419
- issuedAt,
420
- expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
421
- family: params.family,
422
- args: params.args,
423
- controllerProcess: null,
424
- controllerProof: params.authority ? null : params.controllerProof,
425
- ...(params.authority ? { authority: params.authority } : {})
426
- };
427
- return exchangeSandboxControl(request, params);
428
- }
429
-
430
394
  export function requestSandboxTaskFinalization(params: Readonly<{
431
395
  agent: string;
432
396
  channelDir?: string;
@@ -455,33 +419,6 @@ export function requestSandboxTaskFinalization(params: Readonly<{
455
419
  return exchangeSandboxControl(request, params);
456
420
  }
457
421
 
458
- export function requestSandboxTaskWorkflow(params: Readonly<{
459
- workflow: TaskWorkflowRequest;
460
- channelDir?: string;
461
- statusDir?: string;
462
- token?: string;
463
- generation?: string;
464
- timeoutMs?: number;
465
- authority?: LifecycleAuthorityRequestV1;
466
- }>): SandboxControlResponse {
467
- const auth = authority(params);
468
- const issuedAt = Date.now();
469
- const request: SandboxTaskWorkflowRequest = {
470
- version: 3,
471
- id: params.workflow.id,
472
- ...auth,
473
- issuedAt,
474
- expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
475
- family: 'task-workflow',
476
- args: [],
477
- controllerProcess: null,
478
- controllerProof: null,
479
- workflow: params.workflow,
480
- ...(params.authority ? { authority: params.authority } : {})
481
- };
482
- return exchangeSandboxControl(request, params);
483
- }
484
-
485
422
  type CodexControllerClosed = Readonly<{
486
423
  version: 1;
487
424
  status: 'closed';
@@ -31,12 +31,8 @@ import {
31
31
  createSandboxExecutorExecutionContext,
32
32
  dispatchTaskControlOperation,
33
33
  parseTaskControlOperation,
34
- validateLifecycleRecoveryAttestation,
35
- type LifecycleAuthorityResponseV1,
36
- type LifecycleRecoveryAttestationV1,
37
34
  type TaskControlOperation
38
35
  } from '../../task/control-authority.ts';
39
- import { executeTaskWorkflow } from './workflow-executor.ts';
40
36
  import { assertSandboxControlBrokerOwner, readSandboxControlManifest, type BrokerOwner } from './lifecycle.ts';
41
37
  import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
42
38
  import {
@@ -45,7 +41,6 @@ import {
45
41
  openCodexControllerRegistration,
46
42
  resolveCodexControllerBinding
47
43
  } from './controller-registration.ts';
48
- import type { OrchestrationDiagnosticLogger } from '../../task/orchestration.ts';
49
44
 
50
45
  export type SandboxControlExecutionResult = {
51
46
  exitCode: number;
@@ -127,7 +122,6 @@ export async function prepareSandboxControlExecution(params: {
127
122
  request: SandboxControlRequest;
128
123
  requestPath: string;
129
124
  internalCliPath: string;
130
- lifecycleAuthority?: LifecycleAuthorityResponseV1;
131
125
  }): Promise<PreparedSandboxControlExecution> {
132
126
  const nonce = randomUUID();
133
127
  const child = spawn(
@@ -181,17 +175,11 @@ export async function prepareSandboxControlExecution(params: {
181
175
  if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
182
176
  atomicWriteJson(executionPath(params.manifest, params.request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
183
177
  if (!canWrite()) throw new Error('SANDBOX_CONTROL_OWNER_LOST');
184
- const authority = params.lifecycleAuthority?.attestation ?? null;
185
178
  child.send({
186
179
  version: 2,
187
180
  nonce,
188
181
  owner: gateOwner,
189
- requestId: params.request.id,
190
- operationId: authority?.operationId ?? params.request.id,
191
- phase: authority?.phase ?? (params.request.family === 'task-workflow'
192
- && params.request.workflow?.operation === 'artifact-preflight' ? 'artifact.preflight'
193
- : params.request.family === 'task-workflow' ? 'artifact.finalize-local' : 'orchestration.prepare'),
194
- authority
182
+ requestId: params.request.id
195
183
  } satisfies SandboxControlExecutorGateV2);
196
184
  },
197
185
  completion,
@@ -206,7 +194,7 @@ export async function prepareSandboxControlExecution(params: {
206
194
  };
207
195
  }
208
196
 
209
- function waitForGate(nonce: string, timeoutMs = 2_000): Promise<Readonly<{ owner: BrokerOwner; authority: LifecycleRecoveryAttestationV1 | null; requestId: string; operationId: string; phase: SandboxControlExecutorGateV2['phase'] }>> {
197
+ function waitForGate(nonce: string, timeoutMs = 2_000): Promise<Readonly<{ owner: BrokerOwner; requestId: string }>> {
210
198
  return new Promise((resolve, reject) => {
211
199
  const timer = setTimeout(() => reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_TIMEOUT')), timeoutMs);
212
200
  const onDisconnect = () => {
@@ -222,12 +210,9 @@ function waitForGate(nonce: string, timeoutMs = 2_000): Promise<Readonly<{ owner
222
210
  nonce?: unknown;
223
211
  owner?: Partial<BrokerOwner> | null;
224
212
  requestId?: unknown;
225
- operationId?: unknown;
226
- phase?: unknown;
227
- authority?: unknown;
228
213
  } | null;
229
214
  const owner = value?.owner;
230
- if (!value || Object.keys(value).sort().join(',') !== 'authority,nonce,operationId,owner,phase,requestId,version'
215
+ if (!value || Object.keys(value).sort().join(',') !== 'nonce,owner,requestId,version'
231
216
  || value.version !== 2 || value.nonce !== nonce
232
217
  || !owner || owner.version !== 3 || !Number.isSafeInteger(owner.pid) || (owner.pid ?? 0) <= 0
233
218
  || typeof owner.startTime !== 'number' || !Number.isSafeInteger(owner.startTime)
@@ -236,20 +221,13 @@ function waitForGate(nonce: string, timeoutMs = 2_000): Promise<Readonly<{ owner
236
221
  reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_INVALID'));
237
222
  return;
238
223
  }
239
- if (typeof value.requestId !== 'string' || typeof value.operationId !== 'string'
240
- || !['orchestration.prepare', 'artifact.preflight', 'artifact.finalize-local', 'task-event.completed'].includes(value.phase as string)
241
- || (value.authority !== null && value.authority !== undefined && (() => {
242
- try { validateLifecycleRecoveryAttestation(value.authority); return false; } catch { return true; }
243
- })())) {
224
+ if (typeof value.requestId !== 'string') {
244
225
  reject(new Error('SANDBOX_CONTROL_EXECUTOR_GATE_INVALID'));
245
226
  return;
246
227
  }
247
228
  resolve({
248
229
  owner: owner as BrokerOwner,
249
- authority: value.authority === null || value.authority === undefined ? null : value.authority as LifecycleRecoveryAttestationV1,
250
- requestId: value.requestId,
251
- operationId: value.operationId,
252
- phase: value.phase as SandboxControlExecutorGateV2['phase']
230
+ requestId: value.requestId
253
231
  });
254
232
  });
255
233
  });
@@ -273,17 +251,6 @@ function controllerFailure(error: unknown): SandboxControlExecutionResult {
273
251
  return { exitCode: 1, stdout: `${JSON.stringify(payload)}\n`, stderr: '' };
274
252
  }
275
253
 
276
- function orchestrationFailure(code: string, message: string): SandboxControlExecutionResult {
277
- return {
278
- exitCode: 1,
279
- stdout: `${JSON.stringify({
280
- status: 'failed', changed: false, taskId: null, run: null, next: null,
281
- error: { code, message }
282
- })}\n`,
283
- stderr: ''
284
- };
285
- }
286
-
287
254
  function lifecycleFailure(code: string, message: string): SandboxControlExecutionResult {
288
255
  return {
289
256
  exitCode: 1,
@@ -330,7 +297,6 @@ function finalizationResult(result: Awaited<ReturnType<typeof applyTaskFinalizat
330
297
  type ExecuteRequestOptions = Readonly<{
331
298
  buildIdentity?: typeof computeLifecycleBuildIdentity;
332
299
  resolveControllerBinding?: typeof resolveCodexControllerBinding;
333
- lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
334
300
  }>;
335
301
 
336
302
  async function executeRequestInner(
@@ -406,7 +372,6 @@ async function executeRequestInner(
406
372
  return controllerFailure(error);
407
373
  }
408
374
  }
409
- if (request.family === 'task-workflow') return executeTaskWorkflow(manifest, request.workflow, options.lifecycleRecoveryAttestation ?? null);
410
375
  if (request.family === 'task-finalization') {
411
376
  const operation = parseTaskControlOperation(
412
377
  'task-finalization', [manifest.taskId!, 'complete', '--agent', request.agent]
@@ -420,35 +385,21 @@ async function executeRequestInner(
420
385
  taskId: manifest.taskId!,
421
386
  generation: manifest.generation,
422
387
  manifestPath,
423
- requestId: request.id,
424
- lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
388
+ requestId: request.id
425
389
  }),
426
390
  operation
427
391
  );
428
392
  return finalizationResult(await result);
429
393
  }
430
394
  const boundArgs = bindSandboxControlTask(request, manifest.taskId!);
431
- if (request.family === 'task-orchestration') {
432
- boundArgs.push('--git-worktree-root', manifest.worktreeRoot);
433
- }
434
395
  let operation: TaskControlOperation;
435
396
  try {
436
397
  operation = parseTaskControlOperation(request.family, boundArgs);
437
398
  } catch (error) {
438
399
  const message = error instanceof Error ? error.message : String(error);
439
400
  const code = /^([A-Z][A-Z0-9_]+)/u.exec(message)?.[1] ?? 'TASK_CONTROL_OPERATION_INVALID';
440
- return request.family === 'task-lifecycle'
441
- ? lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', message)
442
- : orchestrationFailure(code, message);
401
+ return lifecycleFailure('LIFECYCLE_PAYLOAD_INVALID', `${code}: ${message}`);
443
402
  }
444
- const diagnosticLog: OrchestrationDiagnosticLogger = (event, fields): void => {
445
- appendExecutorAudit(manifest, event.startsWith('orchestration-') ? event : `orchestration-${event}`, {
446
- requestId: request.id,
447
- requestFamily: request.family,
448
- sandboxTaskId: manifest.taskId,
449
- ...fields
450
- });
451
- };
452
403
  const context = createSandboxExecutorExecutionContext({
453
404
  repoRoot: manifest.repoRoot,
454
405
  worktreeRoot: manifest.worktreeRoot,
@@ -456,9 +407,7 @@ async function executeRequestInner(
456
407
  taskId: manifest.taskId!,
457
408
  generation: manifest.generation,
458
409
  manifestPath,
459
- requestId: request.id,
460
- diagnosticLog,
461
- lifecycleRecoveryAttestation: options.lifecycleRecoveryAttestation ?? null
410
+ requestId: request.id
462
411
  });
463
412
  const format = (value: unknown): SandboxControlExecutionResult => {
464
413
  const result = value as { status?: string };
@@ -468,12 +417,8 @@ async function executeRequestInner(
468
417
  stderr: ''
469
418
  };
470
419
  };
471
- if (request.family === 'task-orchestration') {
472
- if (operation.family !== 'task-orchestration') throw new Error('SANDBOX_CONTROL_ORCHESTRATION_OPERATION_INVALID');
473
- return format(await dispatchTaskControlOperation(context, operation));
474
- }
475
420
  if (operation.family !== 'task-lifecycle') throw new Error('SANDBOX_CONTROL_LIFECYCLE_OPERATION_INVALID');
476
- return format(await dispatchTaskControlOperation(context, operation));
421
+ return format(await dispatchTaskControlOperation(context, operation));
477
422
  }
478
423
 
479
424
  export async function executeRequest(
@@ -535,12 +480,6 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
535
480
  const raw = JSON.parse(fs.readFileSync(requestPath, 'utf8')) as unknown;
536
481
  const request = validateSandboxControlRequest(raw, manifest);
537
482
  if (gate.requestId !== request.id) throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_REQUEST_MISMATCH');
538
- if (gate.authority !== null
539
- && (gate.authority.requestId !== request.id
540
- || gate.authority.operationId !== gate.operationId
541
- || gate.authority.phase !== gate.phase)) {
542
- throw new Error('SANDBOX_CONTROL_EXECUTOR_GATE_AUTHORITY_MISMATCH');
543
- }
544
483
  appendExecutorAudit(manifest, 'executor-request-validated', requestAuditFields(manifest, request));
545
484
  const requestDirectory = path.resolve(path.dirname(requestPath));
546
485
  const processingDirectory = path.resolve(path.join(manifest.processingDir, request.id));
@@ -558,7 +497,7 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
558
497
  let result: SandboxControlExecutionResult;
559
498
  try {
560
499
  assertSandboxControlExecutorAuthority(manifest, gateOwner);
561
- const operation = request.family === 'task-lifecycle' || request.family === 'task-orchestration'
500
+ const operation = request.family === 'task-lifecycle'
562
501
  ? (() => {
563
502
  try { return parseTaskControlOperation(request.family, request.args); } catch { return null; }
564
503
  })()
@@ -566,10 +505,8 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
566
505
  const context = createSandboxControlAuditContext(manifest, {
567
506
  requestId: request.id,
568
507
  family: request.family,
569
- operation: operation?.family === 'task-orchestration'
570
- ? operation.intent
571
- : operation?.family === 'task-lifecycle'
572
- ? operation.request.intent
508
+ operation: operation?.family === 'task-lifecycle'
509
+ ? operation.request.intent
573
510
  : request.family === 'task-finalization' ? request.operation : null,
574
511
  phase: 'started-committed',
575
512
  outcome: 'in-progress'
@@ -578,9 +515,7 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
578
515
  writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'started-committed' });
579
516
  const execution = readJsonExecution(executionPath(manifest, request.id));
580
517
  atomicWriteJson(executionPath(manifest, request.id), { ...execution, phase: 'running', updatedAt: Date.now() });
581
- result = await executeRequest(manifest, manifestPath, request, {
582
- lifecycleRecoveryAttestation: gate.authority
583
- });
518
+ result = await executeRequest(manifest, manifestPath, request);
584
519
  } catch (error) {
585
520
  appendExecutorAudit(manifest, 'executor-authority-or-dispatch-failed', {
586
521
  ...requestAuditFields(manifest, request),
@@ -599,9 +534,7 @@ export async function runSandboxControlExecutor(requestPath: string, nonce: stri
599
534
  }
600
535
  : request.family === 'task-create'
601
536
  ? taskCreateExecutionFailure(request.id, code, detail)
602
- : request.family === 'task-lifecycle'
603
- ? lifecycleFailure(code, detail)
604
- : orchestrationFailure(code, detail);
537
+ : lifecycleFailure(code, detail);
605
538
  }
606
539
  process.stdout.write(result.stdout);
607
540
  process.stderr.write(result.stderr);
@@ -5,13 +5,11 @@ export function finalizationTerminalResponse(taskId: string, requestId: string,
5
5
  const pendingSteps = [
6
6
  receipt.taskComment === 'pending' ? 'task-comment' : null,
7
7
  receipt.verification === 'pending' ? 'verification' : null,
8
- receipt.summary === 'pending' ? 'summary' : null,
9
- receipt.postSummaryVerification === 'pending' ? 'post-summary-verification' : null
8
+ receipt.summary === 'pending' ? 'summary' : null
10
9
  ].filter((step): step is string => step !== null);
11
10
  const completedSteps = [
12
11
  'lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment',
13
- receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary',
14
- receipt.postSummaryVerification === 'pending' ? null : 'post-summary-verification'
12
+ receipt.verification === 'pending' ? null : 'verification', receipt.summary === 'pending' ? null : 'summary'
15
13
  ]
16
14
  .filter((step): step is string => step !== null);
17
15
  const warnings = receipt.warnings
@@ -19,11 +17,11 @@ export function finalizationTerminalResponse(taskId: string, requestId: string,
19
17
  .map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
20
18
  const result = {
21
19
  status: 'completed', changed: false, taskId,
20
+ backfill: { status: 'no-op', changed: false, error: null },
22
21
  lifecycle: { status: 'no-op', changed: false, error: null },
23
22
  taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
24
23
  verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
25
24
  summary: receipt.summary === 'pending' ? null : { status: 'no-op', changed: false, error: null },
26
- postSummaryVerification: receipt.postSummaryVerification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
27
25
  completedSteps, pendingSteps,
28
26
  result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
29
27
  ? 'completed_with_warnings' : 'completed',