@fitlab-ai/agent-infra 0.9.11 → 0.9.12
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 +6 -6
- package/README.zh-CN.md +6 -6
- package/dist/bin/internal-cli.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +1 -0
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-pr.js +8 -2
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/platform/github-client.js +7 -1
- package/dist/lib/platform/pr-checks.js +8 -5
- package/dist/lib/platform/pr-summary.js +12 -8
- package/dist/lib/platform/pull-requests.js +393 -91
- package/dist/lib/platform/verification-required.js +13 -2
- package/dist/lib/platform/verification-sync.js +9 -4
- package/dist/lib/pr-review/evidence-grading.js +12 -4
- package/dist/lib/sandbox/config.js +5 -0
- package/dist/lib/sandbox/control/lifecycle.js +29 -7
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/task/artifact-lifecycle.js +2 -2
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +195 -101
- package/dist/lib/task/create.js +33 -7
- package/dist/lib/task/decision-details.js +3 -48
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/human-override.js +5 -2
- package/dist/lib/task/lifecycle.js +106 -9
- package/dist/lib/task/orchestration.js +55 -62
- package/dist/lib/task/pr-delivery-fact.js +182 -0
- package/dist/lib/task/review-finalization.js +4 -8
- package/dist/lib/task/review-fingerprint.js +8 -0
- package/dist/lib/task/verification-engine.js +48 -24
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/update.js +9 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
- package/lib/defaults.json +4 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +3 -0
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-pr.ts +8 -1
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/platform/github-client.ts +8 -1
- package/lib/platform/pr-checks.ts +7 -5
- package/lib/platform/pr-summary.ts +10 -7
- package/lib/platform/pull-requests.ts +425 -87
- package/lib/platform/verification-required.ts +12 -2
- package/lib/platform/verification-sync.ts +9 -4
- package/lib/pr-review/evidence-grading.ts +7 -4
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/control/lifecycle.ts +30 -8
- package/lib/server/process-state.ts +2 -2
- package/lib/task/artifact-lifecycle.ts +2 -2
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +174 -87
- package/lib/task/create.ts +34 -9
- package/lib/task/decision-details.ts +2 -62
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/human-override.ts +5 -2
- package/lib/task/lifecycle.ts +93 -4
- package/lib/task/orchestration.ts +62 -65
- package/lib/task/pr-delivery-fact.ts +254 -0
- package/lib/task/review-finalization.ts +4 -14
- package/lib/task/review-fingerprint.ts +10 -0
- package/lib/task/verification-engine.ts +45 -28
- package/lib/task/verification-review.ts +2 -0
- package/lib/update.ts +10 -0
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- package/templates/.agents/rules/create-issue.en.md +11 -2
- package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
- package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
- package/templates/.agents/rules/issue-sync.en.md +26 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +62 -5
- package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -0
- package/templates/.agents/rules/milestone-inference.en.md +12 -2
- package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
- package/templates/.agents/rules/pr-sync.en.md +22 -13
- package/templates/.agents/rules/pr-sync.zh-CN.md +22 -1
- package/templates/.agents/rules/release-commands.en.md +42 -3
- package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
- package/templates/.agents/rules/security-alerts.en.md +17 -2
- package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
- package/templates/.agents/rules/sync-content-generation.en.md +8 -0
- package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/scripts/security-alerts.github.sh +151 -0
- package/templates/.agents/scripts/security-alerts.sh +31 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +5 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/commit/SKILL.en.md +3 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/config/verify.json +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -1
- 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/summary-update.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +10 -10
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +7 -6
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +7 -6
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +4 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -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/update-agent-infra/scripts/sync-templates.js +4 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/templates/task.en.md +5 -1
- package/templates/.agents/templates/task.zh-CN.md +5 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/feature-development.en.yaml +9 -8
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/refactoring.en.yaml +9 -8
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
- package/templates/.agents/rules/create-issue.github.en.md +0 -14
- package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
- package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
- package/templates/.agents/rules/issue-sync.github.en.md +0 -40
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
- package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
- package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
- package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
- package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
- package/templates/.agents/rules/pr-sync.github.en.md +0 -43
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
- package/templates/.agents/rules/release-commands.github.en.md +0 -44
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
- /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
- /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
|
@@ -6,6 +6,7 @@ import { hasPlatformCapability } from "./adapters.ts";
|
|
|
6
6
|
import { inspectRequiredChecks } from "./pr-checks.ts";
|
|
7
7
|
import { resolvePlatformContext } from "./context.ts";
|
|
8
8
|
import { resolveReviewedHeadRelation } from "./merged-pr-equivalence.ts";
|
|
9
|
+
import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
|
|
9
10
|
|
|
10
11
|
const CHECK_TYPE = "required-checks";
|
|
11
12
|
const SHA_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
@@ -34,7 +35,13 @@ function readHead(repoRoot: any): any {
|
|
|
34
35
|
|
|
35
36
|
export function evaluateRequiredChecks(context: any, shared: any): any {
|
|
36
37
|
const { metadata, localHead, inspection, prFlow } = context;
|
|
37
|
-
|
|
38
|
+
const factRead = readPrDeliveryFact(metadata);
|
|
39
|
+
if (factRead.status === "invalid") {
|
|
40
|
+
return shared.failResult(CHECK_TYPE, factRead.error.message, "check_failed");
|
|
41
|
+
}
|
|
42
|
+
const fact = factRead.status === "valid" ? factRead.fact : null;
|
|
43
|
+
const prNumber = fact?.state === "bound" ? fact.identity.number : null;
|
|
44
|
+
if (prFlow === "disabled" || fact?.state === "skipped" || !validPrNumber(prNumber)) {
|
|
38
45
|
return shared.passResult(CHECK_TYPE, "Skipped: required checks are not applicable to this task");
|
|
39
46
|
}
|
|
40
47
|
|
|
@@ -87,7 +94,10 @@ export function check({ taskDir }: any, shared: any): any {
|
|
|
87
94
|
const task = shared.loadTask(taskDir);
|
|
88
95
|
if (!task.ok) return shared.failResult(CHECK_TYPE, task.message);
|
|
89
96
|
const prFlow = readPrFlow(shared.repoRoot);
|
|
90
|
-
|
|
97
|
+
const factRead = readPrDeliveryFact(task.metadata);
|
|
98
|
+
if (factRead.status === "invalid") return shared.failResult(CHECK_TYPE, factRead.error.message, "check_failed");
|
|
99
|
+
const bound = factRead.status === "valid" && factRead.fact.state === "bound";
|
|
100
|
+
if (prFlow === "disabled" || (factRead.status === "valid" && factRead.fact.state === "skipped") || !bound) {
|
|
91
101
|
return evaluateRequiredChecks({ metadata: task.metadata, localHead: null, inspection: null, prFlow }, shared);
|
|
92
102
|
}
|
|
93
103
|
const platform = resolvePlatformContext({ cwd: shared.repoRoot });
|
|
@@ -10,6 +10,7 @@ import { inspectGitHubIssueMetadata, requirementSectionAnchors } from "./issues.
|
|
|
10
10
|
import { listRemoteComments } from "./issue-comments.ts";
|
|
11
11
|
import { taskTypeLabel } from "./metadata-labels.ts";
|
|
12
12
|
import { inspectGitHubPullRequest } from "./pull-requests.ts";
|
|
13
|
+
import { readPrDeliveryFact } from "../task/pr-delivery-fact.ts";
|
|
13
14
|
|
|
14
15
|
const CHECK_TYPE = "platform-sync";
|
|
15
16
|
const VERSION_LINE_REGEX = /^[0-9]+\.[0-9]+\.x$/;
|
|
@@ -151,12 +152,16 @@ function buildSyncContext({ taskDir, config, artifactFile }: any): any {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
const issueNumber = parseIssueNumber(task.metadata.issue_number);
|
|
154
|
-
const
|
|
155
|
+
const fact = readPrDeliveryFact(task.metadata);
|
|
156
|
+
if (fact.status === "invalid") {
|
|
157
|
+
return { earlyReturn: failResult(CHECK_TYPE, fact.error.message, "check_failed") };
|
|
158
|
+
}
|
|
159
|
+
const prNumber = fact.status === "valid" && fact.fact.state === "bound" ? fact.fact.identity.number : null;
|
|
155
160
|
if (config.when === "issue_number_exists" && !issueNumber) {
|
|
156
161
|
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no issue_number") };
|
|
157
162
|
}
|
|
158
|
-
if (config.when === "
|
|
159
|
-
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no
|
|
163
|
+
if (config.when === "pr_fact_bound" && !prNumber) {
|
|
164
|
+
return { earlyReturn: passResult(CHECK_TYPE, "Skipped: task has no verified bound pull request") };
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
if (!issueNumber) {
|
|
@@ -322,7 +327,7 @@ function fetchRemoteData(context: any): any {
|
|
|
322
327
|
if (context.prMarker) {
|
|
323
328
|
if (!context.prNumber) {
|
|
324
329
|
return {
|
|
325
|
-
earlyReturn: failResult(CHECK_TYPE, "Expected a
|
|
330
|
+
earlyReturn: failResult(CHECK_TYPE, "Expected a verified bound pull request for PR comment verification", "check_failed")
|
|
326
331
|
};
|
|
327
332
|
}
|
|
328
333
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
|
|
5
|
+
import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
|
|
5
6
|
|
|
6
7
|
// ---------------------------------------------------------------------------
|
|
7
8
|
// Host resolution (PL-4): PR -> unique / ambiguous / none.
|
|
@@ -62,7 +63,7 @@ export function extractClosingIssueNumbers(body: string): number[] {
|
|
|
62
63
|
/**
|
|
63
64
|
* Scan `.agents/workspace/active/{task-id}/task.md` files for tasks bound to the
|
|
64
65
|
* PR number or to one of the PR's closing issues. A task is emitted at most once;
|
|
65
|
-
* a
|
|
66
|
+
* a verified fact identity hit takes priority over the issue-number reverse lookup.
|
|
66
67
|
*/
|
|
67
68
|
export function collectHostCandidates(input: {
|
|
68
69
|
prNumber: number;
|
|
@@ -87,9 +88,11 @@ export function collectHostCandidates(input: {
|
|
|
87
88
|
} catch {
|
|
88
89
|
continue;
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
let frontmatter: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
92
|
+
try { frontmatter = parseTypedTaskFrontmatter(content); } catch { continue; }
|
|
91
93
|
const issueNumber = toPositiveNumber(frontmatter.issue_number);
|
|
92
|
-
const
|
|
94
|
+
const fact = readPrDeliveryFact(frontmatter);
|
|
95
|
+
const prNumber = fact.status === 'valid' && fact.fact.state === 'bound' ? fact.fact.identity.number : null;
|
|
93
96
|
if (prNumber === input.prNumber) {
|
|
94
97
|
candidates.push({ taskId: entry.name, taskDir, issueNumber, prNumber });
|
|
95
98
|
} else if (issueNumber !== null && input.closingIssues.includes(issueNumber)) {
|
package/lib/sandbox/config.ts
CHANGED
|
@@ -63,6 +63,7 @@ export type SandboxConfig = {
|
|
|
63
63
|
tools: string[];
|
|
64
64
|
customTools: SandboxTool[];
|
|
65
65
|
agentClientState: AgentClientState;
|
|
66
|
+
delivery?: { remote: string; baseRef: string };
|
|
66
67
|
refreshIntervalDays: number;
|
|
67
68
|
dockerfile: string | null;
|
|
68
69
|
vm: SandboxVmConfig;
|
|
@@ -73,6 +74,7 @@ type AircConfig = {
|
|
|
73
74
|
org?: unknown;
|
|
74
75
|
agentClients?: unknown;
|
|
75
76
|
sandbox?: SandboxConfigInput;
|
|
77
|
+
delivery?: { remote?: unknown; baseRef?: unknown };
|
|
76
78
|
};
|
|
77
79
|
|
|
78
80
|
function detectRepoRoot(): string {
|
|
@@ -135,6 +137,7 @@ export function loadConfig({
|
|
|
135
137
|
const agentClients = normalizeAgentClients(airc);
|
|
136
138
|
const defaults = cloneDefaults();
|
|
137
139
|
const sandbox = airc.sandbox ?? {};
|
|
140
|
+
const delivery = airc.delivery ?? {};
|
|
138
141
|
const engine = validateSandboxEngine(sandbox.engine ?? defaults.engine, { platformFn });
|
|
139
142
|
const project = airc.project;
|
|
140
143
|
|
|
@@ -188,6 +191,10 @@ export function loadConfig({
|
|
|
188
191
|
tools,
|
|
189
192
|
customTools,
|
|
190
193
|
agentClientState: agentClients.state,
|
|
194
|
+
delivery: {
|
|
195
|
+
remote: typeof delivery.remote === 'string' ? delivery.remote : 'origin',
|
|
196
|
+
baseRef: typeof delivery.baseRef === 'string' ? delivery.baseRef : 'main'
|
|
197
|
+
},
|
|
191
198
|
refreshIntervalDays: asNonNegativeIntegerOrDefault(
|
|
192
199
|
sandbox.refreshIntervalDays,
|
|
193
200
|
defaults.refreshIntervalDays
|
|
@@ -494,30 +494,32 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
494
494
|
} catch {
|
|
495
495
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
496
496
|
}
|
|
497
|
-
const candidate = manifest as Partial<SandboxControlManifest
|
|
497
|
+
const candidate = manifest as Partial<SandboxControlManifest>;
|
|
498
498
|
const expectedKeys = [
|
|
499
499
|
'branch', 'channelDir', 'container', 'containerIdentity', 'engine', 'generation',
|
|
500
500
|
'mode', 'processingDir', 'project', 'publicStatusDir', 'repoRoot', 'runtimeDir',
|
|
501
501
|
'taskId', 'token', 'worktreeRoot'
|
|
502
502
|
];
|
|
503
|
-
if ('
|
|
504
|
-
|
|
505
|
-
throw new Error('SANDBOX_CONTROL_MANIFEST_REBUILD_REQUIRED: container-only recreation/rebuild is required');
|
|
503
|
+
if (Object.keys(manifest).sort().join(',') !== expectedKeys.sort().join(',')) {
|
|
504
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
506
505
|
}
|
|
507
506
|
if (typeof candidate.containerIdentity !== 'object' || candidate.containerIdentity === null
|
|
508
507
|
|| Array.isArray(candidate.containerIdentity)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
509
508
|
const containerIdentity = candidate.containerIdentity as Record<string, unknown>;
|
|
510
|
-
if (
|
|
509
|
+
if (Object.keys(containerIdentity).sort().join(',') !== 'id,labels'
|
|
510
|
+
|| typeof candidate.engine !== 'string' || candidate.engine.length === 0
|
|
511
511
|
|| typeof candidate.repoRoot !== 'string' || typeof candidate.worktreeRoot !== 'string'
|
|
512
512
|
|| typeof candidate.project !== 'string' || typeof candidate.container !== 'string'
|
|
513
513
|
|| typeof containerIdentity.id !== 'string' || containerIdentity.id.length === 0
|
|
514
514
|
|| typeof containerIdentity.labels !== 'object' || containerIdentity.labels === null
|
|
515
515
|
|| Array.isArray(containerIdentity.labels)
|
|
516
516
|
|| Object.values(containerIdentity.labels).some((value) => typeof value !== 'string')
|
|
517
|
-
|| typeof candidate.branch !== 'string'
|
|
517
|
+
|| typeof candidate.branch !== 'string'
|
|
518
|
+
|| (candidate.mode !== 'task-bound' && candidate.mode !== 'branch-only')
|
|
518
519
|
|| (candidate.taskId !== null && typeof candidate.taskId !== 'string')
|
|
519
520
|
|| typeof candidate.channelDir !== 'string' || typeof candidate.publicStatusDir !== 'string'
|
|
520
|
-
|| typeof candidate.processingDir !== 'string' || typeof candidate.
|
|
521
|
+
|| typeof candidate.processingDir !== 'string' || typeof candidate.runtimeDir !== 'string'
|
|
522
|
+
|| typeof candidate.token !== 'string'
|
|
521
523
|
|| typeof candidate.generation !== 'string'
|
|
522
524
|
|| (candidate.mode === 'task-bound' && (typeof candidate.taskId !== 'string' || candidate.taskId.length === 0))
|
|
523
525
|
|| (candidate.mode === 'branch-only' && candidate.taskId !== null)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
@@ -530,7 +532,27 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
530
532
|
if (path.resolve(candidate.runtimeDir) !== path.join(root, 'runtime')) {
|
|
531
533
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
532
534
|
}
|
|
533
|
-
|
|
535
|
+
const normalized: SandboxControlManifest = {
|
|
536
|
+
engine: candidate.engine,
|
|
537
|
+
repoRoot: candidate.repoRoot,
|
|
538
|
+
worktreeRoot: candidate.worktreeRoot,
|
|
539
|
+
project: candidate.project,
|
|
540
|
+
container: candidate.container,
|
|
541
|
+
containerIdentity: {
|
|
542
|
+
id: containerIdentity.id as string,
|
|
543
|
+
labels: { ...(containerIdentity.labels as Record<string, string>) }
|
|
544
|
+
},
|
|
545
|
+
branch: candidate.branch,
|
|
546
|
+
mode: candidate.mode,
|
|
547
|
+
taskId: candidate.taskId,
|
|
548
|
+
token: candidate.token,
|
|
549
|
+
generation: candidate.generation,
|
|
550
|
+
channelDir: candidate.channelDir,
|
|
551
|
+
publicStatusDir: candidate.publicStatusDir,
|
|
552
|
+
processingDir: candidate.processingDir,
|
|
553
|
+
runtimeDir: candidate.runtimeDir
|
|
554
|
+
};
|
|
555
|
+
return normalized;
|
|
534
556
|
}
|
|
535
557
|
|
|
536
558
|
export function readSandboxControlManifest(manifestPath: string): SandboxControlManifest {
|
|
@@ -85,8 +85,8 @@ export function isProcessAlive(pid: number, platform: NodeJS.Platform = process.
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
function parseDarwinStartTime(output: string): number | null {
|
|
89
|
-
const match = output.trim().match(/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) ([A-Z][a-z]{2})
|
|
88
|
+
export function parseDarwinStartTime(output: string): number | null {
|
|
89
|
+
const match = output.trim().match(/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) ([A-Z][a-z]{2})\s+(\d{1,2}) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/);
|
|
90
90
|
if (!match) return null;
|
|
91
91
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
92
92
|
const month = months.indexOf(match[1]!);
|
|
@@ -482,9 +482,9 @@ function buildArtifactLinkSection(content: string, artifact: ArtifactIdentity):
|
|
|
482
482
|
const label = english
|
|
483
483
|
? `${spec.labels[1]} (Round ${artifact.round})`
|
|
484
484
|
: `${spec.labels[0]}(Round ${artifact.round})`;
|
|
485
|
-
const link =
|
|
485
|
+
const link = `${label}:\`${artifact.name}\``;
|
|
486
486
|
let body = extractSection(content, aliases);
|
|
487
|
-
if (body.includes(
|
|
487
|
+
if (body.includes(link)) return { aliases, heading, body };
|
|
488
488
|
const placeholders = new Set([
|
|
489
489
|
'[分析阶段的发现。哪些文件受影响?范围是什么?]',
|
|
490
490
|
'[Findings from the analysis phase. Which files are affected? What is the scope?]',
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export type CheckpointIntentState = 'prepared' | 'committed' | 'synced';
|
|
6
|
+
|
|
7
|
+
export type CheckpointIntent = Readonly<{
|
|
8
|
+
version: 1;
|
|
9
|
+
taskId: string;
|
|
10
|
+
branch: string;
|
|
11
|
+
mode: 'local';
|
|
12
|
+
expectedHead: string;
|
|
13
|
+
expectedTree: string;
|
|
14
|
+
paths: readonly string[];
|
|
15
|
+
message: string;
|
|
16
|
+
round: number;
|
|
17
|
+
digest: string;
|
|
18
|
+
state: CheckpointIntentState;
|
|
19
|
+
committedHead: string | null;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
type IntentIdentity = Readonly<{
|
|
25
|
+
taskId: string;
|
|
26
|
+
branch: string;
|
|
27
|
+
mode: 'local';
|
|
28
|
+
expectedHead: string;
|
|
29
|
+
expectedTree: string;
|
|
30
|
+
paths: readonly string[];
|
|
31
|
+
message: string;
|
|
32
|
+
round: number;
|
|
33
|
+
}>;
|
|
34
|
+
|
|
35
|
+
function intentRoot(repoRoot: string): string {
|
|
36
|
+
return path.join(repoRoot, '.agents', 'workspace', '.task-commit-intents');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function intentPath(repoRoot: string, taskId: string): string {
|
|
40
|
+
return path.join(intentRoot(repoRoot), `${taskId}.json`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function checkpointIntentDigest(identity: IntentIdentity): string {
|
|
44
|
+
return createHash('sha256').update(JSON.stringify({
|
|
45
|
+
taskId: identity.taskId,
|
|
46
|
+
branch: identity.branch,
|
|
47
|
+
mode: identity.mode,
|
|
48
|
+
expectedHead: identity.expectedHead,
|
|
49
|
+
expectedTree: identity.expectedTree,
|
|
50
|
+
paths: [...identity.paths],
|
|
51
|
+
message: identity.message,
|
|
52
|
+
round: identity.round
|
|
53
|
+
})).digest('hex');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isIntent(value: unknown): value is CheckpointIntent {
|
|
57
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
58
|
+
const intent = value as Record<string, unknown>;
|
|
59
|
+
return intent.version === 1
|
|
60
|
+
&& typeof intent.taskId === 'string' && intent.taskId.length > 0
|
|
61
|
+
&& typeof intent.branch === 'string' && intent.branch.length > 0
|
|
62
|
+
&& intent.mode === 'local'
|
|
63
|
+
&& typeof intent.expectedHead === 'string' && /^[a-f0-9]{40,64}$/.test(intent.expectedHead)
|
|
64
|
+
&& typeof intent.expectedTree === 'string' && /^[a-f0-9]{40,64}$/.test(intent.expectedTree)
|
|
65
|
+
&& Array.isArray(intent.paths) && intent.paths.every((item) => typeof item === 'string' && item.length > 0)
|
|
66
|
+
&& typeof intent.message === 'string' && intent.message.length > 0
|
|
67
|
+
&& Number.isSafeInteger(intent.round) && Number(intent.round) > 0
|
|
68
|
+
&& typeof intent.digest === 'string' && /^[a-f0-9]{64}$/.test(intent.digest)
|
|
69
|
+
&& ['prepared', 'committed', 'synced'].includes(String(intent.state))
|
|
70
|
+
&& (intent.committedHead === null || (typeof intent.committedHead === 'string' && /^[a-f0-9]{40,64}$/.test(intent.committedHead)))
|
|
71
|
+
&& typeof intent.createdAt === 'string' && intent.createdAt.length > 0
|
|
72
|
+
&& typeof intent.updatedAt === 'string' && intent.updatedAt.length > 0
|
|
73
|
+
&& intent.digest === checkpointIntentDigest(intent as unknown as IntentIdentity);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readCheckpointIntent(repoRoot: string, taskId: string): CheckpointIntent | null {
|
|
77
|
+
const target = intentPath(repoRoot, taskId);
|
|
78
|
+
if (!fs.existsSync(target)) return null;
|
|
79
|
+
const value = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown;
|
|
80
|
+
if (!isIntent(value)) throw new Error('COMMIT_INTENT_INVALID: checkpoint intent schema is invalid');
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeCheckpointIntent(repoRoot: string, value: CheckpointIntent): void {
|
|
85
|
+
if (!isIntent(value)) throw new Error('COMMIT_INTENT_INVALID: checkpoint intent schema is invalid');
|
|
86
|
+
const directory = intentRoot(repoRoot);
|
|
87
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
88
|
+
const target = intentPath(repoRoot, value.taskId);
|
|
89
|
+
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
90
|
+
fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
91
|
+
try { fs.renameSync(temporary, target); }
|
|
92
|
+
catch (error) {
|
|
93
|
+
try { fs.unlinkSync(temporary); } catch { /* preserve primary error */ }
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function updateCheckpointIntent(
|
|
99
|
+
value: CheckpointIntent,
|
|
100
|
+
patch: Partial<Pick<CheckpointIntent, 'state' | 'committedHead' | 'updatedAt'>>
|
|
101
|
+
): CheckpointIntent {
|
|
102
|
+
const next = { ...value, ...patch };
|
|
103
|
+
if (next.state === 'prepared' && next.committedHead !== null) throw new Error('COMMIT_INTENT_INVALID: prepared intent cannot have a committed head');
|
|
104
|
+
if (next.state !== 'prepared' && !next.committedHead) throw new Error('COMMIT_INTENT_INVALID: committed intent requires a committed head');
|
|
105
|
+
return next;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function removeCheckpointIntent(repoRoot: string, taskId: string): void {
|
|
109
|
+
const target = intentPath(repoRoot, taskId);
|
|
110
|
+
try { fs.unlinkSync(target); } catch (error) {
|
|
111
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function sameCheckpointIntent(left: CheckpointIntent, right: IntentIdentity): boolean {
|
|
116
|
+
return left.digest === checkpointIntentDigest(right)
|
|
117
|
+
&& left.taskId === right.taskId
|
|
118
|
+
&& left.branch === right.branch
|
|
119
|
+
&& left.mode === right.mode
|
|
120
|
+
&& left.expectedHead === right.expectedHead
|
|
121
|
+
&& left.expectedTree === right.expectedTree
|
|
122
|
+
&& left.message === right.message
|
|
123
|
+
&& left.round === right.round
|
|
124
|
+
&& JSON.stringify(left.paths) === JSON.stringify(right.paths);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
checkpointIntentDigest,
|
|
129
|
+
intentPath,
|
|
130
|
+
intentRoot,
|
|
131
|
+
isIntent,
|
|
132
|
+
readCheckpointIntent,
|
|
133
|
+
removeCheckpointIntent,
|
|
134
|
+
sameCheckpointIntent,
|
|
135
|
+
updateCheckpointIntent,
|
|
136
|
+
writeCheckpointIntent
|
|
137
|
+
};
|