@fitlab-ai/agent-infra 0.11.4 → 0.11.5

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 (111) hide show
  1. package/dist/bin/internal-cli.js +0 -5
  2. package/dist/lib/decide.js +4 -41
  3. package/dist/lib/git/branch-worktree.js +45 -0
  4. package/dist/lib/internal/cli-route-inventory.js +0 -3
  5. package/dist/lib/internal/task-activity.js +7 -40
  6. package/dist/lib/internal/task-event.js +4 -37
  7. package/dist/lib/internal/task-finalization.js +9 -2
  8. package/dist/lib/internal/task-ledger.js +5 -43
  9. package/dist/lib/internal/task-lifecycle.js +1 -1
  10. package/dist/lib/internal/task-operation-registry.js +1 -4
  11. package/dist/lib/internal/task-review.js +5 -37
  12. package/dist/lib/internal/task-verify.js +3 -47
  13. package/dist/lib/internal/task-warning.js +4 -40
  14. package/dist/lib/platform/pr-summary.js +4 -23
  15. package/dist/lib/platform/verification-sync.js +12 -49
  16. package/dist/lib/sandbox/control/client.js +4 -1
  17. package/dist/lib/sandbox/control/executor.js +12 -4
  18. package/dist/lib/sandbox/control/protocol.js +2 -1
  19. package/dist/lib/sandbox/removal.js +15 -8
  20. package/dist/lib/task/activity-intent.js +1 -2
  21. package/dist/lib/task/artifact-operations.js +36 -2
  22. package/dist/lib/task/artifact-schema.js +7 -7
  23. package/dist/lib/task/control-authority.js +9 -166
  24. package/dist/lib/task/decision-intents.js +2 -4
  25. package/dist/lib/task/events.js +7 -28
  26. package/dist/lib/task/finalization-handoff.js +92 -0
  27. package/dist/lib/task/finalization.js +123 -12
  28. package/dist/lib/task/intermediate-cleanup.js +9 -148
  29. package/dist/lib/task/ledger-intents.js +3 -6
  30. package/dist/lib/task/lifecycle.js +50 -11
  31. package/dist/lib/task/local-artifact-finalization.js +13 -28
  32. package/dist/lib/task/review-command.js +1 -2
  33. package/dist/lib/task/review-finalization.js +1 -2
  34. package/dist/lib/task/snapshot.js +6 -6
  35. package/dist/lib/task/verification-engine.js +16 -11
  36. package/dist/lib/task/verification.js +1 -13
  37. package/dist/lib/task/workflow-warning-intents.js +2 -4
  38. package/dist/lib/task/write.js +1 -2
  39. package/dist/lifecycle-build-manifest.json +3 -3
  40. package/lib/decide.ts +5 -35
  41. package/lib/git/branch-worktree.ts +41 -0
  42. package/lib/internal/cli-route-inventory.ts +0 -2
  43. package/lib/internal/task-activity.ts +6 -34
  44. package/lib/internal/task-event.ts +4 -37
  45. package/lib/internal/task-finalization.ts +9 -2
  46. package/lib/internal/task-ledger.ts +5 -38
  47. package/lib/internal/task-lifecycle.ts +2 -2
  48. package/lib/internal/task-operation-registry.ts +1 -4
  49. package/lib/internal/task-review.ts +5 -31
  50. package/lib/internal/task-verify.ts +3 -39
  51. package/lib/internal/task-warning.ts +3 -32
  52. package/lib/platform/pr-summary.ts +4 -25
  53. package/lib/platform/verification-sync.ts +13 -53
  54. package/lib/sandbox/control/client.ts +5 -1
  55. package/lib/sandbox/control/executor.ts +13 -4
  56. package/lib/sandbox/control/protocol.ts +3 -2
  57. package/lib/sandbox/removal.ts +23 -7
  58. package/lib/task/activity-intent.ts +1 -2
  59. package/lib/task/artifact-operations.ts +58 -2
  60. package/lib/task/artifact-schema.ts +9 -7
  61. package/lib/task/control-authority.ts +14 -188
  62. package/lib/task/decision-intents.ts +2 -4
  63. package/lib/task/events.ts +8 -27
  64. package/lib/task/finalization-handoff.ts +104 -0
  65. package/lib/task/finalization.ts +120 -12
  66. package/lib/task/intermediate-cleanup.ts +9 -125
  67. package/lib/task/ledger-intents.ts +3 -6
  68. package/lib/task/lifecycle.ts +45 -13
  69. package/lib/task/local-artifact-finalization.ts +16 -35
  70. package/lib/task/review-command.ts +2 -5
  71. package/lib/task/review-finalization.ts +1 -4
  72. package/lib/task/snapshot.ts +6 -6
  73. package/lib/task/verification-engine.ts +20 -10
  74. package/lib/task/verification.ts +1 -15
  75. package/lib/task/workflow-warning-intents.ts +2 -4
  76. package/lib/task/write.ts +1 -4
  77. package/package.json +1 -1
  78. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
  79. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
  80. package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
  81. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
  82. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
  83. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
  84. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
  85. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
  86. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  87. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  88. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
  89. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
  90. package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
  91. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
  92. package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
  93. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
  94. package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
  95. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
  96. package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
  97. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
  98. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
  99. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
  100. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  101. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  102. package/dist/lib/internal/task-override.js +0 -138
  103. package/dist/lib/task/artifact-recovery.js +0 -476
  104. package/dist/lib/task/artifact-repair-intent.js +0 -181
  105. package/dist/lib/task/guard-override.js +0 -31
  106. package/dist/lib/task/human-override.js +0 -1259
  107. package/lib/internal/task-override.ts +0 -144
  108. package/lib/task/artifact-recovery.ts +0 -572
  109. package/lib/task/artifact-repair-intent.ts +0 -227
  110. package/lib/task/guard-override.ts +0 -50
  111. package/lib/task/human-override.ts +0 -1522
