@fitlab-ai/agent-infra 0.11.12 → 0.11.13

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 (68) hide show
  1. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +11 -25
  2. package/dist/lib/internal/sandbox-control.js +1 -81
  3. package/dist/lib/internal/task-finalization.js +26 -19
  4. package/dist/lib/internal/task-lifecycle.js +1 -1
  5. package/dist/lib/platform/github-client.js +33 -3
  6. package/dist/lib/sandbox/commands/create.js +6 -2
  7. package/dist/lib/sandbox/commands/ls.js +4 -5
  8. package/dist/lib/sandbox/commands/rm.js +1 -1
  9. package/dist/lib/sandbox/control/client.js +4 -64
  10. package/dist/lib/sandbox/control/executor.js +4 -50
  11. package/dist/lib/sandbox/control/lifecycle.js +66 -4
  12. package/dist/lib/sandbox/control/protocol.js +5 -16
  13. package/dist/lib/sandbox/control/server.js +203 -234
  14. package/dist/lib/sandbox/control/task-view.js +0 -13
  15. package/dist/lib/sandbox/mounts.js +7 -0
  16. package/dist/lib/sandbox/recovery.js +28 -9
  17. package/dist/lib/sandbox/removal.js +19 -0
  18. package/dist/lib/sandbox/workspace-identity.js +18 -6
  19. package/dist/lib/sandbox/workspace-view.js +48 -2
  20. package/dist/lib/task/commands/ls.js +3 -3
  21. package/dist/lib/task/control-authority.js +2 -0
  22. package/dist/lib/task/control-recovery.js +1 -2
  23. package/dist/lib/task/finalization.js +14 -0
  24. package/dist/lib/task/lifecycle.js +203 -31
  25. package/dist/lib/task/orchestration.js +26 -72
  26. package/dist/lib/task/short-id.js +37 -27
  27. package/dist/lib/task/verification-engine.js +4 -11
  28. package/dist/lifecycle-build-manifest.json +3 -3
  29. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +20 -23
  30. package/lib/internal/sandbox-control.ts +0 -97
  31. package/lib/internal/task-finalization.ts +30 -18
  32. package/lib/internal/task-lifecycle.ts +1 -1
  33. package/lib/platform/github-client.ts +34 -3
  34. package/lib/sandbox/commands/create.ts +6 -1
  35. package/lib/sandbox/commands/ls.ts +4 -5
  36. package/lib/sandbox/commands/rm.ts +1 -1
  37. package/lib/sandbox/control/client.ts +5 -78
  38. package/lib/sandbox/control/executor.ts +2 -53
  39. package/lib/sandbox/control/lifecycle.ts +67 -4
  40. package/lib/sandbox/control/protocol.ts +6 -20
  41. package/lib/sandbox/control/server.ts +9 -44
  42. package/lib/sandbox/control/task-view.ts +0 -19
  43. package/lib/sandbox/mounts.ts +8 -0
  44. package/lib/sandbox/recovery.ts +35 -16
  45. package/lib/sandbox/removal.ts +21 -2
  46. package/lib/sandbox/workspace-identity.ts +16 -6
  47. package/lib/sandbox/workspace-view.ts +41 -2
  48. package/lib/task/commands/ls.ts +3 -3
  49. package/lib/task/control-authority.ts +1 -0
  50. package/lib/task/control-recovery.ts +2 -3
  51. package/lib/task/finalization.ts +14 -0
  52. package/lib/task/lifecycle.ts +180 -27
  53. package/lib/task/orchestration.ts +30 -68
  54. package/lib/task/short-id.ts +33 -40
  55. package/lib/task/verification-engine.ts +3 -9
  56. package/package.json +1 -1
  57. package/templates/.agents/rules/README.en.md +1 -0
  58. package/templates/.agents/rules/README.zh-CN.md +1 -0
  59. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  60. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  61. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  62. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  63. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -26
  64. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -23
  65. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  66. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  67. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  68. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
@@ -1,6 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { validateTaskCreateCandidate } from '../../task/create.js';
3
- import { normalizeAgentToken } from '../../agent-clients/tokens.js';
4
3
  import { isAgentClientId } from '../../agent-clients/types.js';
