@fitlab-ai/agent-infra 0.9.4 → 0.9.6
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +13 -3
- package/dist/lib/agent-clients/adapter.js +2 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +492 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +175 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +255 -0
- package/dist/lib/agent-clients/adapters/codex.js +3 -3
- package/dist/lib/git/workflow.js +0 -1
- package/dist/lib/git/worktree-identity.js +64 -0
- package/dist/lib/internal/codex-lifecycle.js +300 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +56 -0
- package/dist/lib/internal/task-orchestration.js +71 -31
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +16 -16
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +318 -89
- package/dist/lib/sandbox/control/client.js +152 -22
- package/dist/lib/sandbox/control/executor.js +136 -0
- package/dist/lib/sandbox/control/lifecycle.js +348 -0
- package/dist/lib/sandbox/control/protocol.js +73 -18
- package/dist/lib/sandbox/control/server.js +235 -80
- package/dist/lib/sandbox/control/state.js +181 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +35 -24
- package/dist/lib/sandbox/recovery.js +262 -45
- package/dist/lib/sandbox/workspace-view.js +80 -11
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/process-control.js +6 -0
- package/dist/lib/task/activity-log.js +32 -5
- package/dist/lib/task/artifact-lifecycle.js +5 -4
- package/dist/lib/task/codex-orchestration.js +161 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +50 -12
- package/dist/lib/task/create-service.js +137 -0
- package/dist/lib/task/create.js +333 -0
- package/dist/lib/task/delegation-receipts.js +25 -0
- package/dist/lib/task/events.js +10 -4
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/orchestration.js +283 -18
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/verification-engine.js +38 -6
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workspace-snapshot.js +78 -3
- package/lib/agent-clients/adapter.ts +4 -4
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +569 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +323 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +298 -0
- package/lib/agent-clients/adapters/codex.ts +3 -3
- package/lib/git/workflow.ts +0 -1
- package/lib/git/worktree-identity.ts +86 -0
- package/lib/internal/codex-lifecycle.ts +308 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +61 -0
- package/lib/internal/task-orchestration.ts +57 -31
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +18 -21
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +368 -103
- package/lib/sandbox/control/client.ts +173 -25
- package/lib/sandbox/control/executor.ts +163 -0
- package/lib/sandbox/control/lifecycle.ts +342 -0
- package/lib/sandbox/control/protocol.ts +110 -48
- package/lib/sandbox/control/server.ts +252 -85
- package/lib/sandbox/control/state.ts +196 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +31 -13
- package/lib/sandbox/recovery.ts +301 -60
- package/lib/sandbox/workspace-view.ts +89 -12
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/process-control.ts +11 -0
- package/lib/task/activity-log.ts +46 -7
- package/lib/task/artifact-lifecycle.ts +5 -4
- package/lib/task/codex-orchestration.ts +233 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +75 -12
- package/lib/task/create-service.ts +171 -0
- package/lib/task/create.ts +372 -0
- package/lib/task/delegation-receipts.ts +52 -3
- package/lib/task/events.ts +9 -4
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/orchestration.ts +381 -22
- package/lib/task/review-fingerprint.ts +9 -0
- package/lib/task/verification-engine.ts +42 -8
- package/lib/task/verification.ts +2 -1
- package/lib/task/workspace-snapshot.ts +98 -4
- package/package.json +7 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +88 -8
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/task-management.en.md +1 -0
- package/templates/.agents/rules/task-management.zh-CN.md +1 -0
- package/templates/.agents/skills/commit/SKILL.en.md +3 -9
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +6 -10
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +4 -3
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +5 -4
- package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +44 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +44 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +50 -0
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
diffWorkspaceSnapshots
|
|
37
37
|
} from './workspace-snapshot.ts';
|
|
38
38
|
import type { RepositorySnapshot } from './workspace-snapshot.ts';
|
|
39
|
+
import type { WorkspaceSnapshotContext } from './workspace-snapshot.ts';
|
|
40
|
+
import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
|
|
39
41
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
40
42
|
import {
|
|
41
43
|
CommitIntentError,
|
|
@@ -55,6 +57,12 @@ import {
|
|
|
55
57
|
} from './commit-finalization.ts';
|
|
56
58
|
import type { CommitFinalizationInspection } from './commit-finalization.ts';
|
|
57
59
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
60
|
+
import {
|
|
61
|
+
appendActivityEntry,
|
|
62
|
+
commitAttemptStartedNote,
|
|
63
|
+
locateActivityLog,
|
|
64
|
+
pairEntries
|
|
65
|
+
} from './activity-log.ts';
|
|
58
66
|
|
|
59
67
|
type OrchestrationStatus = 'running' | 'paused' | 'completed';
|
|
60
68
|
type LegacyOrchestrationModelPolicy = Readonly<{
|
|
@@ -66,7 +74,7 @@ type ModelPolicySource = Readonly<{
|
|
|
66
74
|
client: AgentClientId;
|
|
67
75
|
resolvedAt: string;
|
|
68
76
|
}>;
|
|
69
|
-
type
|
|
77
|
+
type ModelPolicyRecovery = Readonly<{
|
|
70
78
|
code: 'MODEL_POLICY_SUPPLEMENTED';
|
|
71
79
|
recoveredAt: string;
|
|
72
80
|
previousSchemaVersion: 1;
|
|
@@ -77,6 +85,26 @@ type OrchestrationRecovery = Readonly<{
|
|
|
77
85
|
pendingDelegation: false;
|
|
78
86
|
resultingStatus: OrchestrationStatus;
|
|
79
87
|
}>;
|
|
88
|
+
type ClientCapabilityRecovery = Readonly<{
|
|
89
|
+
code: 'CLIENT_CAPABILITY_ENABLED';
|
|
90
|
+
recoveredAt: string;
|
|
91
|
+
previousSchemaVersion: 2;
|
|
92
|
+
previousStatus: 'paused';
|
|
93
|
+
previousPause: Readonly<{ code: 'ORCHESTRATION_CLIENT_UNSUPPORTED'; message: string; recoverable: boolean }>;
|
|
94
|
+
client: 'codex';
|
|
95
|
+
guards: Readonly<{
|
|
96
|
+
stepCount: 0;
|
|
97
|
+
nextStage: null;
|
|
98
|
+
baselineEmpty: true;
|
|
99
|
+
receiptCount: 0;
|
|
100
|
+
pendingDelegation: false;
|
|
101
|
+
commitAuthorizationUnused: true;
|
|
102
|
+
completionEvidenceAbsent: true;
|
|
103
|
+
commitIntentAbsent: true;
|
|
104
|
+
}>;
|
|
105
|
+
resultingStatus: 'running';
|
|
106
|
+
}>;
|
|
107
|
+
type OrchestrationRecovery = ModelPolicyRecovery | ClientCapabilityRecovery;
|
|
80
108
|
type CleanCompletionEvidence = Readonly<{
|
|
81
109
|
kind: 'reviewed-head-clean';
|
|
82
110
|
observedAt: string;
|
|
@@ -147,12 +175,13 @@ type OrchestrationCompletionPlanResult = Readonly<{
|
|
|
147
175
|
}>;
|
|
148
176
|
type OrchestrationOptions = {
|
|
149
177
|
repoRoot?: string;
|
|
178
|
+
gitWorktreeRoot?: string;
|
|
150
179
|
id?: () => string;
|
|
151
180
|
now?: () => string;
|
|
152
181
|
maxSteps?: number;
|
|
153
182
|
client?: AgentClientId;
|
|
154
183
|
modelPolicy?: OrchestrationModelPolicy;
|
|
155
|
-
captureWorkspace?: (
|
|
184
|
+
captureWorkspace?: (context: WorkspaceSnapshotContext) => string;
|
|
156
185
|
captureRepository?: (repoRoot: string) => RepositorySnapshot;
|
|
157
186
|
inspectPullRequest?: (taskId: string, options: { cwd: string }) => Readonly<{
|
|
158
187
|
status: string;
|
|
@@ -188,6 +217,7 @@ type CommitIntentResult = Readonly<{
|
|
|
188
217
|
committedHead: string | null;
|
|
189
218
|
needsAnchor: boolean;
|
|
190
219
|
needsLog: boolean;
|
|
220
|
+
attempt: Readonly<{ attempt: string; baseline: string; agent: string }> | null;
|
|
191
221
|
}>;
|
|
192
222
|
token?: string;
|
|
193
223
|
error: Readonly<{ code: string; message: string }> | null;
|
|
@@ -259,6 +289,11 @@ function repositoryHead(repoRoot: string): string {
|
|
|
259
289
|
}).trim();
|
|
260
290
|
}
|
|
261
291
|
|
|
292
|
+
function gitRootFor(stateRoot: string, options: OrchestrationOptions): string {
|
|
293
|
+
if (!options.gitWorktreeRoot) return stateRoot;
|
|
294
|
+
return assertGitRepositoryBinding(stateRoot, options.gitWorktreeRoot).worktreeRoot;
|
|
295
|
+
}
|
|
296
|
+
|
|
262
297
|
function isAncestor(repoRoot: string, ancestor: string, descendant: string): boolean {
|
|
263
298
|
return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
|
|
264
299
|
cwd: repoRoot,
|
|
@@ -287,7 +322,8 @@ function commitFinalizationView(inspection: CommitFinalizationInspection): NonNu
|
|
|
287
322
|
currentHead: inspection.currentHead,
|
|
288
323
|
committedHead: inspection.committedHead,
|
|
289
324
|
needsAnchor: inspection.needsAnchor,
|
|
290
|
-
needsLog: inspection.needsLog
|
|
325
|
+
needsLog: inspection.needsLog,
|
|
326
|
+
attempt: inspection.attempt
|
|
291
327
|
};
|
|
292
328
|
}
|
|
293
329
|
|
|
@@ -353,6 +389,23 @@ function resolveProjectPolicy(repoRoot: string, client: AgentClientId): Orchestr
|
|
|
353
389
|
return normalizeAgentClients(raw).state[client].orchestration;
|
|
354
390
|
}
|
|
355
391
|
|
|
392
|
+
function canRecoverCodexUnsupportedPause(run: OrchestrationRun, taskDir: string): boolean {
|
|
393
|
+
return run.schemaVersion === 2
|
|
394
|
+
&& run.status === 'paused'
|
|
395
|
+
&& run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
396
|
+
&& run.modelPolicySource?.client === 'codex'
|
|
397
|
+
&& run.stepCount === 0
|
|
398
|
+
&& run.nextStage === null
|
|
399
|
+
&& run.baseline === ''
|
|
400
|
+
&& run.pendingDelegation === null
|
|
401
|
+
&& run.receipts.length === 0
|
|
402
|
+
&& run.commitAuthorization?.issuedAt === null
|
|
403
|
+
&& run.commitAuthorization?.consumedAt === null
|
|
404
|
+
&& run.completionEvidence == null
|
|
405
|
+
&& !fs.existsSync(commitIntentPath(taskDir))
|
|
406
|
+
&& (run.recoveryHistory ?? []).every((entry) => entry.code === 'MODEL_POLICY_SUPPLEMENTED');
|
|
407
|
+
}
|
|
408
|
+
|
|
356
409
|
function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
357
410
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
358
411
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
@@ -399,6 +452,39 @@ function beginOrResumeOrchestration(taskRef: string, options: OrchestrationOptio
|
|
|
399
452
|
return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
|
|
400
453
|
}
|
|
401
454
|
}
|
|
455
|
+
if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
|
|
456
|
+
if (!canRecoverCodexUnsupportedPause(existing, resolved.taskDir)) {
|
|
457
|
+
return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
458
|
+
}
|
|
459
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
460
|
+
const previousPause = existing.pause as ClientCapabilityRecovery['previousPause'];
|
|
461
|
+
const recovery: ClientCapabilityRecovery = {
|
|
462
|
+
code: 'CLIENT_CAPABILITY_ENABLED',
|
|
463
|
+
recoveredAt: now,
|
|
464
|
+
previousSchemaVersion: 2,
|
|
465
|
+
previousStatus: 'paused',
|
|
466
|
+
previousPause,
|
|
467
|
+
client: 'codex',
|
|
468
|
+
guards: {
|
|
469
|
+
stepCount: 0,
|
|
470
|
+
nextStage: null,
|
|
471
|
+
baselineEmpty: true,
|
|
472
|
+
receiptCount: 0,
|
|
473
|
+
pendingDelegation: false,
|
|
474
|
+
commitAuthorizationUnused: true,
|
|
475
|
+
completionEvidenceAbsent: true,
|
|
476
|
+
commitIntentAbsent: true
|
|
477
|
+
},
|
|
478
|
+
resultingStatus: 'running'
|
|
479
|
+
};
|
|
480
|
+
const resumed = withUpdatedRun(existing, {
|
|
481
|
+
status: 'running',
|
|
482
|
+
pause: null,
|
|
483
|
+
recoveryHistory: Object.freeze([...(existing.recoveryHistory ?? []), recovery])
|
|
484
|
+
}, () => now);
|
|
485
|
+
saveRun(resolved.taskDir, resumed);
|
|
486
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
487
|
+
}
|
|
402
488
|
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
403
489
|
const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
|
|
404
490
|
saveRun(resolved.taskDir, resumed);
|
|
@@ -607,6 +693,12 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
|
|
|
607
693
|
function routeOrchestration(taskRef: string, options: OrchestrationOptions = {}): OrchestrationResult {
|
|
608
694
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
609
695
|
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
696
|
+
let gitRoot: string;
|
|
697
|
+
try {
|
|
698
|
+
gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
699
|
+
} catch (error) {
|
|
700
|
+
return failed('ORCHESTRATION_WORKTREE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
701
|
+
}
|
|
610
702
|
let content: string;
|
|
611
703
|
try {
|
|
612
704
|
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
@@ -677,7 +769,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
677
769
|
const capture = options.captureRepository ?? captureRepositorySnapshot;
|
|
678
770
|
let before: RepositorySnapshot;
|
|
679
771
|
try {
|
|
680
|
-
before = capture(
|
|
772
|
+
before = capture(gitRoot);
|
|
681
773
|
} catch (error) {
|
|
682
774
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
683
775
|
}
|
|
@@ -693,7 +785,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
693
785
|
options.inspectPullRequest ?? inspectPlatformPullRequest;
|
|
694
786
|
let inspection: ReturnType<NonNullable<OrchestrationOptions['inspectPullRequest']>>;
|
|
695
787
|
try {
|
|
696
|
-
inspection = inspect(resolved.taskId, { cwd:
|
|
788
|
+
inspection = inspect(resolved.taskId, { cwd: gitRoot });
|
|
697
789
|
} catch (error) {
|
|
698
790
|
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
699
791
|
}
|
|
@@ -714,7 +806,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
714
806
|
|
|
715
807
|
let after: RepositorySnapshot;
|
|
716
808
|
try {
|
|
717
|
-
after = capture(
|
|
809
|
+
after = capture(gitRoot);
|
|
718
810
|
} catch (error) {
|
|
719
811
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
720
812
|
}
|
|
@@ -748,16 +840,119 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
748
840
|
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
749
841
|
}
|
|
750
842
|
|
|
843
|
+
function appendCommitActivity(
|
|
844
|
+
resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
|
|
845
|
+
step: string,
|
|
846
|
+
agent: string,
|
|
847
|
+
note: string
|
|
848
|
+
): Readonly<{ code: string; message: string }> | null {
|
|
849
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
850
|
+
const activity = locateActivityLog(taskContent);
|
|
851
|
+
if (!activity) return { code: 'ORCHESTRATION_TASK_INVALID', message: 'task activity log is missing or ambiguous' };
|
|
852
|
+
const metadata = captureTaskWriteMetadata();
|
|
853
|
+
const written = writeTask({
|
|
854
|
+
taskRef: resolved.taskId,
|
|
855
|
+
expectedState: 'active',
|
|
856
|
+
mutations: [{
|
|
857
|
+
kind: 'section',
|
|
858
|
+
aliases: ['活动日志', 'Activity Log'],
|
|
859
|
+
heading: activity.heading,
|
|
860
|
+
body: appendActivityEntry(activity, { time: metadata.timestamp, step, agent, note })
|
|
861
|
+
}]
|
|
862
|
+
}, { repoRoot: resolved.repoRoot, metadataProvider: () => metadata });
|
|
863
|
+
return written.status === 'failed' ? written.error : null;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function startCommitAttempt(
|
|
867
|
+
taskRef: string,
|
|
868
|
+
input: Readonly<{ agent: string }>,
|
|
869
|
+
options: OrchestrationOptions = {}
|
|
870
|
+
): CommitIntentResult {
|
|
871
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
872
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
873
|
+
try {
|
|
874
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
875
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-attempt.start', () => {
|
|
876
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
877
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
|
|
878
|
+
}
|
|
879
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
880
|
+
const activity = locateActivityLog(taskContent);
|
|
881
|
+
if (!activity) return commitIntentFailed('ORCHESTRATION_TASK_INVALID', 'task activity log is missing or ambiguous', resolved.taskId);
|
|
882
|
+
const open = pairEntries(activity.entries).filter((row) => row.step === 'Commit' && row.started !== '' && row.done === '');
|
|
883
|
+
if (open.length > 0) {
|
|
884
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_ATTEMPT_BUSY', 'an open Commit activity entry already exists', resolved.taskId);
|
|
885
|
+
}
|
|
886
|
+
const currentHead = repositoryHead(gitRoot);
|
|
887
|
+
const attempt = (options.id ?? randomUUID)();
|
|
888
|
+
const error = appendCommitActivity(resolved, 'Commit [started]', input.agent, commitAttemptStartedNote({
|
|
889
|
+
attempt,
|
|
890
|
+
baseline: currentHead,
|
|
891
|
+
agent: input.agent
|
|
892
|
+
}));
|
|
893
|
+
if (error) return commitIntentFailed(error.code, error.message, resolved.taskId);
|
|
894
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
895
|
+
return {
|
|
896
|
+
status: 'ready', changed: true, taskId: resolved.taskId, intent: null,
|
|
897
|
+
finalization: commitFinalizationView(inspection), error: null
|
|
898
|
+
};
|
|
899
|
+
});
|
|
900
|
+
} catch (error) {
|
|
901
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function terminateCommitAttempt(
|
|
906
|
+
taskRef: string,
|
|
907
|
+
input: Readonly<{ attempt: string; agent: string; code: string }>,
|
|
908
|
+
options: OrchestrationOptions = {}
|
|
909
|
+
): CommitIntentResult {
|
|
910
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
911
|
+
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
912
|
+
try {
|
|
913
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
914
|
+
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-attempt.terminate', () => {
|
|
915
|
+
if (fs.existsSync(commitIntentPath(resolved.taskDir))) {
|
|
916
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'an active commit intent must be aborted or recovered first', resolved.taskId);
|
|
917
|
+
}
|
|
918
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
919
|
+
if (
|
|
920
|
+
inspection.disposition !== 'retryable-start'
|
|
921
|
+
|| inspection.attempt?.attempt !== input.attempt
|
|
922
|
+
|| inspection.attempt.agent !== input.agent
|
|
923
|
+
|| inspection.attempt.baseline !== inspection.currentHead
|
|
924
|
+
) {
|
|
925
|
+
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit attempt identity or repository HEAD has drifted', resolved.taskId);
|
|
926
|
+
}
|
|
927
|
+
const error = appendCommitActivity(
|
|
928
|
+
resolved,
|
|
929
|
+
'Commit [aborted]',
|
|
930
|
+
input.agent,
|
|
931
|
+
`aborted; attempt=${input.attempt}; code=${input.code}`
|
|
932
|
+
);
|
|
933
|
+
if (error) return commitIntentFailed(error.code, error.message, resolved.taskId);
|
|
934
|
+
return {
|
|
935
|
+
status: 'ready', changed: true, taskId: resolved.taskId, intent: null,
|
|
936
|
+
finalization: commitFinalizationView(inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId)),
|
|
937
|
+
error: null
|
|
938
|
+
};
|
|
939
|
+
});
|
|
940
|
+
} catch (error) {
|
|
941
|
+
return mapCommitIntentError(error, resolved.taskId);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
751
945
|
function beginCommitIntent(
|
|
752
946
|
taskRef: string,
|
|
753
|
-
input: Readonly<{ agent: string; orchestrated: boolean; baselineHead: string }>,
|
|
947
|
+
input: Readonly<{ agent: string; orchestrated: boolean; baselineHead: string; attempt: string }>,
|
|
754
948
|
options: OrchestrationOptions = {}
|
|
755
949
|
): CommitIntentResult {
|
|
756
950
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
757
951
|
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
758
952
|
try {
|
|
953
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
759
954
|
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.begin', () => {
|
|
760
|
-
const currentHead = repositoryHead(
|
|
955
|
+
const currentHead = repositoryHead(gitRoot);
|
|
761
956
|
if (currentHead !== input.baselineHead) {
|
|
762
957
|
return commitIntentFailed('ORCHESTRATION_COMMIT_BASELINE_MISMATCH', 'baseline HEAD does not match the repository HEAD', resolved.taskId);
|
|
763
958
|
}
|
|
@@ -769,7 +964,19 @@ function beginCommitIntent(
|
|
|
769
964
|
}
|
|
770
965
|
return commitIntentFailed('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists', resolved.taskId);
|
|
771
966
|
}
|
|
772
|
-
|
|
967
|
+
const attemptInspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
968
|
+
if (
|
|
969
|
+
attemptInspection.disposition !== 'retryable-start'
|
|
970
|
+
|| attemptInspection.attempt?.attempt !== input.attempt
|
|
971
|
+
|| attemptInspection.attempt.agent !== input.agent
|
|
972
|
+
|| attemptInspection.attempt.baseline !== input.baselineHead
|
|
973
|
+
) {
|
|
974
|
+
return commitIntentFailed(
|
|
975
|
+
'ORCHESTRATION_COMMIT_ATTEMPT_MISMATCH',
|
|
976
|
+
'commit begin requires one matching retryable Commit attempt',
|
|
977
|
+
resolved.taskId
|
|
978
|
+
);
|
|
979
|
+
}
|
|
773
980
|
const runFile = orchestrationPath(resolved.taskDir);
|
|
774
981
|
let run: OrchestrationRun | null = null;
|
|
775
982
|
let sourceRunBytes: string | null = null;
|
|
@@ -883,13 +1090,14 @@ function checkpointCommitIntent(
|
|
|
883
1090
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
884
1091
|
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
885
1092
|
try {
|
|
1093
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
886
1094
|
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.checkpoint', () => {
|
|
887
1095
|
const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
888
|
-
const currentHead = repositoryHead(
|
|
1096
|
+
const currentHead = repositoryHead(gitRoot);
|
|
889
1097
|
if (currentHead !== input.head) {
|
|
890
1098
|
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'checkpoint HEAD does not match the repository HEAD', resolved.taskId);
|
|
891
1099
|
}
|
|
892
|
-
if (input.kind === 'committed' && !isAncestor(
|
|
1100
|
+
if (input.kind === 'committed' && !isAncestor(gitRoot, intent.baselineHead, input.head)) {
|
|
893
1101
|
return commitIntentFailed('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'committed HEAD does not descend from the baseline', resolved.taskId);
|
|
894
1102
|
}
|
|
895
1103
|
if (input.kind === 'pushed') {
|
|
@@ -1016,9 +1224,10 @@ function completeCommitIntent(
|
|
|
1016
1224
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1017
1225
|
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1018
1226
|
try {
|
|
1227
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
1019
1228
|
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.complete', () => {
|
|
1020
1229
|
readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1021
|
-
const inspection = inspectCommitFinalization(resolved.taskDir,
|
|
1230
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
1022
1231
|
return finalizeCommitIntentUnlocked(resolved, inspection, input.agent, () => {
|
|
1023
1232
|
removeCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1024
1233
|
});
|
|
@@ -1036,8 +1245,15 @@ function recoverCommitIntent(
|
|
|
1036
1245
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1037
1246
|
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1038
1247
|
try {
|
|
1248
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
1039
1249
|
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.recover', () => {
|
|
1040
|
-
const inspection = inspectCommitFinalization(resolved.taskDir,
|
|
1250
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
1251
|
+
if (inspection.disposition === 'retryable-start') {
|
|
1252
|
+
return {
|
|
1253
|
+
status: 'ready', changed: false, taskId: resolved.taskId, intent: null,
|
|
1254
|
+
finalization: commitFinalizationView(inspection), error: null
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1041
1257
|
if (inspection.disposition === 'prepared' && inspection.intentDigest) {
|
|
1042
1258
|
removeCommitIntentByDigest(resolved.taskDir, resolved.taskId, inspection.intentDigest);
|
|
1043
1259
|
return { status: 'ready', changed: true, taskId: resolved.taskId, intent: null, error: null };
|
|
@@ -1066,9 +1282,10 @@ function abortCommitIntent(
|
|
|
1066
1282
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1067
1283
|
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1068
1284
|
try {
|
|
1285
|
+
const gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
1069
1286
|
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'commit-intent.abort', () => {
|
|
1070
1287
|
const intent = readCommitIntent(resolved.taskDir, resolved.taskId, input.token);
|
|
1071
|
-
const currentHead = repositoryHead(
|
|
1288
|
+
const currentHead = repositoryHead(gitRoot);
|
|
1072
1289
|
if (
|
|
1073
1290
|
intent.phase !== 'prepared'
|
|
1074
1291
|
|| intent.pushEvidence !== null
|
|
@@ -1088,7 +1305,13 @@ function abortCommitIntent(
|
|
|
1088
1305
|
function statusCommitIntent(taskRef: string, options: OrchestrationOptions = {}): CommitIntentResult {
|
|
1089
1306
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1090
1307
|
if (!resolved.ok) return commitIntentFailed(resolved.code, resolved.message, resolved.taskId);
|
|
1091
|
-
|
|
1308
|
+
let gitRoot: string;
|
|
1309
|
+
try {
|
|
1310
|
+
gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
1311
|
+
} catch (error) {
|
|
1312
|
+
return commitIntentFailed('ORCHESTRATION_WORKTREE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1313
|
+
}
|
|
1314
|
+
const inspection = inspectCommitFinalization(resolved.taskDir, gitRoot, resolved.taskId);
|
|
1092
1315
|
return {
|
|
1093
1316
|
status: 'ready',
|
|
1094
1317
|
changed: false,
|
|
@@ -1156,7 +1379,11 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
1156
1379
|
}
|
|
1157
1380
|
let beforeFingerprint: string;
|
|
1158
1381
|
try {
|
|
1159
|
-
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(
|
|
1382
|
+
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1383
|
+
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
1384
|
+
stateRoot: resolved.repoRoot,
|
|
1385
|
+
taskId: resolved.taskId
|
|
1386
|
+
});
|
|
1160
1387
|
} catch (error) {
|
|
1161
1388
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1162
1389
|
}
|
|
@@ -1220,7 +1447,7 @@ function matchingDelegations(
|
|
|
1220
1447
|
.filter((entry) => entry.isDirectory())
|
|
1221
1448
|
.flatMap((entry) => {
|
|
1222
1449
|
const run = readRun(path.join(activeRoot, entry.name));
|
|
1223
|
-
if (!run
|
|
1450
|
+
if (run?.status !== 'running' || !run.pendingDelegation) return [];
|
|
1224
1451
|
const receipt = run.pendingDelegation;
|
|
1225
1452
|
return predicate(receipt)
|
|
1226
1453
|
? [{ taskId: entry.name, run }]
|
|
@@ -1254,13 +1481,44 @@ function activateMatchingOrchestrationDelegation(
|
|
|
1254
1481
|
(receipt) => receipt.status === 'prepared',
|
|
1255
1482
|
options
|
|
1256
1483
|
);
|
|
1257
|
-
if ('status' in matched)
|
|
1484
|
+
if ('status' in matched) {
|
|
1485
|
+
if (matched.error?.code !== 'ORCHESTRATION_DELEGATION_MISSING') return matched;
|
|
1486
|
+
const replay = uniqueMatchingDelegation(
|
|
1487
|
+
client,
|
|
1488
|
+
(receipt) => receipt.childId === event.childId && ['activated', 'stage-completed'].includes(receipt.status),
|
|
1489
|
+
options
|
|
1490
|
+
);
|
|
1491
|
+
if ('status' in replay) return matched;
|
|
1492
|
+
const receipt = replay.run.pendingDelegation!;
|
|
1493
|
+
const sameEvidence = receipt.parentId === event.parentId
|
|
1494
|
+
&& receipt.spawnMode === event.spawnMode
|
|
1495
|
+
&& receipt.actualModel === event.actualModel
|
|
1496
|
+
&& receipt.actualReasoningEffort === event.actualReasoningEffort
|
|
1497
|
+
&& receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
|
|
1498
|
+
&& receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
|
|
1499
|
+
&& receipt.hostEvidence?.hookDefinitionHash === event.hostEvidence?.hookDefinitionHash;
|
|
1500
|
+
if (!sameEvidence) {
|
|
1501
|
+
return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
|
|
1502
|
+
}
|
|
1503
|
+
return { status: replay.run.status, changed: false, taskId: replay.taskId, run: replay.run, next: null, error: null };
|
|
1504
|
+
}
|
|
1258
1505
|
if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
|
|
1259
1506
|
return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
|
|
1260
1507
|
}
|
|
1261
1508
|
return activateOrchestrationDelegation(matched.taskId, event, options);
|
|
1262
1509
|
}
|
|
1263
1510
|
|
|
1511
|
+
function pauseMatchingOrchestrationDelegation(
|
|
1512
|
+
client: AgentClientId,
|
|
1513
|
+
code: string,
|
|
1514
|
+
message: string,
|
|
1515
|
+
options: OrchestrationOptions = {}
|
|
1516
|
+
): OrchestrationResult {
|
|
1517
|
+
const matched = uniqueMatchingDelegation(client, () => true, options);
|
|
1518
|
+
if ('status' in matched) return matched;
|
|
1519
|
+
return pauseOrchestration(matched.taskId, code, message, true, options);
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1264
1522
|
function sealMatchingOrchestrationDelegation(
|
|
1265
1523
|
client: AgentClientId,
|
|
1266
1524
|
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
@@ -1280,9 +1538,14 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1280
1538
|
}
|
|
1281
1539
|
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1282
1540
|
try {
|
|
1541
|
+
const gitRoot = gitRootFor(repoRoot, options);
|
|
1283
1542
|
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
1284
|
-
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(
|
|
1285
|
-
|
|
1543
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1544
|
+
gitRoot,
|
|
1545
|
+
stateRoot: repoRoot,
|
|
1546
|
+
taskId: snapshotTaskId
|
|
1547
|
+
});
|
|
1548
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1286
1549
|
return sealOrchestrationDelegation(matched.taskId, {
|
|
1287
1550
|
childId: event.childId,
|
|
1288
1551
|
exitCode: 0,
|
|
@@ -1300,6 +1563,96 @@ function sealMatchingOrchestrationDelegation(
|
|
|
1300
1563
|
}
|
|
1301
1564
|
}
|
|
1302
1565
|
|
|
1566
|
+
function sealMatchingOrchestrationDelegationWithHostEvidence(
|
|
1567
|
+
client: AgentClientId,
|
|
1568
|
+
event: Readonly<{ nativeAgent: string; childId: string }>,
|
|
1569
|
+
consumeEvidence: (receipt: DelegationReceipt) => Readonly<{
|
|
1570
|
+
stopRevision: number;
|
|
1571
|
+
consumer: string;
|
|
1572
|
+
consumedAt: string;
|
|
1573
|
+
}>,
|
|
1574
|
+
options: OrchestrationOptions = {}
|
|
1575
|
+
): OrchestrationResult {
|
|
1576
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
1577
|
+
if (!role) return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
1578
|
+
const matched = uniqueMatchingDelegation(
|
|
1579
|
+
client,
|
|
1580
|
+
(receipt) => (
|
|
1581
|
+
(receipt.status === 'stage-completed' || receipt.status === 'sealed')
|
|
1582
|
+
&& receipt.childId === event.childId
|
|
1583
|
+
),
|
|
1584
|
+
options
|
|
1585
|
+
);
|
|
1586
|
+
if ('status' in matched) return matched;
|
|
1587
|
+
const receipt = matched.run.pendingDelegation!;
|
|
1588
|
+
if (receipt.status === 'sealed') {
|
|
1589
|
+
return reconcileMatchingOrchestrationDelegation(client, event.childId, options);
|
|
1590
|
+
}
|
|
1591
|
+
if (receipt.role !== role) {
|
|
1592
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
1593
|
+
}
|
|
1594
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
1595
|
+
try {
|
|
1596
|
+
const gitRoot = gitRootFor(repoRoot, options);
|
|
1597
|
+
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
1598
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1599
|
+
gitRoot,
|
|
1600
|
+
stateRoot: repoRoot,
|
|
1601
|
+
taskId: snapshotTaskId
|
|
1602
|
+
});
|
|
1603
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
1604
|
+
const baseEvent = { childId: event.childId, exitCode: 0, afterFingerprint, changedPaths };
|
|
1605
|
+
const validated = sealDelegation(receipt, baseEvent, { now: options.now });
|
|
1606
|
+
if (!validated.ok) {
|
|
1607
|
+
return pauseOrchestration(matched.taskId, validated.code, validated.message, true, options);
|
|
1608
|
+
}
|
|
1609
|
+
const hostEvidence = consumeEvidence(receipt);
|
|
1610
|
+
const sealed = sealDelegation(receipt, { ...baseEvent, hostEvidence }, { now: options.now });
|
|
1611
|
+
if (!sealed.ok) return pauseOrchestration(matched.taskId, sealed.code, sealed.message, true, options);
|
|
1612
|
+
const resolved = resolveTaskRef(matched.taskId, { repoRoot });
|
|
1613
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1614
|
+
const updated = withUpdatedRun(matched.run, { pendingDelegation: sealed.receipt }, options.now);
|
|
1615
|
+
saveRun(resolved.taskDir, updated);
|
|
1616
|
+
return { status: 'running', changed: true, taskId: matched.taskId, run: updated, next: null, error: null };
|
|
1617
|
+
} catch (error) {
|
|
1618
|
+
return pauseOrchestration(
|
|
1619
|
+
matched.taskId,
|
|
1620
|
+
'ORCHESTRATION_CODEX_EVIDENCE_FAILED',
|
|
1621
|
+
error instanceof Error ? error.message : String(error),
|
|
1622
|
+
true,
|
|
1623
|
+
options
|
|
1624
|
+
);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
function reconcileMatchingOrchestrationDelegation(
|
|
1629
|
+
client: AgentClientId,
|
|
1630
|
+
childId: string,
|
|
1631
|
+
options: OrchestrationOptions = {}
|
|
1632
|
+
): OrchestrationResult {
|
|
1633
|
+
const matched = uniqueMatchingDelegation(
|
|
1634
|
+
client,
|
|
1635
|
+
(receipt) => receipt.childId === childId,
|
|
1636
|
+
options
|
|
1637
|
+
);
|
|
1638
|
+
if ('status' in matched) return matched;
|
|
1639
|
+
const receipt = matched.run.pendingDelegation!;
|
|
1640
|
+
if (
|
|
1641
|
+
receipt.status !== 'sealed'
|
|
1642
|
+
|| receipt.hostEvidence?.consumer !== receipt.id
|
|
1643
|
+
|| receipt.hostEvidence.consumedAt === null
|
|
1644
|
+
) {
|
|
1645
|
+
return pauseOrchestration(
|
|
1646
|
+
matched.taskId,
|
|
1647
|
+
'ORCHESTRATION_CODEX_RECONCILIATION_FAILED',
|
|
1648
|
+
'Codex native spawn did not produce one sealed receipt with consumed host evidence',
|
|
1649
|
+
true,
|
|
1650
|
+
options
|
|
1651
|
+
);
|
|
1652
|
+
}
|
|
1653
|
+
return { status: matched.run.status, changed: false, taskId: matched.taskId, run: matched.run, next: null, error: null };
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1303
1656
|
function activateOrchestrationDelegation(
|
|
1304
1657
|
taskRef: string,
|
|
1305
1658
|
event: Parameters<typeof activateDelegation>[1],
|
|
@@ -1466,16 +1819,21 @@ export {
|
|
|
1466
1819
|
completeOrchestrationStage,
|
|
1467
1820
|
inspectOrchestrationStage,
|
|
1468
1821
|
orchestrationPath,
|
|
1822
|
+
pauseMatchingOrchestrationDelegation,
|
|
1469
1823
|
pauseOrchestration,
|
|
1470
1824
|
planOrchestrationStageCompletion,
|
|
1471
1825
|
prepareOrchestrationDelegation,
|
|
1472
1826
|
readRun,
|
|
1827
|
+
reconcileMatchingOrchestrationDelegation,
|
|
1473
1828
|
recoverCommitIntent,
|
|
1474
1829
|
routeOrchestration,
|
|
1475
1830
|
sealMatchingOrchestrationDelegation,
|
|
1831
|
+
sealMatchingOrchestrationDelegationWithHostEvidence,
|
|
1476
1832
|
sealOrchestrationDelegation,
|
|
1833
|
+
startCommitAttempt,
|
|
1477
1834
|
statusCommitIntent,
|
|
1478
|
-
statusOrchestration
|
|
1835
|
+
statusOrchestration,
|
|
1836
|
+
terminateCommitAttempt
|
|
1479
1837
|
};
|
|
1480
1838
|
export type {
|
|
1481
1839
|
CommitIntentResult,
|
|
@@ -1486,5 +1844,6 @@ export type {
|
|
|
1486
1844
|
OrchestrationRun,
|
|
1487
1845
|
OrchestrationStageCompletion,
|
|
1488
1846
|
OrchestrationStageIdentity,
|
|
1489
|
-
OrchestrationStatus
|
|
1847
|
+
OrchestrationStatus,
|
|
1848
|
+
OrchestrationOptions
|
|
1490
1849
|
};
|
|
@@ -3,6 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { artifactName, maxRound } from "./review-artifacts.ts";
|
|
5
5
|
|
|
6
|
+
type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
|
|
7
|
+
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
8
|
+
|
|
9
|
+
export function parseReviewedGitTree(value: string): ReviewedGitTree | null {
|
|
10
|
+
return REVIEWED_TREE_RE.test(value) ? value as ReviewedGitTree : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
// Paths excluded from the post-review coverage gate. Fail-closed: the gate
|
|
7
14
|
// covers ALL tracked changes by default. This default denylist is EMPTY —
|
|
8
15
|
// projects opt into exclusions explicitly via review.post_review_exclude_globs.
|
|
@@ -71,6 +78,8 @@ export function extractReviewedSnapshotTree(content: string): string {
|
|
|
71
78
|
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
72
79
|
}
|
|
73
80
|
|
|
81
|
+
export type { ReviewedGitTree };
|
|
82
|
+
|
|
74
83
|
export function parseReviewVerdict(content: string): string {
|
|
75
84
|
const match = String(content).match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.*?)\s*$/m);
|
|
76
85
|
return match ? match[1]!.trim() : "";
|