@fitlab-ai/agent-infra 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/sandbox-control.js +1 -43
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/platform/issue-comments.js +2 -31
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -79
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +49 -343
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +1 -2
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +37 -38
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delegation-receipts.js +2 -2
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +1 -1
- package/dist/lib/task/finalization.js +319 -234
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +11 -1
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +29 -13
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/sandbox-control.ts +0 -42
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/platform/issue-comments.ts +1 -35
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -81
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +47 -362
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +1 -2
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +41 -48
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delegation-receipts.ts +2 -2
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +1 -1
- package/lib/task/finalization.ts +310 -216
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +15 -2
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +56 -27
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/sandbox/control/finalization-response.js +0 -35
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -142
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -21
- package/dist/lib/task/workflow-dispatch.js +0 -213
- package/lib/sandbox/control/finalization-response.ts +0 -37
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -156
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -22
- package/lib/task/workflow-dispatch.ts +0 -214
|
@@ -5,6 +5,7 @@ import fs from 'node:fs';
|
|
|
5
5
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
6
6
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
7
7
|
import type { AgentClientId } from '../agent-clients/types.ts';
|
|
8
|
+
import { getAgentClientAdapter } from '../agent-clients/registry.ts';
|
|
8
9
|
import { consumeHumanOverride, failureId, overrideDryRunConflict } from './human-override.ts';
|
|
9
10
|
import {
|
|
10
11
|
activateMatchingOrchestrationDelegation,
|
|
@@ -22,14 +23,10 @@ import {
|
|
|
22
23
|
sealOrchestrationDelegation,
|
|
23
24
|
statusOrchestration
|
|
24
25
|
} from './orchestration.ts';
|
|
25
|
-
import type {
|
|
26
|
-
OrchestrationDiagnosticLogger,
|
|
27
|
-
OrchestrationOptions,
|
|
28
|
-
OrchestrationResult
|
|
29
|
-
} from './orchestration.ts';
|
|
30
|
-
import { prepareCodexOrchestrationDelegation } from './codex-orchestration.ts';
|
|
26
|
+
import type { OrchestrationOptions, OrchestrationResult } from './orchestration.ts';
|
|
31
27
|
import {
|
|
32
28
|
applyTaskFinalization,
|
|
29
|
+
commitPreparedTaskFinalization,
|
|
33
30
|
type TaskFinalizationRequest,
|
|
34
31
|
type TaskFinalizationResult
|
|
35
32
|
} from './finalization.ts';
|
|
@@ -39,12 +36,10 @@ import {
|
|
|
39
36
|
type TaskLifecycleRequest,
|
|
40
37
|
type TaskLifecycleResult
|
|
41
38
|
} from './lifecycle.ts';
|
|
42
|
-
import {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
} from './lifecycle-recovery.ts';
|
|
47
|
-
import type { LifecycleRecoveryRequest } from './lifecycle-recovery.ts';
|
|
39
|
+
import type {
|
|
40
|
+
AgentClientLifecycleRecoveryRequest as LifecycleRecoveryRequest,
|
|
41
|
+
AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult
|
|
42
|
+
} from '../agent-clients/adapter.ts';
|
|
48
43
|
import { locateActivityLog } from './activity-log.ts';
|
|
49
44
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
50
45
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
@@ -542,7 +537,6 @@ export type TaskControlExecutionContext =
|
|
|
542
537
|
generation: string;
|
|
543
538
|
manifestPath: string;
|
|
544
539
|
requestId: string;
|
|
545
|
-
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
546
540
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
547
541
|
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
548
542
|
}>;
|
|
@@ -631,7 +625,6 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
631
625
|
generation: string;
|
|
632
626
|
manifestPath: string;
|
|
633
627
|
requestId: string;
|
|
634
|
-
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
635
628
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
636
629
|
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
637
630
|
}>): TaskControlExecutionContext {
|
|
@@ -652,7 +645,6 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
652
645
|
generation: params.generation,
|
|
653
646
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
654
647
|
requestId: params.requestId,
|
|
655
|
-
...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
|
|
656
648
|
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
657
649
|
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
658
650
|
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
@@ -697,11 +689,7 @@ function domainOptions(
|
|
|
697
689
|
): OrchestrationOptions {
|
|
698
690
|
return {
|
|
699
691
|
...options,
|
|
700
|
-
repoRoot: context.repoRoot
|
|
701
|
-
...(context.source === 'sandbox-executor' && context.diagnosticLog !== undefined
|
|
702
|
-
? { diagnosticLog: context.diagnosticLog }
|
|
703
|
-
: {}),
|
|
704
|
-
...(context.source === 'sandbox-executor' ? { gitWorktreeRoot: context.worktreeRoot } : {})
|
|
692
|
+
repoRoot: context.repoRoot
|
|
705
693
|
};
|
|
706
694
|
}
|
|
707
695
|
|
|
@@ -725,7 +713,17 @@ async function applyLifecycleWithAuthority(
|
|
|
725
713
|
}
|
|
726
714
|
const execute = async (): Promise<(TaskLifecycleResult | LifecycleRecoveryResult) & { humanOverride?: unknown }> => {
|
|
727
715
|
if (request.intent === 'recover-started') {
|
|
728
|
-
|
|
716
|
+
const adapter = isAgentClientId(request.agent)
|
|
717
|
+
? getAgentClientAdapter(request.agent).orchestrationAdapter
|
|
718
|
+
: undefined;
|
|
719
|
+
if (!adapter?.recoverStarted) {
|
|
720
|
+
return {
|
|
721
|
+
status: 'no-op', changed: false, targetState: 'active',
|
|
722
|
+
requestRef: request.taskRef, intent: 'recover-started', taskId: null,
|
|
723
|
+
receiptId: null, childId: null, error: null
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
return adapter.recoverStarted(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
|
|
729
727
|
}
|
|
730
728
|
const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
731
729
|
if (lifecycleResult.status !== 'failed' || !request.overrideTicket) return lifecycleResult;
|
|
@@ -761,7 +759,12 @@ async function applyLifecycleWithAuthority(
|
|
|
761
759
|
} catch (error) {
|
|
762
760
|
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
763
761
|
if (request.intent === 'recover-started') {
|
|
764
|
-
return
|
|
762
|
+
return {
|
|
763
|
+
status: 'owner-unknown', changed: false, targetState: 'active',
|
|
764
|
+
requestRef: request.taskRef, intent: 'recover-started', taskId,
|
|
765
|
+
receiptId: null, childId: null,
|
|
766
|
+
error: { code: error.code, message: error.message }
|
|
767
|
+
};
|
|
765
768
|
}
|
|
766
769
|
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
767
770
|
}
|
|
@@ -790,8 +793,10 @@ function orchestration(
|
|
|
790
793
|
requestedModel: input.requestedModel as string | undefined,
|
|
791
794
|
requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
|
|
792
795
|
};
|
|
793
|
-
|
|
794
|
-
|
|
796
|
+
const prepareDelegation = getAgentClientAdapter(prepareInput.client)
|
|
797
|
+
.orchestrationAdapter?.prepareDelegation;
|
|
798
|
+
if (prepareDelegation) {
|
|
799
|
+
return prepareDelegation(operation.taskRef, prepareInput, {
|
|
795
800
|
repoRoot: context.repoRoot,
|
|
796
801
|
orchestrationOptions: options,
|
|
797
802
|
});
|
|
@@ -856,7 +861,8 @@ export function dispatchTaskControlOperation(
|
|
|
856
861
|
}
|
|
857
862
|
if (operation.family === 'task-finalization') {
|
|
858
863
|
operationTaskId(context, operation.request.taskRef);
|
|
859
|
-
|
|
864
|
+
const finalize = context.source === 'sandbox-executor' ? commitPreparedTaskFinalization : applyTaskFinalization;
|
|
865
|
+
return finalize(operation.request, {
|
|
860
866
|
repoRoot: context.repoRoot,
|
|
861
867
|
...(context.source === 'sandbox-executor' ? {
|
|
862
868
|
controlBinding: { generation: context.generation, requestId: context.requestId }
|
|
@@ -898,7 +904,7 @@ function operationInvalid(message: string): never {
|
|
|
898
904
|
|
|
899
905
|
const LIFECYCLE_FLAGS = new Set([
|
|
900
906
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
901
|
-
'--
|
|
907
|
+
'--auto', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
902
908
|
]);
|
|
903
909
|
|
|
904
910
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
@@ -909,7 +915,7 @@ const ORCHESTRATION_FLAGS = new Set([
|
|
|
909
915
|
'--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
|
|
910
916
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
911
917
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
|
|
912
|
-
'--changed-paths', '--code', '--message', '--recoverable'
|
|
918
|
+
'--changed-paths', '--code', '--message', '--recoverable'
|
|
913
919
|
]);
|
|
914
920
|
|
|
915
921
|
function parseValues(
|
|
@@ -920,7 +926,7 @@ function parseValues(
|
|
|
920
926
|
const values: Record<string, string | boolean> = {};
|
|
921
927
|
for (let index = start; index < args.length; index += 1) {
|
|
922
928
|
const flag = args[index]!;
|
|
923
|
-
if (flag === '--dry-run') {
|
|
929
|
+
if (flag === '--dry-run' || flag === '--auto') {
|
|
924
930
|
if (!flags.has(flag)) operationInvalid(`unknown option '${flag}'`);
|
|
925
931
|
if (values[flag] !== undefined) operationInvalid(`duplicate option '${flag}'`);
|
|
926
932
|
values[flag] = true;
|
|
@@ -978,28 +984,18 @@ export function parseTaskControlOperation(
|
|
|
978
984
|
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
979
985
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
980
986
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
981
|
-
...(
|
|
982
|
-
...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
|
|
983
|
-
...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
|
|
987
|
+
...(values['--auto'] === true ? { auto: true } : {}),
|
|
984
988
|
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
985
989
|
};
|
|
986
990
|
if (intent === 'recover-started') {
|
|
987
|
-
const recoveryFlags = new Set(['--agent', '--
|
|
991
|
+
const recoveryFlags = new Set(['--agent', '--auto']);
|
|
988
992
|
for (const flag of Object.keys(values)) {
|
|
989
993
|
if (!recoveryFlags.has(flag)) operationInvalid(`option '${flag}' is not supported for recover-started`);
|
|
990
994
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
const round = Number(value(values, '--round'));
|
|
997
|
-
if (!Number.isSafeInteger(round) || round < 1) operationInvalid('--round must be a positive integer');
|
|
998
|
-
if (value(values, '--reason')!.includes('\n') || value(values, '--reason')!.includes('\r')) {
|
|
999
|
-
operationInvalid('--reason must be a single line');
|
|
1000
|
-
}
|
|
1001
|
-
} else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
|
|
1002
|
-
operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
|
|
995
|
+
if (values['--auto'] !== true) operationInvalid('--auto is required for recover-started');
|
|
996
|
+
return { family, request: input as unknown as TaskLifecycleControlRequest };
|
|
997
|
+
} else if (values['--auto'] !== undefined) {
|
|
998
|
+
operationInvalid('--auto is only supported for recover-started');
|
|
1003
999
|
}
|
|
1004
1000
|
return { family, request: input as unknown as TaskLifecycleControlRequest };
|
|
1005
1001
|
}
|
|
@@ -1092,8 +1088,5 @@ export function parseTaskControlOperation(
|
|
|
1092
1088
|
if (recoverable !== 'true' && recoverable !== 'false') operationInvalid('--recoverable must be true or false');
|
|
1093
1089
|
input.code = value(values, '--code'); input.message = value(values, '--message'); input.recoverable = recoverable === 'true';
|
|
1094
1090
|
}
|
|
1095
|
-
|
|
1096
|
-
? undefined
|
|
1097
|
-
: { gitWorktreeRoot: value(values, '--git-worktree-root') };
|
|
1098
|
-
return { family, taskRef, intent: parsedIntent, input, ...(options ? { options } : {}) };
|
|
1091
|
+
return { family, taskRef, intent: parsedIntent, input };
|
|
1099
1092
|
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { isCompletionEvidence } from './orchestration.ts';
|
|
3
|
-
import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.ts';
|
|
4
|
-
import { sameRecoveryWarning } from './recovery-warning.ts';
|
|
5
2
|
|
|
6
3
|
export type ControlRecoveryOutcome = 'not-executed' | 'in-progress' | 'success' | 'failure' | 'unknown' | 'rejected';
|
|
7
4
|
|
|
@@ -16,7 +13,7 @@ export function parseControlOutput(output: string | null): Record<string, unknow
|
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
export type ControlRecoveryOperation = Readonly<{
|
|
19
|
-
family: 'task-lifecycle' | 'task-finalization' | 'task-
|
|
16
|
+
family: 'task-lifecycle' | 'task-finalization' | 'task-create' | 'codex-controller';
|
|
20
17
|
intent: string;
|
|
21
18
|
class: string;
|
|
22
19
|
}>;
|
|
@@ -55,21 +52,10 @@ export type ControlRecoveryDecision = Readonly<{
|
|
|
55
52
|
}>;
|
|
56
53
|
|
|
57
54
|
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'] as const;
|
|
58
|
-
const ORCHESTRATION_INTENTS = [
|
|
59
|
-
'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
|
|
60
|
-
'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
|
|
61
|
-
] as const;
|
|
62
|
-
|
|
63
55
|
export const SANDBOX_CONTROL_RECOVERY_OPERATIONS: readonly ControlRecoveryOperation[] = Object.freeze([
|
|
64
56
|
...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle' as const, intent, class: 'lifecycle-mutation' })),
|
|
65
57
|
{ family: 'task-finalization', intent: 'complete', class: 'finalization' },
|
|
66
|
-
...ORCHESTRATION_INTENTS.map((intent) => ({
|
|
67
|
-
family: 'task-orchestration' as const,
|
|
68
|
-
intent,
|
|
69
|
-
class: intent === 'route.clean-completion' ? 'route.clean-completion' : intent === 'route.read' || intent === 'status' ? 'read-only' : 'orchestration'
|
|
70
|
-
})),
|
|
71
58
|
{ family: 'task-create', intent: 'create', class: 'task-create' },
|
|
72
|
-
...TASK_WORKFLOW_OPERATIONS.map((intent) => ({ family: 'task-workflow' as const, intent, class: intent === 'artifact-inspect' || intent === 'decision-next-id' ? 'read-only' : 'workflow' })),
|
|
73
59
|
...(['open', 'close', 'verify'] as const).map((intent) => ({ family: 'codex-controller' as const, intent, class: 'codex-controller' }))
|
|
74
60
|
]);
|
|
75
61
|
|
|
@@ -95,29 +81,7 @@ function domainEvidenceMatches(
|
|
|
95
81
|
): boolean {
|
|
96
82
|
if (!domain) return false;
|
|
97
83
|
if (domain.consistent !== true) return false;
|
|
98
|
-
if (operation.
|
|
99
|
-
const completion = domain.completionEvidence as Record<string, unknown> | undefined;
|
|
100
|
-
const snapshot = domain.snapshot as Record<string, unknown> | undefined;
|
|
101
|
-
const lastReviewedCommit = domain.lastReviewedCommit;
|
|
102
|
-
return result.status === 'completed'
|
|
103
|
-
&& domain.status === 'completed'
|
|
104
|
-
&& domain.pendingDelegation === null
|
|
105
|
-
&& isCompletionEvidence(completion)
|
|
106
|
-
&& snapshot?.head === completion.head
|
|
107
|
-
&& snapshot.headTree === completion.headTree
|
|
108
|
-
&& snapshot.worktreeTree === completion.worktreeTree
|
|
109
|
-
&& lastReviewedCommit === completion.lastReviewedCommit;
|
|
110
|
-
}
|
|
111
|
-
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
|
|
112
|
-
const retryRequired = domain.recoveryState === 'retry-required'
|
|
113
|
-
&& sameRecoveryWarning(domain.warning, result.warning);
|
|
114
|
-
return result.targetState === 'active'
|
|
115
|
-
&& ['applied', 'no-op'].includes(String(result.status))
|
|
116
|
-
&& domain.recovery === true
|
|
117
|
-
&& domain.targetState === 'active'
|
|
118
|
-
&& (domain.recoveryState === 'released' || retryRequired);
|
|
119
|
-
}
|
|
120
|
-
if (operation.class === 'read-only') return result.changed === false && domain.snapshotValid === true;
|
|
84
|
+
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') return false;
|
|
121
85
|
return true;
|
|
122
86
|
}
|
|
123
87
|
|
|
@@ -167,11 +131,7 @@ export function classifySandboxControlRecovery(input: ControlRecoveryInput): Con
|
|
|
167
131
|
return {
|
|
168
132
|
outcome: 'success',
|
|
169
133
|
responseReconstructable: true,
|
|
170
|
-
reasonCode:
|
|
171
|
-
? 'RECOVERY_ROUTE_COMPLETION_NOOP'
|
|
172
|
-
: input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
|
|
173
|
-
? 'RECOVERY_RELEASE_RETRY_REQUIRED'
|
|
174
|
-
: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
134
|
+
reasonCode: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
175
135
|
};
|
|
176
136
|
}
|
|
177
137
|
|
|
@@ -17,6 +17,7 @@ import { extractSection, extractSubSection, findSectionHeading } from './section
|
|
|
17
17
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
18
18
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
19
19
|
import { allowsManualOverride } from './guard-override.ts';
|
|
20
|
+
import { consumeHumanDecisionReworkIntents, parseReworkIntentDocument, reworkIntentMutation } from './rework-intent.ts';
|
|
20
21
|
|
|
21
22
|
type HumanDecisionRequest = {
|
|
22
23
|
taskRef?: string;
|
|
@@ -177,6 +178,14 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
177
178
|
kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
|
|
178
179
|
body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
|
|
179
180
|
});
|
|
181
|
+
try {
|
|
182
|
+
const parsedRework = parseReworkIntentDocument(content);
|
|
183
|
+
if (!parsedRework.ok) return failed('DECISION_DOCUMENT_INVALID', parsedRework.message, resolved.taskId, row.id);
|
|
184
|
+
const consumed = consumeHumanDecisionReworkIntents(parsedRework.intents, row.id, metadata.timestamp);
|
|
185
|
+
if (consumed.changed) mutations.push(reworkIntentMutation(content, consumed.intents));
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
|
|
188
|
+
}
|
|
180
189
|
const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
|
|
181
190
|
...options,
|
|
182
191
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
|
@@ -248,7 +248,7 @@ function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
|
|
|
248
248
|
&& host !== undefined
|
|
249
249
|
&& Number.isSafeInteger(host.stopRevision)
|
|
250
250
|
&& (host.stopRevision as number) > host.startRevision
|
|
251
|
-
&& host.consumer ===
|
|
251
|
+
&& host.consumer === receipt.id
|
|
252
252
|
&& exactText(host.consumedAt);
|
|
253
253
|
}
|
|
254
254
|
|
|
@@ -562,7 +562,7 @@ function abortActivatedDelegation(
|
|
|
562
562
|
event.childId !== receipt.childId
|
|
563
563
|
|| !Number.isSafeInteger(event.stopRevision)
|
|
564
564
|
|| event.stopRevision <= receipt.hostEvidence.startRevision
|
|
565
|
-
|| event.consumer !==
|
|
565
|
+
|| event.consumer !== receipt.id
|
|
566
566
|
|| !exactText(event.consumedAt)
|
|
567
567
|
) {
|
|
568
568
|
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
|
package/lib/task/delivery.ts
CHANGED
|
@@ -4,8 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
|
|
5
5
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
7
|
-
import { resolveDeliveryTarget,
|
|
8
|
-
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead, findAuthoritativeReviewCodeArtifact } from './review-fingerprint.ts';
|
|
7
|
+
import { resolveDeliveryTarget, validateBaseRef, validateRemote } from './delivery-target.ts';
|
|
9
8
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
10
9
|
import { withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
11
10
|
|
|
@@ -47,40 +46,6 @@ function validateBranch(cwd: string, branch: string): boolean {
|
|
|
47
46
|
return git(cwd, ['check-ref-format', `refs/heads/${branch}`]).status === 0;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
function validateReviewTargetCompatibility(
|
|
51
|
-
repoRoot: string,
|
|
52
|
-
taskDir: string,
|
|
53
|
-
target: { remote: string; baseRef: string },
|
|
54
|
-
reviewedHead: string
|
|
55
|
-
): { ok: true } | { ok: false; code: string; message: string } {
|
|
56
|
-
const artifact = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
57
|
-
if (!artifact.ok || !artifact.path) return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'A current review-code artifact is required before delivery' };
|
|
58
|
-
let content: string;
|
|
59
|
-
try { content = fs.readFileSync(artifact.path, 'utf8'); }
|
|
60
|
-
catch (error) { return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: error instanceof Error ? error.message : String(error) }; }
|
|
61
|
-
const savedReviewedHead = extractReviewedHead(content) || extractReviewBaseline(content);
|
|
62
|
-
const savedTargetHead = extractReviewTargetHead(content);
|
|
63
|
-
const savedDiffBase = extractReviewDiffBase(content);
|
|
64
|
-
if (!savedReviewedHead || !savedTargetHead || !savedDiffBase) {
|
|
65
|
-
return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: 'review-code must save reviewed head, target head, and diff base before delivery' };
|
|
66
|
-
}
|
|
67
|
-
if (savedReviewedHead !== reviewedHead) {
|
|
68
|
-
return { ok: false, code: 'DELIVERY_REVIEW_REQUIRED', message: 'review-code reviewed head does not match last_reviewed_commit' };
|
|
69
|
-
}
|
|
70
|
-
const saved = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
|
|
71
|
-
if (!saved.ok || saved.diffBase !== savedDiffBase) {
|
|
72
|
-
return { ok: false, code: 'DELIVERY_REVIEW_TARGET_INVALID', message: saved.ok ? 'saved review diff base does not match saved target head' : saved.message };
|
|
73
|
-
}
|
|
74
|
-
const currentTarget = resolveTargetHead(repoRoot, target);
|
|
75
|
-
if (!currentTarget.ok) return { ok: false, code: currentTarget.code, message: currentTarget.message };
|
|
76
|
-
const current = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head!);
|
|
77
|
-
if (!current.ok) return { ok: false, code: current.code, message: current.message };
|
|
78
|
-
if (current.diffBase !== savedDiffBase) {
|
|
79
|
-
return { ok: false, code: 'DELIVERY_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
|
|
80
|
-
}
|
|
81
|
-
return { ok: true };
|
|
82
|
-
}
|
|
83
|
-
|
|
84
49
|
function failure(taskId: string | null, code: string, message: string, retryable = false, status: 'failed' | 'blocked' = retryable ? 'blocked' : 'failed'): TaskBranchDeliveryResult {
|
|
85
50
|
return { status, changed: false, taskId, remoteHead: null, localHead: null, state: null, error: { code, message, retryable } };
|
|
86
51
|
}
|
|
@@ -115,10 +80,6 @@ function deliverUnlocked(
|
|
|
115
80
|
const status = git(repoRoot, ['status', '--porcelain=v1']);
|
|
116
81
|
if (status.status !== 0) return failure(taskId, 'DELIVERY_GIT_INSPECT_FAILED', status.stderr.trim() || 'Unable to inspect Git state');
|
|
117
82
|
if (status.stdout.trim()) return failure(taskId, 'DELIVERY_WORKTREE_DIRTY', 'Working tree must be clean before branch delivery');
|
|
118
|
-
const reviewed = frontmatter.last_reviewed_commit ?? '';
|
|
119
|
-
if (reviewed !== localHead) return failure(taskId, 'DELIVERY_REVIEW_REQUIRED', 'Local HEAD must equal last_reviewed_commit before delivery');
|
|
120
|
-
const reviewTarget = validateReviewTargetCompatibility(repoRoot, taskDir, target.value, reviewed);
|
|
121
|
-
if (!reviewTarget.ok) return failure(taskId, reviewTarget.code, reviewTarget.message, reviewTarget.code.includes('UNAVAILABLE'), reviewTarget.code.includes('UNAVAILABLE') ? 'blocked' : 'failed');
|
|
122
83
|
const ref = `refs/heads/${branch}`;
|
|
123
84
|
const remoteOutput = git(repoRoot, ['ls-remote', '--refs', target.value.remote, ref]);
|
|
124
85
|
if (remoteOutput.status !== 0) return failure(taskId, 'DELIVERY_REMOTE_UNAVAILABLE', remoteOutput.stderr.trim() || 'Unable to inspect the delivery remote', true);
|