@fitlab-ai/agent-infra 0.12.1 → 0.12.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 +0 -2
- package/README.zh-CN.md +0 -2
- package/dist/bin/internal-cli.js +2 -17
- package/dist/lib/agent-clients/adapter.js +0 -13
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
- package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
- package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
- package/dist/lib/agent-clients/adapters/codex.js +0 -52
- package/dist/lib/agent-clients/adapters/opencode.js +0 -5
- package/dist/lib/agent-clients/adapters/traecli.js +0 -5
- package/dist/lib/agent-clients/config.js +19 -54
- package/dist/lib/agent-clients/registry.js +1 -12
- package/dist/lib/agent-clients/schema.js +0 -18
- package/dist/lib/agent-clients/tokens.js +1 -2
- package/dist/lib/agent-clients/types.js +0 -1
- package/dist/lib/decide.js +2 -0
- package/dist/lib/git/worktree-identity.js +1 -9
- package/dist/lib/internal/agent-client.js +2 -62
- package/dist/lib/internal/cli-route-inventory.js +1 -5
- package/dist/lib/internal/manual-validation.js +18 -10
- package/dist/lib/internal/task-create.js +1 -1
- package/dist/lib/internal/task-event.js +1 -8
- package/dist/lib/internal/task-finalization.js +0 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +4 -20
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/comment-safety.js +1 -7
- package/dist/lib/platform/in-label-sync.js +1 -14
- package/dist/lib/platform/issue-comments.js +1 -16
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/operation-coordinator.js +4 -2
- package/dist/lib/platform/pr-change-report.js +1 -4
- package/dist/lib/platform/pr-checks.js +2 -8
- package/dist/lib/platform/provider-validation.js +0 -20
- package/dist/lib/platform/pull-requests.js +73 -84
- package/dist/lib/platform/releases.js +1 -43
- package/dist/lib/platform/verification-required.js +0 -2
- package/dist/lib/platform/verification-sync.js +1 -79
- package/dist/lib/process-data/normalize.js +1 -185
- package/dist/lib/process-data/privacy.js +1 -16
- package/dist/lib/process-data/sources.js +2 -279
- package/dist/lib/render.js +0 -12
- package/dist/lib/run/index.js +15 -10
- package/dist/lib/run/skills.js +0 -3
- package/dist/lib/sandbox/commands/create.js +40 -53
- package/dist/lib/sandbox/commands/list-running.js +0 -18
- package/dist/lib/sandbox/commands/start.js +7 -8
- package/dist/lib/sandbox/constants.js +0 -6
- package/dist/lib/sandbox/control/client.js +0 -1
- package/dist/lib/sandbox/control/container-identity.js +1 -16
- package/dist/lib/sandbox/control/executor.js +0 -4
- package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
- package/dist/lib/sandbox/control/lifecycle.js +21 -37
- package/dist/lib/sandbox/control/native-file-lock.js +0 -3
- package/dist/lib/sandbox/control/server.js +18 -38
- package/dist/lib/sandbox/control/state.js +1 -9
- package/dist/lib/sandbox/dockerfile.js +0 -6
- package/dist/lib/sandbox/engine.js +0 -14
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
- package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
- package/dist/lib/sandbox/recovery.js +24 -26
- package/dist/lib/sandbox/removal.js +11 -519
- package/dist/lib/sandbox/task-resolver.js +0 -41
- package/dist/lib/sandbox/tools.js +1 -4
- package/dist/lib/sandbox/workspace-view.js +1 -2
- package/dist/lib/server/display.js +0 -6
- package/dist/lib/task/activity-intent.js +45 -40
- package/dist/lib/task/activity-log.js +9 -16
- package/dist/lib/task/artifact-command.js +1 -1
- package/dist/lib/task/artifact-lifecycle.js +61 -63
- package/dist/lib/task/artifact-name.js +0 -6
- package/dist/lib/task/artifact-operations.js +3 -3
- package/dist/lib/task/capabilities.js +4 -5
- package/dist/lib/task/commands/log.js +0 -6
- package/dist/lib/task/commands/status.js +17 -60
- package/dist/lib/task/commit-operation.js +28 -39
- package/dist/lib/task/completion-facts.js +56 -43
- package/dist/lib/task/control-authority.js +23 -256
- package/dist/lib/task/control-recovery.js +3 -5
- package/dist/lib/task/create.js +2 -4
- package/dist/lib/task/decision-intents.js +8 -9
- package/dist/lib/task/delivery-target.js +1 -40
- package/dist/lib/task/events.js +84 -107
- package/dist/lib/task/finalization.js +44 -8
- package/dist/lib/task/ledger-intents.js +1 -1
- package/dist/lib/task/lifecycle-execution.js +38 -33
- package/dist/lib/task/lifecycle.js +49 -41
- package/dist/lib/task/local-artifact-finalization.js +1 -4
- package/dist/lib/task/manual-validation-evidence.js +1 -37
- package/dist/lib/task/platform-operation-recovery.js +67 -24
- package/dist/lib/task/pr-delivery-fact.js +2 -5
- package/dist/lib/task/qualification-confirmation.js +5 -7
- package/dist/lib/task/resolve-ref.js +1 -25
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +1 -21
- package/dist/lib/task/review-fingerprint.js +0 -4
- package/dist/lib/task/secure-fs.js +0 -56
- package/dist/lib/task/short-id.js +1 -39
- package/dist/lib/task/skill-scope.js +0 -1
- package/dist/lib/task/verification-engine.js +58 -282
- package/dist/lib/task/verification.js +6 -3
- package/dist/lib/task/write.js +11 -1
- package/lib/agent-clients/adapter.ts +0 -76
- package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
- package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
- package/lib/agent-clients/adapters/claude-code.ts +0 -9
- package/lib/agent-clients/adapters/codex.ts +0 -56
- package/lib/agent-clients/adapters/opencode.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +0 -5
- package/lib/agent-clients/config.ts +20 -61
- package/lib/agent-clients/registry.ts +1 -34
- package/lib/agent-clients/schema.ts +0 -18
- package/lib/agent-clients/tokens.ts +1 -2
- package/lib/agent-clients/types.ts +0 -15
- package/lib/decide.ts +1 -0
- package/lib/git/worktree-identity.ts +0 -10
- package/lib/internal/agent-client.ts +2 -70
- package/lib/internal/cli-route-inventory.ts +1 -4
- package/lib/internal/manual-validation.ts +16 -9
- package/lib/internal/task-create.ts +0 -1
- package/lib/internal/task-event.ts +1 -5
- package/lib/internal/task-finalization.ts +0 -1
- package/lib/internal/task-lifecycle.ts +3 -5
- package/lib/internal/task-operation-registry.ts +3 -19
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/comment-safety.ts +0 -10
- package/lib/platform/github-provider.ts +0 -2
- package/lib/platform/in-label-sync.ts +0 -16
- package/lib/platform/issue-comments.ts +0 -21
- package/lib/platform/issues.ts +0 -2
- package/lib/platform/operation-coordinator.ts +4 -2
- package/lib/platform/pr-change-report.ts +0 -5
- package/lib/platform/pr-checks.ts +1 -10
- package/lib/platform/pr-review.ts +1 -1
- package/lib/platform/provider-bridge.ts +0 -1
- package/lib/platform/provider-loader.ts +1 -5
- package/lib/platform/provider-validation.ts +1 -49
- package/lib/platform/pull-requests.ts +64 -99
- package/lib/platform/releases.ts +1 -40
- package/lib/platform/verification-required.ts +0 -2
- package/lib/platform/verification-sync.ts +0 -94
- package/lib/process-data/normalize.ts +1 -186
- package/lib/process-data/privacy.ts +2 -19
- package/lib/process-data/sources.ts +3 -283
- package/lib/render.ts +0 -15
- package/lib/run/index.ts +13 -7
- package/lib/run/skills.ts +0 -4
- package/lib/sandbox/commands/create.ts +37 -74
- package/lib/sandbox/commands/list-running.ts +0 -19
- package/lib/sandbox/commands/start.ts +7 -10
- package/lib/sandbox/constants.ts +0 -8
- package/lib/sandbox/control/client.ts +0 -1
- package/lib/sandbox/control/container-identity.ts +1 -16
- package/lib/sandbox/control/executor.ts +1 -5
- package/lib/sandbox/control/identity-sentinel.ts +0 -8
- package/lib/sandbox/control/lifecycle.ts +18 -51
- package/lib/sandbox/control/native-file-lock.ts +0 -8
- package/lib/sandbox/control/protocol.ts +0 -2
- package/lib/sandbox/control/server.ts +13 -39
- package/lib/sandbox/control/state.ts +1 -10
- package/lib/sandbox/dockerfile.ts +0 -11
- package/lib/sandbox/engine.ts +0 -17
- package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
- package/lib/sandbox/engines/wsl2-paths.ts +0 -4
- package/lib/sandbox/recovery.ts +24 -26
- package/lib/sandbox/removal.ts +11 -696
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/task-resolver.ts +0 -48
- package/lib/sandbox/tools.ts +1 -5
- package/lib/sandbox/workspace-view.ts +2 -4
- package/lib/server/display.ts +0 -14
- package/lib/server/process-control.ts +1 -1
- package/lib/task/activity-intent.ts +36 -44
- package/lib/task/activity-log.ts +9 -22
- package/lib/task/artifact-command.ts +1 -1
- package/lib/task/artifact-lifecycle.ts +58 -67
- package/lib/task/artifact-name.ts +0 -7
- package/lib/task/artifact-operations.ts +3 -3
- package/lib/task/capabilities.ts +5 -6
- package/lib/task/commands/log.ts +0 -7
- package/lib/task/commands/status.ts +14 -79
- package/lib/task/commit-operation.ts +29 -51
- package/lib/task/completion-facts.ts +45 -42
- package/lib/task/control-authority.ts +27 -317
- package/lib/task/control-recovery.ts +2 -5
- package/lib/task/create.ts +2 -5
- package/lib/task/decision-intents.ts +5 -5
- package/lib/task/delivery-target.ts +0 -40
- package/lib/task/events.ts +89 -123
- package/lib/task/finalization.ts +41 -9
- package/lib/task/ledger-intents.ts +1 -1
- package/lib/task/lifecycle-execution.ts +37 -89
- package/lib/task/lifecycle.ts +48 -44
- package/lib/task/local-artifact-finalization.ts +0 -7
- package/lib/task/manual-validation-evidence.ts +1 -47
- package/lib/task/platform-operation-recovery.ts +50 -26
- package/lib/task/pr-delivery-fact.ts +2 -11
- package/lib/task/qualification-confirmation.ts +6 -6
- package/lib/task/resolve-ref.ts +0 -23
- package/lib/task/review-command.ts +2 -2
- package/lib/task/review-finalization.ts +1 -27
- package/lib/task/review-fingerprint.ts +0 -8
- package/lib/task/secure-fs.ts +0 -42
- package/lib/task/short-id.ts +0 -43
- package/lib/task/skill-scope.ts +0 -1
- package/lib/task/verification-engine.ts +56 -287
- package/lib/task/verification.ts +5 -5
- package/lib/task/write.ts +12 -1
- package/package.json +2 -2
- package/templates/.agents/README.en.md +1 -3
- package/templates/.agents/README.zh-CN.md +0 -2
- package/templates/.agents/rules/README.en.md +0 -1
- package/templates/.agents/rules/README.zh-CN.md +0 -1
- package/templates/.agents/rules/commit-and-pr.en.md +1 -1
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +3 -3
- package/templates/.agents/rules/task-management.zh-CN.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/commit/SKILL.en.md +2 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
- package/templates/.agents/templates/task.en.md +1 -0
- package/templates/.agents/templates/task.zh-CN.md +1 -0
- package/templates/.claude/settings.json +0 -24
- package/templates/.codex/hooks.json +0 -50
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
- package/dist/lib/internal/task-orchestration.js +0 -71
- package/dist/lib/platform/verification.js +0 -6
- package/dist/lib/run/prompt.js +0 -2
- package/dist/lib/sandbox/engines/authority.js +0 -289
- package/dist/lib/task/delegation-receipts.js +0 -410
- package/dist/lib/task/orchestration.js +0 -1233
- package/dist/lib/task/verification-checks.js +0 -9
- package/dist/lib/task/workspace-snapshot.js +0 -121
- package/dist/lifecycle-build-manifest.json +0 -6
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
- package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
- package/lib/internal/task-orchestration.ts +0 -74
- package/lib/platform/verification.ts +0 -11
- package/lib/run/prompt.ts +0 -1
- package/lib/sandbox/engines/authority.ts +0 -365
- package/lib/task/delegation-receipts.ts +0 -547
- package/lib/task/orchestration.ts +0 -1651
- package/lib/task/verification-checks.ts +0 -10
- package/lib/task/workspace-snapshot.ts +0 -144
- package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
- package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
- package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
- package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
- package/templates/.claude/commands/run-task.en.md +0 -8
- package/templates/.claude/commands/run-task.zh-CN.md +0 -8
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
- package/templates/.opencode/commands/run-task.en.md +0 -11
- package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
|
@@ -1,1233 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
-
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
5
|
-
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
6
|
-
import { validateCurrentTaskContract } from './current-contract.js';
|
|
7
|
-
import { parseReviewSummary } from './review-artifacts.js';
|
|
8
|
-
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
9
|
-
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
10
|
-
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.js';
|
|
11
|
-
import { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
12
|
-
import { normalizeAgentClients } from '../agent-clients/config.js';
|
|
13
|
-
import { getAgentClientAdapter, getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
|
|
14
|
-
import { isAgentClientId } from '../agent-clients/types.js';
|
|
15
|
-
import { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
16
|
-
import { assertGitRepositoryBinding } from '../git/worktree-identity.js';
|
|
17
|
-
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.js';
|
|
18
|
-
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
|
|
19
|
-
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
|
|
20
|
-
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
21
|
-
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
22
|
-
import { resolveArtifactContextForResolvedTask } from './artifact-lifecycle.js';
|
|
23
|
-
function supportsLifecycleDelegation(client) {
|
|
24
|
-
return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
|
|
25
|
-
&& getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
|
|
26
|
-
&& getAgentClientDelegationEvidence(client).actualModel !== 'unavailable'
|
|
27
|
-
&& getAgentClientDelegationEvidence(client).actualReasoningEffort !== 'unavailable';
|
|
28
|
-
}
|
|
29
|
-
function orchestrationPath(taskDir) {
|
|
30
|
-
return path.join(taskDir, '.runtime', 'orchestration.json');
|
|
31
|
-
}
|
|
32
|
-
const ORCHESTRATION_STATE_INVALID_MESSAGE = 'orchestration.json does not match the current runtime structure; the file was left unchanged; rebuild the sandbox or manually repair the state before retrying';
|
|
33
|
-
class OrchestrationStateError extends Error {
|
|
34
|
-
code = 'ORCHESTRATION_STATE_INVALID';
|
|
35
|
-
taskId;
|
|
36
|
-
reasonCodes;
|
|
37
|
-
constructor(taskId = null, reasonCodes = []) {
|
|
38
|
-
super(ORCHESTRATION_STATE_INVALID_MESSAGE);
|
|
39
|
-
this.name = 'OrchestrationStateError';
|
|
40
|
-
this.taskId = taskId;
|
|
41
|
-
this.reasonCodes = Object.freeze([...reasonCodes]);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function isRecord(value) {
|
|
45
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
46
|
-
}
|
|
47
|
-
function hasExactKeys(value, keys) {
|
|
48
|
-
if (!isRecord(value))
|
|
49
|
-
return false;
|
|
50
|
-
const actual = Object.keys(value);
|
|
51
|
-
return actual.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
52
|
-
}
|
|
53
|
-
function exactText(value) {
|
|
54
|
-
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
55
|
-
}
|
|
56
|
-
function nullableText(value) {
|
|
57
|
-
return value === null || exactText(value);
|
|
58
|
-
}
|
|
59
|
-
function isPause(value) {
|
|
60
|
-
return hasExactKeys(value, ['code', 'message', 'recoverable'])
|
|
61
|
-
&& exactText(value.code)
|
|
62
|
-
&& exactText(value.message)
|
|
63
|
-
&& typeof value.recoverable === 'boolean';
|
|
64
|
-
}
|
|
65
|
-
function isModelPolicySource(value) {
|
|
66
|
-
return hasExactKeys(value, ['kind', 'client', 'resolvedAt'])
|
|
67
|
-
&& ['explicit', 'project-config'].includes(value.kind)
|
|
68
|
-
&& isAgentClientId(value.client)
|
|
69
|
-
&& exactText(value.resolvedAt);
|
|
70
|
-
}
|
|
71
|
-
function isModelPolicy(value) {
|
|
72
|
-
if (!hasExactKeys(value, ['executor', 'reviewer']))
|
|
73
|
-
return false;
|
|
74
|
-
return [value.executor, value.reviewer].every((role) => hasExactKeys(role, ['model', 'reasoningEffort'])
|
|
75
|
-
&& exactText(role.model)
|
|
76
|
-
&& exactText(role.reasoningEffort));
|
|
77
|
-
}
|
|
78
|
-
const RECOVERY_GUARD_KEYS = [
|
|
79
|
-
'stepCount', 'nextStage', 'baselineEmpty', 'receiptCount', 'pendingDelegation',
|
|
80
|
-
'completionEvidenceAbsent'
|
|
81
|
-
];
|
|
82
|
-
function isRecovery(value) {
|
|
83
|
-
if (!hasExactKeys(value, [
|
|
84
|
-
'code', 'recoveredAt', 'previousStatus', 'previousPause', 'client', 'guards', 'resultingStatus'
|
|
85
|
-
]))
|
|
86
|
-
return false;
|
|
87
|
-
const guards = value.guards;
|
|
88
|
-
return value.code === 'CLIENT_CAPABILITY_ENABLED'
|
|
89
|
-
&& exactText(value.recoveredAt)
|
|
90
|
-
&& value.previousStatus === 'paused'
|
|
91
|
-
&& isPause(value.previousPause)
|
|
92
|
-
&& value.previousPause.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
93
|
-
&& isAgentClientId(value.client)
|
|
94
|
-
&& hasExactKeys(guards, RECOVERY_GUARD_KEYS)
|
|
95
|
-
&& guards.stepCount === 0
|
|
96
|
-
&& guards.nextStage === null
|
|
97
|
-
&& guards.baselineEmpty === true
|
|
98
|
-
&& guards.receiptCount === 0
|
|
99
|
-
&& guards.pendingDelegation === false
|
|
100
|
-
&& guards.completionEvidenceAbsent === true
|
|
101
|
-
&& value.resultingStatus === 'running';
|
|
102
|
-
}
|
|
103
|
-
export function isCompletionEvidence(value) {
|
|
104
|
-
return hasExactKeys(value, [
|
|
105
|
-
'kind', 'observedAt', 'head', 'headTree', 'worktreeTree', 'lastReviewedCommit', 'prNumber', 'prHead'
|
|
106
|
-
])
|
|
107
|
-
&& value.kind === 'reviewed-head-clean'
|
|
108
|
-
&& exactText(value.observedAt)
|
|
109
|
-
&& exactText(value.head)
|
|
110
|
-
&& exactText(value.headTree)
|
|
111
|
-
&& exactText(value.worktreeTree)
|
|
112
|
-
&& exactText(value.lastReviewedCommit)
|
|
113
|
-
&& (value.prNumber === null || (Number.isSafeInteger(value.prNumber) && value.prNumber > 0))
|
|
114
|
-
&& (value.prHead === null || exactText(value.prHead));
|
|
115
|
-
}
|
|
116
|
-
const ORCHESTRATION_RUN_KEYS = [
|
|
117
|
-
'taskId', 'runId', 'status', 'nextStage', 'stepCount', 'maxSteps', 'modelPolicy',
|
|
118
|
-
'modelPolicySource', 'recoveryHistory', 'baseline', 'pendingDelegation', 'receipts',
|
|
119
|
-
'pause', 'completionEvidence', 'createdAt', 'updatedAt'
|
|
120
|
-
];
|
|
121
|
-
function orchestrationStateReasonCodes(value) {
|
|
122
|
-
if (!hasExactKeys(value, ORCHESTRATION_RUN_KEYS))
|
|
123
|
-
return ['top-level-keys'];
|
|
124
|
-
const record = value;
|
|
125
|
-
const reasons = [];
|
|
126
|
-
if (!exactText(record.taskId))
|
|
127
|
-
reasons.push('taskId');
|
|
128
|
-
if (!exactText(record.runId))
|
|
129
|
-
reasons.push('runId');
|
|
130
|
-
if (!['running', 'paused', 'completed'].includes(record.status))
|
|
131
|
-
reasons.push('status');
|
|
132
|
-
if (!(record.nextStage === null || ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code', 'commit'].includes(record.nextStage))) {
|
|
133
|
-
reasons.push('nextStage');
|
|
134
|
-
}
|
|
135
|
-
if (!Number.isSafeInteger(record.stepCount) || record.stepCount < 0)
|
|
136
|
-
reasons.push('stepCount');
|
|
137
|
-
if (!Number.isSafeInteger(record.maxSteps) || record.maxSteps < 1)
|
|
138
|
-
reasons.push('maxSteps');
|
|
139
|
-
if (!isModelPolicy(record.modelPolicy))
|
|
140
|
-
reasons.push('modelPolicy');
|
|
141
|
-
if (!isModelPolicySource(record.modelPolicySource))
|
|
142
|
-
reasons.push('modelPolicySource');
|
|
143
|
-
if (!Array.isArray(record.recoveryHistory) || !record.recoveryHistory.every(isRecovery))
|
|
144
|
-
reasons.push('recoveryHistory');
|
|
145
|
-
if (typeof record.baseline !== 'string')
|
|
146
|
-
reasons.push('baseline');
|
|
147
|
-
if (!(record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation)))
|
|
148
|
-
reasons.push('pendingDelegation');
|
|
149
|
-
if (!Array.isArray(record.receipts) || !record.receipts.every(isDelegationReceipt))
|
|
150
|
-
reasons.push('receipts');
|
|
151
|
-
if (!(record.pause === null || isPause(record.pause)))
|
|
152
|
-
reasons.push('pause');
|
|
153
|
-
if (!(record.completionEvidence === null || isCompletionEvidence(record.completionEvidence)))
|
|
154
|
-
reasons.push('completionEvidence');
|
|
155
|
-
if (!exactText(record.createdAt))
|
|
156
|
-
reasons.push('createdAt');
|
|
157
|
-
if (!exactText(record.updatedAt))
|
|
158
|
-
reasons.push('updatedAt');
|
|
159
|
-
if (Array.isArray(record.receipts) && record.receipts.every(isDelegationReceipt)
|
|
160
|
-
&& (record.pendingDelegation === null || isDelegationReceipt(record.pendingDelegation))) {
|
|
161
|
-
const receipts = [...record.receipts, ...(record.pendingDelegation ? [record.pendingDelegation] : [])];
|
|
162
|
-
if (receipts.some((receipt) => receipt.runId !== record.runId)) {
|
|
163
|
-
reasons.push('receipt-identity');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
return reasons;
|
|
167
|
-
}
|
|
168
|
-
function emitDiagnostic(options, event, fields) {
|
|
169
|
-
try {
|
|
170
|
-
options?.diagnosticLog?.(event, fields);
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
// Diagnostics must never change lifecycle behavior.
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function fileObservation(file) {
|
|
177
|
-
try {
|
|
178
|
-
const stat = fs.lstatSync(file);
|
|
179
|
-
let realpath = null;
|
|
180
|
-
try {
|
|
181
|
-
realpath = fs.realpathSync.native(file);
|
|
182
|
-
}
|
|
183
|
-
catch { /* retain the lstat observation */ }
|
|
184
|
-
return {
|
|
185
|
-
file,
|
|
186
|
-
exists: true,
|
|
187
|
-
fileType: stat.isFile() ? 'file' : stat.isDirectory() ? 'directory' : 'other',
|
|
188
|
-
symbolicLink: stat.isSymbolicLink(),
|
|
189
|
-
bytes: stat.isFile() ? stat.size : null,
|
|
190
|
-
mtimeMs: stat.mtimeMs,
|
|
191
|
-
device: stat.dev,
|
|
192
|
-
inode: stat.ino,
|
|
193
|
-
realpath
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
return {
|
|
198
|
-
file,
|
|
199
|
-
exists: false,
|
|
200
|
-
fileType: null,
|
|
201
|
-
symbolicLink: null,
|
|
202
|
-
bytes: null,
|
|
203
|
-
mtimeMs: null,
|
|
204
|
-
device: null,
|
|
205
|
-
inode: null,
|
|
206
|
-
realpath: null,
|
|
207
|
-
fsError: error instanceof Error && 'code' in error ? String(error.code) : null
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function parseOrchestrationRun(value, expectedTaskId) {
|
|
212
|
-
const reasonCodes = orchestrationStateReasonCodes(value);
|
|
213
|
-
if (reasonCodes.length > 0)
|
|
214
|
-
throw new OrchestrationStateError(expectedTaskId ?? null, reasonCodes);
|
|
215
|
-
return value;
|
|
216
|
-
}
|
|
217
|
-
function readRun(taskDir, options = {}) {
|
|
218
|
-
const file = orchestrationPath(taskDir);
|
|
219
|
-
const taskId = path.basename(taskDir);
|
|
220
|
-
const observed = options.diagnosticLog ? fileObservation(file) : {};
|
|
221
|
-
if (!fs.existsSync(file)) {
|
|
222
|
-
emitDiagnostic(options, 'orchestration-state-missing', { taskId, taskDir, ...observed });
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
emitDiagnostic(options, 'orchestration-state-read-start', { taskId, taskDir, ...observed });
|
|
226
|
-
let rawBytes = null;
|
|
227
|
-
let rawSha256 = null;
|
|
228
|
-
try {
|
|
229
|
-
const raw = fs.readFileSync(file, 'utf8');
|
|
230
|
-
if (options.diagnosticLog) {
|
|
231
|
-
rawBytes = Buffer.byteLength(raw, 'utf8');
|
|
232
|
-
rawSha256 = createHash('sha256').update(raw, 'utf8').digest('hex');
|
|
233
|
-
}
|
|
234
|
-
const run = parseOrchestrationRun(JSON.parse(raw), taskId);
|
|
235
|
-
emitDiagnostic(options, 'orchestration-state-read-ok', {
|
|
236
|
-
taskId, taskDir, file, rawBytes, rawSha256, status: run.status, runId: run.runId,
|
|
237
|
-
stepCount: run.stepCount, pendingDelegation: run.pendingDelegation !== null
|
|
238
|
-
});
|
|
239
|
-
return run;
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
const reasonCodes = error instanceof OrchestrationStateError && error.reasonCodes.length > 0
|
|
243
|
-
? error.reasonCodes
|
|
244
|
-
: [error instanceof SyntaxError ? 'json-parse' : 'file-read'];
|
|
245
|
-
emitDiagnostic(options, 'orchestration-state-read-failed', {
|
|
246
|
-
taskId,
|
|
247
|
-
taskDir,
|
|
248
|
-
...observed,
|
|
249
|
-
rawBytes,
|
|
250
|
-
rawSha256,
|
|
251
|
-
reasonCodes: reasonCodes.join(','),
|
|
252
|
-
errorType: error instanceof Error ? error.name : typeof error,
|
|
253
|
-
});
|
|
254
|
-
throw new OrchestrationStateError(taskId, reasonCodes);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function findDelegation(run, receiptId) {
|
|
258
|
-
if (run.pendingDelegation?.id === receiptId)
|
|
259
|
-
return { receipt: run.pendingDelegation, location: 'pending' };
|
|
260
|
-
const receipt = run.receipts.find((entry) => entry.id === receiptId);
|
|
261
|
-
return receipt ? { receipt, location: 'history' } : null;
|
|
262
|
-
}
|
|
263
|
-
function readDelegationAdapterEvidence(taskRef, receiptId, adapterId, options = {}) {
|
|
264
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
265
|
-
if (!resolved.ok)
|
|
266
|
-
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
267
|
-
const run = readRun(resolved.taskDir, options);
|
|
268
|
-
const found = run && findDelegation(run, receiptId);
|
|
269
|
-
if (!found || found.receipt.client !== adapterId)
|
|
270
|
-
return null;
|
|
271
|
-
return found.receipt.adapterEvidence?.[adapterId] ?? null;
|
|
272
|
-
}
|
|
273
|
-
function mutateDelegationAdapterEvidence(taskRef, receiptId, adapterId, mutate, options = {}) {
|
|
274
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
275
|
-
if (!resolved.ok)
|
|
276
|
-
throw new Error(`${resolved.code}: ${resolved.message}`);
|
|
277
|
-
return mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options);
|
|
278
|
-
}
|
|
279
|
-
function mutateDelegationAdapterEvidenceAtResolved(resolved, receiptId, adapterId, mutate, options) {
|
|
280
|
-
const update = () => {
|
|
281
|
-
try {
|
|
282
|
-
const run = readRun(resolved.taskDir, options);
|
|
283
|
-
const found = run && findDelegation(run, receiptId);
|
|
284
|
-
if (!run || !found || found.receipt.client !== adapterId) {
|
|
285
|
-
throw new Error('ORCHESTRATION_DELEGATION_MISSING: adapter evidence has no matching task receipt');
|
|
286
|
-
}
|
|
287
|
-
const current = found.receipt.adapterEvidence?.[adapterId] ?? null;
|
|
288
|
-
const nextState = mutate(current);
|
|
289
|
-
const serialized = JSON.stringify(nextState);
|
|
290
|
-
if (serialized === undefined)
|
|
291
|
-
throw new Error('Agent adapter evidence must be JSON serializable');
|
|
292
|
-
if (serialized === JSON.stringify(current))
|
|
293
|
-
return JSON.parse(serialized);
|
|
294
|
-
const adapterEvidence = Object.freeze({
|
|
295
|
-
...(found.receipt.adapterEvidence ?? {}),
|
|
296
|
-
[adapterId]: JSON.parse(serialized)
|
|
297
|
-
});
|
|
298
|
-
const nextReceipt = Object.freeze({ ...found.receipt, adapterEvidence });
|
|
299
|
-
const updated = found.location === 'pending'
|
|
300
|
-
? withUpdatedRun(run, { pendingDelegation: nextReceipt }, options.now)
|
|
301
|
-
: withUpdatedRun(run, {
|
|
302
|
-
receipts: Object.freeze(run.receipts.map((receipt) => receipt.id === receiptId ? nextReceipt : receipt))
|
|
303
|
-
}, options.now);
|
|
304
|
-
saveRun(resolved.taskDir, updated);
|
|
305
|
-
return JSON.parse(serialized);
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
mutationError = error;
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
let mutationError;
|
|
313
|
-
const result = update();
|
|
314
|
-
if (mutationError)
|
|
315
|
-
throw mutationError;
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
318
|
-
function atomicWrite(file, value) {
|
|
319
|
-
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
320
|
-
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
321
|
-
fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
322
|
-
fs.renameSync(temp, file);
|
|
323
|
-
}
|
|
324
|
-
function saveRun(taskDir, run) {
|
|
325
|
-
atomicWrite(orchestrationPath(taskDir), run);
|
|
326
|
-
}
|
|
327
|
-
function withUpdatedRun(run, updates, now) {
|
|
328
|
-
return Object.freeze({ ...run, ...updates, updatedAt: (now ?? (() => new Date().toISOString()))() });
|
|
329
|
-
}
|
|
330
|
-
function failed(code, message, taskId = null, details = {}) {
|
|
331
|
-
return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message, ...details } };
|
|
332
|
-
}
|
|
333
|
-
function gitRootFor(stateRoot, options) {
|
|
334
|
-
if (!options.gitWorktreeRoot)
|
|
335
|
-
return stateRoot;
|
|
336
|
-
return assertGitRepositoryBinding(stateRoot, options.gitWorktreeRoot).worktreeRoot;
|
|
337
|
-
}
|
|
338
|
-
function validModel(value) {
|
|
339
|
-
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
340
|
-
}
|
|
341
|
-
function validateModelPolicy(policy) {
|
|
342
|
-
if (!policy
|
|
343
|
-
|| !validModel(policy.executor?.model)
|
|
344
|
-
|| !validModel(policy.executor?.reasoningEffort)
|
|
345
|
-
|| !validModel(policy.reviewer?.model)
|
|
346
|
-
|| !validModel(policy.reviewer?.reasoningEffort)) {
|
|
347
|
-
return { code: 'ORCHESTRATION_MODEL_POLICY_REQUIRED', message: 'executor and reviewer model and reasoning effort are required' };
|
|
348
|
-
}
|
|
349
|
-
return null;
|
|
350
|
-
}
|
|
351
|
-
function sameModelPolicy(left, right) {
|
|
352
|
-
return left.executor.model === right.executor.model
|
|
353
|
-
&& left.executor.reasoningEffort === right.executor.reasoningEffort
|
|
354
|
-
&& left.reviewer.model === right.reviewer.model
|
|
355
|
-
&& left.reviewer.reasoningEffort === right.reviewer.reasoningEffort;
|
|
356
|
-
}
|
|
357
|
-
function rolePolicy(run, role) {
|
|
358
|
-
return run.modelPolicy[role];
|
|
359
|
-
}
|
|
360
|
-
function resolveProjectPolicy(repoRoot, client) {
|
|
361
|
-
const configPath = path.join(repoRoot, '.agents', '.airc.json');
|
|
362
|
-
if (!fs.existsSync(configPath))
|
|
363
|
-
return undefined;
|
|
364
|
-
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
365
|
-
return normalizeAgentClients(raw).state[client].orchestration;
|
|
366
|
-
}
|
|
367
|
-
function canRecoverUnsupportedClientPause(run) {
|
|
368
|
-
return run.status === 'paused'
|
|
369
|
-
&& run.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED'
|
|
370
|
-
&& isAgentClientId(run.modelPolicySource?.client)
|
|
371
|
-
&& supportsLifecycleDelegation(run.modelPolicySource.client)
|
|
372
|
-
&& run.stepCount === 0
|
|
373
|
-
&& run.nextStage === null
|
|
374
|
-
&& run.baseline === ''
|
|
375
|
-
&& run.pendingDelegation === null
|
|
376
|
-
&& run.receipts.length === 0
|
|
377
|
-
&& run.completionEvidence == null
|
|
378
|
-
&& run.recoveryHistory.length === 0;
|
|
379
|
-
}
|
|
380
|
-
function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
381
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
382
|
-
if (!resolved.ok)
|
|
383
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
384
|
-
if (!isAgentClientId(options.client)) {
|
|
385
|
-
return failed('ORCHESTRATION_PAYLOAD_INVALID', 'begin-or-resume requires a known client', resolved.taskId);
|
|
386
|
-
}
|
|
387
|
-
let existing;
|
|
388
|
-
try {
|
|
389
|
-
existing = readRun(resolved.taskDir, options);
|
|
390
|
-
}
|
|
391
|
-
catch (error) {
|
|
392
|
-
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
393
|
-
}
|
|
394
|
-
if (existing) {
|
|
395
|
-
if (existing.status === 'completed') {
|
|
396
|
-
return { status: 'completed', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
397
|
-
}
|
|
398
|
-
if (existing.modelPolicySource.client !== options.client) {
|
|
399
|
-
return failed('ORCHESTRATION_CLIENT_MISMATCH', 'provided client does not match the persisted run policy source', resolved.taskId);
|
|
400
|
-
}
|
|
401
|
-
if (options.modelPolicy) {
|
|
402
|
-
const suppliedError = validateModelPolicy(options.modelPolicy);
|
|
403
|
-
if (suppliedError)
|
|
404
|
-
return failed(suppliedError.code, suppliedError.message, resolved.taskId);
|
|
405
|
-
if (!sameModelPolicy(existing.modelPolicy, options.modelPolicy)) {
|
|
406
|
-
return failed('ORCHESTRATION_MODEL_POLICY_MISMATCH', 'provided model policy does not match the persisted run policy', resolved.taskId);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (existing.status === 'paused' && existing.pause?.code === 'ORCHESTRATION_CLIENT_UNSUPPORTED') {
|
|
410
|
-
if (!canRecoverUnsupportedClientPause(existing)) {
|
|
411
|
-
return { status: 'paused', changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
412
|
-
}
|
|
413
|
-
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
414
|
-
const recovery = {
|
|
415
|
-
code: 'CLIENT_CAPABILITY_ENABLED',
|
|
416
|
-
recoveredAt: now,
|
|
417
|
-
previousStatus: 'paused',
|
|
418
|
-
previousPause: existing.pause,
|
|
419
|
-
client: existing.modelPolicySource.client,
|
|
420
|
-
guards: {
|
|
421
|
-
stepCount: 0,
|
|
422
|
-
nextStage: null,
|
|
423
|
-
baselineEmpty: true,
|
|
424
|
-
receiptCount: 0,
|
|
425
|
-
pendingDelegation: false,
|
|
426
|
-
completionEvidenceAbsent: true
|
|
427
|
-
},
|
|
428
|
-
resultingStatus: 'running'
|
|
429
|
-
};
|
|
430
|
-
const resumed = withUpdatedRun(existing, {
|
|
431
|
-
status: 'running',
|
|
432
|
-
pause: null,
|
|
433
|
-
recoveryHistory: Object.freeze([...existing.recoveryHistory, recovery])
|
|
434
|
-
}, () => now);
|
|
435
|
-
saveRun(resolved.taskDir, resumed);
|
|
436
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
437
|
-
}
|
|
438
|
-
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
439
|
-
const resumed = withUpdatedRun(existing, { status: 'running', pause: null }, options.now);
|
|
440
|
-
saveRun(resolved.taskDir, resumed);
|
|
441
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
442
|
-
}
|
|
443
|
-
return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
444
|
-
}
|
|
445
|
-
let policy = options.modelPolicy;
|
|
446
|
-
let sourceKind = 'explicit';
|
|
447
|
-
if (!policy) {
|
|
448
|
-
try {
|
|
449
|
-
policy = resolveProjectPolicy(resolved.repoRoot, options.client);
|
|
450
|
-
sourceKind = 'project-config';
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
const policyError = validateModelPolicy(policy);
|
|
457
|
-
if (policyError)
|
|
458
|
-
return failed(policyError.code, policyError.message, resolved.taskId, {
|
|
459
|
-
client: options.client,
|
|
460
|
-
missingFields: ['executor.model', 'executor.reasoningEffort', 'reviewer.model', 'reviewer.reasoningEffort'],
|
|
461
|
-
modelSelectionContext: getAgentClientModelSelection(options.client)
|
|
462
|
-
});
|
|
463
|
-
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
464
|
-
const source = { kind: sourceKind, client: options.client, resolvedAt: now };
|
|
465
|
-
const run = {
|
|
466
|
-
taskId: resolved.taskId,
|
|
467
|
-
runId: (options.id ?? randomUUID)(),
|
|
468
|
-
status: 'running',
|
|
469
|
-
nextStage: null,
|
|
470
|
-
stepCount: 0,
|
|
471
|
-
maxSteps: options.maxSteps ?? 24,
|
|
472
|
-
modelPolicy: policy,
|
|
473
|
-
modelPolicySource: source,
|
|
474
|
-
recoveryHistory: [],
|
|
475
|
-
baseline: '',
|
|
476
|
-
pendingDelegation: null,
|
|
477
|
-
receipts: [],
|
|
478
|
-
pause: null,
|
|
479
|
-
completionEvidence: null,
|
|
480
|
-
createdAt: now,
|
|
481
|
-
updatedAt: now
|
|
482
|
-
};
|
|
483
|
-
atomicWrite(orchestrationPath(resolved.taskDir), run);
|
|
484
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
|
|
485
|
-
}
|
|
486
|
-
function validateSavedCurrentDiffBase(repoRoot, metadata, taskDir, reviewedHead) {
|
|
487
|
-
const reviewInventory = inspectArtifactDirectory(taskDir, 'review-code');
|
|
488
|
-
if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
|
|
489
|
-
return { code: 'ORCHESTRATION_REVIEW_INVALID', message: 'latest completed review-code artifact is unavailable' };
|
|
490
|
-
}
|
|
491
|
-
const reviewContent = fs.readFileSync(reviewInventory.latest.path, 'utf8');
|
|
492
|
-
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
493
|
-
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
494
|
-
const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
495
|
-
if (!savedTargetHead && !savedDiffBase && !savedReviewedHead)
|
|
496
|
-
return null;
|
|
497
|
-
if (savedReviewedHead && savedReviewedHead !== reviewedHead) {
|
|
498
|
-
return { code: 'ORCHESTRATION_REVIEWED_HEAD_MISMATCH', message: 'review-code saved reviewed head does not match last_reviewed_commit' };
|
|
499
|
-
}
|
|
500
|
-
if (!savedTargetHead || !savedDiffBase) {
|
|
501
|
-
return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: 'review-code must save target head and diff base together' };
|
|
502
|
-
}
|
|
503
|
-
const savedBase = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
|
|
504
|
-
if (!savedBase.ok)
|
|
505
|
-
return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: savedBase.message };
|
|
506
|
-
if (savedBase.diffBase !== savedDiffBase) {
|
|
507
|
-
return { code: 'ORCHESTRATION_REVIEW_DIFF_BASE_MISMATCH', message: 'saved review diff base does not match reviewed head and saved target head' };
|
|
508
|
-
}
|
|
509
|
-
const target = resolveDeliveryTarget(repoRoot, {
|
|
510
|
-
remote: typeof metadata.delivery_remote === 'string' ? metadata.delivery_remote : undefined,
|
|
511
|
-
baseRef: typeof metadata.delivery_base_ref === 'string' ? metadata.delivery_base_ref : undefined
|
|
512
|
-
});
|
|
513
|
-
if (!target.ok)
|
|
514
|
-
return { code: target.code, message: target.message };
|
|
515
|
-
const currentTarget = resolveTargetHead(repoRoot, target.value);
|
|
516
|
-
if (!currentTarget.ok)
|
|
517
|
-
return { code: currentTarget.code, message: currentTarget.message };
|
|
518
|
-
const currentBase = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head);
|
|
519
|
-
if (!currentBase.ok)
|
|
520
|
-
return { code: currentBase.code, message: currentBase.message };
|
|
521
|
-
if (currentBase.diffBase !== savedDiffBase) {
|
|
522
|
-
return { code: 'ORCHESTRATION_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
|
|
523
|
-
}
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
function routeFromFacts(facts) {
|
|
527
|
-
const recommendation = recommendNext(facts);
|
|
528
|
-
if (!recommendation.action)
|
|
529
|
-
return recommendation.reasonCode === 'LIFECYCLE_REVIEWED' ? { completion: true } : null;
|
|
530
|
-
const action = recommendation.action;
|
|
531
|
-
if (action === 'manual-validation' || action === 'validation-run')
|
|
532
|
-
return null;
|
|
533
|
-
const command = action === 'analysis' ? 'analyze-task' : action === 'plan' ? 'plan-task' : action === 'code' ? 'code-task' : action;
|
|
534
|
-
const role = action.startsWith('review-') ? 'reviewer' : 'executor';
|
|
535
|
-
return { action: command, role, stage: action };
|
|
536
|
-
}
|
|
537
|
-
function routeResolvedTask(taskRef, resolved, options) {
|
|
538
|
-
let gitRoot;
|
|
539
|
-
try {
|
|
540
|
-
gitRoot = gitRootFor(resolved.repoRoot, options);
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
return failed('ORCHESTRATION_WORKTREE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
544
|
-
}
|
|
545
|
-
let content;
|
|
546
|
-
try {
|
|
547
|
-
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
548
|
-
}
|
|
549
|
-
catch (error) {
|
|
550
|
-
return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
|
|
551
|
-
}
|
|
552
|
-
let metadata;
|
|
553
|
-
let currentLedger = null;
|
|
554
|
-
if (resolved.state === 'active') {
|
|
555
|
-
const contract = validateCurrentTaskContract(content);
|
|
556
|
-
if (!contract.ok)
|
|
557
|
-
return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
558
|
-
metadata = contract.metadata;
|
|
559
|
-
currentLedger = contract.ledger;
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
try {
|
|
563
|
-
metadata = parseTypedTaskFrontmatter(content);
|
|
564
|
-
}
|
|
565
|
-
catch (error) {
|
|
566
|
-
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
let run;
|
|
570
|
-
try {
|
|
571
|
-
run = readRun(resolved.taskDir, options);
|
|
572
|
-
}
|
|
573
|
-
catch (error) {
|
|
574
|
-
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
575
|
-
}
|
|
576
|
-
const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state, hasOpenLifecycleExecution(content) || Boolean(run?.pendingDelegation), resolved.repoRoot);
|
|
577
|
-
if (!facts.ok)
|
|
578
|
-
return failed('ORCHESTRATION_CAPABILITY_FACTS_INVALID', facts.message, resolved.taskId);
|
|
579
|
-
const routed = routeFromFacts(facts.facts);
|
|
580
|
-
if (!routed)
|
|
581
|
-
return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
582
|
-
if ('completion' in routed) {
|
|
583
|
-
if (facts.facts.executionBusy && !run?.pendingDelegation) {
|
|
584
|
-
return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
|
|
585
|
-
}
|
|
586
|
-
const reviewInventory = inspectArtifactDirectory(resolved.taskDir, 'review-code');
|
|
587
|
-
if (reviewInventory.status !== 'ready' || !reviewInventory.latest) {
|
|
588
|
-
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest completed review-code artifact is unavailable', resolved.taskId);
|
|
589
|
-
}
|
|
590
|
-
const review = parseReviewSummary(fs.readFileSync(reviewInventory.latest.path, 'utf8'));
|
|
591
|
-
if (!review.ok || review.summary.manualValidation === null) {
|
|
592
|
-
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
593
|
-
}
|
|
594
|
-
const ledger = currentLedger ?? parseLedgerDocument(content);
|
|
595
|
-
if (!ledger.present)
|
|
596
|
-
return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
|
|
597
|
-
const rows = ledger.rows;
|
|
598
|
-
const ledgerError = validateLedgerRows(rows);
|
|
599
|
-
if (ledgerError)
|
|
600
|
-
return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
|
|
601
|
-
if (!summarizeLedgerStage(rows, 'code').canAdvance) {
|
|
602
|
-
return failed('ORCHESTRATION_LEDGER_BLOCKED', 'code review ledger has unresolved findings or human decisions', resolved.taskId);
|
|
603
|
-
}
|
|
604
|
-
if (run?.status === 'completed') {
|
|
605
|
-
return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
606
|
-
}
|
|
607
|
-
if (!run) {
|
|
608
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
609
|
-
}
|
|
610
|
-
if (run.status !== 'running' || run.pendingDelegation !== null) {
|
|
611
|
-
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
|
|
612
|
-
}
|
|
613
|
-
const capture = options.captureRepository ?? captureRepositorySnapshot;
|
|
614
|
-
let before;
|
|
615
|
-
try {
|
|
616
|
-
before = capture(gitRoot);
|
|
617
|
-
}
|
|
618
|
-
catch (error) {
|
|
619
|
-
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
620
|
-
}
|
|
621
|
-
if (before.headTree !== before.worktreeTree) {
|
|
622
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
623
|
-
}
|
|
624
|
-
const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
|
|
625
|
-
if (before.head !== lastReviewedCommit) {
|
|
626
|
-
return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
|
|
627
|
-
}
|
|
628
|
-
const diffBaseError = validateSavedCurrentDiffBase(resolved.repoRoot, metadata, resolved.taskDir, lastReviewedCommit);
|
|
629
|
-
if (diffBaseError)
|
|
630
|
-
return failed(diffBaseError.code, diffBaseError.message, resolved.taskId);
|
|
631
|
-
let after;
|
|
632
|
-
try {
|
|
633
|
-
after = capture(gitRoot);
|
|
634
|
-
}
|
|
635
|
-
catch (error) {
|
|
636
|
-
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
637
|
-
}
|
|
638
|
-
if (after.headTree !== after.worktreeTree) {
|
|
639
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
640
|
-
}
|
|
641
|
-
if (after.head !== before.head || after.headTree !== before.headTree) {
|
|
642
|
-
return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
|
|
643
|
-
}
|
|
644
|
-
const observedAt = (options.now ?? (() => new Date().toISOString()))();
|
|
645
|
-
const completionEvidence = {
|
|
646
|
-
kind: 'reviewed-head-clean',
|
|
647
|
-
observedAt,
|
|
648
|
-
head: after.head,
|
|
649
|
-
headTree: after.headTree,
|
|
650
|
-
worktreeTree: after.worktreeTree,
|
|
651
|
-
lastReviewedCommit,
|
|
652
|
-
prNumber: null,
|
|
653
|
-
prHead: null
|
|
654
|
-
};
|
|
655
|
-
const completed = withUpdatedRun(run, {
|
|
656
|
-
status: 'completed',
|
|
657
|
-
nextStage: null,
|
|
658
|
-
pause: null,
|
|
659
|
-
completionEvidence
|
|
660
|
-
});
|
|
661
|
-
saveRun(resolved.taskDir, completed);
|
|
662
|
-
return { status: 'completed', changed: true, taskId: resolved.taskId, run: completed, next: null, error: null };
|
|
663
|
-
}
|
|
664
|
-
const action = routed.stage;
|
|
665
|
-
const family = action === 'analysis' ? 'analysis' : action;
|
|
666
|
-
const artifactContext = resolveArtifactContextForResolvedTask(resolved, family, {
|
|
667
|
-
repoRoot: resolved.repoRoot
|
|
668
|
-
});
|
|
669
|
-
if (artifactContext.status !== 'ready' || !artifactContext.selection) {
|
|
670
|
-
return failed(artifactContext.error?.code ?? 'ORCHESTRATION_ARTIFACT_SELECTION_FAILED', artifactContext.error?.message ?? 'cannot determine artifact selection', resolved.taskId);
|
|
671
|
-
}
|
|
672
|
-
if (artifactContext.selection.disposition === 'reuse') {
|
|
673
|
-
return failed('ORCHESTRATION_REUSE_STALLED', `lifecycle still routes to ${action} after reusing ${artifactContext.selection.artifact.name}`, resolved.taskId);
|
|
674
|
-
}
|
|
675
|
-
const selected = artifactContext.selection.artifact;
|
|
676
|
-
const capability = canStart(action, facts.facts, {
|
|
677
|
-
initiator: 'orchestrator', requestId: `orchestration:${resolved.taskId}:${routed.stage}:${selected.round}`,
|
|
678
|
-
requestedAction: action, reasonCode: 'user-request', explicitRequest: true
|
|
679
|
-
});
|
|
680
|
-
if (!capability.allowed)
|
|
681
|
-
return failed('ORCHESTRATION_CAPABILITY_DENIED', `${capability.reasonCode}: ${capability.evidence.join(', ')}`, resolved.taskId);
|
|
682
|
-
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
683
|
-
const next = {
|
|
684
|
-
...routed,
|
|
685
|
-
round: selected.round,
|
|
686
|
-
artifact: selected.name,
|
|
687
|
-
requestedModel: policy?.model ?? null,
|
|
688
|
-
requestedReasoningEffort: policy?.reasoningEffort ?? null
|
|
689
|
-
};
|
|
690
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
691
|
-
}
|
|
692
|
-
function routeOrchestration(taskRef, options = {}) {
|
|
693
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
694
|
-
if (!resolved.ok)
|
|
695
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
696
|
-
return routeResolvedTask(taskRef, resolved, options);
|
|
697
|
-
}
|
|
698
|
-
function statusOrchestration(taskRef, options = {}) {
|
|
699
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
700
|
-
if (!resolved.ok)
|
|
701
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
702
|
-
const run = readRun(resolved.taskDir, options);
|
|
703
|
-
if (!run)
|
|
704
|
-
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
705
|
-
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
706
|
-
}
|
|
707
|
-
function validatePreparedModelPolicy(taskId, run, next, input) {
|
|
708
|
-
if (!validModel(input.requestedModel)) {
|
|
709
|
-
return failed('ORCHESTRATION_REQUESTED_MODEL_REQUIRED', 'prepare requires the exact requested model identity', taskId);
|
|
710
|
-
}
|
|
711
|
-
if (!validModel(input.requestedReasoningEffort)) {
|
|
712
|
-
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', taskId);
|
|
713
|
-
}
|
|
714
|
-
const expectedPolicy = run.modelPolicy[next.role];
|
|
715
|
-
if (input.requestedModel !== expectedPolicy.model) {
|
|
716
|
-
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, taskId);
|
|
717
|
-
}
|
|
718
|
-
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
719
|
-
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, taskId);
|
|
720
|
-
}
|
|
721
|
-
return null;
|
|
722
|
-
}
|
|
723
|
-
function captureTaskSnapshot(taskId, repoRoot, options) {
|
|
724
|
-
return (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
725
|
-
gitRoot: gitRootFor(repoRoot, options),
|
|
726
|
-
stateRoot: repoRoot,
|
|
727
|
-
taskId
|
|
728
|
-
});
|
|
729
|
-
}
|
|
730
|
-
function validateDelegationRun(taskRef, taskId, run, options) {
|
|
731
|
-
if (!run || run.status !== 'running') {
|
|
732
|
-
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', taskId);
|
|
733
|
-
}
|
|
734
|
-
if (run.pendingDelegation)
|
|
735
|
-
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', taskId);
|
|
736
|
-
if (run.stepCount >= run.maxSteps) {
|
|
737
|
-
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
738
|
-
}
|
|
739
|
-
return null;
|
|
740
|
-
}
|
|
741
|
-
function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
742
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
743
|
-
if (!resolved.ok)
|
|
744
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
745
|
-
if (!(options.supportsLifecycleDelegation ?? supportsLifecycleDelegation)(input.client)) {
|
|
746
|
-
return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
|
|
747
|
-
}
|
|
748
|
-
emitDiagnostic(options, 'orchestration-prepare-start', {
|
|
749
|
-
taskId: resolved.taskId,
|
|
750
|
-
taskDir: resolved.taskDir,
|
|
751
|
-
repoRoot: resolved.repoRoot,
|
|
752
|
-
client: input.client,
|
|
753
|
-
requestedModel: input.requestedModel ?? null,
|
|
754
|
-
requestedReasoningEffort: input.requestedReasoningEffort ?? null
|
|
755
|
-
});
|
|
756
|
-
const run = readRun(resolved.taskDir, options);
|
|
757
|
-
const runFailure = validateDelegationRun(taskRef, resolved.taskId, run, options);
|
|
758
|
-
if (runFailure)
|
|
759
|
-
return runFailure;
|
|
760
|
-
const activeRun = run;
|
|
761
|
-
const routed = routeResolvedTask(taskRef, resolved, options);
|
|
762
|
-
if (!routed.next)
|
|
763
|
-
return routed;
|
|
764
|
-
const next = routed.next;
|
|
765
|
-
const policyFailure = validatePreparedModelPolicy(resolved.taskId, activeRun, next, input);
|
|
766
|
-
if (policyFailure)
|
|
767
|
-
return policyFailure;
|
|
768
|
-
let beforeFingerprint;
|
|
769
|
-
try {
|
|
770
|
-
beforeFingerprint = captureTaskSnapshot(resolved.taskId, resolved.repoRoot, options);
|
|
771
|
-
}
|
|
772
|
-
catch (error) {
|
|
773
|
-
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
774
|
-
}
|
|
775
|
-
const receipt = prepareDelegation({
|
|
776
|
-
taskId: resolved.taskId,
|
|
777
|
-
runId: activeRun.runId,
|
|
778
|
-
role: next.role,
|
|
779
|
-
stage: next.stage,
|
|
780
|
-
round: next.round,
|
|
781
|
-
artifact: next.artifact,
|
|
782
|
-
client: input.client,
|
|
783
|
-
requestedModel: input.requestedModel,
|
|
784
|
-
requestedReasoningEffort: input.requestedReasoningEffort,
|
|
785
|
-
workspaceSnapshotScope: 'task',
|
|
786
|
-
beforeFingerprint
|
|
787
|
-
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
788
|
-
const updated = withUpdatedRun(activeRun, {
|
|
789
|
-
nextStage: next.stage,
|
|
790
|
-
pendingDelegation: receipt
|
|
791
|
-
});
|
|
792
|
-
saveRun(resolved.taskDir, updated);
|
|
793
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
794
|
-
}
|
|
795
|
-
function dispatchOrchestrationDelegation(taskRef, options = {}) {
|
|
796
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
797
|
-
if (!resolved.ok)
|
|
798
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
799
|
-
const run = readRun(resolved.taskDir, options);
|
|
800
|
-
if (!run?.pendingDelegation)
|
|
801
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
802
|
-
if (run.status !== 'running') {
|
|
803
|
-
return failed('ORCHESTRATION_STATE_INVALID', 'spawn dispatch requires a running orchestration', resolved.taskId);
|
|
804
|
-
}
|
|
805
|
-
const result = dispatchDelegation(run.pendingDelegation, {
|
|
806
|
-
now: options.now,
|
|
807
|
-
monotonicNow: options.monotonicNow
|
|
808
|
-
});
|
|
809
|
-
if (!result.ok)
|
|
810
|
-
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
811
|
-
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
812
|
-
saveRun(resolved.taskDir, updated);
|
|
813
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
814
|
-
}
|
|
815
|
-
function matchingDelegations(predicate, options) {
|
|
816
|
-
const taskId = options.taskId;
|
|
817
|
-
if (taskId === undefined || taskId === '') {
|
|
818
|
-
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_REQUIRED', 'taskId is required for lifecycle matching') };
|
|
819
|
-
}
|
|
820
|
-
if (typeof taskId !== 'string' || !TASK_ID_RE.test(taskId)) {
|
|
821
|
-
return { ok: false, error: failed('ORCHESTRATION_TASK_ID_INVALID', 'taskId must be a canonical TASK-id') };
|
|
822
|
-
}
|
|
823
|
-
const repoRoot = options.repoRoot ?? process.cwd();
|
|
824
|
-
const taskDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
|
|
825
|
-
const run = readRun(taskDir, options);
|
|
826
|
-
const matches = run && run.status === 'running' && run.pendingDelegation && predicate(run.pendingDelegation)
|
|
827
|
-
? [{ taskId, run }]
|
|
828
|
-
: [];
|
|
829
|
-
return { ok: true, matches };
|
|
830
|
-
}
|
|
831
|
-
function uniqueMatchingDelegation(client, predicate, options) {
|
|
832
|
-
const lookup = matchingDelegations((receipt) => receipt.client === client && predicate(receipt), options);
|
|
833
|
-
if (!lookup.ok)
|
|
834
|
-
return lookup.error;
|
|
835
|
-
const matches = lookup.matches;
|
|
836
|
-
if (matches.length === 0)
|
|
837
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
838
|
-
return matches[0];
|
|
839
|
-
}
|
|
840
|
-
function hasActivatableOrchestrationDelegation(client, childId, options = {}) {
|
|
841
|
-
const lookup = matchingDelegations((receipt) => receipt.client === client && (receipt.status === 'prepared'
|
|
842
|
-
|| (receipt.childId === childId && ['activated', 'stage-completed'].includes(receipt.status))), options);
|
|
843
|
-
return lookup.ok && lookup.matches.length > 0;
|
|
844
|
-
}
|
|
845
|
-
function hasSealableOrchestrationDelegation(client, childId, options = {}) {
|
|
846
|
-
const lookup = matchingDelegations((receipt) => receipt.client === client
|
|
847
|
-
&& receipt.childId === childId
|
|
848
|
-
&& ['stage-completed', 'sealed'].includes(receipt.status), options);
|
|
849
|
-
return lookup.ok && lookup.matches.length > 0;
|
|
850
|
-
}
|
|
851
|
-
function activateMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
852
|
-
const role = managedDelegationRole(event.nativeAgent);
|
|
853
|
-
if (!role)
|
|
854
|
-
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
855
|
-
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status === 'prepared', options);
|
|
856
|
-
if ('status' in matched) {
|
|
857
|
-
if (matched.error?.code !== 'ORCHESTRATION_DELEGATION_MISSING')
|
|
858
|
-
return matched;
|
|
859
|
-
const replay = uniqueMatchingDelegation(client, (receipt) => receipt.childId === event.childId && ['activated', 'stage-completed'].includes(receipt.status), options);
|
|
860
|
-
if ('status' in replay)
|
|
861
|
-
return matched;
|
|
862
|
-
const receipt = replay.run.pendingDelegation;
|
|
863
|
-
const sameEvidence = receipt.parentId === event.parentId
|
|
864
|
-
&& (receipt.spawnMode ?? null) === (event.spawnMode ?? null)
|
|
865
|
-
&& (receipt.actualModel ?? null) === foldBlankToNull(event.actualModel)
|
|
866
|
-
&& (receipt.actualReasoningEffort ?? null) === foldBlankToNull(event.actualReasoningEffort)
|
|
867
|
-
&& receipt.modelFallbackReason === (event.modelFallbackReason ?? null)
|
|
868
|
-
&& receipt.reasoningEffortFallbackReason === (event.reasoningEffortFallbackReason ?? null)
|
|
869
|
-
&& (event.clientEvidence === undefined
|
|
870
|
-
|| JSON.stringify(receipt.adapterEvidence?.[client]) === JSON.stringify(event.clientEvidence));
|
|
871
|
-
if (!sameEvidence) {
|
|
872
|
-
return pauseOrchestration(replay.taskId, 'DELEGATION_REPLAY_CONFLICT', 'replayed native start evidence conflicts with the active receipt', true, options);
|
|
873
|
-
}
|
|
874
|
-
return { status: replay.run.status, changed: false, taskId: replay.taskId, run: replay.run, next: null, error: null };
|
|
875
|
-
}
|
|
876
|
-
if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
|
|
877
|
-
return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
|
|
878
|
-
}
|
|
879
|
-
return activateOrchestrationDelegation(matched.taskId, event, options);
|
|
880
|
-
}
|
|
881
|
-
function pauseMatchingOrchestrationDelegation(client, code, message, options = {}) {
|
|
882
|
-
const matched = uniqueMatchingDelegation(client, () => true, options);
|
|
883
|
-
if ('status' in matched)
|
|
884
|
-
return matched;
|
|
885
|
-
return pauseOrchestration(matched.taskId, code, message, true, options);
|
|
886
|
-
}
|
|
887
|
-
function sealMatchingOrchestrationDelegation(client, event, options = {}, beforeSeal) {
|
|
888
|
-
const role = managedDelegationRole(event.nativeAgent);
|
|
889
|
-
if (!role)
|
|
890
|
-
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
891
|
-
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status !== 'prepared', options);
|
|
892
|
-
if ('status' in matched)
|
|
893
|
-
return matched;
|
|
894
|
-
const receipt = matched.run.pendingDelegation;
|
|
895
|
-
if (receipt.role !== role) {
|
|
896
|
-
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
897
|
-
}
|
|
898
|
-
const repoRoot = options.repoRoot ?? process.cwd();
|
|
899
|
-
try {
|
|
900
|
-
const gitRoot = gitRootFor(repoRoot, options);
|
|
901
|
-
const snapshotTaskId = receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null;
|
|
902
|
-
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
903
|
-
gitRoot,
|
|
904
|
-
stateRoot: repoRoot,
|
|
905
|
-
taskId: snapshotTaskId
|
|
906
|
-
});
|
|
907
|
-
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(gitRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
908
|
-
return sealOrchestrationDelegation(matched.taskId, {
|
|
909
|
-
childId: event.childId,
|
|
910
|
-
exitCode: 0,
|
|
911
|
-
afterFingerprint,
|
|
912
|
-
changedPaths,
|
|
913
|
-
actualModel: event.actualModel,
|
|
914
|
-
actualReasoningEffort: event.actualReasoningEffort,
|
|
915
|
-
modelFallbackReason: event.modelFallbackReason,
|
|
916
|
-
reasoningEffortFallbackReason: event.reasoningEffortFallbackReason
|
|
917
|
-
}, { runId: receipt.runId, receiptId: receipt.id }, options, beforeSeal);
|
|
918
|
-
}
|
|
919
|
-
catch (error) {
|
|
920
|
-
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), true, options);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
function reconcileMatchingOrchestrationDelegation(client, childId, options = {}) {
|
|
924
|
-
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.childId === childId, options);
|
|
925
|
-
if ('status' in matched)
|
|
926
|
-
return matched;
|
|
927
|
-
const receipt = matched.run.pendingDelegation;
|
|
928
|
-
if (receipt.status !== 'sealed' || receipt.childId !== childId) {
|
|
929
|
-
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_DELEGATION_RECONCILIATION_FAILED', 'native child did not produce one sealed delegation receipt', true, options);
|
|
930
|
-
}
|
|
931
|
-
return { status: matched.run.status, changed: false, taskId: matched.taskId, run: matched.run, next: null, error: null };
|
|
932
|
-
}
|
|
933
|
-
function activateOrchestrationDelegation(taskRef, event, options = {}) {
|
|
934
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
935
|
-
if (!resolved.ok)
|
|
936
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
937
|
-
const run = readRun(resolved.taskDir, options);
|
|
938
|
-
if (!run?.pendingDelegation)
|
|
939
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
940
|
-
if (run.status !== 'running') {
|
|
941
|
-
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
942
|
-
}
|
|
943
|
-
const validateAdapterEvidence = getAgentClientAdapter(run.pendingDelegation.client)
|
|
944
|
-
.orchestrationAdapter?.validateActivationEvidence;
|
|
945
|
-
if (validateAdapterEvidence && !validateAdapterEvidence(event.clientEvidence, event.parentId)) {
|
|
946
|
-
return pauseOrchestration(taskRef, 'DELEGATION_ADAPTER_EVIDENCE_REQUIRED', 'native activation lacks valid evidence from the agent adapter', true, options);
|
|
947
|
-
}
|
|
948
|
-
const result = activateDelegation(run.pendingDelegation, event, {
|
|
949
|
-
now: options.now,
|
|
950
|
-
monotonicNow: options.monotonicNow,
|
|
951
|
-
evidencePolicy: getAgentClientDelegationEvidence(run.pendingDelegation.client)
|
|
952
|
-
});
|
|
953
|
-
if (!result.ok) {
|
|
954
|
-
if (result.code === 'DELEGATION_IGNORED')
|
|
955
|
-
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
956
|
-
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
957
|
-
}
|
|
958
|
-
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
959
|
-
saveRun(resolved.taskDir, updated);
|
|
960
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
961
|
-
}
|
|
962
|
-
async function awaitOrchestrationDelegationActivation(taskRef, event, options = {}) {
|
|
963
|
-
const sleep = options.sleep
|
|
964
|
-
?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
965
|
-
while (true) {
|
|
966
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
967
|
-
if (!resolved.ok)
|
|
968
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
969
|
-
const run = readRun(resolved.taskDir, options);
|
|
970
|
-
if (!run?.pendingDelegation) {
|
|
971
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
972
|
-
}
|
|
973
|
-
const receipt = run.pendingDelegation;
|
|
974
|
-
const matches = receipt.stage === event.stage
|
|
975
|
-
&& receipt.round === event.round
|
|
976
|
-
&& receipt.artifact === event.artifact
|
|
977
|
-
&& receipt.role === event.role;
|
|
978
|
-
if (!matches) {
|
|
979
|
-
return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'activation barrier identity does not match the pending delegation', true, options);
|
|
980
|
-
}
|
|
981
|
-
if (receipt.status === 'activated' || receipt.status === 'stage-completed') {
|
|
982
|
-
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
983
|
-
}
|
|
984
|
-
if (run.status !== 'running') {
|
|
985
|
-
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
986
|
-
}
|
|
987
|
-
if (receipt.status !== 'prepared') {
|
|
988
|
-
return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_STATE_INVALID', `activation barrier found receipt status '${receipt.status}'`, true, options);
|
|
989
|
-
}
|
|
990
|
-
if (receipt.activationDeadlineAt == null
|
|
991
|
-
|| !Number.isFinite(Date.parse(receipt.activationDeadlineAt))) {
|
|
992
|
-
return pauseOrchestration(taskRef, 'ORCHESTRATION_DELEGATION_NOT_DISPATCHED', 'activation barrier was entered before the native spawn dispatch boundary', true, options);
|
|
993
|
-
}
|
|
994
|
-
if (Date.parse((options.now ?? (() => new Date().toISOString()))()) >= Date.parse(receipt.activationDeadlineAt)) {
|
|
995
|
-
return pauseOrchestration(taskRef, 'ORCHESTRATION_ACTIVATION_TIMEOUT', 'fresh child did not receive trusted activation evidence before the deadline', true, options);
|
|
996
|
-
}
|
|
997
|
-
await sleep(100);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
|
|
1001
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1002
|
-
if (!resolved.ok)
|
|
1003
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1004
|
-
try {
|
|
1005
|
-
return (() => {
|
|
1006
|
-
const run = readRun(resolved.taskDir, options);
|
|
1007
|
-
const receipt = run?.pendingDelegation;
|
|
1008
|
-
if (!run || !receipt) {
|
|
1009
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1010
|
-
}
|
|
1011
|
-
if (receipt.status !== 'prepared') {
|
|
1012
|
-
return failed('ORCHESTRATION_PREPARED_RECOVERY_UNSAFE', 'only a never-activated prepared receipt can recover', resolved.taskId);
|
|
1013
|
-
}
|
|
1014
|
-
const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
|
|
1015
|
-
if (receipt.activationDeadlineAt != null
|
|
1016
|
-
&& Number.isFinite(Date.parse(receipt.activationDeadlineAt))
|
|
1017
|
-
&& wallNow <= Date.parse(receipt.activationDeadlineAt)) {
|
|
1018
|
-
return failed('ORCHESTRATION_PREPARED_RECOVERY_EARLY', 'prepared receipt activation deadline has not elapsed', resolved.taskId);
|
|
1019
|
-
}
|
|
1020
|
-
const activeAdapterEvidence = options.hasActiveLifecycleEvidence?.(receipt)
|
|
1021
|
-
?? Object.keys(receipt.adapterEvidence ?? {}).length > 0;
|
|
1022
|
-
if (activeAdapterEvidence) {
|
|
1023
|
-
return failed('ORCHESTRATION_PREPARED_RECOVERY_ACTIVE_EVIDENCE', 'active agent adapter evidence blocks prepared receipt recovery', resolved.taskId);
|
|
1024
|
-
}
|
|
1025
|
-
const current = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
1026
|
-
gitRoot: gitRootFor(resolved.repoRoot, options),
|
|
1027
|
-
stateRoot: resolved.repoRoot,
|
|
1028
|
-
taskId: receipt.workspaceSnapshotScope === 'task' ? receipt.taskId : null
|
|
1029
|
-
});
|
|
1030
|
-
if (current !== receipt.beforeFingerprint) {
|
|
1031
|
-
return failed('ORCHESTRATION_PREPARED_RECOVERY_DRIFT', 'workspace changed after delegation preparation', resolved.taskId);
|
|
1032
|
-
}
|
|
1033
|
-
const aborted = abortPreparedDelegation(receipt);
|
|
1034
|
-
if (!aborted.ok)
|
|
1035
|
-
return failed(aborted.code, aborted.message, resolved.taskId);
|
|
1036
|
-
const updated = withUpdatedRun(run, {
|
|
1037
|
-
status: 'running',
|
|
1038
|
-
pause: null,
|
|
1039
|
-
nextStage: null,
|
|
1040
|
-
pendingDelegation: null,
|
|
1041
|
-
receipts: Object.freeze([...run.receipts, aborted.receipt])
|
|
1042
|
-
}, options.now);
|
|
1043
|
-
saveRun(resolved.taskDir, updated);
|
|
1044
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1045
|
-
})();
|
|
1046
|
-
}
|
|
1047
|
-
catch (error) {
|
|
1048
|
-
return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
function recoveryReceiptMatches(receipt, event) {
|
|
1052
|
-
return receipt.stage === event.stage
|
|
1053
|
-
&& receipt.round === event.round
|
|
1054
|
-
&& receipt.artifact === event.artifact
|
|
1055
|
-
&& receipt.childId === event.childId
|
|
1056
|
-
&& normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
|
|
1057
|
-
}
|
|
1058
|
-
function recoverActivatedOrchestrationDelegation(taskRef, event, options = {}) {
|
|
1059
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1060
|
-
if (!resolved.ok)
|
|
1061
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1062
|
-
const run = readRun(resolved.taskDir, options);
|
|
1063
|
-
if (!run)
|
|
1064
|
-
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
1065
|
-
const matches = [
|
|
1066
|
-
...(run.pendingDelegation ? [run.pendingDelegation] : []),
|
|
1067
|
-
...run.receipts.filter((receipt) => receipt.id === event.receiptId)
|
|
1068
|
-
];
|
|
1069
|
-
const receipt = matches.find((candidate) => candidate.id === event.receiptId);
|
|
1070
|
-
if (!receipt)
|
|
1071
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
|
|
1072
|
-
if (!recoveryReceiptMatches(receipt, event)) {
|
|
1073
|
-
return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
|
|
1074
|
-
}
|
|
1075
|
-
if (receipt.status === 'aborted' && run.pendingDelegation === null) {
|
|
1076
|
-
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1077
|
-
}
|
|
1078
|
-
if (run.pendingDelegation?.id !== receipt.id || receipt.status !== 'activated') {
|
|
1079
|
-
return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'recovery requires one matching activated pending delegation', resolved.taskId);
|
|
1080
|
-
}
|
|
1081
|
-
if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
|
|
1082
|
-
return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
|
|
1083
|
-
}
|
|
1084
|
-
if (run.status !== 'running' || run.pause !== null) {
|
|
1085
|
-
return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'activated recovery cannot replace an unrelated orchestration pause', resolved.taskId);
|
|
1086
|
-
}
|
|
1087
|
-
const aborted = abortActivatedDelegation(receipt, {
|
|
1088
|
-
childId: event.childId,
|
|
1089
|
-
clientEvidence: event.clientEvidence
|
|
1090
|
-
});
|
|
1091
|
-
if (!aborted.ok)
|
|
1092
|
-
return failed(aborted.code, aborted.message, resolved.taskId);
|
|
1093
|
-
const updated = withUpdatedRun(run, {
|
|
1094
|
-
status: 'running',
|
|
1095
|
-
pause: null,
|
|
1096
|
-
nextStage: null,
|
|
1097
|
-
pendingDelegation: null,
|
|
1098
|
-
receipts: Object.freeze([...run.receipts, aborted.receipt])
|
|
1099
|
-
}, options.now);
|
|
1100
|
-
saveRun(resolved.taskDir, updated);
|
|
1101
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1102
|
-
}
|
|
1103
|
-
function completeOrchestrationStage(taskRef, event, options = {}) {
|
|
1104
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1105
|
-
if (!resolved.ok)
|
|
1106
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1107
|
-
const run = readRun(resolved.taskDir, options);
|
|
1108
|
-
if (!run)
|
|
1109
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
1110
|
-
if (!run.pendingDelegation)
|
|
1111
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
|
|
1112
|
-
const result = completeDelegationStage(run.pendingDelegation, event);
|
|
1113
|
-
if (!result.ok)
|
|
1114
|
-
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1115
|
-
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
1116
|
-
saveRun(resolved.taskDir, updated);
|
|
1117
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1118
|
-
}
|
|
1119
|
-
function inspectOrchestrationStage(taskRef, event, options = {}) {
|
|
1120
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1121
|
-
if (!resolved.ok)
|
|
1122
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1123
|
-
let run;
|
|
1124
|
-
try {
|
|
1125
|
-
run = readRun(resolved.taskDir, options);
|
|
1126
|
-
}
|
|
1127
|
-
catch (error) {
|
|
1128
|
-
return failed('ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1129
|
-
}
|
|
1130
|
-
if (!run)
|
|
1131
|
-
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
1132
|
-
const receipt = run.pendingDelegation;
|
|
1133
|
-
if (!receipt
|
|
1134
|
-
|| receipt.status !== 'activated'
|
|
1135
|
-
|| receipt.stage !== event.stage
|
|
1136
|
-
|| receipt.round !== event.round
|
|
1137
|
-
|| receipt.artifact !== event.artifact
|
|
1138
|
-
|| receipt.role !== event.role) {
|
|
1139
|
-
return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'active run does not have one matching activated delegation', resolved.taskId);
|
|
1140
|
-
}
|
|
1141
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
1142
|
-
}
|
|
1143
|
-
function planOrchestrationStageCompletion(taskRef, event, options = {}) {
|
|
1144
|
-
const inspected = inspectOrchestrationStage(taskRef, event, options);
|
|
1145
|
-
if (inspected.status === 'failed' || !inspected.run?.pendingDelegation) {
|
|
1146
|
-
return { result: inspected, plan: null };
|
|
1147
|
-
}
|
|
1148
|
-
const receipt = inspected.run.pendingDelegation;
|
|
1149
|
-
const completed = completeDelegationStage(receipt, event);
|
|
1150
|
-
if (!completed.ok) {
|
|
1151
|
-
return { result: failed(completed.code, completed.message, inspected.taskId), plan: null };
|
|
1152
|
-
}
|
|
1153
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1154
|
-
if (!resolved.ok)
|
|
1155
|
-
return { result: failed(resolved.code, resolved.message, resolved.taskId), plan: null };
|
|
1156
|
-
const updatedRun = withUpdatedRun(inspected.run, { pendingDelegation: completed.receipt }, options.now);
|
|
1157
|
-
return {
|
|
1158
|
-
result: { status: 'running', changed: true, taskId: resolved.taskId, run: updatedRun, next: null, error: null },
|
|
1159
|
-
plan: {
|
|
1160
|
-
taskId: resolved.taskId,
|
|
1161
|
-
taskDir: resolved.taskDir,
|
|
1162
|
-
updatedRun
|
|
1163
|
-
}
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
function commitOrchestrationStageCompletion(plan) {
|
|
1167
|
-
saveRun(plan.taskDir, plan.updatedRun);
|
|
1168
|
-
}
|
|
1169
|
-
function sealOrchestrationDelegation(taskRef, event, expected, options = {}, beforeSeal) {
|
|
1170
|
-
const orchestrationOptions = options;
|
|
1171
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: orchestrationOptions.repoRoot });
|
|
1172
|
-
if (!resolved.ok)
|
|
1173
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1174
|
-
return (() => {
|
|
1175
|
-
const run = readRun(resolved.taskDir, orchestrationOptions);
|
|
1176
|
-
if (!run?.pendingDelegation)
|
|
1177
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1178
|
-
if (expected && (run.runId !== expected.runId || run.pendingDelegation.id !== expected.receiptId)) {
|
|
1179
|
-
return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed while collecting completion evidence', resolved.taskId);
|
|
1180
|
-
}
|
|
1181
|
-
const preSealFailure = beforeSeal?.();
|
|
1182
|
-
if (preSealFailure)
|
|
1183
|
-
return preSealFailure;
|
|
1184
|
-
const current = beforeSeal ? readRun(resolved.taskDir, orchestrationOptions) : run;
|
|
1185
|
-
if (!current?.pendingDelegation)
|
|
1186
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1187
|
-
if (expected && (current.runId !== expected.runId || current.pendingDelegation.id !== expected.receiptId)) {
|
|
1188
|
-
return failed('ORCHESTRATION_DELEGATION_CHANGED', 'pending delegation changed before it was sealed', resolved.taskId);
|
|
1189
|
-
}
|
|
1190
|
-
const result = sealDelegation(current.pendingDelegation, event, { now: orchestrationOptions.now });
|
|
1191
|
-
if (!result.ok)
|
|
1192
|
-
return pauseOrchestration(taskRef, result.code, result.message, true, orchestrationOptions);
|
|
1193
|
-
const updated = withUpdatedRun(current, { pendingDelegation: result.receipt });
|
|
1194
|
-
saveRun(resolved.taskDir, updated);
|
|
1195
|
-
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1196
|
-
})();
|
|
1197
|
-
}
|
|
1198
|
-
function advanceOrchestration(taskRef, options = {}) {
|
|
1199
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1200
|
-
if (!resolved.ok)
|
|
1201
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1202
|
-
const run = readRun(resolved.taskDir, options);
|
|
1203
|
-
if (!run?.pendingDelegation)
|
|
1204
|
-
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1205
|
-
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1206
|
-
if (!result.ok)
|
|
1207
|
-
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1208
|
-
if (result.receipt.stage === 'commit') {
|
|
1209
|
-
return failed('ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', resolved.taskId);
|
|
1210
|
-
}
|
|
1211
|
-
const updated = withUpdatedRun(run, {
|
|
1212
|
-
status: 'running',
|
|
1213
|
-
nextStage: null,
|
|
1214
|
-
stepCount: run.stepCount + 1,
|
|
1215
|
-
pendingDelegation: null,
|
|
1216
|
-
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1217
|
-
});
|
|
1218
|
-
saveRun(resolved.taskDir, updated);
|
|
1219
|
-
return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1220
|
-
}
|
|
1221
|
-
function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
|
|
1222
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1223
|
-
if (!resolved.ok)
|
|
1224
|
-
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
1225
|
-
const run = readRun(resolved.taskDir, options);
|
|
1226
|
-
if (!run)
|
|
1227
|
-
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
1228
|
-
const updated = withUpdatedRun(run, { status: 'paused', pause: { code, message, recoverable } });
|
|
1229
|
-
saveRun(resolved.taskDir, updated);
|
|
1230
|
-
return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
1231
|
-
}
|
|
1232
|
-
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, readDelegationAdapterEvidence, mutateDelegationAdapterEvidence, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, };
|
|
1233
|
-
//# sourceMappingURL=orchestration.js.map
|