@@ -1,20 +1,16 @@
1
1
  import path from 'node:path';
2
2
  import crypto from 'node:crypto';
3
- import fs from 'node:fs';
4
3
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
5
4
  import { isAgentClientId } from '../agent-clients/types.js';
6
5
  import { getAgentClientAdapter } from '../agent-clients/registry.js';
7
- import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.js';
8
6
  import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
9
7
  import { applyTaskFinalization, commitPreparedTaskFinalization } from './finalization.js';
10
8
  import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
11
- import { locateActivityLog } from './activity-log.js';
12
9
  import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
13
10
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
14
11
  import { verifyTaskEvent } from './verification.js';
15
12
  import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
16
- import { findArtifactRecoveryIntentsByOperation, writeArtifactRecoveryIntent } from './artifact-repair-intent.js';
17
- import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
13
+ import { computeLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
18
14
  const lifecycleAuthorityPhases = [
19
15
  'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
20
16
  ];
@@ -184,7 +180,6 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
184
180
  const store = options.capabilityStore ?? createCodexCapabilityStore();
185
181
  const registry = authorityOperationMap.get(store);
186
182
  const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
187
- const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
188
183
  const capabilities = store.findByRecoveryOperation(operationId);
189
184
  const phaseMap = new Map();
190
185
  for (const capability of capabilities) {
@@ -208,23 +203,10 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
208
203
  ? 'consumed' : 'issued'
209
204
  });
210
205
  }
211
- for (const intent of intents) {
212
- if (!intent.phase)
213
- continue;
214
- const key = `${intent.phase}\0${intent.requestId}`;
215
- if (phaseMap.has(key))
216
- continue;
217
- phaseMap.set(key, {
218
- phase: intent.phase,
219
- requestId: intent.requestId,
220
- attestationId: null,
221
- state: intent.state === 'consumed' ? 'consumed' : 'observed'
222
- });
223
- }
224
206
  const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
225
207
  ? 'consumed'
226
208
  : capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
227
- const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
209
+ const committed = capabilityState === 'consumed';
228
210
  return {
229
211
  version: 1,
230
212
  operationId,
@@ -233,120 +215,6 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
233
215
  phases: Object.freeze([...phaseMap.values()])
234
216
  };
235
217
  }