5
4
  export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
6
5
  export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
@@ -11,7 +10,7 @@ export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
11
10
  export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
12
11
  export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
13
12
  export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
14
- export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', 'agent-client'];
13
+ export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-create', 'agent-client'];
15
14
  export const DEFAULT_SANDBOX_CONTROL_TIMING = Object.freeze({
16
15
  controlTickMs: 250,
17
16
  parkedBindingInitialMs: 1_000,
@@ -151,31 +150,21 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
151
150
  }
152
151
  return request;
153
152
  }
154
- if (request.family === 'task-finalization') {
155
- const expected = ['agent', 'args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
156
- if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
157
- || request.operation !== 'complete'
158
- || !Array.isArray(request.args) || request.args.length !== 0
159
- || typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent) {
160
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
161
- }
162
- if (manifest.mode !== 'task-bound' || !manifest.taskId) {
163
- fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot finalize tasks');
164
- }
165
- return request;
166
- }
167
153
  const expected = ['args', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
168
154
  if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
169
155
  || !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
170
156
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
171
157
  }
158
+ if (request.args[1] === 'complete') {
159
+ fail('SANDBOX_CONTROL_COMMAND_DENIED', 'complete must use task-finalization');
160
+ }
172
161
  if (manifest.mode !== 'task-bound' || !manifest.taskId) {
173
162
  fail('SANDBOX_CONTROL_BRANCH_ONLY', "branch-only sandboxes cannot coordinate tasks; return to the host and run 'ai sandbox start --recreate <task-ref-or-correct-branch>'");
174
163
  }
175
164
  return request;
176
165
  }
