@fitlab-ai/agent-infra 0.9.9 → 0.9.11
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 +13 -3
- package/README.zh-CN.md +13 -3
- package/bin/cli.ts +17 -4
- package/dist/bin/cli.js +19 -4
- package/dist/bin/internal-cli.js +55 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +17 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +11 -31
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +15 -33
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
- package/dist/lib/agent-clients/adapters/index.js +3 -1
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +65 -0
- package/dist/lib/agent-clients/config.js +14 -99
- package/dist/lib/agent-clients/reconcile.js +1 -11
- package/dist/lib/agent-clients/schema.js +5 -4
- package/dist/lib/agent-clients/tokens.js +7 -17
- package/dist/lib/agent-clients/types.js +2 -1
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +9 -0
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/codex-lifecycle.js +2 -2
- package/dist/lib/internal/codex-sandbox-controller.js +4 -3
- package/dist/lib/internal/release-workflow.js +0 -1
- package/dist/lib/internal/sandbox-control.js +2 -2
- package/dist/lib/internal/task-finalization.js +19 -9
- package/dist/lib/internal/task-ledger.js +8 -2
- package/dist/lib/internal/task-lifecycle.js +17 -93
- package/dist/lib/internal/task-orchestration.js +30 -216
- package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +77 -43
- package/dist/lib/merge.js +205 -103
- package/dist/lib/process-data/normalize.js +2 -9
- package/dist/lib/run/host.js +46 -16
- package/dist/lib/run/tui.js +5 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
- package/dist/lib/sandbox/commands/create.js +3 -3
- package/dist/lib/sandbox/commands/enter.js +18 -4
- package/dist/lib/sandbox/config.js +3 -12
- package/dist/lib/sandbox/control/controller-registration.js +4 -2
- package/dist/lib/sandbox/control/executor.js +135 -40
- package/dist/lib/sandbox/control/lifecycle.js +29 -20
- package/dist/lib/sandbox/control/server.js +18 -16
- package/dist/lib/sandbox/mounts.js +3 -2
- package/dist/lib/sandbox/recovery.js +123 -22
- package/dist/lib/sandbox/workspace-identity.js +14 -0
- package/dist/lib/sandbox/workspace-view.js +8 -9
- package/dist/lib/self-update.js +278 -0
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +4 -2
- package/dist/lib/task/codex-orchestration.js +32 -35
- package/dist/lib/task/command-options.js +1 -3
- package/dist/lib/task/commands/cat.js +0 -5
- package/dist/lib/task/commands/decisions.js +76 -92
- package/dist/lib/task/commands/files.js +6 -6
- package/dist/lib/task/commands/grep.js +25 -61
- package/dist/lib/task/commands/issue-body.js +6 -6
- package/dist/lib/task/commands/log.js +25 -10
- package/dist/lib/task/commands/show.js +5 -6
- package/dist/lib/task/commands/status.js +8 -8
- package/dist/lib/task/commit-operation.js +21 -25
- package/dist/lib/task/commit-policy.js +1 -1
- package/dist/lib/task/control-authority.js +431 -0
- package/dist/lib/task/create.js +6 -1
- package/dist/lib/task/current-contract.js +35 -0
- package/dist/lib/task/decision-details.js +263 -0
- package/dist/lib/task/decision-intents.js +5 -2
- package/dist/lib/task/delegation-receipts.js +8 -7
- package/dist/lib/task/events.js +11 -5
- package/dist/lib/task/finalization.js +72 -46
- package/dist/lib/task/index.js +19 -29
- package/dist/lib/task/ledger-intents.js +8 -9
- package/dist/lib/task/ledger.js +13 -6
- package/dist/lib/task/lifecycle.js +16 -0
- package/dist/lib/task/orchestration.js +51 -14
- package/dist/lib/task/review-artifacts.js +31 -12
- package/dist/lib/task/review-finalization.js +19 -6
- package/dist/lib/task/verification-engine.js +38 -18
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/workspace-snapshot.js +5 -11
- package/dist/lib/task/write.js +11 -1
- package/dist/lib/update.js +6 -37
- package/dist/lib/version.js +5 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +0 -1
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +25 -10
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +16 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +16 -35
- package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
- package/lib/agent-clients/adapters/index.ts +3 -1
- package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +66 -0
- package/lib/agent-clients/config.ts +14 -144
- package/lib/agent-clients/reconcile.ts +1 -12
- package/lib/agent-clients/schema.ts +5 -4
- package/lib/agent-clients/tokens.ts +7 -20
- package/lib/agent-clients/types.ts +2 -1
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +9 -0
- package/lib/init.ts +2 -2
- package/lib/internal/codex-lifecycle.ts +2 -2
- package/lib/internal/codex-sandbox-controller.ts +4 -3
- package/lib/internal/release-workflow.ts +0 -1
- package/lib/internal/sandbox-control.ts +2 -2
- package/lib/internal/task-finalization.ts +27 -13
- package/lib/internal/task-ledger.ts +8 -2
- package/lib/internal/task-lifecycle.ts +33 -76
- package/lib/internal/task-orchestration.ts +33 -190
- package/lib/{task/commands/validate.ts → internal/task-validate.ts} +77 -41
- package/lib/merge.ts +236 -120
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- package/lib/run/tui.ts +5 -3
- package/lib/sandbox/agent-client-reconciler.ts +5 -15
- package/lib/sandbox/commands/create.ts +4 -4
- package/lib/sandbox/commands/enter.ts +24 -4
- package/lib/sandbox/config.ts +2 -14
- package/lib/sandbox/control/container-identity.ts +2 -2
- package/lib/sandbox/control/controller-registration.ts +8 -3
- package/lib/sandbox/control/executor.ts +156 -53
- package/lib/sandbox/control/lifecycle.ts +34 -28
- package/lib/sandbox/control/protocol.ts +1 -3
- package/lib/sandbox/control/server.ts +15 -12
- package/lib/sandbox/control/state.ts +7 -7
- package/lib/sandbox/mounts.ts +4 -3
- package/lib/sandbox/recovery.ts +162 -34
- package/lib/sandbox/tools.ts +1 -1
- package/lib/sandbox/workspace-identity.ts +28 -0
- package/lib/sandbox/workspace-view.ts +14 -10
- package/lib/self-update.ts +341 -0
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +6 -1
- package/lib/task/artifact-lifecycle.ts +5 -3
- package/lib/task/codex-orchestration.ts +40 -45
- package/lib/task/command-options.ts +1 -2
- package/lib/task/commands/cat.ts +0 -4
- package/lib/task/commands/decisions.ts +80 -75
- package/lib/task/commands/files.ts +6 -6
- package/lib/task/commands/grep.ts +23 -51
- package/lib/task/commands/issue-body.ts +6 -6
- package/lib/task/commands/log.ts +22 -10
- package/lib/task/commands/show.ts +5 -6
- package/lib/task/commands/status.ts +8 -8
- package/lib/task/commit-operation.ts +25 -30
- package/lib/task/commit-policy.ts +1 -2
- package/lib/task/control-authority.ts +563 -0
- package/lib/task/create.ts +6 -1
- package/lib/task/current-contract.ts +49 -0
- package/lib/task/decision-details.ts +340 -0
- package/lib/task/decision-intents.ts +7 -2
- package/lib/task/delegation-receipts.ts +8 -6
- package/lib/task/events.ts +10 -5
- package/lib/task/finalization.ts +84 -42
- package/lib/task/index.ts +19 -28
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/ledger.ts +17 -7
- package/lib/task/lifecycle.ts +20 -0
- package/lib/task/orchestration.ts +42 -16
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +33 -7
- package/lib/task/verification-engine.ts +33 -21
- package/lib/task/verification-review.ts +1 -1
- package/lib/task/workspace-snapshot.ts +4 -13
- package/lib/task/write.ts +20 -1
- package/lib/update.ts +6 -40
- package/lib/version.ts +6 -1
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +2 -3
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -3
- package/templates/.agents/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/README.en.md +1 -0
- package/templates/.agents/rules/README.zh-CN.md +1 -0
- package/templates/.agents/rules/compatibility-policy.en.md +40 -0
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +40 -0
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +9 -7
- package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
- package/templates/.agents/rules/issue-fields.github.en.md +1 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +1 -1
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
- package/templates/.agents/rules/milestone-inference.github.en.md +1 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +3 -3
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.en.md +2 -2
- package/templates/.agents/rules/pr-sync.github.en.md +3 -2
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +3 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +2 -1
- package/templates/.agents/rules/release-commands.github.en.md +6 -0
- package/templates/.agents/rules/release-commands.github.zh-CN.md +6 -0
- package/templates/.agents/rules/review-handshake.en.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/review-method.en.md +1 -0
- package/templates/.agents/rules/review-method.zh-CN.md +1 -0
- package/templates/.agents/rules/task-management.en.md +1 -2
- package/templates/.agents/rules/task-management.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +4 -3
- 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/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +4 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/commit/SKILL.en.md +27 -7
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -5
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -5
- 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/create-pr/SKILL.en.md +15 -11
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +15 -11
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +46 -0
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +46 -0
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
- package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -4
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +9 -6
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +9 -6
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +6 -2
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +159 -104
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/analyze-task.en.md +1 -1
- package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.claude/commands/block-task.en.md +1 -1
- package/templates/.claude/commands/block-task.zh-CN.md +1 -1
- package/templates/.claude/commands/cancel-task.en.md +1 -1
- package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
- package/templates/.claude/commands/check-task.en.md +1 -1
- package/templates/.claude/commands/check-task.zh-CN.md +1 -1
- package/templates/.claude/commands/code-task.en.md +1 -1
- package/templates/.claude/commands/code-task.zh-CN.md +1 -1
- package/templates/.claude/commands/commit.en.md +1 -1
- package/templates/.claude/commands/commit.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-task.en.md +1 -1
- package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
- package/templates/.claude/commands/create-pr.en.md +1 -1
- package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
- package/templates/.claude/commands/plan-task.en.md +1 -1
- package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
- package/templates/.claude/commands/review-analysis.en.md +1 -1
- package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.claude/commands/review-code.en.md +1 -1
- package/templates/.claude/commands/review-code.zh-CN.md +1 -1
- package/templates/.claude/commands/review-plan.en.md +1 -1
- package/templates/.claude/commands/review-plan.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/templates/.claude/commands/run-task.en.md +1 -1
- package/templates/.claude/commands/run-task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/.traecli/commands/update-agent-infra.en.md +7 -0
- package/templates/.traecli/commands/update-agent-infra.zh-CN.md +7 -0
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
package/lib/task/create.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
+
import { isValidAgentInfraVersion, VERSION } from '../version.ts';
|
|
5
6
|
import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.ts';
|
|
6
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
7
8
|
|
|
@@ -294,6 +295,10 @@ function recoverPublishedTask(activeRoot: string, repoRoot: string, keyDigest: s
|
|
|
294
295
|
|
|
295
296
|
function createLocalTask(value: unknown, options: LocalTaskCreateOptions): LocalTaskCreateResult {
|
|
296
297
|
const candidate = validateTaskCreateCandidate(value);
|
|
298
|
+
const agentInfraVersion = options.agentInfraVersion ?? VERSION;
|
|
299
|
+
if (!isValidAgentInfraVersion(agentInfraVersion)) {
|
|
300
|
+
throw new Error('TASK_CREATE_VERSION_INVALID: agentInfraVersion must be a valid v-prefixed semver');
|
|
301
|
+
}
|
|
297
302
|
const repoRoot = fs.realpathSync.native(options.repoRoot);
|
|
298
303
|
const workspaceRoot = path.join(repoRoot, '.agents', 'workspace');
|
|
299
304
|
const activeRoot = path.join(workspaceRoot, 'active');
|
|
@@ -342,7 +347,7 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
342
347
|
if (!templateStat.isFile() || templateStat.isSymbolicLink()) throw new Error('TASK_CREATE_TEMPLATE_INVALID');
|
|
343
348
|
const rendered = renderTask({
|
|
344
349
|
template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project, timestamp,
|
|
345
|
-
agentInfraVersion
|
|
350
|
+
agentInfraVersion, keyDigest, candidateDigest
|
|
346
351
|
});
|
|
347
352
|
const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
|
|
348
353
|
fs.mkdirSync(temporary, { mode: 0o700 });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isValidAgentInfraVersion } from '../version.ts';
|
|
2
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
3
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, validateLedgerRows } from './ledger.ts';
|
|
4
|
+
import type { LedgerDocument } from './ledger.ts';
|
|
5
|
+
|
|
6
|
+
type CurrentTaskContractErrorCode = 'TASK_DOCUMENT_INVALID' | 'TASK_CURRENT_CONTRACT_INVALID';
|
|
7
|
+
type CurrentTaskContractResult =
|
|
8
|
+
| {
|
|
9
|
+
ok: true;
|
|
10
|
+
metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
11
|
+
ledger: LedgerDocument;
|
|
12
|
+
}
|
|
13
|
+
| {
|
|
14
|
+
ok: false;
|
|
15
|
+
code: CurrentTaskContractErrorCode;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function validateCurrentTaskContract(content: string): CurrentTaskContractResult {
|
|
20
|
+
let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
21
|
+
try {
|
|
22
|
+
metadata = parseTypedTaskFrontmatter(content);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return { ok: false, code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
25
|
+
}
|
|
26
|
+
if (!isValidAgentInfraVersion(metadata.agent_infra_version)) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
code: 'TASK_CURRENT_CONTRACT_INVALID',
|
|
30
|
+
message: `agent_infra_version must be a valid v-prefixed semver (received ${String(metadata.agent_infra_version ?? 'missing')})`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let ledger: LedgerDocument;
|
|
35
|
+
try {
|
|
36
|
+
ledger = parseLedgerDocument(content);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return { ok: false, code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
39
|
+
}
|
|
40
|
+
if (!ledger.present) {
|
|
41
|
+
return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
|
|
42
|
+
}
|
|
43
|
+
const invalid = validateLedgerRows(ledger.rows);
|
|
44
|
+
if (invalid) return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
45
|
+
return { ok: true, metadata, ledger };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { validateCurrentTaskContract };
|
|
49
|
+
export type { CurrentTaskContractErrorCode, CurrentTaskContractResult };
|
|
@@ -0,0 +1,340 @@
|
|
|
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
|
+
};
|
|
21
|
+
|
|
22
|
+
type DecisionDetailBlock = {
|
|
23
|
+
id: string;
|
|
24
|
+
heading: string;
|
|
25
|
+
canonical: boolean;
|
|
26
|
+
start: number;
|
|
27
|
+
end: number;
|
|
28
|
+
content: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type DecisionDetailResolution =
|
|
32
|
+
| { status: 'found'; block: DecisionDetailBlock }
|
|
33
|
+
| { status: 'missing'; reason: string }
|
|
34
|
+
| { status: 'ambiguous'; reason: string; candidates: DecisionDetailBlock[] };
|
|
35
|
+
|
|
36
|
+
type DecisionDetailDuplicate = {
|
|
37
|
+
id: string;
|
|
38
|
+
blocks: DecisionDetailBlock[];
|
|
39
|
+
repairable: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type DecisionDetailInspection =
|
|
43
|
+
| { ok: true; blocks: DecisionDetailBlock[] }
|
|
44
|
+
| {
|
|
45
|
+
ok: false;
|
|
46
|
+
code: 'DECISION_DETAIL_DUPLICATE';
|
|
47
|
+
message: string;
|
|
48
|
+
blocks: DecisionDetailBlock[];
|
|
49
|
+
duplicates: DecisionDetailDuplicate[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type DecisionDetailRepair =
|
|
53
|
+
| { ok: true; changed: boolean; content: string }
|
|
54
|
+
| {
|
|
55
|
+
ok: false;
|
|
56
|
+
code: 'DECISION_DETAIL_AMBIGUOUS';
|
|
57
|
+
message: string;
|
|
58
|
+
content: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const DECISION_ID = /^(AN|PL|CD|HD)-[1-9]\d*(?=$|[\s::])/i;
|
|
62
|
+
const HEADING = /^ {0,3}(#{1,6})[ \t]+(.+?)\s*$/;
|
|
63
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
64
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})\s*$/;
|
|
65
|
+
const ANCHOR = /^\s*<a\s+id=(['"])([^'"]+)\1\s*><\/a>\s*$/;
|
|
66
|
+
const FIELD = /^\s*[-*]\s+\*\*([^*]+)\*\*\s*[::]\s*/;
|
|
67
|
+
const OPTION_HEADING = /^(?:方案|Option)\s+[A-Z](?:\b|\s*[::])/i;
|
|
68
|
+
|
|
69
|
+
function sourceLines(content: string): SourceLine[] {
|
|
70
|
+
const lines: SourceLine[] = [];
|
|
71
|
+
let start = 0;
|
|
72
|
+
while (start < content.length) {
|
|
73
|
+
const newline = content.indexOf('\n', start);
|
|
74
|
+
const end = newline === -1 ? content.length : newline;
|
|
75
|
+
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
76
|
+
start = newline === -1 ? content.length : newline + 1;
|
|
77
|
+
}
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function scanVisibleMarkdown(content: string): VisibleMarkdown {
|
|
82
|
+
const lines = sourceLines(content);
|
|
83
|
+
const visible: SourceLine[] = [];
|
|
84
|
+
const headings: VisibleHeading[] = [];
|
|
85
|
+
const anchors: VisibleAnchor[] = [];
|
|
86
|
+
let fence: { character: '`' | '~'; length: number } | null = null;
|
|
87
|
+
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
if (fence) {
|
|
90
|
+
const close = line.text.match(FENCE_CLOSE);
|
|
91
|
+
if (close && close[1]![0] === fence.character && close[1]!.length >= fence.length) fence = null;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const open = line.text.match(FENCE_OPEN);
|
|
95
|
+
const marker = open?.[1];
|
|
96
|
+
const info = open?.[2] ?? '';
|
|
97
|
+
if (marker && !(marker[0] === '`' && info.includes('`'))) {
|
|
98
|
+
fence = { character: marker[0] as '`' | '~', length: marker.length };
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
visible.push(line);
|
|
102
|
+
const heading = line.text.match(HEADING);
|
|
103
|
+
if (heading) headings.push({ ...line, level: heading[1]!.length, text: heading[2]!.trim() });
|
|
104
|
+
const anchor = line.text.match(ANCHOR);
|
|
105
|
+
if (anchor) anchors.push({ ...line, id: anchor[2]! });
|
|
106
|
+
}
|
|
107
|
+
return { lines: visible, headings, anchors };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseDecisionDetailBlocks(content: string): DecisionDetailBlock[] {
|
|
111
|
+
const scanned = scanVisibleMarkdown(content);
|
|
112
|
+
const details = scanned.headings.filter((heading) => heading.level === 3 && DECISION_ID.test(heading.text));
|
|
113
|
+
return details.map((heading) => {
|
|
114
|
+
const next = scanned.headings.find((candidate) => (
|
|
115
|
+
candidate.start > heading.start && candidate.level <= 3
|
|
116
|
+
));
|
|
117
|
+
const end = next?.start ?? content.length;
|
|
118
|
+
return {
|
|
119
|
+
id: heading.text.match(DECISION_ID)![0]!,
|
|
120
|
+
heading: heading.text,
|
|
121
|
+
canonical: /\[needs-human-decision\]/i.test(heading.text),
|
|
122
|
+
start: heading.start,
|
|
123
|
+
end,
|
|
124
|
+
content: content.slice(heading.start, end).trim()
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function parseDecisionEvidence(evidence: string): { ok: true; artifact: string; anchor: string } | { ok: false } {
|
|
130
|
+
const separator = evidence.indexOf('#');
|
|
131
|
+
if (separator <= 0 || evidence.indexOf('#', separator + 1) !== -1) return { ok: false };
|
|
132
|
+
const artifact = evidence.slice(0, separator);
|
|
133
|
+
const anchor = evidence.slice(separator + 1);
|
|
134
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/.test(artifact)) return { ok: false };
|
|
135
|
+
if (!anchor || /[\s\u0000-\u001f#]/.test(anchor)) return { ok: false };
|
|
136
|
+
return { ok: true, artifact, anchor };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sameId(left: string, right: string): boolean {
|
|
140
|
+
return left.toUpperCase() === right.toUpperCase();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveDecisionDetail(content: string, rowId: string, anchor: string): DecisionDetailResolution {
|
|
144
|
+
const scanned = scanVisibleMarkdown(content);
|
|
145
|
+
const blocks = parseDecisionDetailBlocks(content);
|
|
146
|
+
const explicit = scanned.anchors.filter((candidate) => candidate.id === anchor);
|
|
147
|
+
if (explicit.length > 1) {
|
|
148
|
+
return { status: 'ambiguous', reason: `evidence anchor '${anchor}' occurs more than once`, candidates: [] };
|
|
149
|
+
}
|
|
150
|
+
if (explicit.length === 1) {
|
|
151
|
+
const marker = explicit[0]!;
|
|
152
|
+
const followingLine = scanned.lines.find((line) => line.start > marker.end && line.text.trim());
|
|
153
|
+
const followingHeading = followingLine
|
|
154
|
+
? scanned.headings.find((heading) => heading.start === followingLine.start)
|
|
155
|
+
: undefined;
|
|
156
|
+
const candidate = followingHeading?.level === 3
|
|
157
|
+
? blocks.find((block) => block.start === followingHeading.start)
|
|
158
|
+
: undefined;
|
|
159
|
+
if (!candidate || !sameId(candidate.id, rowId) || !candidate.canonical) {
|
|
160
|
+
return { status: 'missing', reason: `evidence anchor '${anchor}' does not identify a canonical ${rowId} detail` };
|
|
161
|
+
}
|
|
162
|
+
return { status: 'found', block: candidate };
|
|
163
|
+
}
|
|
164
|
+
if (!sameId(anchor, rowId)) {
|
|
165
|
+
return { status: 'missing', reason: `evidence anchor '${anchor}' is not the ledger id '${rowId}'` };
|
|
166
|
+
}
|
|
167
|
+
const matches = blocks.filter((block) => sameId(block.id, rowId) && block.canonical);
|
|
168
|
+
if (matches.length === 0) return { status: 'missing', reason: `no canonical ${rowId} detail was found` };
|
|
169
|
+
if (matches.length > 1) {
|
|
170
|
+
return { status: 'ambiguous', reason: `more than one canonical ${rowId} detail was found`, candidates: matches };
|
|
171
|
+
}
|
|
172
|
+
return { status: 'found', block: matches[0]! };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const INFORMAL_SHORT_HEADING = /^(?:AN|PL|CD|HD)-[1-9]\d*[::]\s*(?:上一轮)?简短复核$/i;
|
|
176
|
+
const INFORMAL_SHORT_FRAGMENT = /^简短(?:结论|说明)$/;
|
|
177
|
+
|
|
178
|
+
function isInformalShortBlock(block: DecisionDetailBlock): boolean {
|
|
179
|
+
if (block.canonical || block.content.length > 600) return false;
|
|
180
|
+
if (!INFORMAL_SHORT_HEADING.test(block.heading)) return false;
|
|
181
|
+
const scanned = scanVisibleMarkdown(block.content);
|
|
182
|
+
const bodyLines = scanned.lines
|
|
183
|
+
.filter((line) => !scanned.headings.some((heading) => heading.start === line.start) && line.text.trim())
|
|
184
|
+
.map((line) => line.text);
|
|
185
|
+
if (bodyLines.length === 0 || bodyLines.length > 2) return false;
|
|
186
|
+
const fragments = bodyLines.map((line) => line.match(/^\s*[-*]\s+([^\s].*?)\s*$/)?.[1] ?? null);
|
|
187
|
+
if (fragments.some((fragment) => fragment === null)) return false;
|
|
188
|
+
return fragments.every((fragment) => INFORMAL_SHORT_FRAGMENT.test(fragment!));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function hasDirectPrecedingAnchor(block: DecisionDetailBlock, scanned: VisibleMarkdown): boolean {
|
|
192
|
+
return scanned.anchors.some((anchor) => (
|
|
193
|
+
anchor.end < block.start
|
|
194
|
+
&& !scanned.lines.some((line) => line.start > anchor.end && line.start < block.start && line.text.trim())
|
|
195
|
+
));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function inspectDecisionDetailDuplicates(content: string): DecisionDetailInspection {
|
|
199
|
+
const blocks = parseDecisionDetailBlocks(content);
|
|
200
|
+
const scanned = scanVisibleMarkdown(content);
|
|
201
|
+
const grouped = new Map<string, DecisionDetailBlock[]>();
|
|
202
|
+
for (const block of blocks) {
|
|
203
|
+
const key = block.id.toUpperCase();
|
|
204
|
+
const group = grouped.get(key) ?? [];
|
|
205
|
+
group.push(block);
|
|
206
|
+
grouped.set(key, group);
|
|
207
|
+
}
|
|
208
|
+
const duplicates: DecisionDetailDuplicate[] = [];
|
|
209
|
+
for (const group of grouped.values()) {
|
|
210
|
+
if (group.length < 2) continue;
|
|
211
|
+
const canonical = group.filter((block) => block.canonical);
|
|
212
|
+
const repairable = canonical.length === 1 && group.every((block) => (
|
|
213
|
+
block.canonical || (!hasDirectPrecedingAnchor(block, scanned) && isInformalShortBlock(block))
|
|
214
|
+
));
|
|
215
|
+
duplicates.push({ id: canonical[0]?.id ?? group[0]!.id, blocks: group, repairable });
|
|
216
|
+
}
|
|
217
|
+
if (duplicates.length === 0) return { ok: true, blocks };
|
|
218
|
+
const summary = duplicates.map((duplicate) => {
|
|
219
|
+
const types = duplicate.blocks.map((block) => block.canonical ? 'canonical' : 'informal').join(', ');
|
|
220
|
+
return `${duplicate.id} (${duplicate.blocks.length} blocks: ${types}${duplicate.repairable ? '; safe repair available' : ''})`;
|
|
221
|
+
}).join(', ');
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
code: 'DECISION_DETAIL_DUPLICATE',
|
|
225
|
+
message: `Duplicate decision detail ids: ${summary}`,
|
|
226
|
+
blocks,
|
|
227
|
+
duplicates
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function repairDecisionDetailDuplicates(content: string): DecisionDetailRepair {
|
|
232
|
+
const inspection = inspectDecisionDetailDuplicates(content);
|
|
233
|
+
if (inspection.ok) return { ok: true, changed: false, content };
|
|
234
|
+
const unsafe = inspection.duplicates.filter((duplicate) => !duplicate.repairable);
|
|
235
|
+
if (unsafe.length > 0) {
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
code: 'DECISION_DETAIL_AMBIGUOUS',
|
|
239
|
+
message: `Cannot safely repair duplicate decision detail ids: ${unsafe.map((duplicate) => duplicate.id).join(', ')}`,
|
|
240
|
+
content
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const removals = inspection.duplicates.flatMap((duplicate) => duplicate.blocks.filter((block) => !block.canonical));
|
|
244
|
+
let repaired = content;
|
|
245
|
+
for (const block of removals.sort((left, right) => right.start - left.start)) {
|
|
246
|
+
repaired = repaired.slice(0, block.start) + repaired.slice(block.end);
|
|
247
|
+
}
|
|
248
|
+
return { ok: true, changed: repaired !== content, content: repaired };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
type FieldKey = 'decision' | 'why' | 'impact' | 'risk' | 'analogy' | 'recommend' | 'reason' | 'implementation';
|
|
252
|
+
|
|
253
|
+
const FIELD_KEYS: Record<string, FieldKey> = {
|
|
254
|
+
'为什么现在要决定': 'why', 'why decide now': 'why', 'why a decision is needed now': 'why',
|
|
255
|
+
'要决定什么': 'decision', 'what to decide': 'decision', 'what needs a decision': 'decision',
|
|
256
|
+
'会影响什么': 'impact', 'what it affects': 'impact', 'what this affects': 'impact',
|
|
257
|
+
'如果选错或暂不处理': 'risk', 'if wrong or postponed': 'risk',
|
|
258
|
+
'what could go wrong': 'risk',
|
|
259
|
+
'打个比方': 'analogy', '例子': 'analogy', 'example': 'analogy',
|
|
260
|
+
'建议选哪个': 'recommend', 'recommended option': 'recommend', 'recommended choice': 'recommend',
|
|
261
|
+
'为什么这样建议': 'reason', 'why this recommendation': 'reason', 'why this choice': 'reason',
|
|
262
|
+
'是否需要实现': 'implementation', 'implementation required': 'implementation'
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const TERMINAL_FIELD_KEYS = new Set<FieldKey>(['recommend', 'reason', 'implementation']);
|
|
266
|
+
|
|
267
|
+
function normalizeDecisionDetailForDisplay(block: DecisionDetailBlock): string {
|
|
268
|
+
const body = block.content;
|
|
269
|
+
const scanned = scanVisibleMarkdown(body);
|
|
270
|
+
const heading = scanned.headings.find((candidate) => candidate.level === 3);
|
|
271
|
+
if (!heading) return body.trim();
|
|
272
|
+
const options = scanned.headings.filter((candidate) => candidate.level === 4 && OPTION_HEADING.test(candidate.text));
|
|
273
|
+
const optionRanges = options.map((option, index) => ({
|
|
274
|
+
start: option.start,
|
|
275
|
+
end: options[index + 1]?.start
|
|
276
|
+
?? scanned.headings.find((candidate) => candidate.start > option.start && candidate.level <= 3)?.start
|
|
277
|
+
?? scanned.lines.find((line) => (
|
|
278
|
+
line.start > option.start
|
|
279
|
+
&& FIELD.test(line.text)
|
|
280
|
+
&& TERMINAL_FIELD_KEYS.has(FIELD_KEYS[line.text.match(FIELD)![1]!.trim().toLowerCase()] as FieldKey)
|
|
281
|
+
))?.start
|
|
282
|
+
?? body.length
|
|
283
|
+
}));
|
|
284
|
+
const topLines = scanned.lines.filter((line) => (
|
|
285
|
+
line.start > heading.end && !optionRanges.some((range) => line.start >= range.start && line.start < range.end)
|
|
286
|
+
));
|
|
287
|
+
const fields: { key: FieldKey | null; line: SourceLine }[] = [];
|
|
288
|
+
for (const line of topLines) {
|
|
289
|
+
const match = line.text.match(FIELD);
|
|
290
|
+
if (!match) continue;
|
|
291
|
+
const key = FIELD_KEYS[match[1]!.trim().toLowerCase()];
|
|
292
|
+
fields.push({ key: key ?? null, line });
|
|
293
|
+
}
|
|
294
|
+
if (fields.some((field) => field.key === null)) return body.trim();
|
|
295
|
+
const byKey = new Map<FieldKey, string>();
|
|
296
|
+
const selectedRanges: { start: number; end: number }[] = [];
|
|
297
|
+
for (let index = 0; index < fields.length; index += 1) {
|
|
298
|
+
const field = fields[index]!;
|
|
299
|
+
if (!field.key) continue;
|
|
300
|
+
const nextOption = optionRanges.find((range) => range.start > field.line.start)?.start ?? body.length;
|
|
301
|
+
const end = Math.min(fields[index + 1]?.line.start ?? body.length, nextOption);
|
|
302
|
+
const value = body.slice(field.line.start, end).trim();
|
|
303
|
+
byKey.set(field.key, value);
|
|
304
|
+
selectedRanges.push({ start: field.line.start, end });
|
|
305
|
+
}
|
|
306
|
+
selectedRanges.push(...optionRanges);
|
|
307
|
+
const required: FieldKey[] = ['decision', 'why', 'impact', 'recommend', 'reason'];
|
|
308
|
+
if (required.some((key) => !byKey.has(key)) || options.length < 2) return body.trim();
|
|
309
|
+
const sortedRanges = selectedRanges.sort((left, right) => left.start - right.start);
|
|
310
|
+
let cursor = heading.end;
|
|
311
|
+
for (const range of sortedRanges) {
|
|
312
|
+
if (/\S/.test(body.slice(cursor, range.start))) return body.trim();
|
|
313
|
+
cursor = Math.max(cursor, range.end);
|
|
314
|
+
}
|
|
315
|
+
if (/\S/.test(body.slice(cursor))) return body.trim();
|
|
316
|
+
const ordered: string[] = [
|
|
317
|
+
byKey.get('decision')!, byKey.get('why')!, byKey.get('impact')!
|
|
318
|
+
];
|
|
319
|
+
for (const optional of ['risk', 'analogy'] as const) if (byKey.has(optional)) ordered.push(byKey.get(optional)!);
|
|
320
|
+
ordered.push(...optionRanges.map((range) => body.slice(range.start, range.end).trim()));
|
|
321
|
+
ordered.push(byKey.get('recommend')!, byKey.get('reason')!);
|
|
322
|
+
if (byKey.has('implementation')) ordered.push(byKey.get('implementation')!);
|
|
323
|
+
return [body.slice(heading.start, heading.end).trim(), ...ordered].join('\n\n').trim();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export {
|
|
327
|
+
inspectDecisionDetailDuplicates,
|
|
328
|
+
normalizeDecisionDetailForDisplay,
|
|
329
|
+
parseDecisionDetailBlocks,
|
|
330
|
+
parseDecisionEvidence,
|
|
331
|
+
repairDecisionDetailDuplicates,
|
|
332
|
+
resolveDecisionDetail
|
|
333
|
+
};
|
|
334
|
+
export type {
|
|
335
|
+
DecisionDetailBlock,
|
|
336
|
+
DecisionDetailDuplicate,
|
|
337
|
+
DecisionDetailInspection,
|
|
338
|
+
DecisionDetailRepair,
|
|
339
|
+
DecisionDetailResolution
|
|
340
|
+
};
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
renderImplementationInputs,
|
|
11
11
|
selectDeclaredImplementationInput
|
|
12
12
|
} from './implementation-inputs.ts';
|
|
13
|
-
import { LEDGER_COLUMNS, LEDGER_HEADINGS,
|
|
13
|
+
import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument } from './ledger.ts';
|
|
14
14
|
import type { LedgerRow } from './ledger.ts';
|
|
15
15
|
import { resolveTaskContext } from './resolve-ref.ts';
|
|
16
16
|
import { extractSection, extractSubSection, findSectionHeading } from './sections.ts';
|
|
@@ -90,7 +90,12 @@ function applyHumanDecision(request: HumanDecisionRequest, options: TaskWriteOpt
|
|
|
90
90
|
if (resolved.state !== 'active' && !stateOverride) return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
91
91
|
let content: string;
|
|
92
92
|
let rows: LedgerRow[];
|
|
93
|
-
try {
|
|
93
|
+
try {
|
|
94
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
95
|
+
const ledger = parseLedgerDocument(content);
|
|
96
|
+
if (!ledger.present) return failed(LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, resolved.taskId);
|
|
97
|
+
rows = ledger.rows as LedgerRow[];
|
|
98
|
+
}
|
|
94
99
|
catch (error) { return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId); }
|
|
95
100
|
|
|
96
101
|
const direct = rows.filter((row) => row.id.toUpperCase() === request.selector.toUpperCase());
|
|
@@ -108,6 +108,13 @@ function exactText(value: unknown): value is string {
|
|
|
108
108
|
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
function validCodexBuildMetadata(value: Readonly<Record<string, unknown>>): boolean {
|
|
112
|
+
return Number.isSafeInteger(value.protocolVersion) && (value.protocolVersion as number) > 0
|
|
113
|
+
&& exactText(value.packageVersion)
|
|
114
|
+
&& exactText(value.internalExecutableBuildHash)
|
|
115
|
+
&& exactText(value.lifecycleContractHash);
|
|
116
|
+
}
|
|
117
|
+
|
|
111
118
|
function nullableText(value: unknown): value is string | null {
|
|
112
119
|
return value === null || exactText(value);
|
|
113
120
|
}
|
|
@@ -185,9 +192,6 @@ function hasCurrentCodexEvidence(receipt: DelegationReceipt): boolean {
|
|
|
185
192
|
if (
|
|
186
193
|
host?.kind !== 'codex-lifecycle-v2'
|
|
187
194
|
|| host.protocolVersion !== provenance.protocolVersion
|
|
188
|
-
|| host.packageVersion !== provenance.packageVersion
|
|
189
|
-
|| host.internalExecutableBuildHash !== provenance.internalExecutableBuildHash
|
|
190
|
-
|| host.lifecycleContractHash !== provenance.lifecycleContractHash
|
|
191
195
|
|| host.hookDefinitionHash !== provenance.hookDefinitionHash
|
|
192
196
|
|| host.hookSource !== provenance.hookSource
|
|
193
197
|
|| host.hookSourcePathDigest !== provenance.hookSourcePathDigest
|
|
@@ -531,9 +535,6 @@ function activateDelegation(
|
|
|
531
535
|
if (
|
|
532
536
|
!expected
|
|
533
537
|
|| event.hostEvidence.protocolVersion !== expected.protocolVersion
|
|
534
|
-
|| event.hostEvidence.packageVersion !== expected.packageVersion
|
|
535
|
-
|| event.hostEvidence.internalExecutableBuildHash !== expected.internalExecutableBuildHash
|
|
536
|
-
|| event.hostEvidence.lifecycleContractHash !== expected.lifecycleContractHash
|
|
537
538
|
|| event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
|
|
538
539
|
|| event.hostEvidence.hookSource !== expected.hookSource
|
|
539
540
|
|| event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
|
|
@@ -548,6 +549,7 @@ function activateDelegation(
|
|
|
548
549
|
|| event.parentId !== expected.capabilitySessionId
|
|
549
550
|
|| (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
|
|
550
551
|
|| (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
|
|
552
|
+
|| !validCodexBuildMetadata(event.hostEvidence)
|
|
551
553
|
) return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
|
|
552
554
|
}
|
|
553
555
|
return { ok: true, receipt: Object.freeze({
|
package/lib/task/events.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
|
|
3
|
-
import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-log.ts';
|
|
3
|
+
import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
|
|
4
4
|
import {
|
|
5
5
|
buildArtifactLinkSection,
|
|
6
6
|
artifactName,
|
|
@@ -21,9 +21,9 @@ import {
|
|
|
21
21
|
parseImplementationInputs,
|
|
22
22
|
renderImplementationInputs
|
|
23
23
|
} from './implementation-inputs.ts';
|
|
24
|
-
import {
|
|
24
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
25
25
|
import type { ReviewStage } from './ledger.ts';
|
|
26
|
-
import { parseReviewSummary } from './review-artifacts.ts';
|
|
26
|
+
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.ts';
|
|
27
27
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
28
28
|
import { findSectionHeading } from './sections.ts';
|
|
29
29
|
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
@@ -50,6 +50,7 @@ type TaskEventErrorCode =
|
|
|
50
50
|
| 'EVENT_UNKNOWN' | 'EVENT_PAYLOAD_INVALID' | 'EVENT_TRANSITION_INVALID'
|
|
51
51
|
| 'EVENT_LOG_MISSING' | 'EVENT_START_MISSING' | 'EVENT_ALREADY_COMPLETED'
|
|
52
52
|
| 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
|
|
53
|
+
| 'EVENT_VERDICT_INVALID'
|
|
53
54
|
| 'EVENT_ORCHESTRATION_COMMIT_FAILED'
|
|
54
55
|
| ArtifactErrorCode | TaskWriteErrorCode;
|
|
55
56
|
type TaskEventRequest = {
|
|
@@ -151,7 +152,9 @@ function validateReviewFindingCounts(
|
|
|
151
152
|
): TaskEventError | null {
|
|
152
153
|
const stage = REVIEW_LEDGER_STAGES[family];
|
|
153
154
|
if (!stage || !artifactPath) return null;
|
|
154
|
-
const
|
|
155
|
+
const ledger = parseLedgerDocument(content);
|
|
156
|
+
if (!ledger.present) return { code: 'EVENT_FINDING_COUNT_MISMATCH', message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
|
|
157
|
+
const rows = ledger.rows;
|
|
155
158
|
const invalid = validateLedgerRows(rows);
|
|
156
159
|
if (invalid) return { code: 'TASK_DOCUMENT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
157
160
|
const expected = summarizeLedgerStage(rows, stage).unresolvedFindingCounts;
|
|
@@ -173,6 +176,8 @@ function validateReviewFindingCounts(
|
|
|
173
176
|
message: parsed.ok ? 'review summary finding counts are not finalized' : parsed.message
|
|
174
177
|
};
|
|
175
178
|
}
|
|
179
|
+
const canonical = resolveCanonicalVerdict(parsed.summary);
|
|
180
|
+
if (!canonical.ok) return { code: 'EVENT_VERDICT_INVALID', message: `${canonical.code}: ${canonical.message}` };
|
|
176
181
|
const report = parsed.summary.counts;
|
|
177
182
|
const reportVerdict = parsed.summary.verdict === 'Approved'
|
|
178
183
|
? 'approved'
|
|
@@ -370,7 +375,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
370
375
|
catch (error) { return failed(request, { code: 'TASK_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
|
|
371
376
|
const section = locateActivityLog(content);
|
|
372
377
|
if (!section) return failed(request, { code: 'EVENT_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
373
|
-
const rows = pairEntries(section.entries);
|
|
378
|
+
const rows = startedBackedRows(pairEntries(section.entries));
|
|
374
379
|
let normalized = request;
|
|
375
380
|
let artifactContext: ArtifactContextResult | null = null;
|
|
376
381
|
const initialParts = eventParts(request.event);
|