236
- function lifecycleRecoveryCompensationError(code, message) {
237
- const error = new Error(`${code}: ${message}`);
238
- error.name = code;
239
- return error;
240
- }
241
- function lifecycleRecoveryEventCommitted(selector, repoRoot) {
242
- const resolved = resolveTaskRef(selector.taskId, { repoRoot });
243
- if (!resolved.ok || resolved.taskId !== selector.taskId)
244
- return false;
245
- let content;
246
- try {
247
- content = fs.readFileSync(resolved.taskMdPath, 'utf8');
248
- }
249
- catch {
250
- return false;
251
- }
252
- const section = locateActivityLog(content);
253
- if (!section)
254
- return false;
255
- const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' };
256
- const prefix = `${labels[selector.family]} (Round ${selector.round}`;
257
- return section.entries.some((entry) => {
258
- if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix))
259
- return false;
260
- const delimiter = entry.step[prefix.length];
261
- return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
262
- });
263
- }
264
- function lifecycleRecoveryIntentMatches(intent, selector) {
265
- return intent.taskId === selector.taskId
266
- && intent.family === selector.family
267
- && intent.artifact === selector.artifact
268
- && (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
269
- }
270
- /**
271
- * Completes only the durable tail of a committed task event after a process
272
- * restart. The task event itself is the commit boundary and is never replayed.
273
- */
274
- export function recoverLifecycleRecoveryOperation(input, options) {
275
- const selector = validateLifecycleAuthorityRequest(input);
276
- if (selector.phase !== 'task-event.completed') {
277
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
278
- }
279
- const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
280
- if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
281
- throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
282
- }
283
- if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
284
- || selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
285
- throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
286
- }
287
- const store = options.capabilityStore ?? createCodexCapabilityStore();
288
- const expected = {
289
- taskId: selector.taskId,
290
- hookDefinitionHash: selector.expectedHookDefinitionHash,
291
- buildIdentity,
292
- controller: options.controllerBinding
293
- };
294
- const capabilities = store.findByRecoveryOperation(selector.operationId);
295
- if (capabilities.length !== 1 || capabilities[0].capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
296
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
297
- }
298
- const capability = capabilities[0];
299
- const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
300
- const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
301
- if (!build.ok
302
- || capability.taskId !== selector.taskId
303
- || capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
304
- || capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
305
- || capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
306
- || capability.recoveryState === 'unreserved'
307
- || capability.recoveryOperationId !== selector.operationId
308
- || (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
309
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
310
- }
311
- const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
312
- if (!phase) {
313
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
314
- }
315
- const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
316
- .filter((intent) => intent.taskId === selector.taskId
317
- && intent.family === selector.family
318
- && intent.artifact === selector.artifact);
319
- if (intents.length !== 1) {
320
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
321
- }
322
- const intent = intents[0];
323
- if (!lifecycleRecoveryIntentMatches(intent, selector)
324
- || intent.phase !== selector.phase
325
- || intent.requestId !== selector.lifecycleRequestId
326
- || intent.authorityDigest === null) {
327
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
328
- }
329
- if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
330
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
331
- }
332
- if (capability.recoveryState === 'reserved') {
333
- if (phase.state === 'issued') {
334
- store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
335
- }
336
- store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
337
- }
338
- else if (phase.state !== 'consumed') {
339
- throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
340
- }
341
- if (intent.state !== 'consumed') {
342
- writeArtifactRecoveryIntent(options.repoRoot, {
343
- ...intent,
344
- state: 'consumed',
345
- updatedAt: (options.now ?? Date.now)()
346
- }, { expected: intent });
347
- }
348
- return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
349
- }
350
218
  function authorityRejected(request, code, message) {
351
219
  return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
352
220
  }