177
166
  export function bindSandboxControlTask(request, taskId) {
178
- if (request.family === 'task-create' || request.family === 'agent-client' || request.family === 'task-finalization') {
167
+ if (request.family === 'task-create' || request.family === 'agent-client') {
179
168
  fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
180
169
  }
181
170
  if (request.args.length === 0)
@@ -20,41 +20,12 @@ import { nextSandboxControlBackoff } from './timing.js';
20
20
  import { readTaskFinalizationReceipt } from '../../task/finalization.js';
21
21
  import { recoverAgentClientOperation, validateAgentClientOperation } from './agent-operation.js';
22
22
  import { validateSandboxControlIdentity } from './identity-sentinel.js';
23
- import { mergeSandboxTaskView, taskViewAfterFinalization, taskViewForManifest } from './task-view.js';
23
+ import { mergeSandboxTaskView, taskViewForManifest } from './task-view.js';
24
24
  import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
25
- async function taskViewForFinalizationResult(manifest, request, result) {
26
- if (request.family !== 'task-finalization' || result?.exitCode !== 0 || !manifest.taskId)
27
- return null;
28
- let receipt = null;
29
- for (let attempt = 0; attempt < 100 && !receipt; attempt += 1) {
30
- try {
31
- receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
32
- }
33
- catch {
34
- // A missing or temporarily moving task directory is retried before publishing completion.
35
- }
36
- if (!receipt)
37
- await new Promise((resolve) => setTimeout(resolve, 10));
38
- }
39
- const finalized = taskViewAfterFinalization({
40
- taskId: manifest.taskId,
41
- generation: manifest.generation,
42
- requestId: request.id,
43
- receipt
44
- });
45
- if (finalized.state === 'unknown')
46
- return finalized;
47
- const canonical = taskViewForManifest({ ...manifest, receipt });
48
- return canonical.state === 'current' && canonical.observedSource === 'completed'
49
- ? canonical
50
- : finalized;
51
- }
52
25
  function appendBrokerAudit(manifest, event, fields = {}) {
53
26
  appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
54
27
  }
55
28
  function operationKey(request) {
56
- if (request.family === 'task-finalization')
57
- return request.operation;
58
29
  if (request.family === 'task-create')
59
30
  return 'create';
60
31
  if (request.family === 'agent-client')
@@ -220,9 +191,7 @@ function taskCreateDomainEvidence(manifest, output) {
220
191
  async function readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payloadOutput) {
221
192
  if (!operation)
222
193
  return { domain: null };
223
- const taskRef = request.family === 'task-finalization'
224
- ? manifest.taskId
225
- : 'args' in request ? request.args[0] ?? null : manifest.taskId;
194
+ const taskRef = 'args' in request ? request.args[0] ?? null : manifest.taskId;
226
195
  const output = parseControlOutput(payloadOutput);
227
196
  if (!taskRef && operation.family !== 'task-create' && operation.family !== 'agent-client') {
228
197
  return { domain: null };
@@ -730,6 +699,8 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
730
699
  try {
731
700
  if (!brokerOwns())
732
701
  return;
702
+ if (isSandboxControlRootQuiescing(root))
703
+ return;
733
704
  if (taskView.state === 'current' && taskView.observedSource === 'completed') {
734
705
  // Recheck durable re-entry evidence before publishing a trusted startup view.
735
706
  const unverified = { ...taskView, state: 'unknown',
@@ -836,11 +807,6 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
836
807
  settledExecution = active;
837
808
  active = null;
838
809
  }
839
- const finalizationView = settledExecution
840
- ? await taskViewForFinalizationResult(manifest, settledExecution.request, settledExecution.result)
841
- : null;
842
- if (finalizationView)
843
- taskView = finalizationView;
844
810
  let reasonCode = null;
845
811
  try {
846
812
  if (!brokerOwns())
@@ -918,224 +884,232 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
918
884
  fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
919
885
  }
920
886
  let retiring = false;
921
- for (const name of fs.readdirSync(requestsDir).sort()) {
922
- if (!/^[a-f0-9-]{16,64}\.json$/.test(name))
923
- continue;
924
- const id = name.slice(0, -5);
925
- const source = path.join(requestsDir, name);
926
- let claimed = null;
927
- let validatedRequest = null;
928
- let prepared = null;
929
- try {
930
- if (!brokerOwns()) {
931
- retiring = true;
932
- break;
933
- }
934
- claimed = claimRequest(manifest, source, id);
935
- appendBrokerAudit(manifest, 'request-claimed', {
936
- requestId: id,
937
- requestSourcePath: source,
938
- requestClaimedPath: claimed,
939
- requestProcessingDir: path.dirname(claimed)
940
- });
941
- if (!brokerOwns()) {
942
- retiring = true;
943
- break;
944
- }
945
- consumeRequest(consumedDir, id);
946
- if (!brokerOwns()) {
947
- retiring = true;
948
- break;
949
- }
950
- if (reasonCode)
951
- throw new Error(reasonCode);
952
- if (active)
953
- throw new Error('SANDBOX_CONTROL_BUSY');
954
- const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
955
- if (request.family === 'agent-client')
956
- validateAgentClientOperation(request, manifest);
957
- validatedRequest = request;
958
- criticalRequestPhase(manifest, request, 'validated', 'in-progress');
959
- appendBrokerAudit(manifest, 'request-validated', {
960
- ...requestAuditFields(manifest, request),
961
- requestPath: claimed
962
- });
963
- if (bindingCheck(manifest))
964
- throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
965
- if (readActiveLease(manifest))
966
- throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
967
- criticalRequestPhase(manifest, request, 'gated', 'in-progress');
968
- appendBrokerAudit(manifest, 'request-gates-passed', {
969
- ...requestAuditFields(manifest, request),
970
- requestPath: claimed,
971
- bindingChecked: true,
972
- handoffLeaseChecked: true
973
- });
974
- criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
975
- writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
976
- appendBrokerAudit(manifest, 'executor-reservation-written', {
977
- ...requestAuditFields(manifest, request),
978
- requestPath: claimed,
979
- executionPath: executionPath(manifest, request.id)
980
- });
981
- appendBrokerAudit(manifest, 'executor-prepare-start', {
982
- ...requestAuditFields(manifest, request),
983
- requestPath: claimed,
984
- executorCwd: manifest.repoRoot,
985
- executorEntry: options.internalCliPath ?? process.argv[1] ?? null
986
- });
987
- prepared = await prepareExecution({
988
- manifest, manifestPath, request, requestPath: claimed,
989
- internalCliPath: options.internalCliPath ?? process.argv[1]
990
- });
991
- const preparedExecution = prepared;
992
- if (!preparedExecution)
993
- throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
994
- criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
995
- appendBrokerAudit(manifest, 'executor-prepared', {
996
- ...requestAuditFields(manifest, request),
997
- requestPath: claimed,
998
- executorCwd: manifest.repoRoot,
999
- childPid: preparedExecution.execution.child.pid,
1000
- childStartTime: preparedExecution.execution.child.startTime,
1001
- childProcessGroupId: preparedExecution.execution.child.processGroupId,
1002
- executionPath: executionPath(manifest, request.id)
1003
- });
1004
- const execution = {
1005
- request, prepared: preparedExecution, result: null,
1006
- resultEvidenceWritten: false, failure: null, settled: false
1007
- };
1008
- active = execution;
1009
- preparedExecution.completion.then((result) => {
1010
- execution.result = sanitizeSandboxControlResult(manifest, result);
1011
- appendBrokerAudit(manifest, 'executor-completed', {
887
+ const quiescing = isSandboxControlRootQuiescing(root);
888
+ if (quiescing && !active)
889
+ break;
890
+ if (!quiescing)
891
+ for (const name of fs.readdirSync(requestsDir).sort()) {
892
+ if (!/^[a-f0-9-]{16,64}\.json$/.test(name))
893
+ continue;
894
+ const id = name.slice(0, -5);
895
+ const source = path.join(requestsDir, name);
896
+ let claimed = null;
897
+ let validatedRequest = null;
898
+ let prepared = null;
899
+ try {
900
+ if (isSandboxControlRootQuiescing(root)) {
901
+ retiring = true;
902
+ break;
903
+ }
904
+ if (!brokerOwns()) {
905
+ retiring = true;
906
+ break;
907
+ }
908
+ claimed = claimRequest(manifest, source, id);
909
+ appendBrokerAudit(manifest, 'request-claimed', {
910
+ requestId: id,
911
+ requestSourcePath: source,
912
+ requestClaimedPath: claimed,
913
+ requestProcessingDir: path.dirname(claimed)
914
+ });
915
+ if (!brokerOwns()) {
916
+ retiring = true;
917
+ break;
918
+ }
919
+ consumeRequest(consumedDir, id);
920
+ if (!brokerOwns()) {
921
+ retiring = true;
922
+ break;
923
+ }
924
+ if (reasonCode)
925
+ throw new Error(reasonCode);
926
+ if (active)
927
+ throw new Error('SANDBOX_CONTROL_BUSY');
928
+ const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
929
+ if (request.family === 'agent-client')
930
+ validateAgentClientOperation(request, manifest);
931
+ validatedRequest = request;
932
+ criticalRequestPhase(manifest, request, 'validated', 'in-progress');
933
+ appendBrokerAudit(manifest, 'request-validated', {
934
+ ...requestAuditFields(manifest, request),
935
+ requestPath: claimed
936
+ });
937
+ if (bindingCheck(manifest))
938
+ throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
939
+ if (readActiveLease(manifest))
940
+ throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
941
+ criticalRequestPhase(manifest, request, 'gated', 'in-progress');
942
+ appendBrokerAudit(manifest, 'request-gates-passed', {
1012
943
  ...requestAuditFields(manifest, request),
1013
- ...resultAuditFields(execution.result),
944
+ requestPath: claimed,
945
+ bindingChecked: true,
946
+ handoffLeaseChecked: true
947
+ });
948
+ criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
949
+ writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
950
+ appendBrokerAudit(manifest, 'executor-reservation-written', {
951
+ ...requestAuditFields(manifest, request),
952
+ requestPath: claimed,
953
+ executionPath: executionPath(manifest, request.id)
954
+ });
955
+ appendBrokerAudit(manifest, 'executor-prepare-start', {
956
+ ...requestAuditFields(manifest, request),
957
+ requestPath: claimed,
958
+ executorCwd: manifest.repoRoot,
959
+ executorEntry: options.internalCliPath ?? process.argv[1] ?? null
960
+ });
961
+ prepared = await prepareExecution({
962
+ manifest, manifestPath, request, requestPath: claimed,
963
+ internalCliPath: options.internalCliPath ?? process.argv[1]
964
+ });
965
+ const preparedExecution = prepared;
966
+ if (!preparedExecution)
967
+ throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
968
+ criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
969
+ appendBrokerAudit(manifest, 'executor-prepared', {
970
+ ...requestAuditFields(manifest, request),
971
+ requestPath: claimed,
972
+ executorCwd: manifest.repoRoot,
1014
973
  childPid: preparedExecution.execution.child.pid,
1015
- childStartTime: preparedExecution.execution.child.startTime
974
+ childStartTime: preparedExecution.execution.child.startTime,
975
+ childProcessGroupId: preparedExecution.execution.child.processGroupId,
976
+ executionPath: executionPath(manifest, request.id)
1016
977
  });
1017
- try {
1018
- writeSandboxControlResultEvidence(manifest, request.id, execution.result);
1019
- execution.resultEvidenceWritten = true;
1020
- execution.settled = true;
1021
- appendBrokerAudit(manifest, 'executor-result-evidence-written', {
978
+ const execution = {
979
+ request, prepared: preparedExecution, result: null,
980
+ resultEvidenceWritten: false, failure: null, settled: false
981
+ };
982
+ active = execution;
983
+ preparedExecution.completion.then((result) => {
984
+ execution.result = sanitizeSandboxControlResult(manifest, result);
985
+ appendBrokerAudit(manifest, 'executor-completed', {
1022
986
  ...requestAuditFields(manifest, request),
1023
- resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
1024
- ...resultAuditFields(execution.result)
987
+ ...resultAuditFields(execution.result),
988
+ childPid: preparedExecution.execution.child.pid,
989
+ childStartTime: preparedExecution.execution.child.startTime
1025
990
  });
1026
- }
1027
- catch (error) {
991
+ try {
992
+ writeSandboxControlResultEvidence(manifest, request.id, execution.result);
993
+ execution.resultEvidenceWritten = true;
994
+ execution.settled = true;
995
+ appendBrokerAudit(manifest, 'executor-result-evidence-written', {
996
+ ...requestAuditFields(manifest, request),
997
+ resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
998
+ ...resultAuditFields(execution.result)
999
+ });
1000
+ }
1001
+ catch (error) {
1002
+ execution.failure = error;
1003
+ appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
1004
+ ...requestAuditFields(manifest, request),
1005
+ errorType: error instanceof Error ? error.name : typeof error,
1006
+ });
1007
+ }
1008
+ }, (error) => {
1028
1009
  execution.failure = error;
1029
- appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
1010
+ execution.settled = true;
1011
+ appendBrokerAudit(manifest, 'executor-failed', {
1030
1012
  ...requestAuditFields(manifest, request),
1013
+ childPid: preparedExecution.execution.child.pid,
1014
+ childStartTime: preparedExecution.execution.child.startTime,
1031
1015
  errorType: error instanceof Error ? error.name : typeof error,
1032
1016
  });
1033
- }
1034
- }, (error) => {
1035
- execution.failure = error;
1036
- execution.settled = true;
1037
- appendBrokerAudit(manifest, 'executor-failed', {
1038
- ...requestAuditFields(manifest, request),
1039
- childPid: preparedExecution.execution.child.pid,
1040
- childStartTime: preparedExecution.execution.child.startTime,
1041
- errorType: error instanceof Error ? error.name : typeof error,
1042
1017
  });
1043
- });
1044
- if (!brokerOwns()) {
1045
- prepared.terminate(false);
1046
- active = null;
1047
- retiring = true;
1048
- continue;
1049
- }
1050
- criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
1051
- writeAcceptedResponse(manifest, {
1052
- version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
1053
- });
1054
- writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
1055
- appendBrokerAudit(manifest, 'request-accepted', {
1056
- ...requestAuditFields(manifest, request),
1057
- acceptedPath: acceptedResponsePath(manifest, id),
1058
- executionPhase: 'prepared'
1059
- });
1060
- if (!brokerOwns()) {
1061
- prepared.terminate(false);
1062
- active = null;
1063
- retiring = true;
1064
- break;
1065
- }
1066
- try {
1067
- criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
1068
- appendBrokerAudit(manifest, 'executor-start', {
1069
- ...requestAuditFields(manifest, request),
1070
- childPid: prepared.execution.child.pid,
1071
- childStartTime: prepared.execution.child.startTime,
1072
- executionPath: executionPath(manifest, request.id)
1018
+ if (!brokerOwns()) {
1019
+ prepared.terminate(false);
1020
+ active = null;
1021
+ retiring = true;
1022
+ continue;
1023
+ }
1024
+ criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
1025
+ writeAcceptedResponse(manifest, {
1026
+ version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
1073
1027
  });
1074
- prepared.start(brokerOwns);
1075
- }
1076
- catch (error) {
1077
- appendBrokerAudit(manifest, 'executor-start-failed', {
1028
+ writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
1029
+ appendBrokerAudit(manifest, 'request-accepted', {
1078
1030
  ...requestAuditFields(manifest, request),
1079
- errorType: error instanceof Error ? error.name : typeof error,
1031
+ acceptedPath: acceptedResponsePath(manifest, id),
1032
+ executionPhase: 'prepared'
1080
1033
  });
1081
- const owned = brokerOwns();
1082
- prepared.terminate(owned);
1083
- if (!owned || !brokerOwns()) {
1034
+ if (!brokerOwns()) {
1035
+ prepared.terminate(false);
1084
1036
  active = null;
1085
1037
  retiring = true;
1086
1038
  break;
1087
1039
  }
1088
- writeSandboxControlResponse(manifest, unknown(id));
1089
- if (!brokerOwns()) {
1040
+ try {
1041
+ criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
1042
+ appendBrokerAudit(manifest, 'executor-start', {
1043
+ ...requestAuditFields(manifest, request),
1044
+ childPid: prepared.execution.child.pid,
1045
+ childStartTime: prepared.execution.child.startTime,
1046
+ executionPath: executionPath(manifest, request.id)
1047
+ });
1048
+ prepared.start(brokerOwns);
1049
+ }
1050
+ catch (error) {
1051
+ appendBrokerAudit(manifest, 'executor-start-failed', {
1052
+ ...requestAuditFields(manifest, request),
1053
+ errorType: error instanceof Error ? error.name : typeof error,
1054
+ });
1055
+ const owned = brokerOwns();
1056
+ prepared.terminate(owned);
1057
+ if (!owned || !brokerOwns()) {
1058
+ active = null;
1059
+ retiring = true;
1060
+ break;
1061
+ }
1062
+ writeSandboxControlResponse(manifest, unknown(id));
1063
+ if (!brokerOwns()) {
1064
+ active = null;
1065
+ retiring = true;
1066
+ break;
1067
+ }
1068
+ removeAcceptedResponse(manifest, id);
1069
+ fs.rmSync(path.join(manifest.processingDir, id), { recursive: true, force: true });
1070
+ active = null;
1071
+ claimed = null;
1072
+ if (!brokerOwns()) {
1073
+ retiring = true;
1074
+ break;
1075
+ }
1076
+ appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
1077
+ continue;
1078
+ }
1079
+ }
1080
+ catch (error) {
1081
+ if (prepared) {
1082
+ try {
1083
+ prepared.terminate(brokerOwns());
1084
+ }
1085
+ catch { /* cleanup is best effort after a failed admission */ }
1086
+ prepared = null;
1090
1087
  active = null;
1088
+ }
1089
+ if (!brokerOwns()) {
1091
1090
  retiring = true;
1092
1091
  break;
1093
1092
  }
1094
- removeAcceptedResponse(manifest, id);
1095
- fs.rmSync(path.join(manifest.processingDir, id), { recursive: true, force: true });
1096
- active = null;
1097
- claimed = null;
1093
+ const detail = controlError(error);
1094
+ appendBrokerAudit(manifest, 'request-rejected', {
1095
+ requestId: id,
1096
+ requestFamily: validatedRequest?.family ?? null,
1097
+ sandboxTaskId: manifest.taskId,
1098
+ requestPath: claimed,
1099
+ errorCode: detail.code,
1100
+ errorRetryable: detail.retryable,
1101
+ errorType: error instanceof Error ? error.name : typeof error,
1102
+ });
1103
+ writeSandboxControlResponse(manifest, rejected(id, error));
1098
1104
  if (!brokerOwns()) {
1099
1105
  retiring = true;
1100
1106
  break;
1101
1107
  }
1102
- appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
1103
- continue;
1104
- }
1105
- }
1106
- catch (error) {
1107
- if (prepared) {
1108
- try {
1109
- prepared.terminate(brokerOwns());
1110
- }
1111
- catch { /* cleanup is best effort after a failed admission */ }
1112
- prepared = null;
1113
- active = null;
1114
- }
1115
- if (!brokerOwns()) {
1116
- retiring = true;
1117
- break;
1118
- }
1119
- const detail = controlError(error);
1120
- appendBrokerAudit(manifest, 'request-rejected', {
1121
- requestId: id,
1122
- requestFamily: validatedRequest?.family ?? null,
1123
- sandboxTaskId: manifest.taskId,
1124
- requestPath: claimed,
1125
- errorCode: detail.code,
1126
- errorRetryable: detail.retryable,
1127
- errorType: error instanceof Error ? error.name : typeof error,
1128
- });
1129
- writeSandboxControlResponse(manifest, rejected(id, error));
1130
- if (!brokerOwns()) {
1131
- retiring = true;
1132
- break;
1108
+ removeAcceptedResponse(manifest, id);
1109
+ if (claimed)
1110
+ fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
1133
1111
  }
1134
- removeAcceptedResponse(manifest, id);
1135
- if (claimed)
1136
- fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
1137
1112
  }
1138
- }
1139
1113
  if (retiring || !brokerOwns())
1140
1114
  break;
1141
1115
  await delay(timing.controlTickMs);
@@ -1157,11 +1131,6 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
1157
1131
  }
1158
1132
  }
1159
1133
  if (owned && active.result && active.resultEvidenceWritten) {
1160
- const finalizationView = await taskViewForFinalizationResult(manifest, active.request, active.result);
1161
- if (finalizationView) {
1162
- taskView = finalizationView;
1163
- writeSandboxControlStatus(manifest, broker, 'healthy', null, null, Date.now(), taskView);
1164
- }
1165
1134
  if (publishExecutionResult(manifest, active.request, active.result, brokerOwns)) {
1166
1135
  if (brokerOwns()) {
1167
1136
  removeAcceptedResponse(manifest, active.request.id);
@@ -130,19 +130,6 @@ export function projectSandboxTaskView(input) {
130
130
  reasonCode: receipt ? 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED' : 'SANDBOX_TASK_VIEW_EVIDENCE_UNAVAILABLE'
131
131
  };
132
132
  }
133
- export function taskViewAfterFinalization(params) {
134
- const receipt = receiptIdentity(params.receipt, params.generation, params.requestId);
135
- if (!receipt) {
136
- return {
137
- state: 'unknown', taskId: params.taskId, observedSource: 'unknown', receipt: null,
138
- reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
139
- };
140
- }
141
- return {
142
- state: 'finalized-stale', taskId: params.taskId, observedSource: 'active', receipt,
143
- reasonCode: 'SANDBOX_TASK_VIEW_FINALIZED'
144
- };
145
- }
146
133
  export function mergeSandboxTaskView(canonical, previous) {
147
134
  if (previous.state !== 'finalized-stale' && previous.state !== 'unknown')
148
135
  return canonical;
@@ -44,6 +44,13 @@ export function sandboxCoreBindMounts(config, branch, overrides) {
44
44
  containerPath: taskMountPath,
45
45
  readOnly: false
46
46
  });
47
+ if (overrides.completedTaskSource) {
48
+ mounts.push({
49
+ hostPaths: [overrides.completedTaskSource],
50
+ containerPath: path.posix.join('/workspace/.agents/workspace/completed', overrides.taskId),
51
+ readOnly: false
52
+ });
53
+ }
47
54
  }
48
55
  mounts.push({
49
56
  hostPaths: [shareCommonDir(config)],