@fitlab-ai/agent-infra 0.9.1 → 0.9.2
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 +16 -4
- package/README.zh-CN.md +16 -4
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +15 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/release-workflow.js +71 -16
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/merged-pr-equivalence.js +41 -11
- package/dist/lib/platform/pr-checks.js +38 -10
- package/dist/lib/platform/pull-requests.js +110 -6
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +8 -10
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +76 -180
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/shell.js +1 -1
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +100 -1
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +4 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +156 -28
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +3 -1
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/release-workflow.ts +68 -16
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/merged-pr-equivalence.ts +51 -14
- package/lib/platform/pr-checks.ts +50 -22
- package/lib/platform/pull-requests.ts +134 -25
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +12 -10
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +111 -192
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +110 -2
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +5 -0
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +177 -28
- package/lib/task/verification-review.ts +1 -0
- package/lib/task/verification.ts +5 -2
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.github.en.md +11 -5
- package/templates/.agents/rules/release-commands.github.zh-CN.md +11 -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/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-task/SKILL.en.md +11 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
package/dist/lib/task/events.js
CHANGED
|
@@ -3,8 +3,11 @@ import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-
|
|
|
3
3
|
import { buildArtifactLinkSection, artifactName, parseArtifactName, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
|
|
4
4
|
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
5
5
|
import { consumeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
|
|
6
|
+
import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
7
|
+
import { parseReviewSummary } from './review-artifacts.js';
|
|
6
8
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
9
|
import { findSectionHeading } from './sections.js';
|
|
10
|
+
import { completeOrchestrationStage, validateOrchestrationStage } from './orchestration.js';
|
|
8
11
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
9
12
|
const eventCatalog = [
|
|
10
13
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
@@ -75,12 +78,75 @@ function validateTaskEventRequest(request) {
|
|
|
75
78
|
const FAMILY = {
|
|
76
79
|
analyze: { artifact: 'analysis', started: ['requirement-analysis', 'requirement-analysis-review', 'code'], completed: ['requirement-analysis', 'requirement-analysis-review', 'code'], target: 'requirement-analysis', label: 'Analyze Task' },
|
|
77
80
|
'review-analysis': { artifact: 'review-analysis', started: ['requirement-analysis', 'requirement-analysis-review'], completed: ['requirement-analysis', 'requirement-analysis-review'], target: 'requirement-analysis-review', label: 'Review Analysis' },
|
|
78
|
-
plan: { artifact: 'plan', started: ['requirement-analysis-review', 'technical-design-review'], completed: ['requirement-analysis-review', 'technical-design-review'], target: 'technical-design', label: 'Plan Task' },
|
|
81
|
+
plan: { artifact: 'plan', started: ['requirement-analysis-review', 'technical-design-review', 'commit'], completed: ['requirement-analysis-review', 'technical-design-review', 'commit'], target: 'technical-design', label: 'Plan Task' },
|
|
79
82
|
'review-plan': { artifact: 'review-plan', started: ['technical-design', 'technical-design-review'], completed: ['technical-design', 'technical-design-review'], target: 'technical-design-review', label: 'Review Plan' },
|
|
80
83
|
code: { artifact: 'code', started: ['technical-design-review', 'code-review'], completed: ['technical-design-review', 'code-review'], target: 'code', label: 'Code Task' },
|
|
81
84
|
'review-code': { artifact: 'review-code', started: ['code', 'code-review', 'commit'], completed: ['code', 'code-review', 'commit'], target: 'code-review', label: 'Review Code' },
|
|
82
85
|
'manual-validation': { artifact: 'manual-validation', started: ['code-review', 'commit'], completed: ['code-review', 'commit'], target: null, label: 'Complete Manual Validation' }
|
|
83
86
|
};
|
|
87
|
+
const REVIEW_LEDGER_STAGES = {
|
|
88
|
+
'review-analysis': 'analysis',
|
|
89
|
+
'review-plan': 'plan',
|
|
90
|
+
'review-code': 'code'
|
|
91
|
+
};
|
|
92
|
+
function validateReviewFindingCounts(request, content, family, artifactPath) {
|
|
93
|
+
const stage = REVIEW_LEDGER_STAGES[family];
|
|
94
|
+
if (!stage || !artifactPath)
|
|
95
|
+
return null;
|
|
96
|
+
const rows = parseLedger(content);
|
|
97
|
+
const invalid = validateLedgerRows(rows);
|
|
98
|
+
if (invalid)
|
|
99
|
+
return { code: 'TASK_DOCUMENT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
100
|
+
const expected = summarizeLedgerStage(rows, stage).unresolvedFindingCounts;
|
|
101
|
+
const payload = {
|
|
102
|
+
blocker: request.blockers,
|
|
103
|
+
major: request.major,
|
|
104
|
+
minor: request.minor
|
|
105
|
+
};
|
|
106
|
+
let reportContent;
|
|
107
|
+
try {
|
|
108
|
+
reportContent = fs.readFileSync(artifactPath, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return { code: 'EVENT_FINDING_COUNT_MISMATCH', message: String(error) };
|
|
112
|
+
}
|
|
113
|
+
const parsed = parseReviewSummary(reportContent);
|
|
114
|
+
if (!parsed.ok || !parsed.summary.counts) {
|
|
115
|
+
return {
|
|
116
|
+
code: 'EVENT_FINDING_COUNT_MISMATCH',
|
|
117
|
+
message: parsed.ok ? 'review summary finding counts are not finalized' : parsed.message
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const report = parsed.summary.counts;
|
|
121
|
+
const reportVerdict = parsed.summary.verdict === 'Approved'
|
|
122
|
+
? 'approved'
|
|
123
|
+
: parsed.summary.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
|
|
124
|
+
const fields = [
|
|
125
|
+
['blocker', 'blockers'],
|
|
126
|
+
['major', 'major'],
|
|
127
|
+
['minor', 'minor']
|
|
128
|
+
];
|
|
129
|
+
const differences = fields.flatMap(([severity, cliField]) => {
|
|
130
|
+
const values = [
|
|
131
|
+
expected[severity] === payload[severity]
|
|
132
|
+
? null
|
|
133
|
+
: `${cliField} ledger ${expected[severity]}, payload ${payload[severity]}`,
|
|
134
|
+
expected[severity] === report[severity]
|
|
135
|
+
? null
|
|
136
|
+
: `${cliField} ledger ${expected[severity]}, report ${report[severity]}`
|
|
137
|
+
];
|
|
138
|
+
return values.filter((value) => value !== null);
|
|
139
|
+
});
|
|
140
|
+
if (request.verdict !== reportVerdict) {
|
|
141
|
+
differences.push(`verdict report ${reportVerdict}, payload ${request.verdict}`);
|
|
142
|
+
}
|
|
143
|
+
if (differences.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
return {
|
|
146
|
+
code: 'EVENT_FINDING_COUNT_MISMATCH',
|
|
147
|
+
message: `review summary, payload, and ${stage} ledger do not match: ${differences.join('; ')}`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
84
150
|
function eventParts(event) {
|
|
85
151
|
const [family, suffix] = event.split('.');
|
|
86
152
|
return { family, phase: suffix === 'started' ? 'started' : suffix === 'awaiting-input' ? 'waiting' : 'completed' };
|
|
@@ -257,6 +323,24 @@ function applyTaskEvent(request, options = {}) {
|
|
|
257
323
|
const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
|
|
258
324
|
if (!allowed.includes(currentStep))
|
|
259
325
|
return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
326
|
+
const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
|
|
327
|
+
if (findingCountError)
|
|
328
|
+
return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
329
|
+
if (eventIdentity.phase === 'completed' && !normalized.dryRun && eventIdentity.family !== 'manual-validation') {
|
|
330
|
+
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
331
|
+
const provenance = validateOrchestrationStage(normalized.taskRef, {
|
|
332
|
+
stage: orchestrationStage,
|
|
333
|
+
round: normalized.round,
|
|
334
|
+
artifact: normalized.artifact,
|
|
335
|
+
role: eventIdentity.family.startsWith('review-') ? 'reviewer' : 'executor'
|
|
336
|
+
}, { repoRoot: options.repoRoot });
|
|
337
|
+
if (provenance.status === 'failed' || provenance.status === 'paused') {
|
|
338
|
+
return failed(normalized, {
|
|
339
|
+
code: 'EVENT_TRANSITION_INVALID',
|
|
340
|
+
message: provenance.error?.message ?? provenance.run?.pause?.message ?? 'orchestration provenance validation failed'
|
|
341
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
260
344
|
let metadata;
|
|
261
345
|
try {
|
|
262
346
|
metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
|
|
@@ -295,6 +379,21 @@ function applyTaskEvent(request, options = {}) {
|
|
|
295
379
|
const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
|
|
296
380
|
if (result.status === 'failed')
|
|
297
381
|
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 });
|
|
382
|
+
if (eventIdentity.phase === 'completed' && !normalized.dryRun && eventIdentity.family !== 'manual-validation') {
|
|
383
|
+
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
384
|
+
const orchestration = completeOrchestrationStage(normalized.taskRef, {
|
|
385
|
+
stage: orchestrationStage,
|
|
386
|
+
round: normalized.round,
|
|
387
|
+
artifact: normalized.artifact,
|
|
388
|
+
agent: normalized.agent
|
|
389
|
+
}, { repoRoot: options.repoRoot });
|
|
390
|
+
if (orchestration.status === 'failed' || orchestration.status === 'paused') {
|
|
391
|
+
return failed(normalized, {
|
|
392
|
+
code: 'EVENT_TRANSITION_INVALID',
|
|
393
|
+
message: orchestration.error?.message ?? orchestration.run?.pause?.message ?? 'orchestration provenance validation failed'
|
|
394
|
+
}, { 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 });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
298
397
|
return {
|
|
299
398
|
status: result.status, changed: result.changed, event: normalized.event,
|
|
300
399
|
requestRef: normalized.taskRef, taskId: result.taskId, taskMdPath: result.taskMdPath,
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
5
|
+
import { parseReviewSummary } from './review-artifacts.js';
|
|
6
|
+
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
7
|
+
import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
8
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
9
|
+
import { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
10
|
+
import { getAgentClientCapability } from '../agent-clients/registry.js';
|
|
11
|
+
import { captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
12
|
+
function orchestrationPath(taskDir) {
|
|
13
|
+
return path.join(taskDir, 'orchestration.json');
|
|
14
|
+
}
|
|
15
|
+
function readRun(taskDir) {
|
|
16
|
+
const file = orchestrationPath(taskDir);
|
|
17
|
+
if (!fs.existsSync(file))
|
|
18
|
+
return null;
|
|
19
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
function atomicWrite(file, value) {
|
|
22
|
+
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
23
|
+
fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
24
|
+
fs.renameSync(temp, file);
|
|
25
|
+
}
|
|
26
|
+
function saveRun(taskDir, run) {
|
|
27
|
+
atomicWrite(orchestrationPath(taskDir), run);
|
|
28
|
+
}
|
|
29
|
+
function withUpdatedRun(run, updates) {
|
|
30
|
+
return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
|
|
31
|
+
}
|
|
32
|
+
function failed(code, message, taskId = null) {
|
|
33
|
+
return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message } };
|
|
34
|
+
}
|
|
35
|
+
function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
36
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
37
|
+
if (!resolved.ok)
|
|
38
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
39
|
+
const existing = readRun(resolved.taskDir);
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
42
|
+
const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
|
|
43
|
+
saveRun(resolved.taskDir, resumed);
|
|
44
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
45
|
+
}
|
|
46
|
+
return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
47
|
+
}
|
|
48
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
49
|
+
const run = {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
taskId: resolved.taskId,
|
|
52
|
+
runId: (options.id ?? randomUUID)(),
|
|
53
|
+
status: 'running',
|
|
54
|
+
nextStage: null,
|
|
55
|
+
stepCount: 0,
|
|
56
|
+
maxSteps: options.maxSteps ?? 24,
|
|
57
|
+
baseline: '',
|
|
58
|
+
pendingDelegation: null,
|
|
59
|
+
receipts: [],
|
|
60
|
+
pause: null,
|
|
61
|
+
commitAuthorization: { issuedAt: null, consumedAt: null },
|
|
62
|
+
createdAt: now,
|
|
63
|
+
updatedAt: now
|
|
64
|
+
};
|
|
65
|
+
atomicWrite(orchestrationPath(resolved.taskDir), run);
|
|
66
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
|
|
67
|
+
}
|
|
68
|
+
function highestRound(taskDir, family) {
|
|
69
|
+
const pattern = new RegExp(`^${family}(?:-r(\\d+))?\\.md$`);
|
|
70
|
+
return fs.readdirSync(taskDir).reduce((max, name) => {
|
|
71
|
+
const match = pattern.exec(name);
|
|
72
|
+
return match ? Math.max(max, match[1] ? Number(match[1]) : 1) : max;
|
|
73
|
+
}, 0);
|
|
74
|
+
}
|
|
75
|
+
function artifactName(family, round) {
|
|
76
|
+
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
77
|
+
}
|
|
78
|
+
function hasReviewAfterArtifact(taskDir, family, reviewFamily) {
|
|
79
|
+
const artifact = inspectArtifactDirectory(taskDir, family);
|
|
80
|
+
const review = inspectArtifactDirectory(taskDir, reviewFamily);
|
|
81
|
+
return artifact.status === 'ready'
|
|
82
|
+
&& review.status === 'ready'
|
|
83
|
+
&& artifact.latest !== null
|
|
84
|
+
&& review.reviewedInput?.name === artifact.latest.name;
|
|
85
|
+
}
|
|
86
|
+
function latestReviewApproved(taskDir, family) {
|
|
87
|
+
const round = highestRound(taskDir, family);
|
|
88
|
+
if (round === 0)
|
|
89
|
+
return false;
|
|
90
|
+
const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'));
|
|
91
|
+
return parsed.ok && parsed.summary.verdict === 'Approved' && parsed.summary.counts !== null;
|
|
92
|
+
}
|
|
93
|
+
function routeFromFacts(taskDir) {
|
|
94
|
+
const analysisRound = highestRound(taskDir, 'analysis');
|
|
95
|
+
const analysisReviewRound = highestRound(taskDir, 'review-analysis');
|
|
96
|
+
const planRound = highestRound(taskDir, 'plan');
|
|
97
|
+
const planReviewRound = highestRound(taskDir, 'review-plan');
|
|
98
|
+
const codeRound = highestRound(taskDir, 'code');
|
|
99
|
+
const codeReviewRound = highestRound(taskDir, 'review-code');
|
|
100
|
+
if (analysisRound === 0) {
|
|
101
|
+
const round = analysisRound + 1;
|
|
102
|
+
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
103
|
+
}
|
|
104
|
+
if (!hasReviewAfterArtifact(taskDir, 'analysis', 'review-analysis')) {
|
|
105
|
+
const round = analysisReviewRound + 1;
|
|
106
|
+
return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
|
|
107
|
+
}
|
|
108
|
+
if (!latestReviewApproved(taskDir, 'review-analysis')) {
|
|
109
|
+
const round = analysisRound + 1;
|
|
110
|
+
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
111
|
+
}
|
|
112
|
+
if (planRound === 0) {
|
|
113
|
+
const round = planRound + 1;
|
|
114
|
+
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
115
|
+
}
|
|
116
|
+
if (!hasReviewAfterArtifact(taskDir, 'plan', 'review-plan')) {
|
|
117
|
+
const round = planReviewRound + 1;
|
|
118
|
+
return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
|
|
119
|
+
}
|
|
120
|
+
if (!latestReviewApproved(taskDir, 'review-plan')) {
|
|
121
|
+
const round = planRound + 1;
|
|
122
|
+
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
123
|
+
}
|
|
124
|
+
if (codeRound === 0) {
|
|
125
|
+
const round = codeRound + 1;
|
|
126
|
+
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
127
|
+
}
|
|
128
|
+
if (!hasReviewAfterArtifact(taskDir, 'code', 'review-code')) {
|
|
129
|
+
const round = codeReviewRound + 1;
|
|
130
|
+
return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
|
|
131
|
+
}
|
|
132
|
+
if (!latestReviewApproved(taskDir, 'review-code')) {
|
|
133
|
+
const round = codeRound + 1;
|
|
134
|
+
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
135
|
+
}
|
|
136
|
+
return { action: 'commit', role: 'executor', stage: 'commit', round: 1, artifact: 'commit' };
|
|
137
|
+
}
|
|
138
|
+
function routeOrchestration(taskRef, options = {}) {
|
|
139
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
140
|
+
if (!resolved.ok)
|
|
141
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
142
|
+
let content;
|
|
143
|
+
try {
|
|
144
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
parseTypedTaskFrontmatter(content);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
154
|
+
}
|
|
155
|
+
const next = routeFromFacts(resolved.taskDir);
|
|
156
|
+
if (!next)
|
|
157
|
+
return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
158
|
+
if (next.stage === 'commit') {
|
|
159
|
+
const reviewRound = highestRound(resolved.taskDir, 'review-code');
|
|
160
|
+
const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
|
|
161
|
+
if (!review.ok || review.summary.manualValidation === null) {
|
|
162
|
+
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
163
|
+
}
|
|
164
|
+
if (review.summary.manualValidation > 0) {
|
|
165
|
+
return failed('ORCHESTRATION_MANUAL_VALIDATION_PENDING', 'manual validation must be completed before commit', resolved.taskId);
|
|
166
|
+
}
|
|
167
|
+
const rows = parseLedger(content);
|
|
168
|
+
const ledgerError = validateLedgerRows(rows);
|
|
169
|
+
if (ledgerError)
|
|
170
|
+
return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
|
|
171
|
+
if (!summarizeLedgerStage(rows, 'code').canAdvance) {
|
|
172
|
+
return failed('ORCHESTRATION_LEDGER_BLOCKED', 'code review ledger has unresolved findings or human decisions', resolved.taskId);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: readRun(resolved.taskDir), next, error: null };
|
|
176
|
+
}
|
|
177
|
+
function statusOrchestration(taskRef, options = {}) {
|
|
178
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
179
|
+
if (!resolved.ok)
|
|
180
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
181
|
+
const run = readRun(resolved.taskDir);
|
|
182
|
+
if (!run)
|
|
183
|
+
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
184
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
185
|
+
}
|
|
186
|
+
function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
187
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
188
|
+
if (!resolved.ok)
|
|
189
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
190
|
+
if (getAgentClientCapability(input.client, 'subagents').level === 'unsupported'
|
|
191
|
+
|| getAgentClientCapability(input.client, 'orchestration').level === 'unsupported') {
|
|
192
|
+
return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
|
|
193
|
+
}
|
|
194
|
+
const run = readRun(resolved.taskDir);
|
|
195
|
+
if (!run || run.status !== 'running')
|
|
196
|
+
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
|
|
197
|
+
if (run.pendingDelegation)
|
|
198
|
+
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
|
|
199
|
+
const repositoryPending = ['claude-code', 'codex']
|
|
200
|
+
.flatMap((client) => matchingDelegations(client, () => true, options));
|
|
201
|
+
if (repositoryPending.length > 0) {
|
|
202
|
+
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
203
|
+
}
|
|
204
|
+
if (run.stepCount >= run.maxSteps)
|
|
205
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
206
|
+
const routed = routeOrchestration(taskRef, options);
|
|
207
|
+
if (!routed.next)
|
|
208
|
+
return routed;
|
|
209
|
+
const next = routed.next;
|
|
210
|
+
let beforeFingerprint;
|
|
211
|
+
try {
|
|
212
|
+
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
216
|
+
}
|
|
217
|
+
const receipt = prepareDelegation({
|
|
218
|
+
taskId: resolved.taskId,
|
|
219
|
+
runId: run.runId,
|
|
220
|
+
role: next.role,
|
|
221
|
+
stage: next.stage,
|
|
222
|
+
round: next.round,
|
|
223
|
+
artifact: next.artifact,
|
|
224
|
+
client: input.client,
|
|
225
|
+
requestedModel: input.requestedModel ?? null,
|
|
226
|
+
beforeFingerprint
|
|
227
|
+
}, { id: options.id, now: options.now });
|
|
228
|
+
const updated = withUpdatedRun(run, {
|
|
229
|
+
nextStage: next.stage,
|
|
230
|
+
pendingDelegation: receipt,
|
|
231
|
+
commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
|
|
232
|
+
? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
|
|
233
|
+
: run.commitAuthorization
|
|
234
|
+
});
|
|
235
|
+
saveRun(resolved.taskDir, updated);
|
|
236
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
237
|
+
}
|
|
238
|
+
function matchingDelegations(client, predicate, options) {
|
|
239
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
240
|
+
const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
241
|
+
if (!fs.existsSync(activeRoot))
|
|
242
|
+
return [];
|
|
243
|
+
return fs.readdirSync(activeRoot, { withFileTypes: true })
|
|
244
|
+
.filter((entry) => entry.isDirectory())
|
|
245
|
+
.flatMap((entry) => {
|
|
246
|
+
const run = readRun(path.join(activeRoot, entry.name));
|
|
247
|
+
if (!run?.pendingDelegation)
|
|
248
|
+
return [];
|
|
249
|
+
const receipt = run.pendingDelegation;
|
|
250
|
+
return receipt.client === client && predicate(receipt)
|
|
251
|
+
? [{ taskId: entry.name, run }]
|
|
252
|
+
: [];
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function uniqueMatchingDelegation(client, predicate, options) {
|
|
256
|
+
const matches = matchingDelegations(client, predicate, options);
|
|
257
|
+
if (matches.length === 0)
|
|
258
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
259
|
+
if (matches.length > 1)
|
|
260
|
+
return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
|
|
261
|
+
return matches[0];
|
|
262
|
+
}
|
|
263
|
+
function activateMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
264
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
265
|
+
if (!role)
|
|
266
|
+
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
267
|
+
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status === 'prepared', options);
|
|
268
|
+
if ('status' in matched)
|
|
269
|
+
return matched;
|
|
270
|
+
if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
|
|
271
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
|
|
272
|
+
}
|
|
273
|
+
return activateOrchestrationDelegation(matched.taskId, event, options);
|
|
274
|
+
}
|
|
275
|
+
function sealMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
276
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
277
|
+
if (!role)
|
|
278
|
+
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
279
|
+
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status !== 'prepared', options);
|
|
280
|
+
if ('status' in matched)
|
|
281
|
+
return matched;
|
|
282
|
+
const receipt = matched.run.pendingDelegation;
|
|
283
|
+
if (receipt.role !== role) {
|
|
284
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
285
|
+
}
|
|
286
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
287
|
+
try {
|
|
288
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot);
|
|
289
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(repoRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
290
|
+
return sealOrchestrationDelegation(matched.taskId, {
|
|
291
|
+
childId: event.childId,
|
|
292
|
+
exitCode: 0,
|
|
293
|
+
afterFingerprint,
|
|
294
|
+
changedPaths
|
|
295
|
+
}, options);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), true, options);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function activateOrchestrationDelegation(taskRef, event, options = {}) {
|
|
302
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
303
|
+
if (!resolved.ok)
|
|
304
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
305
|
+
const run = readRun(resolved.taskDir);
|
|
306
|
+
if (!run?.pendingDelegation)
|
|
307
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
308
|
+
const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
|
|
309
|
+
if (!result.ok) {
|
|
310
|
+
if (result.code === 'DELEGATION_IGNORED')
|
|
311
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
312
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
313
|
+
}
|
|
314
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
315
|
+
saveRun(resolved.taskDir, updated);
|
|
316
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
317
|
+
}
|
|
318
|
+
function completeOrchestrationStage(taskRef, event, options = {}) {
|
|
319
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
320
|
+
if (!resolved.ok)
|
|
321
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
322
|
+
const run = readRun(resolved.taskDir);
|
|
323
|
+
if (!run)
|
|
324
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
325
|
+
if (!run.pendingDelegation)
|
|
326
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
|
|
327
|
+
const result = completeDelegationStage(run.pendingDelegation, event);
|
|
328
|
+
if (!result.ok)
|
|
329
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
330
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
331
|
+
saveRun(resolved.taskDir, updated);
|
|
332
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
333
|
+
}
|
|
334
|
+
function completeCommitOrchestrationStage(taskRef, agent, options = {}) {
|
|
335
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
336
|
+
if (!resolved.ok)
|
|
337
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
338
|
+
const run = readRun(resolved.taskDir);
|
|
339
|
+
if (!run)
|
|
340
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
341
|
+
const receipt = run.pendingDelegation;
|
|
342
|
+
if (!receipt || receipt.stage !== 'commit') {
|
|
343
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
|
|
344
|
+
}
|
|
345
|
+
return completeOrchestrationStage(taskRef, {
|
|
346
|
+
stage: receipt.stage,
|
|
347
|
+
round: receipt.round,
|
|
348
|
+
artifact: receipt.artifact,
|
|
349
|
+
agent
|
|
350
|
+
}, options);
|
|
351
|
+
}
|
|
352
|
+
function validateOrchestrationStage(taskRef, event, options = {}) {
|
|
353
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
354
|
+
if (!resolved.ok)
|
|
355
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
356
|
+
const run = readRun(resolved.taskDir);
|
|
357
|
+
if (!run)
|
|
358
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
359
|
+
const receipt = run.pendingDelegation;
|
|
360
|
+
if (!receipt
|
|
361
|
+
|| receipt.status !== 'activated'
|
|
362
|
+
|| receipt.stage !== event.stage
|
|
363
|
+
|| receipt.round !== event.round
|
|
364
|
+
|| receipt.artifact !== event.artifact
|
|
365
|
+
|| receipt.role !== event.role) {
|
|
366
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'active run does not have one matching activated delegation', true, options);
|
|
367
|
+
}
|
|
368
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
369
|
+
}
|
|
370
|
+
function sealOrchestrationDelegation(taskRef, event, options = {}) {
|
|
371
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
372
|
+
if (!resolved.ok)
|
|
373
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
374
|
+
const run = readRun(resolved.taskDir);
|
|
375
|
+
if (!run?.pendingDelegation)
|
|
376
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
377
|
+
const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
|
|
378
|
+
if (!result.ok)
|
|
379
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
380
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
381
|
+
saveRun(resolved.taskDir, updated);
|
|
382
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
383
|
+
}
|
|
384
|
+
function advanceOrchestration(taskRef, options = {}) {
|
|
385
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
386
|
+
if (!resolved.ok)
|
|
387
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
388
|
+
const run = readRun(resolved.taskDir);
|
|
389
|
+
if (!run?.pendingDelegation)
|
|
390
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
391
|
+
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
392
|
+
if (!result.ok)
|
|
393
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
394
|
+
const completed = result.receipt.stage === 'commit';
|
|
395
|
+
const updated = withUpdatedRun(run, {
|
|
396
|
+
status: completed ? 'completed' : 'running',
|
|
397
|
+
nextStage: null,
|
|
398
|
+
stepCount: run.stepCount + 1,
|
|
399
|
+
pendingDelegation: null,
|
|
400
|
+
receipts: Object.freeze([...run.receipts, result.receipt]),
|
|
401
|
+
commitAuthorization: completed
|
|
402
|
+
? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
|
|
403
|
+
: run.commitAuthorization
|
|
404
|
+
});
|
|
405
|
+
saveRun(resolved.taskDir, updated);
|
|
406
|
+
return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
407
|
+
}
|
|
408
|
+
function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
|
|
409
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
410
|
+
if (!resolved.ok)
|
|
411
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
412
|
+
const run = readRun(resolved.taskDir);
|
|
413
|
+
if (!run)
|
|
414
|
+
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
415
|
+
const updated = withUpdatedRun(run, { status: 'paused', pause: { code, message, recoverable } });
|
|
416
|
+
saveRun(resolved.taskDir, updated);
|
|
417
|
+
return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
418
|
+
}
|
|
419
|
+
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, completeOrchestrationStage, orchestrationPath, pauseOrchestration, prepareOrchestrationDelegation, readRun, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, validateOrchestrationStage };
|
|
420
|
+
//# sourceMappingURL=orchestration.js.map
|