@fitlab-ai/agent-infra 0.11.4 → 0.11.6
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/dist/bin/internal-cli.js +0 -5
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +9 -2
- package/dist/lib/internal/task-ledger.js +5 -43
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/merge.js +62 -10
- package/dist/lib/platform/github-provider.js +9 -14
- package/dist/lib/platform/github-release-notes.js +80 -11
- package/dist/lib/platform/none-provider.js +1 -1
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/provider-contract.d.ts +39 -16
- package/dist/lib/platform/provider-contract.js +1 -1
- package/dist/lib/platform/provider-validation.js +83 -13
- package/dist/lib/platform/release-notes.js +7 -8
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/capture.js +1 -1
- package/dist/lib/sandbox/commands/create.js +46 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/start.js +1 -1
- package/dist/lib/sandbox/config.js +10 -0
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/lifecycle.js +18 -13
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-resolver.js +62 -2
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-operations.js +36 -2
- package/dist/lib/task/artifact-schema.js +7 -7
- package/dist/lib/task/control-authority.js +9 -166
- package/dist/lib/task/decision-intents.js +2 -4
- package/dist/lib/task/events.js +7 -28
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +123 -12
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +3 -6
- package/dist/lib/task/lifecycle.js +50 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -28
- package/dist/lib/task/resolve-ref.js +3 -3
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +1 -2
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +16 -11
- package/dist/lib/task/verification.js +1 -13
- package/dist/lib/task/workflow-warning-intents.js +2 -4
- package/dist/lib/task/write.js +122 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +9 -2
- package/lib/internal/task-ledger.ts +5 -38
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +1 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/merge.ts +59 -10
- package/lib/platform/github-provider.ts +11 -18
- package/lib/platform/github-release-notes.ts +89 -14
- package/lib/platform/none-provider.ts +1 -1
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/provider-contract.ts +38 -12
- package/lib/platform/provider-validation.ts +83 -12
- package/lib/platform/release-notes.ts +11 -12
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/capture.ts +1 -1
- package/lib/sandbox/commands/create.ts +52 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/start.ts +1 -1
- package/lib/sandbox/config.ts +12 -0
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/lifecycle.ts +22 -13
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-resolver.ts +51 -2
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-operations.ts +58 -2
- package/lib/task/artifact-schema.ts +9 -7
- package/lib/task/control-authority.ts +14 -188
- package/lib/task/decision-intents.ts +2 -4
- package/lib/task/events.ts +8 -27
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +120 -12
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +3 -6
- package/lib/task/lifecycle.ts +45 -13
- package/lib/task/local-artifact-finalization.ts +16 -35
- package/lib/task/resolve-ref.ts +3 -3
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +1 -4
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +20 -10
- package/lib/task/verification.ts +1 -15
- package/lib/task/workflow-warning-intents.ts +2 -4
- package/lib/task/write.ts +116 -5
- package/package.json +1 -1
- package/templates/.agents/rules/evidence-reporting.en.md +1 -2
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
- package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -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/create-release-note/SKILL.en.md +17 -11
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- 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/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -78,6 +78,14 @@ function arrayValue(value, label) {
|
|
|
78
78
|
throw new Error(`${label} must be an array`);
|
|
79
79
|
return value;
|
|
80
80
|
}
|
|
81
|
+
function positiveNumberValue(value, label) {
|
|
82
|
+
if (!Number.isSafeInteger(value) || Number(value) <= 0)
|
|
83
|
+
throw new Error(`${label} must be a positive integer`);
|
|
84
|
+
return Number(value);
|
|
85
|
+
}
|
|
86
|
+
function stringArray(value, label) {
|
|
87
|
+
return arrayValue(value, label).map((entry, index) => stringValue(entry, `${label}[${index}]`));
|
|
88
|
+
}
|
|
81
89
|
function stableUnique(values, key, label) {
|
|
82
90
|
const seen = new Set();
|
|
83
91
|
for (const value of values) {
|
|
@@ -88,6 +96,16 @@ function stableUnique(values, key, label) {
|
|
|
88
96
|
}
|
|
89
97
|
return [...values].sort((left, right) => key(left).localeCompare(key(right)));
|
|
90
98
|
}
|
|
99
|
+
function uniqueInOrder(values, key, label) {
|
|
100
|
+
const seen = new Set();
|
|
101
|
+
for (const value of values) {
|
|
102
|
+
const identityKey = key(value);
|
|
103
|
+
if (seen.has(identityKey))
|
|
104
|
+
throw new Error(`${label} contains duplicate identities`);
|
|
105
|
+
seen.add(identityKey);
|
|
106
|
+
}
|
|
107
|
+
return values;
|
|
108
|
+
}
|
|
91
109
|
function declaredIdentityKind(declaration, resourceKind) {
|
|
92
110
|
const kind = declaration?.[resourceKind];
|
|
93
111
|
return kind === 'id' || kind === 'number' || kind === 'key' ? kind : undefined;
|
|
@@ -358,21 +376,73 @@ function validateMetadata(value) {
|
|
|
358
376
|
}
|
|
359
377
|
function validateReleaseNotes(value, declaration) {
|
|
360
378
|
const item = record(value, 'release notes');
|
|
361
|
-
exactKeys(item, ['history', '
|
|
379
|
+
exactKeys(item, ['history', 'commits', 'mergedPullRequests'], 'release notes');
|
|
380
|
+
const releaseAuthor = (raw, label) => {
|
|
381
|
+
if (raw === null)
|
|
382
|
+
return null;
|
|
383
|
+
const entry = record(raw, label);
|
|
384
|
+
exactKeys(entry, ['name', 'login', 'bot', 'resolution'], label);
|
|
385
|
+
if (typeof entry.bot !== 'boolean')
|
|
386
|
+
throw new Error(`${label}.bot must be boolean`);
|
|
387
|
+
if (!['platform-user', 'platform-noreply', 'unresolved'].includes(String(entry.resolution))) {
|
|
388
|
+
throw new Error(`${label}.resolution is invalid`);
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
name: stringValue(entry.name, `${label}.name`, true),
|
|
392
|
+
login: nullableString(entry.login, `${label}.login`),
|
|
393
|
+
bot: entry.bot,
|
|
394
|
+
resolution: entry.resolution
|
|
395
|
+
};
|
|
396
|
+
};
|
|
397
|
+
const issue = (raw, label) => {
|
|
398
|
+
const entry = record(raw, label);
|
|
399
|
+
exactKeys(entry, ['id', 'identity', 'number', 'title', 'displayUrl', 'author'], label);
|
|
400
|
+
return {
|
|
401
|
+
id: stringValue(entry.id, `${label}.id`),
|
|
402
|
+
identity: identity(entry.identity, `${label}.identity`, declaredIdentityKind(declaration, 'issue')),
|
|
403
|
+
number: positiveNumberValue(entry.number, `${label}.number`),
|
|
404
|
+
title: stringValue(entry.title, `${label}.title`, true),
|
|
405
|
+
displayUrl: stringValue(entry.displayUrl, `${label}.displayUrl`, true),
|
|
406
|
+
author: releaseAuthor(entry.author, `${label}.author`)
|
|
407
|
+
};
|
|
408
|
+
};
|
|
362
409
|
return {
|
|
363
|
-
history:
|
|
410
|
+
history: uniqueInOrder(arrayValue(item.history, 'release notes.history').map((raw) => {
|
|
364
411
|
const entry = record(raw, 'release notes.history[]');
|
|
365
|
-
exactKeys(entry, ['
|
|
366
|
-
return {
|
|
367
|
-
}), (entry) => entry.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
412
|
+
exactKeys(entry, ['tag', 'body', 'url'], 'release notes.history[]');
|
|
413
|
+
return { tag: stringValue(entry.tag, 'history.tag'), body: stringValue(entry.body, 'history.body', true), url: nullableString(entry.url, 'history.url') };
|
|
414
|
+
}), (entry) => entry.tag, 'release notes.history'),
|
|
415
|
+
commits: stableUnique(arrayValue(item.commits, 'release notes.commits').map((raw) => {
|
|
416
|
+
const entry = record(raw, 'release notes.commits[]');
|
|
417
|
+
exactKeys(entry, ['sha', 'url', 'pullRequestNumbers', 'authors'], 'release notes.commits[]');
|
|
418
|
+
return {
|
|
419
|
+
sha: stringValue(entry.sha, 'commits.sha'),
|
|
420
|
+
url: nullableString(entry.url, 'commits.url'),
|
|
421
|
+
pullRequestNumbers: stableUnique(arrayValue(entry.pullRequestNumbers, 'commits.pullRequestNumbers').map((number) => positiveNumberValue(number, 'commits.pullRequestNumbers[]')), (number) => String(number), 'commits.pullRequestNumbers'),
|
|
422
|
+
authors: arrayValue(entry.authors, 'commits.authors').map((rawAuthor) => {
|
|
423
|
+
const normalized = releaseAuthor(rawAuthor, 'commits.author');
|
|
424
|
+
if (!normalized)
|
|
425
|
+
throw new Error('commits.author must identify an author');
|
|
426
|
+
return normalized;
|
|
427
|
+
})
|
|
428
|
+
};
|
|
429
|
+
}), (entry) => entry.sha, 'release notes.commits'),
|
|
430
|
+
mergedPullRequests: stableUnique(arrayValue(item.mergedPullRequests, 'release notes.mergedPullRequests').map((raw) => {
|
|
431
|
+
const entry = record(raw, 'release notes.mergedPullRequests[]');
|
|
432
|
+
exactKeys(entry, ['id', 'identity', 'number', 'title', 'body', 'mergedAt', 'displayUrl', 'labels', 'author', 'closingIssues'], 'release notes.mergedPullRequests[]');
|
|
433
|
+
return {
|
|
434
|
+
id: stringValue(entry.id, 'pullRequest.id'),
|
|
435
|
+
identity: identity(entry.identity, 'pullRequest.identity', declaredIdentityKind(declaration, 'pull-request')),
|
|
436
|
+
number: positiveNumberValue(entry.number, 'pullRequest.number'),
|
|
437
|
+
title: stringValue(entry.title, 'pullRequest.title', true),
|
|
438
|
+
body: stringValue(entry.body, 'pullRequest.body', true),
|
|
439
|
+
mergedAt: utcTimestamp(entry.mergedAt, 'pullRequest.mergedAt'),
|
|
440
|
+
displayUrl: stringValue(entry.displayUrl, 'pullRequest.displayUrl', true),
|
|
441
|
+
labels: stringArray(entry.labels, 'pullRequest.labels'),
|
|
442
|
+
author: releaseAuthor(entry.author, 'pullRequest.author'),
|
|
443
|
+
closingIssues: stableUnique(arrayValue(entry.closingIssues, 'pullRequest.closingIssues').map((entry) => issue(entry, 'pullRequest.closingIssues[]')), (entry) => serializeResourceIdentity(entry.identity), 'pullRequest.closingIssues')
|
|
444
|
+
};
|
|
445
|
+
}), (entry) => serializeResourceIdentity(entry.identity), 'release notes.mergedPullRequests')
|
|
376
446
|
};
|
|
377
447
|
}
|
|
378
448
|
function utcTimestamp(value, label) {
|
|
@@ -154,16 +154,16 @@ async function releaseNoteContext(input, options = {}) {
|
|
|
154
154
|
return { ...baseResult(platform.error.retryable ? 'blocked' : 'failed'), error: providerError(platform.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') };
|
|
155
155
|
const commits = commitFacts.map((commit) => {
|
|
156
156
|
const seen = new Set();
|
|
157
|
-
const
|
|
158
|
-
|
|
157
|
+
const facts = platform.value.commits.find((item) => item.sha === commit.oid);
|
|
158
|
+
const authors = (facts?.authors || [])
|
|
159
159
|
.filter((actor) => {
|
|
160
|
-
const key = actor.
|
|
160
|
+
const key = actor.login || actor.name;
|
|
161
161
|
if (seen.has(key))
|
|
162
162
|
return false;
|
|
163
163
|
seen.add(key);
|
|
164
164
|
return true;
|
|
165
165
|
});
|
|
166
|
-
return { ...commit, authors };
|
|
166
|
+
return { ...commit, url: facts?.url || null, pullRequestNumbers: facts?.pullRequestNumbers || [], authors };
|
|
167
167
|
});
|
|
168
168
|
const pullRequests = platform.value.mergedPullRequests
|
|
169
169
|
.map((item) => ({
|
|
@@ -174,14 +174,13 @@ async function releaseNoteContext(input, options = {}) {
|
|
|
174
174
|
url: item.displayUrl || '',
|
|
175
175
|
mergedAt: item.mergedAt || '',
|
|
176
176
|
labels: item.labels || [],
|
|
177
|
-
author: item.author
|
|
178
|
-
closingIssues:
|
|
177
|
+
author: item.author,
|
|
178
|
+
closingIssues: item.closingIssues.map((issue) => ({
|
|
179
179
|
number: issue.number || resourceIdentityNumber(issue.identity) || 0,
|
|
180
180
|
...(issue.identity ? { identity: issue.identity } : {}),
|
|
181
181
|
title: issue.title,
|
|
182
182
|
url: issue.displayUrl || '',
|
|
183
|
-
|
|
184
|
-
author: issue.author ? { login: issue.author.name || issue.author.id || '', bot: false } : null
|
|
183
|
+
author: issue.author
|
|
185
184
|
}))
|
|
186
185
|
}))
|
|
187
186
|
.sort((left, right) => left.number - right.number);
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { resolveBranchWorktree } from "../git/branch-worktree.js";
|
|
6
7
|
import { resolvePlatformProviderContext } from "./context.js";
|
|
7
8
|
import { hasCheckedRequirement, resolveRequirementSection } from "./issue-metadata.js";
|
|
8
9
|
import { requirementSectionAnchors } from "./issues.js";
|
|
@@ -807,7 +808,10 @@ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, sha
|
|
|
807
808
|
return { ok: false, type: "check_failed", message: "Task has no delivery_base_ref for in-label evidence" };
|
|
808
809
|
}
|
|
809
810
|
const branch = typeof task.metadata?.branch === "string" ? task.metadata.branch.trim() : "";
|
|
810
|
-
const gitCwd = branch ?
|
|
811
|
+
const gitCwd = branch ? resolveBranchWorktree(shared.repoRoot, branch) : taskDir;
|
|
812
|
+
if (!gitCwd) {
|
|
813
|
+
return { ok: false, type: "check_failed", message: `Task branch '${branch}' is not checked out in a usable worktree` };
|
|
814
|
+
}
|
|
811
815
|
const changedFilesResult = gitText(["diff", `${baseRef}...HEAD`, "--name-only"], gitCwd);
|
|
812
816
|
if (!changedFilesResult.ok) {
|
|
813
817
|
return { ...changedFilesResult, type: "network_error" };
|
|
@@ -831,26 +835,6 @@ function computeExpectedInLabels(taskDir, repositoryLabels, mappingOverride, sha
|
|
|
831
835
|
}
|
|
832
836
|
return { ok: true, labels: planned.target, mode: "mapped" };
|
|
833
837
|
}
|
|
834
|
-
function worktreeForBranch(repositoryRoot, branch) {
|
|
835
|
-
try {
|
|
836
|
-
const records = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
837
|
-
cwd: repositoryRoot,
|
|
838
|
-
encoding: "utf8",
|
|
839
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
840
|
-
}).trim().split(/\r?\n\r?\n/);
|
|
841
|
-
const expectedRef = `refs/heads/${branch}`;
|
|
842
|
-
for (const record of records) {
|
|
843
|
-
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
844
|
-
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
845
|
-
if (worktree && branchRef === expectedRef)
|
|
846
|
-
return worktree;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
catch {
|
|
850
|
-
// Fall back to taskDir so ordinary in-repository task workspaces retain their behavior.
|
|
851
|
-
}
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
838
|
function loadInLabelMapping(shared) {
|
|
855
839
|
const configPath = path.join(shared.repoRoot, ".agents", ".airc.json");
|
|
856
840
|
if (!fs.existsSync(configPath)) {
|
|
@@ -888,36 +872,15 @@ function resolvePrHeadSha(context) {
|
|
|
888
872
|
if (!branch) {
|
|
889
873
|
return fallback();
|
|
890
874
|
}
|
|
891
|
-
const
|
|
892
|
-
if (!worktreeList.ok) {
|
|
893
|
-
return fallback();
|
|
894
|
-
}
|
|
895
|
-
const matchedWorktree = findWorktreeForBranch(worktreeList.value, branch);
|
|
875
|
+
const matchedWorktree = resolveBranchWorktree(context.taskDir, branch);
|
|
896
876
|
if (!matchedWorktree) {
|
|
897
|
-
return
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
}
|
|
903
|
-
return headInWorktree;
|
|
904
|
-
}
|
|
905
|
-
function findWorktreeForBranch(porcelainOutput, branch) {
|
|
906
|
-
let currentWorktree = "";
|
|
907
|
-
for (const rawLine of String(porcelainOutput || "").split("\n")) {
|
|
908
|
-
const line = rawLine.trimEnd();
|
|
909
|
-
if (line.startsWith("worktree ")) {
|
|
910
|
-
currentWorktree = line.slice("worktree ".length).trim();
|
|
911
|
-
continue;
|
|
912
|
-
}
|
|
913
|
-
if (line.startsWith("branch refs/heads/")) {
|
|
914
|
-
const usedBranch = line.slice("branch refs/heads/".length).trim();
|
|
915
|
-
if (usedBranch === branch && currentWorktree) {
|
|
916
|
-
return currentWorktree;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
877
|
+
return {
|
|
878
|
+
ok: false,
|
|
879
|
+
type: "check_failed",
|
|
880
|
+
message: `Task branch '${branch}' is not checked out in a usable worktree`
|
|
881
|
+
};
|
|
919
882
|
}
|
|
920
|
-
return
|
|
883
|
+
return gitText(["rev-parse", "HEAD"], matchedWorktree);
|
|
921
884
|
}
|
|
922
885
|
function interpolate(template, taskDir, artifactFile) {
|
|
923
886
|
const artifactStem = artifactFile ? path.basename(artifactFile, path.extname(artifactFile)) : "";
|
|
@@ -154,7 +154,7 @@ export async function runInSandbox(request, options = {}) {
|
|
|
154
154
|
allowRecreate: request.recreate,
|
|
155
155
|
recreate: async () => {
|
|
156
156
|
const { create } = await import('./commands/create.js');
|
|
157
|
-
await create([request.taskRef, '--no-refresh']);
|
|
157
|
+
await create([request.taskRef, '--no-refresh'], { runProjectInitCommand: false });
|
|
158
158
|
}
|
|
159
159
|
})).container;
|
|
160
160
|
}
|
|
@@ -18,6 +18,7 @@ import { createSandboxCapabilityPlan, runSandboxHooks, runBoundedSandboxHookComm
|
|
|
18
18
|
import { resolveTools, tmpfsSeedStagingPath, tmpfsSeedTargetPath, toolConfigDirCandidates } from '../tools.js';
|
|
19
19
|
import { assertFreshSandboxReady, hydrateTmpfsSeedEntries, prepareTmpfsMounts, startSandboxControlBroker } from '../recovery.js';
|
|
20
20
|
import { acquireSandboxControlReplacement, assertSandboxControlCutoverSnapshot, beginSandboxControlReplacement, captureSandboxControlCutoverSnapshot, commitSandboxControlReplacement, quiesceSandboxControlRoot, recoverSandboxControlReplacement, readSandboxControlManifest } from '../control/lifecycle.js';
|
|
21
|
+
import { atomicWriteJson } from '../control/state.js';
|
|
21
22
|
import { inspectSandboxControlContainer } from '../control/container-identity.js';
|
|
22
23
|
import { hostJoin, toEnginePath, volumeArg } from '../engines/wsl2-paths.js';
|
|
23
24
|
import { sandboxCoreBindMounts } from '../mounts.js';
|
|
@@ -652,7 +653,24 @@ function readImageLabels(config, engine) {
|
|
|
652
653
|
config.imageName
|
|
653
654
|
]));
|
|
654
655
|
}
|
|
655
|
-
|
|
656
|
+
function readProjectInitCommandDigest(controlRoot) {
|
|
657
|
+
const markerPath = path.join(controlRoot, 'project-init.json');
|
|
658
|
+
if (!fs.existsSync(markerPath))
|
|
659
|
+
return null;
|
|
660
|
+
try {
|
|
661
|
+
const marker = JSON.parse(fs.readFileSync(markerPath, 'utf8'));
|
|
662
|
+
return marker.version === 1 && typeof marker.commandSha256 === 'string' && /^[a-f0-9]{64}$/.test(marker.commandSha256)
|
|
663
|
+
? marker.commandSha256
|
|
664
|
+
: null;
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function writeProjectInitCommandDigest(controlRoot, commandSha256) {
|
|
671
|
+
atomicWriteJson(path.join(controlRoot, 'project-init.json'), { version: 1, commandSha256 });
|
|
672
|
+
}
|
|
673
|
+
export async function create(args, { runProjectInitCommand = true } = {}) {
|
|
656
674
|
const { values, positionals } = parseArgs({
|
|
657
675
|
args,
|
|
658
676
|
allowPositionals: true,
|
|
@@ -720,6 +738,7 @@ export async function create(args) {
|
|
|
720
738
|
identity: target.workspace
|
|
721
739
|
});
|
|
722
740
|
const worktree = worktreeCandidates.find((candidate) => fs.existsSync(candidate)) ?? worktreeCandidates[0] ?? '';
|
|
741
|
+
let worktreeCreated = false;
|
|
723
742
|
const shareCommon = shareCommonDir(effectiveConfig);
|
|
724
743
|
const shareBranch = shareBranchDir(effectiveConfig, branch);
|
|
725
744
|
const preparedDockerfile = prepareDockerfile(effectiveConfig, capabilityPlan.image);
|
|
@@ -821,6 +840,7 @@ export async function create(args) {
|
|
|
821
840
|
toEnginePath(engine, worktree),
|
|
822
841
|
branch
|
|
823
842
|
]);
|
|
843
|
+
worktreeCreated = true;
|
|
824
844
|
}
|
|
825
845
|
else {
|
|
826
846
|
message(`Creating branch '${branch}' from '${baseBranch}'...`);
|
|
@@ -834,6 +854,7 @@ export async function create(args) {
|
|
|
834
854
|
toEnginePath(engine, worktree),
|
|
835
855
|
baseBranch
|
|
836
856
|
]);
|
|
857
|
+
worktreeCreated = true;
|
|
837
858
|
}
|
|
838
859
|
return `Worktree ready at ${worktree}`;
|
|
839
860
|
}
|
|
@@ -886,6 +907,7 @@ export async function create(args) {
|
|
|
886
907
|
let replacementCutover = null;
|
|
887
908
|
try {
|
|
888
909
|
const recoveryResult = await recoverSandboxControlReplacement(controlPaths.root, replacementLease);
|
|
910
|
+
const previousProjectInitCommandDigest = readProjectInitCommandDigest(controlPaths.root);
|
|
889
911
|
const hadExistingControlRoot = hadControlRootBeforeAcquire || recoveryResult === 'restored';
|
|
890
912
|
const previousManifest = fs.existsSync(controlPaths.manifestPath)
|
|
891
913
|
? readSandboxControlManifest(controlPaths.manifestPath)
|
|
@@ -1295,6 +1317,29 @@ export async function create(args) {
|
|
|
1295
1317
|
throw new Error(afterStartFailure.message
|
|
1296
1318
|
?? `Sandbox hook '${afterStartFailure.hookId}' failed.`);
|
|
1297
1319
|
}
|
|
1320
|
+
const initCommand = effectiveConfig.initCommand;
|
|
1321
|
+
const initCommandDigest = initCommand === null ? null : createHash('sha256').update(initCommand).digest('hex');
|
|
1322
|
+
const shouldRunProjectInitCommand = runProjectInitCommand
|
|
1323
|
+
&& initCommand !== null
|
|
1324
|
+
&& initCommandDigest !== null
|
|
1325
|
+
&& (worktreeCreated || previousProjectInitCommandDigest !== initCommandDigest);
|
|
1326
|
+
let projectInitCompleted = !worktreeCreated
|
|
1327
|
+
&& initCommandDigest !== null
|
|
1328
|
+
&& previousProjectInitCommandDigest === initCommandDigest;
|
|
1329
|
+
if (shouldRunProjectInitCommand && initCommand !== null) {
|
|
1330
|
+
try {
|
|
1331
|
+
runVerboseEngine(engine, 'docker', [
|
|
1332
|
+
'exec', '--workdir', '/workspace', container, 'bash', '-lc', initCommand
|
|
1333
|
+
]);
|
|
1334
|
+
projectInitCompleted = true;
|
|
1335
|
+
}
|
|
1336
|
+
catch (error) {
|
|
1337
|
+
throw new Error(`Project initialization failed for '${branch}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (projectInitCompleted && initCommandDigest !== null) {
|
|
1341
|
+
writeProjectInitCommandDigest(controlPaths.root, initCommandDigest);
|
|
1342
|
+
}
|
|
1298
1343
|
replacementLease.release();
|
|
1299
1344
|
replacementLeaseHeld = false;
|
|
1300
1345
|
return 'Container started';
|
|
@@ -109,7 +109,7 @@ export async function enter(args) {
|
|
|
109
109
|
allowRecreate: parsed.recreate,
|
|
110
110
|
recreate: async () => {
|
|
111
111
|
const { create } = await import('./create.js');
|
|
112
|
-
await create([target.requestedRef, '--no-refresh']);
|
|
112
|
+
await create([target.requestedRef, '--no-refresh'], { runProjectInitCommand: false });
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
115
|
const container = ready.container;
|
|
@@ -65,7 +65,7 @@ export async function start(args) {
|
|
|
65
65
|
forceRecreate: parsed.recreate,
|
|
66
66
|
recreate: async () => {
|
|
67
67
|
const { create } = await import('./create.js');
|
|
68
|
-
await create([target.requestedRef, '--no-refresh']);
|
|
68
|
+
await create([target.requestedRef, '--no-refresh'], { runProjectInitCommand: false });
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
const message = ready.path === 'healthy'
|
|
@@ -14,6 +14,7 @@ const DEFAULTS = Object.freeze({
|
|
|
14
14
|
tools: ['agent-infra'],
|
|
15
15
|
refreshIntervalDays: 7,
|
|
16
16
|
dockerfile: null,
|
|
17
|
+
initCommand: null,
|
|
17
18
|
vm: {
|
|
18
19
|
cpu: null,
|
|
19
20
|
memory: null,
|
|
@@ -44,6 +45,7 @@ function cloneDefaults() {
|
|
|
44
45
|
tools: [...DEFAULTS.tools],
|
|
45
46
|
refreshIntervalDays: DEFAULTS.refreshIntervalDays,
|
|
46
47
|
dockerfile: DEFAULTS.dockerfile,
|
|
48
|
+
initCommand: DEFAULTS.initCommand,
|
|
47
49
|
vm: { ...DEFAULTS.vm }
|
|
48
50
|
};
|
|
49
51
|
}
|
|
@@ -77,6 +79,13 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
77
79
|
? [...sandbox.runtimes]
|
|
78
80
|
: defaults.runtimes;
|
|
79
81
|
const dockerfile = typeof sandbox.dockerfile === 'string' ? sandbox.dockerfile : defaults.dockerfile ?? null;
|
|
82
|
+
if (sandbox.initCommand !== undefined &&
|
|
83
|
+
(typeof sandbox.initCommand !== 'string' || sandbox.initCommand.trim().length === 0)) {
|
|
84
|
+
throw new Error('sandbox.initCommand must be a non-empty string when configured');
|
|
85
|
+
}
|
|
86
|
+
const initCommand = typeof sandbox.initCommand === 'string'
|
|
87
|
+
? sandbox.initCommand
|
|
88
|
+
: null;
|
|
80
89
|
if (!dockerfile) {
|
|
81
90
|
let enginesNode;
|
|
82
91
|
try {
|
|
@@ -118,6 +127,7 @@ export function loadConfig({ platformFn = platform, writeStderr = (chunk) => pro
|
|
|
118
127
|
},
|
|
119
128
|
refreshIntervalDays: asNonNegativeIntegerOrDefault(sandbox.refreshIntervalDays, defaults.refreshIntervalDays),
|
|
120
129
|
dockerfile,
|
|
130
|
+
initCommand,
|
|
121
131
|
vm: {
|
|
122
132
|
cpu: asPositiveNumberOrNull(sandbox.vm?.cpu) ?? defaults.vm.cpu,
|
|
123
133
|
memory: asPositiveNumberOrNull(sandbox.vm?.memory) ?? defaults.vm.memory,
|
|
@@ -340,15 +340,18 @@ export function requestSandboxTaskFinalization(params) {
|
|
|
340
340
|
clientError('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization agent is invalid', false);
|
|
341
341
|
const auth = authority(params);
|
|
342
342
|
const issuedAt = Date.now();
|
|
343
|
+
const id = randomUUID();
|
|
344
|
+
const handoffSha256 = (params.prepareHandoff ?? (() => '0'.repeat(64)))({ generation: auth.generation, requestId: id });
|
|
343
345
|
const request = {
|
|
344
346
|
version: 3,
|
|
345
|
-
id
|
|
347
|
+
id,
|
|
346
348
|
...auth,
|
|
347
349
|
issuedAt,
|
|
348
350
|
expiresAt: issuedAt + SANDBOX_CONTROL_ADMISSION_WINDOW_MS,
|
|
349
351
|
family: 'task-finalization',
|
|
350
352
|
operation: 'complete',
|
|
351
353
|
agent,
|
|
354
|
+
handoffSha256,
|
|
352
355
|
args: [],
|
|
353
356
|
controllerProcess: null,
|
|
354
357
|
controllerProof: null
|
|
@@ -2,6 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
5
6
|
import { getProcessStartTime } from '../../server/process-state.js';
|
|
6
7
|
import { createTask, projectTaskCreateResult, taskCreateExitCode, taskCreateFailure } from '../../task/create-service.js';
|
|
7
8
|
import { applyTaskFinalization } from '../../task/finalization.js';
|
|
@@ -13,6 +14,10 @@ import { createSandboxExecutorExecutionContext, dispatchTaskControlOperation, pa
|
|
|
13
14
|
import { assertSandboxControlBrokerOwner, readSandboxControlManifest } from './lifecycle.js';
|
|
14
15
|
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
15
16
|
import { closeCodexControllerRegistration, CodexControllerRegistrationError, openCodexControllerRegistration, resolveCodexControllerBinding } from './controller-registration.js';
|
|
17
|
+
import { shareBranchDir } from '../constants.js';
|
|
18
|
+
function handoffDirectory(manifest) {
|
|
19
|
+
return shareBranchDir({ shareBase: path.join(homedir(), '.agent-infra', 'share', manifest.project) }, manifest.branch);
|
|
20
|
+
}
|
|
16
21
|
function sameBrokerOwner(left, right) {
|
|
17
22
|
return left.version === right.version
|
|
18
23
|
&& left.pid === right.pid
|
|
@@ -295,9 +300,10 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
295
300
|
}
|
|
296
301
|
}
|
|
297
302
|
if (request.family === 'task-finalization') {
|
|
298
|
-
const
|
|
299
|
-
if (
|
|
303
|
+
const parsed = parseTaskControlOperation('task-finalization', [manifest.taskId, 'complete', '--agent', request.agent]);
|
|
304
|
+
if (parsed.family !== 'task-finalization')
|
|
300
305
|
throw new Error('SANDBOX_CONTROL_FINALIZATION_OPERATION_INVALID');
|
|
306
|
+
const operation = { ...parsed, request: { ...parsed.request, handoffSha256: request.handoffSha256 } };
|
|
301
307
|
const result = dispatchTaskControlOperation(createSandboxExecutorExecutionContext({
|
|
302
308
|
repoRoot: manifest.repoRoot,
|
|
303
309
|
worktreeRoot: manifest.worktreeRoot,
|
|
@@ -305,7 +311,8 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
305
311
|
taskId: manifest.taskId,
|
|
306
312
|
generation: manifest.generation,
|
|
307
313
|
manifestPath,
|
|
308
|
-
requestId: request.id
|
|
314
|
+
requestId: request.id,
|
|
315
|
+
handoffDirectory: handoffDirectory(manifest)
|
|
309
316
|
}), operation);
|
|
310
317
|
return finalizationResult(await result);
|
|
311
318
|
}
|
|
@@ -326,7 +333,8 @@ async function executeRequestInner(manifest, manifestPath, request, options = {}
|
|
|
326
333
|
taskId: manifest.taskId,
|
|
327
334
|
generation: manifest.generation,
|
|
328
335
|
manifestPath,
|
|
329
|
-
requestId: request.id
|
|
336
|
+
requestId: request.id,
|
|
337
|
+
handoffDirectory: handoffDirectory(manifest)
|
|
330
338
|
});
|
|
331
339
|
const format = (value) => {
|
|
332
340
|
const result = value;
|
|
@@ -1152,17 +1152,25 @@ export async function quiesceSandboxControlRoot(root, options = {}) {
|
|
|
1152
1152
|
if (!manifest && brokerLive)
|
|
1153
1153
|
throw new Error('SANDBOX_CONTROL_OWNER_MISMATCH');
|
|
1154
1154
|
const platform = options.platform ?? process.platform;
|
|
1155
|
+
const processControl = options.executionProcessControl ?? {
|
|
1156
|
+
isAlive: (execution) => executionAlive(execution, platform, identityProbe),
|
|
1157
|
+
terminate: (execution, phase) => phase === 'graceful'
|
|
1158
|
+
? terminateSandboxControlExecution(execution, { platform, timeoutMs: 0, allowForce: false, identityProbe })
|
|
1159
|
+
: terminateSandboxControlExecution(execution, {
|
|
1160
|
+
platform, timeoutMs: Math.max(0, deadlineAt - Date.now()), deadlineAt, forceAt: Date.now(), identityProbe
|
|
1161
|
+
})
|
|
1162
|
+
};
|
|
1155
1163
|
let executions = manifest ? readExecutions(manifest) : [];
|
|
1156
1164
|
const owner = brokerLive ? broker : statusLive ? statusOwner : null;
|
|
1157
1165
|
if (!owner) {
|
|
1158
1166
|
if (manifest && !broker && !status)
|
|
1159
1167
|
throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_MISSING');
|
|
1160
1168
|
for (const execution of executions) {
|
|
1161
|
-
|
|
1169
|
+
processControl.terminate(execution, 'graceful');
|
|
1162
1170
|
}
|
|
1163
1171
|
const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
|
|
1164
1172
|
while (Date.now() < softDeadlineAt) {
|
|
1165
|
-
if (!executions.some((execution) =>
|
|
1173
|
+
if (!executions.some((execution) => processControl.isAlive(execution)))
|
|
1166
1174
|
break;
|
|
1167
1175
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1168
1176
|
}
|
|
@@ -1171,29 +1179,26 @@ export async function quiesceSandboxControlRoot(root, options = {}) {
|
|
|
1171
1179
|
if (Date.now() >= deadlineAt)
|
|
1172
1180
|
throw new Error('SANDBOX_CONTROL_QUIESCE_DEADLINE_EXCEEDED');
|
|
1173
1181
|
for (const execution of executions) {
|
|
1174
|
-
if (!
|
|
1175
|
-
platform, timeoutMs: Math.max(0, deadlineAt - Date.now()), deadlineAt, forceAt: Date.now(), identityProbe
|
|
1176
|
-
})) {
|
|
1182
|
+
if (!processControl.terminate(execution, 'force')) {
|
|
1177
1183
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${execution.requestId}`);
|
|
1178
1184
|
}
|
|
1179
1185
|
}
|
|
1180
|
-
if (executions.some((execution) =>
|
|
1186
|
+
if (executions.some((execution) => processControl.isAlive(execution))) {
|
|
1181
1187
|
throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
1182
1188
|
}
|
|
1183
1189
|
return broker || status ? 'stale' : 'missing';
|
|
1184
1190
|
}
|
|
1185
|
-
const remaining = () => Math.max(0, deadlineAt - Date.now());
|
|
1186
1191
|
signalOwner(owner, platform, false);
|
|
1187
1192
|
for (const execution of executions) {
|
|
1188
|
-
|
|
1193
|
+
processControl.terminate(execution, 'graceful');
|
|
1189
1194
|
}
|
|
1190
1195
|
const softDeadlineAt = Math.min(forceAt, Date.now() + Math.floor(Math.max(0, forceAt - Date.now()) / 2));
|
|
1191
1196
|
while (Date.now() < softDeadlineAt) {
|
|
1192
|
-
if (!ownerLive(owner, identityProbe) && !executions.some((execution) =>
|
|
1197
|
+
if (!ownerLive(owner, identityProbe) && !executions.some((execution) => processControl.isAlive(execution)))
|
|
1193
1198
|
break;
|
|
1194
1199
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1195
1200
|
}
|
|
1196
|
-
if (!ownerLive(owner, identityProbe) && !executions.some((execution) =>
|
|
1201
|
+
if (!ownerLive(owner, identityProbe) && !executions.some((execution) => processControl.isAlive(execution))) {
|
|
1197
1202
|
return 'stopped';
|
|
1198
1203
|
}
|
|
1199
1204
|
if (manifest)
|
|
@@ -1201,17 +1206,17 @@ export async function quiesceSandboxControlRoot(root, options = {}) {
|
|
|
1201
1206
|
if (Date.now() >= deadlineAt)
|
|
1202
1207
|
throw new Error('SANDBOX_CONTROL_QUIESCE_DEADLINE_EXCEEDED');
|
|
1203
1208
|
for (const execution of executions) {
|
|
1204
|
-
|
|
1209
|
+
processControl.terminate(execution, 'force');
|
|
1205
1210
|
}
|
|
1206
1211
|
signalOwner(owner, platform, true);
|
|
1207
1212
|
while (Date.now() < deadlineAt) {
|
|
1208
|
-
if (!ownerLive(owner, identityProbe) && !executions.some((execution) =>
|
|
1213
|
+
if (!ownerLive(owner, identityProbe) && !executions.some((execution) => processControl.isAlive(execution)))
|
|
1209
1214
|
return 'stopped';
|
|
1210
1215
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1211
1216
|
}
|
|
1212
1217
|
if (ownerLive(owner, identityProbe))
|
|
1213
1218
|
throw new Error('SANDBOX_CONTROL_BROKER_STILL_RUNNING');
|
|
1214
|
-
if (executions.some((execution) =>
|
|
1219
|
+
if (executions.some((execution) => processControl.isAlive(execution)))
|
|
1215
1220
|
throw new Error('SANDBOX_CONTROL_EXECUTION_STILL_RUNNING');
|
|
1216
1221
|
return 'stopped';
|
|
1217
1222
|
}
|
|
@@ -158,11 +158,12 @@ export function validateSandboxControlRequest(value, manifest, options = {}) {
|
|
|
158
158
|
return request;
|
|
159
159
|
}
|
|
160
160
|
if (request.family === 'task-finalization') {
|
|
161
|
-
const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
161
|
+
const expected = ['agent', 'args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'handoffSha256', 'id', 'issuedAt', 'operation', 'token', 'version'];
|
|
162
162
|
if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
|
|
163
163
|
|| request.operation !== 'complete'
|
|
164
164
|
|| !Array.isArray(request.args) || request.args.length !== 0
|
|
165
165
|
|| typeof request.agent !== 'string' || normalizeAgentToken(request.agent) !== request.agent
|
|
166
|
+
|| typeof request.handoffSha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(request.handoffSha256)
|
|
166
167
|
|| request.controllerProcess !== null || request.controllerProof !== null) {
|
|
167
168
|
fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-finalization request schema or authorization is invalid');
|
|
168
169
|
}
|