@fitlab-ai/agent-infra 0.11.7 → 0.11.9
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +8 -2
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/agent-clients/config.js +11 -3
- package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
- package/dist/lib/agent-clients/next-steps.js +1 -1
- package/dist/lib/agent-clients/reconcile.js +3 -3
- package/dist/lib/agent-clients/schema.js +18 -10
- package/dist/lib/defaults.json +6 -3
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/agent-client.js +3 -3
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/platform-pr-review.js +20 -3
- package/dist/lib/internal/task-operation-registry.js +3 -1
- package/dist/lib/internal/task-platform-recovery.js +56 -0
- package/dist/lib/merge.js +25 -5
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/in-label-sync.js +12 -1
- package/dist/lib/platform/issue-comments.js +58 -4
- package/dist/lib/platform/issues.js +105 -5
- package/dist/lib/platform/operation-coordinator.js +74 -0
- package/dist/lib/platform/pr-review.js +107 -1
- package/dist/lib/platform/pr-summary.js +79 -57
- package/dist/lib/platform/pull-requests.js +96 -4
- package/dist/lib/sandbox/commands/create.js +6 -3
- package/dist/lib/sandbox/commands/ls.js +4 -3
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/config.js +22 -4
- package/dist/lib/sandbox/git-metadata.js +50 -0
- package/dist/lib/sandbox/index.js +1 -1
- package/dist/lib/sandbox/mounts.js +12 -0
- package/dist/lib/sandbox/recovery.js +12 -22
- package/dist/lib/sandbox/removal.js +58 -20
- package/dist/lib/sandbox/tools.js +31 -7
- package/dist/lib/task/activity-intent.js +4 -3
- package/dist/lib/task/create-service.js +5 -3
- package/dist/lib/task/create.js +22 -5
- package/dist/lib/task/finalization-artifacts.js +4 -1
- package/dist/lib/task/finalization.js +243 -113
- package/dist/lib/task/lifecycle.js +38 -2
- package/dist/lib/task/platform-operation-journal.js +152 -0
- package/dist/lib/task/platform-operation-recovery.js +459 -0
- package/dist/lib/task/verification.js +0 -1
- package/dist/lib/task/workflow-warning-intents.js +7 -1
- package/dist/lib/update.js +2 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/agent-clients/config.ts +9 -3
- package/lib/agent-clients/custom-tool-invocations.ts +129 -0
- package/lib/agent-clients/next-steps.ts +3 -3
- package/lib/agent-clients/reconcile.ts +3 -3
- package/lib/agent-clients/schema.ts +18 -10
- package/lib/defaults.json +6 -3
- package/lib/init.ts +2 -2
- package/lib/internal/agent-client.ts +3 -3
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/task-operation-registry.ts +3 -1
- package/lib/internal/task-platform-recovery.ts +53 -0
- package/lib/merge.ts +26 -6
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/in-label-sync.ts +12 -0
- package/lib/platform/issue-comments.ts +59 -3
- package/lib/platform/issues.ts +103 -5
- package/lib/platform/operation-coordinator.ts +96 -0
- package/lib/platform/pr-review.ts +113 -1
- package/lib/platform/pr-summary.ts +22 -1
- package/lib/platform/pull-requests.ts +105 -5
- package/lib/sandbox/commands/create.ts +7 -8
- package/lib/sandbox/commands/ls.ts +4 -3
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/config.ts +23 -5
- package/lib/sandbox/git-metadata.ts +66 -0
- package/lib/sandbox/index.ts +1 -1
- package/lib/sandbox/mounts.ts +14 -0
- package/lib/sandbox/recovery.ts +13 -25
- package/lib/sandbox/removal.ts +57 -19
- package/lib/sandbox/tool-types.ts +2 -1
- package/lib/sandbox/tools.ts +38 -7
- package/lib/task/activity-intent.ts +4 -3
- package/lib/task/create-service.ts +5 -3
- package/lib/task/create.ts +17 -5
- package/lib/task/finalization-artifacts.ts +4 -1
- package/lib/task/finalization.ts +242 -106
- package/lib/task/lifecycle.ts +35 -3
- package/lib/task/platform-operation-journal.ts +230 -0
- package/lib/task/platform-operation-recovery.ts +435 -0
- package/lib/task/verification.ts +1 -2
- package/lib/task/workflow-warning-intents.ts +9 -1
- package/lib/update.ts +2 -3
- package/package.json +1 -1
- package/templates/.agents/README.en.md +20 -66
- package/templates/.agents/README.zh-CN.md +19 -65
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +1 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +12 -0
- package/templates/.agents/rules/task-management.zh-CN.md +12 -0
- package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
- package/templates/.agents/skills/review-pr/SKILL.en.md +11 -11
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +11 -11
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +21 -274
- package/dist/lib/agent-clients/custom-tuis.js +0 -99
- package/lib/agent-clients/custom-tuis.ts +0 -149
|
@@ -8,7 +8,7 @@ import { ENGINES, detectEngine, engineDisplayName, isManagedEngine, stopManagedV
|
|
|
8
8
|
import { pruneSandboxDanglingImages } from './image-prune.js';
|
|
9
9
|
import { assertManagedPath, removeManagedDir, removeWorktreeDir } from './managed-fs.js';
|
|
10
10
|
import { run, runEngine, runOk, runOkEngine, runSafe, runSafeEngine } from './shell.js';
|
|
11
|
-
import { parseSandboxWorkspaceIdentity, resolveSandboxCleanupTarget, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
|
|
11
|
+
import { parseSandboxWorkspaceIdentity, resolveSandboxCleanupTarget, resolveSandboxTarget, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
|
|
12
12
|
import { sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
|
|
13
13
|
import { advanceSandboxRemovalJournalToPhase, advanceSandboxRemovalJournalPhase, claimSandboxRemovalJournal, clearSandboxRemovalJournalRecord, isDefaultSandboxRemovalJournal, listSandboxRemovalJournals, removeSandboxControlRoot, readSandboxControlManifest, sandboxRemovalPhaseIndex } from './control/lifecycle.js';
|
|
14
14
|
import { inspectSandboxControlContainer } from './control/container-identity.js';
|
|
@@ -792,38 +792,76 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
792
792
|
p.outro(pc.green('All project sandboxes removed'));
|
|
793
793
|
}
|
|
794
794
|
async function rmUnbound(config, tools, options) {
|
|
795
|
-
return rmUnboundCore(config, tools, options);
|
|
795
|
+
return withRepositoryMutationLock(config.repoRoot, () => rmUnboundCore(config, tools, options));
|
|
796
796
|
}
|
|
797
797
|
async function rmUnboundCore(config, tools, options) {
|
|
798
798
|
const engine = detectEngine(config);
|
|
799
799
|
const listed = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
|
|
800
|
-
const
|
|
800
|
+
const discovered = [...listed.running, ...listed.nonRunning];
|
|
801
|
+
const branchMismatches = [];
|
|
802
|
+
const classified = discovered.map((row) => {
|
|
803
|
+
if (!row.branch)
|
|
804
|
+
return { row, protected: true, cleanupTarget: null };
|
|
805
|
+
if (row.workspaceMode === 'task-bound' && row.taskId) {
|
|
806
|
+
try {
|
|
807
|
+
resolveSandboxTarget(row.taskId, config.repoRoot);
|
|
808
|
+
return { row, protected: true, cleanupTarget: null };
|
|
809
|
+
}
|
|
810
|
+
catch {
|
|
811
|
+
try {
|
|
812
|
+
const cleanupTarget = resolveSandboxCleanupTarget(row.taskId, config.repoRoot, { allowProtected: true });
|
|
813
|
+
if (cleanupTarget.taskState === 'blocked'
|
|
814
|
+
|| cleanupTarget.taskState === 'completed'
|
|
815
|
+
|| cleanupTarget.taskState === 'archive') {
|
|
816
|
+
if (row.branch !== cleanupTarget.branch) {
|
|
817
|
+
branchMismatches.push({ container: row.name, labelBranch: row.branch, taskBranch: cleanupTarget.branch });
|
|
818
|
+
return { row, protected: true, cleanupTarget: null };
|
|
819
|
+
}
|
|
820
|
+
const activeBranchTarget = resolveSandboxTarget(cleanupTarget.branch, config.repoRoot);
|
|
821
|
+
if (activeBranchTarget.workspace.mode === 'branch-only') {
|
|
822
|
+
return { row, protected: false, cleanupTarget };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
// An unresolved task identity has no active short-id evidence, so keep it out of bulk cleanup.
|
|
828
|
+
}
|
|
829
|
+
return { row, protected: true, cleanupTarget: null };
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (row.workspaceMode !== 'branch-only')
|
|
833
|
+
return { row, protected: true, cleanupTarget: null };
|
|
834
|
+
const cleanupTarget = resolveSandboxCleanupTarget(row.branch, config.repoRoot);
|
|
835
|
+
return cleanupTarget.workspace.mode === 'branch-only'
|
|
836
|
+
? { row, protected: false, cleanupTarget }
|
|
837
|
+
: { row, protected: true, cleanupTarget: null };
|
|
838
|
+
});
|
|
839
|
+
const protectedBranches = new Set(classified
|
|
840
|
+
.filter((entry) => entry.protected)
|
|
841
|
+
.map(({ row }) => row.branch));
|
|
842
|
+
const targets = classified
|
|
843
|
+
.filter((entry) => !entry.protected && !protectedBranches.has(entry.row.branch) && entry.cleanupTarget)
|
|
844
|
+
.map(({ row, cleanupTarget }) => ({
|
|
845
|
+
row,
|
|
846
|
+
cleanupTarget: cleanupTarget,
|
|
847
|
+
target: resolveRmTarget(config, tools, cleanupTarget, { discoveredContainers: [row.name] })
|
|
848
|
+
}));
|
|
801
849
|
p.intro(pc.cyan(`Removing sandboxes for ${config.project}`));
|
|
850
|
+
for (const mismatch of branchMismatches) {
|
|
851
|
+
p.log.warn(`Skipping task-bound sandbox '${mismatch.container}': container branch '${mismatch.labelBranch}' differs from task record branch '${mismatch.taskBranch}'. Inspect the task record and container labels before retrying cleanup.`);
|
|
852
|
+
}
|
|
802
853
|
if (options.dryRun) {
|
|
803
|
-
p.outro(`Dry run: ${
|
|
854
|
+
p.outro(`Dry run: ${targets.length} sandbox(es) found, nothing deleted`);
|
|
804
855
|
return;
|
|
805
856
|
}
|
|
806
|
-
for (const row of
|
|
807
|
-
|
|
808
|
-
runSafeEngine(engine, 'docker', ['rm', '-f', row.name]);
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
const cleanupTarget = {
|
|
812
|
-
requestedRef: row.branch,
|
|
813
|
-
branch: row.branch,
|
|
814
|
-
workspace: row.workspaceMode === 'task-bound' && row.taskId
|
|
815
|
-
? { mode: 'task-bound', taskId: row.taskId }
|
|
816
|
-
: { mode: 'branch-only' },
|
|
817
|
-
taskState: 'branch-only'
|
|
818
|
-
};
|
|
819
|
-
const target = resolveRmTarget(config, tools, cleanupTarget, { discoveredContainers: [row.name] });
|
|
820
|
-
await rmOne(config, tools, cleanupTarget.branch, {
|
|
857
|
+
for (const { row, cleanupTarget, target } of targets) {
|
|
858
|
+
await runRmOneUnderRepositoryLock(config, tools, cleanupTarget.branch, {
|
|
821
859
|
assumeYes: options.assumeYes,
|
|
822
860
|
target,
|
|
823
861
|
cleanupTarget
|
|
824
862
|
});
|
|
825
863
|
}
|
|
826
|
-
p.outro(pc.green(`Removed ${
|
|
864
|
+
p.outro(pc.green(`Removed ${targets.length} sandbox(es)`));
|
|
827
865
|
}
|
|
828
866
|
export { authorizeWorktrees, rmOne, rmPurge, rmUnbound };
|
|
829
867
|
//# sourceMappingURL=removal.js.map
|
|
@@ -56,6 +56,15 @@ function asOptionalNonEmptyString(value, field, context) {
|
|
|
56
56
|
}
|
|
57
57
|
return value;
|
|
58
58
|
}
|
|
59
|
+
function asOptionalBoolean(value, field, context) {
|
|
60
|
+
if (value === undefined) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
if (typeof value !== 'boolean') {
|
|
64
|
+
throw new Error(`${context}: field "${field}" must be a boolean when provided`);
|
|
65
|
+
}
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
59
68
|
function asStringRecord(value, field, context) {
|
|
60
69
|
if (value === undefined) {
|
|
61
70
|
return undefined;
|
|
@@ -158,13 +167,14 @@ function parseTmpfs(value, context) {
|
|
|
158
167
|
if (!isPlainObject(value)) {
|
|
159
168
|
throw new Error(`${context}: field "tmpfs" must be an object when provided`);
|
|
160
169
|
}
|
|
170
|
+
const exec = asOptionalBoolean(value.exec, 'tmpfs.exec', context);
|
|
161
171
|
return {
|
|
162
172
|
size: asOptionalNonEmptyString(value.size, 'tmpfs.size', context),
|
|
163
|
-
seed: asStringArray(value.seed, 'tmpfs.seed', context)
|
|
173
|
+
seed: asStringArray(value.seed, 'tmpfs.seed', context),
|
|
174
|
+
...(exec === undefined ? {} : { exec })
|
|
164
175
|
};
|
|
165
176
|
}
|
|
166
|
-
export function parseCustomTool(entry, index, options) {
|
|
167
|
-
const context = `customTools[${index}]`;
|
|
177
|
+
export function parseCustomTool(entry, index, options, context = `customTools[${index}]`) {
|
|
168
178
|
if (!isPlainObject(entry)) {
|
|
169
179
|
throw new Error(`${context} must be an object`);
|
|
170
180
|
}
|
|
@@ -177,6 +187,7 @@ export function parseCustomTool(entry, index, options) {
|
|
|
177
187
|
if (!containerMount.startsWith('/')) {
|
|
178
188
|
throw new Error(`${context}: field "containerMount" must be an absolute path`);
|
|
179
189
|
}
|
|
190
|
+
const invoke = asOptionalNonEmptyString(entry.invoke, 'invoke', context);
|
|
180
191
|
const tool = {
|
|
181
192
|
id,
|
|
182
193
|
name: asOptionalNonEmptyString(entry.name, 'name', context) ?? id,
|
|
@@ -186,6 +197,7 @@ export function parseCustomTool(entry, index, options) {
|
|
|
186
197
|
versionCmd: asOptionalNonEmptyString(entry.versionCmd, 'versionCmd', context) ?? `which ${id}`,
|
|
187
198
|
setupHint: asOptionalNonEmptyString(entry.setupHint, 'setupHint', context)
|
|
188
199
|
?? `Run \`${id}\` inside the container to set up.`,
|
|
200
|
+
...(invoke === undefined ? {} : { invoke }),
|
|
189
201
|
envVars: asStringRecord(entry.envVars, 'envVars', context),
|
|
190
202
|
hostPreSeedFiles: parseHostPreSeedFiles(entry.hostPreSeedFiles, context),
|
|
191
203
|
hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
|
|
@@ -198,13 +210,25 @@ export function parseCustomTool(entry, index, options) {
|
|
|
198
210
|
return tool;
|
|
199
211
|
}
|
|
200
212
|
export function parseCustomTools(value, options) {
|
|
201
|
-
if (value === undefined
|
|
213
|
+
if (value === undefined) {
|
|
202
214
|
return [];
|
|
203
215
|
}
|
|
204
|
-
if (!
|
|
205
|
-
throw new Error('sandbox
|
|
216
|
+
if (!isPlainObject(value)) {
|
|
217
|
+
throw new Error('sandbox.tools.definitions must be an object');
|
|
206
218
|
}
|
|
207
|
-
return value.map((
|
|
219
|
+
return Object.entries(value).map(([id, definition], index) => {
|
|
220
|
+
const context = `sandbox.tools.definitions.${id}`;
|
|
221
|
+
if (!TOOL_ID_PATTERN.test(id)) {
|
|
222
|
+
throw new Error(`${context}: tool id must match ${TOOL_ID_PATTERN.source}`);
|
|
223
|
+
}
|
|
224
|
+
if (!isPlainObject(definition)) {
|
|
225
|
+
throw new Error(`${context} must be an object`);
|
|
226
|
+
}
|
|
227
|
+
if (Object.hasOwn(definition, 'id')) {
|
|
228
|
+
throw new Error(`${context}.id is not allowed; the definition key is the tool id`);
|
|
229
|
+
}
|
|
230
|
+
return parseCustomTool({ ...definition, id }, index, options, context);
|
|
231
|
+
});
|
|
208
232
|
}
|
|
209
233
|
export function resolveTools(config) {
|
|
210
234
|
return [...createSandboxCapabilityPlan(config).tools];
|
|
@@ -11,6 +11,7 @@ const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
|
11
11
|
const VERDICTS = new Set(['approved', 'changes-requested', 'commented']);
|
|
12
12
|
const OUTCOMES = new Set(['aborted', 'superseded']);
|
|
13
13
|
const SUCCESS_STATUSES = new Set(['applied', 'no-op']);
|
|
14
|
+
const LOCAL_COMPLETION_STATUSES = new Set(['applied', 'no-op', 'blocked', 'failed']);
|
|
14
15
|
const ARTIFACT_STATUSES = new Set([
|
|
15
16
|
'pending', 'applied', 'no-op', 'blocked', 'failed', 'aborted', 'superseded'
|
|
16
17
|
]);
|
|
@@ -102,7 +103,7 @@ function readSnapshot(taskRef, repoRoot) {
|
|
|
102
103
|
}
|
|
103
104
|
if (terminal.length === 1) {
|
|
104
105
|
const note = terminal[0].note;
|
|
105
|
-
const terminalMatchesStatus = (note.startsWith('Verdict: ') &&
|
|
106
|
+
const terminalMatchesStatus = (note.startsWith('Verdict: ') && LOCAL_COMPLETION_STATUSES.has(parsed.status))
|
|
106
107
|
|| (note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
|
|
107
108
|
|| (note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
|
|
108
109
|
|| (note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
|
|
@@ -233,8 +234,8 @@ function applyLocked(intent, normalizedAgent, options) {
|
|
|
233
234
|
if (roundState.startedAgent === null) {
|
|
234
235
|
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${base} has no matching started row`, state.taskId);
|
|
235
236
|
}
|
|
236
|
-
if (intent.kind === 'pr-review-complete' && !
|
|
237
|
-
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} must
|
|
237
|
+
if (intent.kind === 'pr-review-complete' && !LOCAL_COMPLETION_STATUSES.has(roundState.status)) {
|
|
238
|
+
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} must record a publication outcome before completion`, state.taskId);
|
|
238
239
|
}
|
|
239
240
|
if (intent.kind === 'pr-review-terminate' && roundState.status !== intent.outcome) {
|
|
240
241
|
return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} status must match outcome ${intent.outcome}`, state.taskId);
|
|
@@ -3,6 +3,7 @@ import { createPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
|
3
3
|
import { VERSION } from '../version.js';
|
|
4
4
|
import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
|
|
5
5
|
import { createLocalTask } from './create.js';
|
|
6
|
+
import { TaskExecutionLockError } from './task-execution-lock.js';
|
|
6
7
|
import { verifyTaskEvent } from './verification.js';
|
|
7
8
|
const DEFAULT_DEPENDENCIES = {
|
|
8
9
|
createIssue: createPlatformIssue,
|
|
@@ -151,8 +152,9 @@ async function createTask(value, options) {
|
|
|
151
152
|
}
|
|
152
153
|
catch (error) {
|
|
153
154
|
const message = error instanceof Error ? error.message : String(error);
|
|
154
|
-
const
|
|
155
|
-
|
|
155
|
+
const lockBusy = error instanceof TaskExecutionLockError && error.code === 'ORCHESTRATION_LOCK_BUSY';
|
|
156
|
+
const code = lockBusy ? error.code : /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
|
|
157
|
+
return taskCreateFailure({ code, message, retryable: lockBusy || code === 'TASK_CREATE_LOCK_TIMEOUT' });
|
|
156
158
|
}
|
|
157
159
|
const operations = [{
|
|
158
160
|
name: 'task:local', status: local.status, reasonCode: null
|
|
@@ -203,7 +205,7 @@ async function createTask(value, options) {
|
|
|
203
205
|
task: local.task, issue, operations, warnings: [], error: verified.error
|
|
204
206
|
};
|
|
205
207
|
return {
|
|
206
|
-
status:
|
|
208
|
+
status: 'degraded',
|
|
207
209
|
changed: local.changed,
|
|
208
210
|
task: local.task,
|
|
209
211
|
issue,
|
package/dist/lib/task/create.js
CHANGED
|
@@ -4,7 +4,7 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { isValidAgentInfraVersion, VERSION } from '../version.js';
|
|
6
6
|
import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
|
|
7
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
7
|
+
import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
|
|
8
8
|
import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.js';
|
|
9
9
|
import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.js';
|
|
10
10
|
import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, parseTaskQualification } from './qualification-audit.js';
|
|
@@ -375,7 +375,7 @@ function createLocalTask(value, options) {
|
|
|
375
375
|
const canonical = canonicalTaskCreateCandidate(candidate);
|
|
376
376
|
const candidateDigest = sha256(canonical);
|
|
377
377
|
const keyDigest = sha256(candidate.idempotencyKey);
|
|
378
|
-
|
|
378
|
+
function createUnderRepositoryLock() {
|
|
379
379
|
const recovered = recoverPublishedTask(repoRoot, keyDigest, candidateDigest);
|
|
380
380
|
if (recovered) {
|
|
381
381
|
return { status: 'no-op', changed: false, task: { id: recovered.taskId, shortId: recovered.shortId, state: recovered.state } };
|
|
@@ -402,12 +402,29 @@ function createLocalTask(value, options) {
|
|
|
402
402
|
fs.writeFileSync(path.join(temporary, 'task.md'), rendered, { flag: 'wx', mode: 0o600 });
|
|
403
403
|
const destination = path.join(activeRoot, taskId);
|
|
404
404
|
fs.renameSync(temporary, destination);
|
|
405
|
-
let shortId;
|
|
406
405
|
const allocated = mutateShortIdRegistry(repoRoot, taskId, 'alloc');
|
|
407
406
|
if (!allocated.shortId)
|
|
408
407
|
throw new Error('TASK_CREATE_SHORT_ID_FAILED');
|
|
409
|
-
shortId
|
|
410
|
-
|
|
408
|
+
return { status: 'applied', changed: true, task: { id: taskId, shortId: allocated.shortId, state: 'active' } };
|
|
409
|
+
}
|
|
410
|
+
return withCreateLock(repoRoot, projectConfig.project, () => {
|
|
411
|
+
let callbackError;
|
|
412
|
+
try {
|
|
413
|
+
return withRepositoryMutationLock(repoRoot, () => {
|
|
414
|
+
try {
|
|
415
|
+
return createUnderRepositoryLock();
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
callbackError = error;
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
if (callbackError !== undefined)
|
|
425
|
+
throw callbackError;
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
411
428
|
});
|
|
412
429
|
}
|
|
413
430
|
export { canonicalTaskCreateCandidate, createLocalTask, validateTaskCreateCandidate };
|
|
@@ -5,7 +5,10 @@ const COMPLETION_BACKFILL_FAMILIES = [
|
|
|
5
5
|
'plan',
|
|
6
6
|
'review-plan',
|
|
7
7
|
'code',
|
|
8
|
-
'review-code'
|
|
8
|
+
'review-code',
|
|
9
|
+
'manual-validation',
|
|
10
|
+
'validation-run',
|
|
11
|
+
'pr-review'
|
|
9
12
|
];
|
|
10
13
|
function inspectCompletionArtifacts(taskRef, options = {}) {
|
|
11
14
|
const artifacts = [];
|