@fitlab-ai/agent-infra 0.9.14 → 0.10.0
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 +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isCompletionEvidence } from './orchestration.ts';
|
|
3
|
+
import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.ts';
|
|
4
|
+
import { sameRecoveryWarning } from './recovery-warning.ts';
|
|
5
|
+
|
|
6
|
+
export type ControlRecoveryOutcome = 'not-executed' | 'in-progress' | 'success' | 'failure' | 'unknown' | 'rejected';
|
|
7
|
+
|
|
8
|
+
/** Normalize the command result once at the control transport boundary. */
|
|
9
|
+
export function parseControlOutput(output: string | null): Record<string, unknown> | null {
|
|
10
|
+
if (output === null) return null;
|
|
11
|
+
try {
|
|
12
|
+
const value = JSON.parse(output);
|
|
13
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
14
|
+
return value.result && typeof value.result === 'object' && !Array.isArray(value.result) ? value.result : value;
|
|
15
|
+
} catch { return null; }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type ControlRecoveryOperation = Readonly<{
|
|
19
|
+
family: 'task-lifecycle' | 'task-finalization' | 'task-orchestration' | 'task-create' | 'codex-controller' | 'task-workflow';
|
|
20
|
+
intent: string;
|
|
21
|
+
class: string;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
export type ControlRecoveryBinding = Readonly<{
|
|
25
|
+
requestId: string;
|
|
26
|
+
generation: string;
|
|
27
|
+
taskId: string | null;
|
|
28
|
+
intentDigest: string;
|
|
29
|
+
}>;
|
|
30
|
+
|
|
31
|
+
export type ControlRecoveryInput = Readonly<{
|
|
32
|
+
operation: ControlRecoveryOperation;
|
|
33
|
+
binding: ControlRecoveryBinding;
|
|
34
|
+
startedCommitted: boolean;
|
|
35
|
+
terminalResult?: Readonly<Record<string, unknown>> | null;
|
|
36
|
+
criticalPhases?: readonly string[];
|
|
37
|
+
domain?: Readonly<Record<string, unknown>> | null;
|
|
38
|
+
journal?: Readonly<{
|
|
39
|
+
exists: boolean;
|
|
40
|
+
completedSteps?: readonly string[];
|
|
41
|
+
failure?: string | null;
|
|
42
|
+
}> | null;
|
|
43
|
+
explicitRejection?: boolean;
|
|
44
|
+
evidenceConflict?: boolean;
|
|
45
|
+
}>;
|
|
46
|
+
|
|
47
|
+
export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
|
|
48
|
+
'completed', 'evidence-written', 'publish-authorized'
|
|
49
|
+
] as const);
|
|
50
|
+
|
|
51
|
+
export type ControlRecoveryDecision = Readonly<{
|
|
52
|
+
outcome: ControlRecoveryOutcome;
|
|
53
|
+
responseReconstructable: boolean;
|
|
54
|
+
reasonCode: string;
|
|
55
|
+
}>;
|
|
56
|
+
|
|
57
|
+
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'] as const;
|
|
58
|
+
const ORCHESTRATION_INTENTS = [
|
|
59
|
+
'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
|
|
60
|
+
'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export const SANDBOX_CONTROL_RECOVERY_OPERATIONS: readonly ControlRecoveryOperation[] = Object.freeze([
|
|
64
|
+
...LIFECYCLE_INTENTS.map((intent) => ({ family: 'task-lifecycle' as const, intent, class: 'lifecycle-mutation' })),
|
|
65
|
+
{ family: 'task-finalization', intent: 'complete', class: 'finalization' },
|
|
66
|
+
...ORCHESTRATION_INTENTS.map((intent) => ({
|
|
67
|
+
family: 'task-orchestration' as const,
|
|
68
|
+
intent,
|
|
69
|
+
class: intent === 'route.clean-completion' ? 'route.clean-completion' : intent === 'route.read' || intent === 'status' ? 'read-only' : 'orchestration'
|
|
70
|
+
})),
|
|
71
|
+
{ family: 'task-create', intent: 'create', class: 'task-create' },
|
|
72
|
+
...TASK_WORKFLOW_OPERATIONS.map((intent) => ({ family: 'task-workflow' as const, intent, class: intent === 'artifact-inspect' || intent === 'decision-next-id' ? 'read-only' : 'workflow' })),
|
|
73
|
+
...(['open', 'close', 'verify'] as const).map((intent) => ({ family: 'codex-controller' as const, intent, class: 'codex-controller' }))
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
export function digestControlRecoveryIntent(family: string, intent: string): string {
|
|
77
|
+
return createHash('sha256').update(`${family}\0${intent}`, 'utf8').digest('hex');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function findSandboxControlRecoveryOperation(family: ControlRecoveryOperation['family'], intent: string): ControlRecoveryOperation | null {
|
|
81
|
+
return SANDBOX_CONTROL_RECOVERY_OPERATIONS.find((operation) => operation.family === family && operation.intent === intent) ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function bindingMatchesResult(binding: ControlRecoveryBinding, result: Readonly<Record<string, unknown>>): boolean {
|
|
85
|
+
return result.requestId === binding.requestId
|
|
86
|
+
&& result.generation === binding.generation
|
|
87
|
+
&& (result.taskId === undefined || result.taskId === binding.taskId)
|
|
88
|
+
&& (result.intentDigest === undefined || result.intentDigest === binding.intentDigest);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function domainEvidenceMatches(
|
|
92
|
+
operation: ControlRecoveryOperation,
|
|
93
|
+
result: Readonly<Record<string, unknown>>,
|
|
94
|
+
domain: Readonly<Record<string, unknown>> | null | undefined
|
|
95
|
+
): boolean {
|
|
96
|
+
if (!domain) return false;
|
|
97
|
+
if (domain.consistent !== true) return false;
|
|
98
|
+
if (operation.class === 'route.clean-completion') {
|
|
99
|
+
const completion = domain.completionEvidence as Record<string, unknown> | undefined;
|
|
100
|
+
const snapshot = domain.snapshot as Record<string, unknown> | undefined;
|
|
101
|
+
const lastReviewedCommit = domain.lastReviewedCommit;
|
|
102
|
+
return result.status === 'completed'
|
|
103
|
+
&& domain.status === 'completed'
|
|
104
|
+
&& domain.pendingDelegation === null
|
|
105
|
+
&& isCompletionEvidence(completion)
|
|
106
|
+
&& snapshot?.head === completion.head
|
|
107
|
+
&& snapshot.headTree === completion.headTree
|
|
108
|
+
&& snapshot.worktreeTree === completion.worktreeTree
|
|
109
|
+
&& lastReviewedCommit === completion.lastReviewedCommit;
|
|
110
|
+
}
|
|
111
|
+
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
|
|
112
|
+
const retryRequired = domain.recoveryState === 'retry-required'
|
|
113
|
+
&& sameRecoveryWarning(domain.warning, result.warning);
|
|
114
|
+
return result.targetState === 'active'
|
|
115
|
+
&& ['applied', 'no-op'].includes(String(result.status))
|
|
116
|
+
&& domain.recovery === true
|
|
117
|
+
&& domain.targetState === 'active'
|
|
118
|
+
&& (domain.recoveryState === 'released' || retryRequired);
|
|
119
|
+
}
|
|
120
|
+
if (operation.class === 'read-only') return result.changed === false && domain.snapshotValid === true;
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function classifySandboxControlRecovery(input: ControlRecoveryInput): ControlRecoveryDecision {
|
|
125
|
+
const result = input.terminalResult ?? null;
|
|
126
|
+
if (input.evidenceConflict) return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_EVIDENCE_CONFLICT' };
|
|
127
|
+
if (input.explicitRejection && !input.startedCommitted) {
|
|
128
|
+
return { outcome: 'not-executed', responseReconstructable: true, reasonCode: 'RECOVERY_EXPLICIT_REJECTION' };
|
|
129
|
+
}
|
|
130
|
+
if (!input.startedCommitted) {
|
|
131
|
+
return { outcome: 'not-executed', responseReconstructable: false, reasonCode: 'RECOVERY_NOT_STARTED' };
|
|
132
|
+
}
|
|
133
|
+
if (!result) return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_TERMINAL_RESULT_MISSING' };
|
|
134
|
+
if (!bindingMatchesResult(input.binding, result)) {
|
|
135
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_RESULT_BINDING_MISMATCH' };
|
|
136
|
+
}
|
|
137
|
+
if (!SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.every((phase) => input.criticalPhases?.includes(phase))) {
|
|
138
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_CRITICAL_AUDIT_INCOMPLETE' };
|
|
139
|
+
}
|
|
140
|
+
const status = result.status;
|
|
141
|
+
const failed = status === 'failed' || result.error !== null && result.error !== undefined;
|
|
142
|
+
if (failed) {
|
|
143
|
+
if (input.domain && input.domain.consistent === false) {
|
|
144
|
+
return { outcome: 'failure', responseReconstructable: true, reasonCode: 'RECOVERY_TERMINAL_FAILURE' };
|
|
145
|
+
}
|
|
146
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_FAILURE_EVIDENCE_INCOMPLETE' };
|
|
147
|
+
}
|
|
148
|
+
if (result.changed === null) {
|
|
149
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_RESULT_CHANGE_FLAG_MISSING' };
|
|
150
|
+
}
|
|
151
|
+
if (!domainEvidenceMatches(input.operation, result, input.domain)) {
|
|
152
|
+
if (input.journal?.exists && (input.journal.completedSteps?.length ?? 0) > 0) {
|
|
153
|
+
return { outcome: 'in-progress', responseReconstructable: false, reasonCode: 'RECOVERY_DOMAIN_EVIDENCE_INCOMPLETE' };
|
|
154
|
+
}
|
|
155
|
+
return { outcome: 'unknown', responseReconstructable: false, reasonCode: 'RECOVERY_DOMAIN_EVIDENCE_MISSING' };
|
|
156
|
+
}
|
|
157
|
+
if (input.operation.family === 'task-lifecycle' && input.journal?.exists) {
|
|
158
|
+
if (input.journal.failure) {
|
|
159
|
+
return { outcome: 'failure', responseReconstructable: true, reasonCode: 'RECOVERY_LIFECYCLE_JOURNAL_FAILURE' };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
outcome: input.journal.completedSteps && input.journal.completedSteps.length > 0 ? 'in-progress' : 'unknown',
|
|
163
|
+
responseReconstructable: false,
|
|
164
|
+
reasonCode: 'RECOVERY_LIFECYCLE_JOURNAL_PRESENT'
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
outcome: 'success',
|
|
169
|
+
responseReconstructable: true,
|
|
170
|
+
reasonCode: input.operation.class === 'route.clean-completion' && result.changed === false
|
|
171
|
+
? 'RECOVERY_ROUTE_COMPLETION_NOOP'
|
|
172
|
+
: input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
|
|
173
|
+
? 'RECOVERY_RELEASE_RETRY_REQUIRED'
|
|
174
|
+
: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function operationRecoveryBinding(
|
|
179
|
+
requestId: string,
|
|
180
|
+
generation: string,
|
|
181
|
+
taskId: string | null,
|
|
182
|
+
family: string,
|
|
183
|
+
intent: string
|
|
184
|
+
): ControlRecoveryBinding {
|
|
185
|
+
return { requestId, generation, taskId, intentDigest: digestControlRecoveryIntent(family, intent) };
|
|
186
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
1
|
import { syncPlatformComment } from '../platform/issue-comments.ts';
|
|
5
2
|
import { createPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
|
|
6
3
|
import type { PlatformResult } from '../platform/types.ts';
|
|
@@ -190,12 +187,6 @@ function platformOperations(prefix: string, operations: readonly { name: string;
|
|
|
190
187
|
}));
|
|
191
188
|
}
|
|
192
189
|
|
|
193
|
-
function issueFromTask(repoRoot: string, taskId: string): { number: number; url: string } | null {
|
|
194
|
-
const content = fs.readFileSync(path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md'), 'utf8');
|
|
195
|
-
const match = /^issue_number:\s*(\d+)\s*$/m.exec(content);
|
|
196
|
-
return match ? { number: Number(match[1]), url: '' } : null;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
190
|
async function createTask(value: unknown, options: CreateTaskOptions): Promise<TaskCreateResult> {
|
|
200
191
|
const dependencies = { ...DEFAULT_DEPENDENCIES, ...options.dependencies };
|
|
201
192
|
let candidate: TaskCreateCandidateV1;
|
|
@@ -215,7 +206,7 @@ async function createTask(value: unknown, options: CreateTaskOptions): Promise<T
|
|
|
215
206
|
const operations: TaskCreateOperation[] = [{
|
|
216
207
|
name: 'task:local', status: local.status, reasonCode: null
|
|
217
208
|
}];
|
|
218
|
-
let issue =
|
|
209
|
+
let issue: TaskCreateResult['issue'] = null;
|
|
219
210
|
const created = await dependencies.createIssue(local.task.id, { cwd: options.repoRoot, agent: candidate.agent });
|
|
220
211
|
operations.push(...platformOperations('platform-create', created.operations));
|
|
221
212
|
if (created.issue) issue = { number: created.issue.number, url: created.issue.url };
|
package/lib/task/create.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.ts';
|
|
|
7
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
8
8
|
import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.ts';
|
|
9
9
|
import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.ts';
|
|
10
|
-
import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS } from './qualification-audit.ts';
|
|
10
|
+
import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, parseTaskQualification } from './qualification-audit.ts';
|
|
11
11
|
|
|
12
12
|
const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'] as const;
|
|
13
13
|
const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'] as const;
|
|
@@ -191,11 +191,64 @@ function renderTable(columns: readonly string[], rows: readonly (readonly string
|
|
|
191
191
|
].join('\n');
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
function
|
|
194
|
+
function escapeRegExp(value: string): string {
|
|
195
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function candidateIdFor(index: number): string {
|
|
199
|
+
let value = index + 1;
|
|
200
|
+
let id = '';
|
|
201
|
+
while (value > 0) {
|
|
202
|
+
const remainder = (value - 1) % 26;
|
|
203
|
+
id = `${String.fromCharCode(65 + remainder)}${id}`;
|
|
204
|
+
value = Math.floor((value - 1) / 26);
|
|
205
|
+
}
|
|
206
|
+
return id;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function appendCanonicalRows(content: string, heading: string, columns: readonly string[], rows: readonly (readonly string[])[]): string {
|
|
195
210
|
if (rows.length === 0) return content;
|
|
196
211
|
const table = renderTable(columns, rows);
|
|
197
|
-
const separator =
|
|
198
|
-
|
|
212
|
+
const [header, separator, ...rowLines] = table.split('\n');
|
|
213
|
+
const eol = content.includes('\r\n') ? '\r\n' : '\n';
|
|
214
|
+
const lines = content.split(/\r?\n/);
|
|
215
|
+
const headingPattern = new RegExp(`^###\\s+${escapeRegExp(heading)}\\s*$`);
|
|
216
|
+
const sectionStart = lines.findIndex((line) => headingPattern.test(line));
|
|
217
|
+
if (sectionStart < 0) throw new Error(`TASK_CREATE_QUALIFICATION_INVALID: section '${heading}' is missing`);
|
|
218
|
+
let sectionEnd = lines.length;
|
|
219
|
+
for (let index = sectionStart + 1; index < lines.length; index += 1) {
|
|
220
|
+
if (/^#{2,3}\s+/.test(lines[index]!)) {
|
|
221
|
+
sectionEnd = index;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const matches: number[] = [];
|
|
226
|
+
for (let index = sectionStart + 1; index + 1 < sectionEnd; index += 1) {
|
|
227
|
+
if (lines[index]!.trim() === header && lines[index + 1]!.trim() === separator) matches.push(index);
|
|
228
|
+
}
|
|
229
|
+
if (matches.length !== 1) {
|
|
230
|
+
throw new Error(`TASK_CREATE_QUALIFICATION_INVALID: section '${heading}' must contain exactly one canonical qualification table`);
|
|
231
|
+
}
|
|
232
|
+
lines.splice(matches[0]! + 2, 0, ...rowLines);
|
|
233
|
+
return lines.join(eol);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function validateRenderedQualification(content: string, candidate: TaskCreateCandidateV1): void {
|
|
237
|
+
const parsed = parseTaskQualification(content);
|
|
238
|
+
if (!parsed.ok) {
|
|
239
|
+
throw new Error(`TASK_CREATE_QUALIFICATION_INVALID: ${parsed.code}: ${parsed.message}`);
|
|
240
|
+
}
|
|
241
|
+
if (!parsed.qualification.present) {
|
|
242
|
+
throw new Error('TASK_CREATE_QUALIFICATION_INVALID: rendered task qualification contract is missing');
|
|
243
|
+
}
|
|
244
|
+
const expectedConstraintIds = candidate.taskInput.constraints.map((_, index) => `C-${index + 1}`);
|
|
245
|
+
const expectedCandidateIds = candidate.taskInput.alternatives.map((_, index) => candidateIdFor(index));
|
|
246
|
+
const actualConstraintIds = parsed.qualification.constraints.map((row) => row.constraintId);
|
|
247
|
+
const actualCandidateIds = parsed.qualification.candidates.map((row) => row.candidateId);
|
|
248
|
+
if (JSON.stringify(actualConstraintIds) !== JSON.stringify(expectedConstraintIds)
|
|
249
|
+
|| JSON.stringify(actualCandidateIds) !== JSON.stringify(expectedCandidateIds)) {
|
|
250
|
+
throw new Error('TASK_CREATE_QUALIFICATION_INVALID: rendered task qualification rows do not match task input');
|
|
251
|
+
}
|
|
199
252
|
}
|
|
200
253
|
|
|
201
254
|
function replaceEmptySubsection(content: string, heading: string, items: readonly string[]): string {
|
|
@@ -252,11 +305,11 @@ function renderTask(params: Readonly<{
|
|
|
252
305
|
];
|
|
253
306
|
for (const [heading, key] of sections) content = replaceEmptySubsection(content, heading, candidate.taskInput[key]);
|
|
254
307
|
const constraintIds = candidate.taskInput.constraints.map((_, index) => `C-${index + 1}`);
|
|
255
|
-
content = appendCanonicalRows(content, CONSTRAINT_COLUMNS, candidate.taskInput.constraints.map((statement, index) => [
|
|
308
|
+
content = appendCanonicalRows(content, '约束', CONSTRAINT_COLUMNS, candidate.taskInput.constraints.map((statement, index) => [
|
|
256
309
|
constraintIds[index]!, statement, 'assumption', 'create-task', 'taskInput', 'create-task input', '', ''
|
|
257
310
|
]));
|
|
258
|
-
content = appendCanonicalRows(content, CANDIDATE_COLUMNS, candidate.taskInput.alternatives.map((statement, index) => [
|
|
259
|
-
|
|
311
|
+
content = appendCanonicalRows(content, '候选与否决方案', CANDIDATE_COLUMNS, candidate.taskInput.alternatives.map((statement, index) => [
|
|
312
|
+
candidateIdFor(index), statement, 'pending', constraintIds.join(','), 'requires qualification', 'create-task input'
|
|
260
313
|
]));
|
|
261
314
|
content = content.replace('- **关联 Issue**:#XXX', '- **关联 Issue**:N/A');
|
|
262
315
|
content = content.replace('- **关联 PR**:#XXX', '- **关联 PR**:N/A');
|
|
@@ -365,7 +418,6 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
365
418
|
|
|
366
419
|
return withCreateLock(repoRoot, workspaceRoot, () => {
|
|
367
420
|
if (fs.existsSync(receipts)) assertRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
|
|
368
|
-
else fs.mkdirSync(receipts, { mode: 0o700 });
|
|
369
421
|
const receiptPath = path.join(receipts, `${keyDigest}.json`);
|
|
370
422
|
if (fs.existsSync(receiptPath)) {
|
|
371
423
|
const stat = fs.lstatSync(receiptPath);
|
|
@@ -380,6 +432,7 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
380
432
|
|
|
381
433
|
const recovered = recoverPublishedTask(activeRoot, repoRoot, keyDigest, candidateDigest);
|
|
382
434
|
if (recovered) {
|
|
435
|
+
ensureRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
|
|
383
436
|
writeReceipt(receiptPath, {
|
|
384
437
|
version: 1, candidateDigest, taskId: recovered.taskId, shortId: recovered.shortId, status: 'recovered'
|
|
385
438
|
});
|
|
@@ -401,6 +454,8 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
401
454
|
template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project: projectConfig.project, delivery: projectConfig.delivery, timestamp,
|
|
402
455
|
agentInfraVersion, keyDigest, candidateDigest
|
|
403
456
|
});
|
|
457
|
+
validateRenderedQualification(rendered, candidate);
|
|
458
|
+
ensureRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
|
|
404
459
|
const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
|
|
405
460
|
fs.mkdirSync(temporary, { mode: 0o700 });
|
|
406
461
|
fs.writeFileSync(path.join(temporary, 'task.md'), rendered, { flag: 'wx', mode: 0o600 });
|
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
type SourceLine
|
|
2
|
-
start: number;
|
|
3
|
-
end: number;
|
|
4
|
-
text: string;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
type VisibleHeading = SourceLine & {
|
|
8
|
-
level: number;
|
|
9
|
-
text: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type VisibleAnchor = SourceLine & {
|
|
13
|
-
id: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
type VisibleMarkdown = {
|
|
17
|
-
lines: SourceLine[];
|
|
18
|
-
headings: VisibleHeading[];
|
|
19
|
-
anchors: VisibleAnchor[];
|
|
20
|
-
};
|
|
1
|
+
import { scanVisibleMarkdown, type SourceLine } from './markdown.ts';
|
|
21
2
|
|
|
22
3
|
type DecisionDetailBlock = {
|
|
23
4
|
id: string;
|
|
@@ -49,54 +30,9 @@ type DecisionDetailInspection =
|
|
|
49
30
|
};
|
|
50
31
|
|
|
51
32
|
const DECISION_ID = /^(AN|PL|CD|HD)-[1-9]\d*(?=$|[\s::])/i;
|
|
52
|
-
const HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)\s*$/;
|
|
53
|
-
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
54
|
-
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})\s*$/;
|
|
55
|
-
const ANCHOR = /^\s*<a\s+id=(['"])([^'"]+)\1\s*><\/a>\s*$/;
|
|
56
33
|
const FIELD = /^\s*[-*]\s+\*\*([^*]+)\*\*\s*[::]\s*/;
|
|
57
34
|
const OPTION_HEADING = /^(?:方案|Option)\s+[A-Z](?:\b|\s*[::])/i;
|
|
58
35
|
|
|
59
|
-
function sourceLines(content: string): SourceLine[] {
|
|
60
|
-
const lines: SourceLine[] = [];
|
|
61
|
-
let start = 0;
|
|
62
|
-
while (start < content.length) {
|
|
63
|
-
const newline = content.indexOf('\n', start);
|
|
64
|
-
const end = newline === -1 ? content.length : newline;
|
|
65
|
-
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
66
|
-
start = newline === -1 ? content.length : newline + 1;
|
|
67
|
-
}
|
|
68
|
-
return lines;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function scanVisibleMarkdown(content: string): VisibleMarkdown {
|
|
72
|
-
const lines = sourceLines(content);
|
|
73
|
-
const visible: SourceLine[] = [];
|
|
74
|
-
const headings: VisibleHeading[] = [];
|
|
75
|
-
const anchors: VisibleAnchor[] = [];
|
|
76
|
-
let fence: { character: '`' | '~'; length: number } | null = null;
|
|
77
|
-
|
|
78
|
-
for (const line of lines) {
|
|
79
|
-
if (fence) {
|
|
80
|
-
const close = line.text.match(FENCE_CLOSE);
|
|
81
|
-
if (close && close[1]![0] === fence.character && close[1]!.length >= fence.length) fence = null;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
const open = line.text.match(FENCE_OPEN);
|
|
85
|
-
const marker = open?.[1];
|
|
86
|
-
const info = open?.[2] ?? '';
|
|
87
|
-
if (marker && !(marker[0] === '`' && info.includes('`'))) {
|
|
88
|
-
fence = { character: marker[0] as '`' | '~', length: marker.length };
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
visible.push(line);
|
|
92
|
-
const heading = line.text.match(HEADING);
|
|
93
|
-
if (heading) headings.push({ ...line, level: heading[1]!.length, text: heading[2]!.trim() });
|
|
94
|
-
const anchor = line.text.match(ANCHOR);
|
|
95
|
-
if (anchor) anchors.push({ ...line, id: anchor[2]! });
|
|
96
|
-
}
|
|
97
|
-
return { lines: visible, headings, anchors };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
36
|
function parseDecisionDetailBlocks(content: string): DecisionDetailBlock[] {
|
|
101
37
|
const scanned = scanVisibleMarkdown(content);
|
|
102
38
|
const details = scanned.headings.filter((heading) => heading.level === 3 && DECISION_ID.test(heading.text));
|
|
@@ -270,15 +206,11 @@ export {
|
|
|
270
206
|
normalizeDecisionDetailForDisplay,
|
|
271
207
|
parseDecisionDetailBlocks,
|
|
272
208
|
parseDecisionEvidence,
|
|
273
|
-
resolveDecisionDetail
|
|
274
|
-
scanVisibleMarkdown
|
|
209
|
+
resolveDecisionDetail
|
|
275
210
|
};
|
|
276
211
|
export type {
|
|
277
212
|
DecisionDetailBlock,
|
|
278
213
|
DecisionDetailDuplicate,
|
|
279
214
|
DecisionDetailInspection,
|
|
280
|
-
DecisionDetailResolution
|
|
281
|
-
SourceLine,
|
|
282
|
-
VisibleHeading,
|
|
283
|
-
VisibleMarkdown
|
|
215
|
+
DecisionDetailResolution
|
|
284
216
|
};
|
|
@@ -186,7 +186,8 @@ function isDelegationHostEvidence(value: unknown): value is DelegationHostEviden
|
|
|
186
186
|
function hasCurrentCodexEvidence(receipt: DelegationReceipt): boolean {
|
|
187
187
|
const provenance = receipt.lifecycleProvenance;
|
|
188
188
|
if (!provenance) return false;
|
|
189
|
-
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status)
|
|
189
|
+
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status)
|
|
190
|
+
|| (receipt.status === 'aborted' && receipt.activatedAt !== null);
|
|
190
191
|
if (!activated) return receipt.hostEvidence === null;
|
|
191
192
|
const host = receipt.hostEvidence;
|
|
192
193
|
if (
|
|
@@ -219,6 +220,12 @@ function hasCurrentCodexEvidence(receipt: DelegationReceipt): boolean {
|
|
|
219
220
|
&& host.consumer === receipt.id
|
|
220
221
|
&& exactText(host.consumedAt);
|
|
221
222
|
}
|
|
223
|
+
if (receipt.status === 'aborted') {
|
|
224
|
+
return Number.isSafeInteger(host.stopRevision)
|
|
225
|
+
&& (host.stopRevision as number) > host.startRevision
|
|
226
|
+
&& host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
227
|
+
&& exactText(host.consumedAt);
|
|
228
|
+
}
|
|
222
229
|
return host.stopRevision === null && host.consumer === null && host.consumedAt === null;
|
|
223
230
|
}
|
|
224
231
|
|
|
@@ -244,7 +251,8 @@ function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
|
|
|
244
251
|
const dispatchComplete = dispatchFields.every((field) => field !== null);
|
|
245
252
|
if (!dispatchEmpty && !dispatchComplete) return false;
|
|
246
253
|
|
|
247
|
-
const beforeActivation = ['prepared', '
|
|
254
|
+
const beforeActivation = ['prepared', 'expired'].includes(receipt.status)
|
|
255
|
+
|| (receipt.status === 'aborted' && receipt.activatedAt === null);
|
|
248
256
|
if (beforeActivation) {
|
|
249
257
|
return receipt.parentId === null
|
|
250
258
|
&& receipt.childId === null
|
|
@@ -282,6 +290,21 @@ function hasStatusBoundEvidence(receipt: DelegationReceipt): boolean {
|
|
|
282
290
|
&& receipt.consumedAt === null;
|
|
283
291
|
}
|
|
284
292
|
|
|
293
|
+
if (receipt.status === 'aborted') {
|
|
294
|
+
const host = receipt.hostEvidence;
|
|
295
|
+
return receipt.agent === null
|
|
296
|
+
&& receipt.afterFingerprint === null
|
|
297
|
+
&& receipt.changedPaths.length === 0
|
|
298
|
+
&& receipt.sealedAt === null
|
|
299
|
+
&& receipt.consumedAt === null
|
|
300
|
+
&& host !== null
|
|
301
|
+
&& host !== undefined
|
|
302
|
+
&& Number.isSafeInteger(host.stopRevision)
|
|
303
|
+
&& (host.stopRevision as number) > host.startRevision
|
|
304
|
+
&& host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
305
|
+
&& exactText(host.consumedAt);
|
|
306
|
+
}
|
|
307
|
+
|
|
285
308
|
if (
|
|
286
309
|
!exactText(receipt.agent)
|
|
287
310
|
|| normalizeAgentToken(receipt.agent) !== normalizeAgentToken(receipt.client)
|
|
@@ -584,6 +607,53 @@ function abortPreparedDelegation(receipt: DelegationReceipt): ReceiptResult {
|
|
|
584
607
|
};
|
|
585
608
|
}
|
|
586
609
|
|
|
610
|
+
function abortActivatedDelegation(
|
|
611
|
+
receipt: DelegationReceipt,
|
|
612
|
+
event: Readonly<{ childId: string; stopRevision: number; consumer: string; consumedAt: string }>
|
|
613
|
+
): ReceiptResult {
|
|
614
|
+
if (receipt.status !== 'activated') {
|
|
615
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
|
|
616
|
+
}
|
|
617
|
+
if (
|
|
618
|
+
receipt.client !== 'codex'
|
|
619
|
+
|| !receipt.lifecycleProvenance
|
|
620
|
+
|| receipt.hostEvidence?.kind !== 'codex-lifecycle-v2'
|
|
621
|
+
|| typeof receipt.lifecycleProvenance.controllerInstanceDigest !== 'string'
|
|
622
|
+
|| typeof receipt.lifecycleProvenance.controlGeneration !== 'string'
|
|
623
|
+
|| typeof receipt.hostEvidence.controllerInstanceDigest !== 'string'
|
|
624
|
+
|| typeof receipt.hostEvidence.controlGeneration !== 'string'
|
|
625
|
+
) {
|
|
626
|
+
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'activated delegation lacks the current Codex controller binding');
|
|
627
|
+
}
|
|
628
|
+
if (
|
|
629
|
+
event.childId !== receipt.childId
|
|
630
|
+
|| !Number.isSafeInteger(event.stopRevision)
|
|
631
|
+
|| event.stopRevision <= receipt.hostEvidence.startRevision
|
|
632
|
+
|| event.consumer !== `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
633
|
+
|| !exactText(event.consumedAt)
|
|
634
|
+
) {
|
|
635
|
+
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
|
|
636
|
+
}
|
|
637
|
+
return {
|
|
638
|
+
ok: true,
|
|
639
|
+
receipt: Object.freeze({
|
|
640
|
+
...receipt,
|
|
641
|
+
status: 'aborted' as const,
|
|
642
|
+
agent: null,
|
|
643
|
+
afterFingerprint: null,
|
|
644
|
+
changedPaths: Object.freeze([]),
|
|
645
|
+
sealedAt: null,
|
|
646
|
+
consumedAt: null,
|
|
647
|
+
hostEvidence: Object.freeze({
|
|
648
|
+
...receipt.hostEvidence,
|
|
649
|
+
stopRevision: event.stopRevision,
|
|
650
|
+
consumer: event.consumer,
|
|
651
|
+
consumedAt: event.consumedAt
|
|
652
|
+
})
|
|
653
|
+
})
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
587
657
|
function completeDelegationStage(
|
|
588
658
|
receipt: DelegationReceipt,
|
|
589
659
|
event: Readonly<{ stage: DelegationStage; round: number; artifact: string; agent: string }>
|
|
@@ -666,6 +736,7 @@ function consumeDelegation(receipt: DelegationReceipt, options: { now?: () => st
|
|
|
666
736
|
|
|
667
737
|
export {
|
|
668
738
|
activateDelegation,
|
|
739
|
+
abortActivatedDelegation,
|
|
669
740
|
abortPreparedDelegation,
|
|
670
741
|
completeDelegationStage,
|
|
671
742
|
consumeDelegation,
|