@@ -406,6 +274,7 @@ export function createSandboxExecutorExecutionContext(params) {
406
274
  generation: params.generation,
407
275
  manifestPath: absolute('manifestPath', params.manifestPath),
408
276
  requestId: params.requestId,
277
+ handoffDirectory: absolute('handoffDirectory', params.handoffDirectory ?? path.join(params.worktreeRoot, '.agent-infra-handoff')),
409
278
  ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
410
279
  ...(params.lifecycleRecoveryAttestation === undefined ? {} : {
411
280
  lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
@@ -433,6 +302,7 @@ export function assertTaskControlExecutionContext(context) {
433
302
  absolute('worktreeRoot', context.worktreeRoot);
434
303
  absolute('runtimeDir', context.runtimeDir);
435
304
  absolute('manifestPath', context.manifestPath);
305
+ absolute('handoffDirectory', context.handoffDirectory);
436
306
  if (path.resolve(context.runtimeDir) !== path.join(path.dirname(path.resolve(context.manifestPath)), 'runtime')) {
437
307
  invalidContext('runtimeDir is not bound to the manifest control root');
438
308
  }
@@ -463,10 +333,6 @@ function lifecycleTaskId(request, repoRoot) {
463
333
  }
464
334
  }
465
335
  async function applyLifecycleWithAuthority(context, request) {
466
- const conflict = overrideDryRunConflict(request);
467
- if (conflict) {
468
- return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
469
- }
470
336
  const execute = async () => {
471
337
  if (request.intent === 'recover-started') {
472
338
  const adapter = isAgentClientId(request.agent)
@@ -481,29 +347,7 @@ async function applyLifecycleWithAuthority(context, request) {
481
347
  }
482
348
  return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
483
349
  }
484
- const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
485
- if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
486
- return lifecycleResult;
487
- if (!request.overrideTarget || !request.overrideScope) {
488
- return {
489
- ...lifecycleResult,
490
- error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
491
- };
492
- }
493
- const override = await consumeHumanOverride({
494
- taskRef: request.taskRef,
495
- ticketId: request.overrideTicket,
496
- failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
497
- target: request.overrideTarget,
498
- scope: request.overrideScope,
499
- intent: request.intent,
500
- ...('alertNumber' in request && request.alertNumber ? { alertNumber: request.alertNumber } : {}),
501
- ...('issueNumber' in request && request.issueNumber ? { issueNumber: request.issueNumber } : {}),
502
- ...('stagingDir' in request && request.stagingDir ? { stagingDir: request.stagingDir } : {})
503
- }, { repoRoot: context.repoRoot });
504
- if (override.status === 'failed')
505
- return { ...lifecycleResult, humanOverride: override };
506
- return { ...lifecycleResult, ...override, humanOverride: override, error: null };
350
+ return applyTaskLifecycle(request, { repoRoot: context.repoRoot });
507
351
  };
508
352
  const taskId = lifecycleTaskId(request, context.repoRoot);
509
353
  if (!taskId)
@@ -594,7 +438,9 @@ export function dispatchTaskControlOperation(context, operation) {
594
438
  return finalize(operation.request, {
595
439
  repoRoot: context.repoRoot,
596
440
  ...(context.source === 'sandbox-executor' ? {
597
- controlBinding: { generation: context.generation, requestId: context.requestId }
441
+ controlBinding: { generation: context.generation, requestId: context.requestId },
442
+ handoffDirectory: context.handoffDirectory,
443
+ manifestPath: context.manifestPath
598
444
  } : {}),
599
445
  preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
600
446
  });
@@ -629,7 +475,7 @@ function operationInvalid(message) {
629
475
  }
630
476
  const LIFECYCLE_FLAGS = new Set([
631
477
  '--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
632
- '--auto', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
478
+ '--auto', '--dry-run'
633
479
  ]);
634
480
  const FINALIZATION_FLAGS = new Set(['--agent']);
635
481
  const ORCHESTRATION_FLAGS = new Set([
@@ -700,9 +546,6 @@ export function parseTaskControlOperation(family, args) {
700
546
  ...(value(values, '--unblock-condition') ? { unblockCondition: value(values, '--unblock-condition') } : {}),
701
547
  ...(value(values, '--note') ? { note: value(values, '--note') } : {}),
702
548
  ...(value(values, '--staging-dir') ? { stagingDir: value(values, '--staging-dir') } : {}),
703
- ...(value(values, '--override-ticket') ? { overrideTicket: value(values, '--override-ticket') } : {}),
704
- ...(value(values, '--override-target') ? { overrideTarget: value(values, '--override-target') } : {}),
705
- ...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
706
549
  ...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
707
550
  ...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
708
551
  ...(values['--auto'] === true ? { auto: true } : {}),
@@ -6,7 +6,6 @@ import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SE
6
6
  import { resolveTaskContext } from './resolve-ref.js';
7
7
  import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
8
8
  import { captureTaskWriteMetadata, writeTask } from './write.js';
9
- import { allowsManualOverride } from './guard-override.js';
10
9
  import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.js';
11
10
  const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
12
11
  const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
@@ -58,8 +57,7 @@ function applyHumanDecision(request, options = {}) {
58
57
  const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
59
58
  if (!resolved.ok)
60
59
  return failed(resolved.code, resolved.message, resolved.taskId);
61
- const stateOverride = allowsManualOverride(options.manualOverride, 'decision-intent', 'TASK_STATE_MISMATCH');
62
- if (resolved.state !== 'active' && !stateOverride)
60
+ if (resolved.state !== 'active')
63
61
  return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
64
62
  let content;
65
63
  let rows;
@@ -172,7 +170,7 @@ function applyHumanDecision(request, options = {}) {
172
170
  catch (error) {
173
171
  return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
174
172
  }
175
- const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
173
+ const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
176
174
  ...options,
177
175
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
178
176
  metadataProvider: () => metadata
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
+ import { resolveBranchWorktree } from '../git/branch-worktree.js';
4
5
  import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
5
6
  import { buildArtifactLinkSection, inspectArtifactDirectory, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
6
7
  import { artifactName, parseArtifactName } from './artifact-name.js';
@@ -16,7 +17,6 @@ import { validateLifecycleExecution } from './lifecycle-execution.js';
16
17
  import { commitOrchestrationStageCompletion } from './orchestration.js';
17
18
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
18
19
  import { captureTaskWriteMetadata, writeTask } from './write.js';
19
- import { allowsManualOverride } from './guard-override.js';
20
20
  import { validateLocalArtifact } from './local-artifact-finalization.js';
21
21
  import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.js';
22
22
  import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.js';
@@ -41,24 +41,7 @@ const eventCatalog = [
41
41
  function branchWorktree(repoRoot, branch) {
42
42
  if (!branch)
43
43
  return repoRoot;
44
- try {
45
- const records = execFileSync('git', ['worktree', 'list', '--porcelain'], {
46
- cwd: repoRoot,
47
- encoding: 'utf8',
48
- stdio: ['ignore', 'pipe', 'pipe']
49
- }).trim().split(/\r?\n\r?\n/);
50
- const expectedRef = `refs/heads/${branch}`;
51
- for (const record of records) {
52
- const worktree = /^worktree (.+)$/m.exec(record)?.[1];
53
- const branchRef = /^branch (.+)$/m.exec(record)?.[1];
54
- if (worktree && branchRef === expectedRef)
55
- return worktree;
56
- }
57
- }
58
- catch {
59
- // A missing worktree must not anchor a review to an unrelated repository.
60
- }
61
- return null;
44
+ return resolveBranchWorktree(repoRoot, branch);
62
45
  }
63
46
  function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot, branch) {
64
47
  if (verdict !== 'approved')
@@ -84,7 +67,7 @@ function approvedCleanReviewedCommit(reviewContent, verdict, repoRoot, branch) {
84
67
  return null;
85
68
  }
86
69
  }
87
- const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
70
+ const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'initiator', 'requestId', 'reasonCode', 'sourceFinding', 'sourceArtifact', 'sourceSha256']);
88
71
  const SCHEMAS = {
89
72
  'analyze.started': { optional: ['round'] },
90
73
  'analyze.awaiting-input': { required: ['question'] },
@@ -722,8 +705,7 @@ function applyTaskEventUnlocked(request, options = {}) {
722
705
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
723
706
  if (!resolved.ok)
724
707
  return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
725
- const stateOverride = allowsManualOverride(options.manualOverride, 'task-event', 'TASK_STATE_MISMATCH');
726
- if (resolved.state !== 'active' && !stateOverride)
708
+ if (resolved.state !== 'active')
727
709
  return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
728
710
  let content;
729
711
  try {
@@ -842,7 +824,7 @@ function applyTaskEventUnlocked(request, options = {}) {
842
824
  }
843
825
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
844
826
  }
845
- if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0 && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
827
+ if (eventIdentity.phase === 'completed' && !row?.started && completedRows.length === 0)
846
828
  return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires a started event or an earlier completed attempt' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
847
829
  if (eventIdentity.phase === 'completed') {
848
830
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
@@ -912,12 +894,9 @@ function applyTaskEventUnlocked(request, options = {}) {
912
894
  if (!facts.ok)
913
895
  return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${facts.code}: ${facts.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
914
896
  const capability = canStart(lifecycleAction(eventIdentity.family), facts.facts, trigger);
915
- const capabilityOverride = capability.reasonCode === 'TASK_NOT_ACTIVE'
916
- ? stateOverride
917
- : allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID');
918
897
  const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
919
898
  || capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
920
- if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure) && !capabilityOverride) {
899
+ if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure)) {
921
900
  const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
922
901
  return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
923
902
  }
@@ -1073,7 +1052,7 @@ function applyTaskEventUnlocked(request, options = {}) {
1073
1052
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
1074
1053
  const sourceCompletion = eventIdentity.phase === 'completed'
1075
1054
  && ['analyze', 'plan', 'code'].includes(eventIdentity.family);
1076
- const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
1055
+ const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
1077
1056
  if (result.status === 'failed')
1078
1057
  return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1079
1058
  if (!normalized.dryRun && orchestrationCompletion) {
@@ -0,0 +1,92 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ const HANDOFF_VERSION = 1;
5
+ const MAX_HANDOFF_BYTES = 1024 * 1024;
6
+ function digest(value) {
7
+ return createHash('sha256').update(JSON.stringify(value)).digest('hex');
8
+ }
9
+ export function finalizationHandoffPath(directory, taskId) {
10
+ return path.join(directory, '.agent-infra', 'task-finalization', `${taskId}.json`);
11
+ }
12
+ export function publishTaskFinalizationHandoff(directory, receipt, binding) {
13
+ if (receipt.taskId === '' || receipt.controlBinding?.generation !== binding.generation
14
+ || receipt.controlBinding.requestId !== binding.requestId) {
15
+ throw new Error('TASK_FINALIZATION_HANDOFF_BINDING_INVALID');
16
+ }
17
+ const envelope = {
18
+ version: HANDOFF_VERSION, taskId: receipt.taskId, binding, receipt, receiptSha256: digest(receipt)
19
+ };
20
+ const target = finalizationHandoffPath(directory, receipt.taskId);
21
+ fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
22
+ const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
23
+ fs.writeFileSync(temporary, `${JSON.stringify(envelope)}\n`, { mode: 0o600, flag: 'wx' });
24
+ try {
25
+ fs.renameSync(temporary, target);
26
+ }
27
+ catch (error) {
28
+ try {
29
+ fs.unlinkSync(temporary);
30
+ }
31
+ catch { /* preserve the primary error */ }
32
+ throw error;
33
+ }
34
+ return digest(envelope);
35
+ }
36
+ export function readTaskFinalizationHandoff(directory, taskId, binding, handoffSha256) {
37
+ const file = finalizationHandoffPath(directory, taskId);
38
+ const initial = fs.lstatSync(file);
39
+ if (!initial.isFile() || initial.isSymbolicLink() || initial.size > MAX_HANDOFF_BYTES) {
40
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
41
+ }
42
+ const descriptor = fs.openSync(file, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
43
+ let content;
44
+ try {
45
+ const opened = fs.fstatSync(descriptor);
46
+ if (!opened.isFile() || opened.dev !== initial.dev || opened.ino !== initial.ino
47
+ || opened.size > MAX_HANDOFF_BYTES)
48
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
49
+ const bytes = Buffer.alloc(MAX_HANDOFF_BYTES + 1);
50
+ const length = fs.readSync(descriptor, bytes, 0, bytes.length, 0);
51
+ if (length !== opened.size || length > MAX_HANDOFF_BYTES)
52
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
53
+ content = bytes.toString('utf8', 0, length);
54
+ const after = fs.fstatSync(descriptor);
55
+ if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size) {
56
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
57
+ }
58
+ }
59
+ finally {
60
+ fs.closeSync(descriptor);
61
+ }
62
+ const envelope = JSON.parse(content);
63
+ if (!envelope || typeof envelope !== 'object' || Array.isArray(envelope)
64
+ || Object.keys(envelope).sort().join('\0') !== ['binding', 'receipt', 'receiptSha256', 'taskId', 'version'].join('\0')
65
+ || envelope.version !== HANDOFF_VERSION || envelope.taskId !== taskId
66
+ || !envelope.binding || typeof envelope.binding !== 'object' || Array.isArray(envelope.binding)
67
+ || Object.keys(envelope.binding).sort().join('\0') !== ['generation', 'requestId'].join('\0')
68
+ || envelope.binding.generation !== binding.generation
69
+ || envelope.binding.requestId !== binding.requestId || !envelope.receipt
70
+ || envelope.receipt.controlBinding?.generation !== binding.generation
71
+ || envelope.receipt.controlBinding.requestId !== binding.requestId
72
+ || typeof envelope.receiptSha256 !== 'string' || envelope.receiptSha256 !== digest(envelope.receipt)
73
+ || digest(envelope) !== handoffSha256) {
74
+ throw new Error('TASK_FINALIZATION_HANDOFF_INVALID');
75
+ }
76
+ return {
77
+ receipt: envelope.receipt,
78
+ cleanup: () => {
79
+ try {
80
+ const current = fs.lstatSync(file);
81
+ if (current.isFile() && !current.isSymbolicLink()
82
+ && current.dev === initial.dev && current.ino === initial.ino)
83
+ fs.unlinkSync(file);
84
+ }
85
+ catch (error) {
86
+ if (error.code !== 'ENOENT')
87
+ throw error;
88
+ }
89
+ }
90
+ };
91
+ }
92
+ //# sourceMappingURL=finalization-handoff.js.map