@fitlab-ai/agent-infra 0.11.15 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
- package/dist/lib/decide.js +2 -7
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/manual-validation.js +8 -16
- package/dist/lib/internal/platform-comment.js +3 -2
- package/dist/lib/internal/task-activity.js +1 -12
- package/dist/lib/internal/task-event.js +1 -12
- package/dist/lib/internal/task-ledger.js +1 -12
- package/dist/lib/internal/task-operation-registry.js +2 -1
- package/dist/lib/internal/task-qualification.js +6 -15
- package/dist/lib/internal/task-review.js +3 -16
- package/dist/lib/internal/task-short-id.js +2 -1
- package/dist/lib/internal/task-verify.js +1 -11
- package/dist/lib/internal/task-warning.js +1 -12
- package/dist/lib/internal/task-workspace.js +43 -0
- package/dist/lib/merge.js +4 -3
- package/dist/lib/platform/pr-summary.js +6 -15
- package/dist/lib/platform/pull-requests.js +3 -15
- package/dist/lib/pr-review/evidence-grading.js +3 -2
- package/dist/lib/process-data/index.js +5 -4
- package/dist/lib/process-data/sources.js +6 -0
- package/dist/lib/run/index.js +3 -2
- package/dist/lib/sandbox/commands/create.js +5 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/list-running.js +2 -1
- package/dist/lib/sandbox/control/client.js +8 -13
- package/dist/lib/sandbox/control/completed-reentry.js +2 -1
- package/dist/lib/sandbox/control/task-view.js +4 -2
- package/dist/lib/sandbox/mounts.js +4 -4
- package/dist/lib/sandbox/recovery.js +26 -16
- package/dist/lib/sandbox/removal.js +3 -10
- package/dist/lib/sandbox/task-resolver.js +9 -6
- package/dist/lib/sandbox/workspace-identity.js +6 -5
- package/dist/lib/sandbox/workspace-view.js +29 -22
- package/dist/lib/task/activity-intent.js +2 -12
- package/dist/lib/task/artifact-lifecycle.js +17 -11
- package/dist/lib/task/artifact-operations.js +41 -49
- package/dist/lib/task/capabilities.js +2 -2
- package/dist/lib/task/commands/ls.js +4 -3
- package/dist/lib/task/commands/status.js +1 -1
- package/dist/lib/task/commit-operation.js +6 -6
- package/dist/lib/task/control-authority.js +32 -32
- package/dist/lib/task/create-service.js +2 -4
- package/dist/lib/task/create.js +12 -65
- package/dist/lib/task/delegation-receipts.js +1 -1
- package/dist/lib/task/delivery.js +2 -8
- package/dist/lib/task/events.js +5 -31
- package/dist/lib/task/finalization.js +17 -22
- package/dist/lib/task/lifecycle.js +9 -48
- package/dist/lib/task/local-artifact-finalization.js +1 -2
- package/dist/lib/task/orchestration.js +24 -57
- package/dist/lib/task/platform-operation-recovery.js +1 -2
- package/dist/lib/task/resolve-ref.js +17 -12
- package/dist/lib/task/review-finalization.js +2 -35
- package/dist/lib/task/review-identity.js +3 -1
- package/dist/lib/task/short-id.js +10 -8
- package/dist/lib/task/verification-engine.js +19 -58
- package/dist/lib/task/workspace-root.js +59 -0
- package/dist/lib/task/workspace-snapshot.js +4 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
- package/lib/decide.ts +2 -7
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/manual-validation.ts +8 -19
- package/lib/internal/platform-comment.ts +3 -2
- package/lib/internal/task-activity.ts +1 -10
- package/lib/internal/task-event.ts +1 -10
- package/lib/internal/task-ledger.ts +1 -10
- package/lib/internal/task-operation-registry.ts +2 -1
- package/lib/internal/task-qualification.ts +5 -11
- package/lib/internal/task-review.ts +3 -14
- package/lib/internal/task-short-id.ts +2 -1
- package/lib/internal/task-verify.ts +1 -9
- package/lib/internal/task-warning.ts +1 -10
- package/lib/internal/task-workspace.ts +39 -0
- package/lib/merge.ts +4 -3
- package/lib/platform/pr-summary.ts +7 -16
- package/lib/platform/pull-requests.ts +3 -24
- package/lib/pr-review/evidence-grading.ts +3 -2
- package/lib/process-data/index.ts +4 -3
- package/lib/process-data/sources.ts +6 -0
- package/lib/run/index.ts +3 -2
- package/lib/sandbox/commands/create.ts +5 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/list-running.ts +2 -1
- package/lib/sandbox/control/client.ts +7 -12
- package/lib/sandbox/control/completed-reentry.ts +2 -1
- package/lib/sandbox/control/task-view.ts +4 -2
- package/lib/sandbox/mounts.ts +4 -4
- package/lib/sandbox/recovery.ts +26 -18
- package/lib/sandbox/removal.ts +3 -26
- package/lib/sandbox/task-resolver.ts +9 -6
- package/lib/sandbox/workspace-identity.ts +6 -5
- package/lib/sandbox/workspace-view.ts +28 -20
- package/lib/task/activity-intent.ts +3 -17
- package/lib/task/artifact-lifecycle.ts +39 -11
- package/lib/task/artifact-operations.ts +28 -35
- package/lib/task/capabilities.ts +2 -2
- package/lib/task/commands/ls.ts +4 -3
- package/lib/task/commands/status.ts +1 -1
- package/lib/task/commit-operation.ts +5 -6
- package/lib/task/control-authority.ts +29 -35
- package/lib/task/create-service.ts +2 -4
- package/lib/task/create.ts +10 -51
- package/lib/task/delegation-receipts.ts +1 -1
- package/lib/task/delivery.ts +2 -10
- package/lib/task/events.ts +5 -32
- package/lib/task/finalization.ts +16 -24
- package/lib/task/lifecycle.ts +9 -50
- package/lib/task/local-artifact-finalization.ts +1 -4
- package/lib/task/orchestration.ts +31 -80
- package/lib/task/platform-operation-recovery.ts +1 -2
- package/lib/task/resolve-ref.ts +18 -12
- package/lib/task/review-finalization.ts +2 -52
- package/lib/task/review-identity.ts +3 -1
- package/lib/task/short-id.ts +10 -8
- package/lib/task/verification-engine.ts +20 -62
- package/lib/task/workspace-root.ts +78 -0
- package/lib/task/workspace-snapshot.ts +4 -3
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +19 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
- package/templates/.agents/README.en.md +10 -15
- package/templates/.agents/README.zh-CN.md +9 -13
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +2 -2
- package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/release-commands.en.md +3 -0
- package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +0 -2
- package/templates/.agents/rules/task-management.zh-CN.md +0 -2
- package/templates/.agents/rules/task-short-id.en.md +2 -2
- package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
- package/templates/.agents/scripts/task-short-id.js +2 -8
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/config/verify.json +0 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- 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/commit/config/verify.json +0 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
- 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/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -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 +7 -7
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/create-pr/config/verify.json +0 -1
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/config/verify.json +0 -1
- 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-codescan/config/verify.json +0 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/config/verify.json +0 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/restore-task/config/verify.json +0 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +0 -3
- package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/watch-pr/SKILL.en.md +3 -3
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/templates/handoff.en.md +1 -1
- package/templates/.agents/templates/handoff.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +1 -1
- package/templates/.agents/workflows/refactoring.en.yaml +1 -1
- package/dist/lib/task/task-execution-lock.js +0 -250
- package/lib/task/task-execution-lock.ts +0 -320
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
|
|
3
4
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
@@ -29,8 +30,6 @@ import {
|
|
|
29
30
|
} from './finalization.ts';
|
|
30
31
|
import {
|
|
31
32
|
applyTaskLifecycle,
|
|
32
|
-
applyTaskLifecycleWithinTaskLock,
|
|
33
|
-
taskLifecycleFailure,
|
|
34
33
|
type TaskLifecycleRequest,
|
|
35
34
|
type TaskLifecycleResult
|
|
36
35
|
} from './lifecycle.ts';
|
|
@@ -39,8 +38,8 @@ import type {
|
|
|
39
38
|
AgentClientLifecycleRecoveryResult as LifecycleRecoveryResult
|
|
40
39
|
} from '../agent-clients/adapter.ts';
|
|
41
40
|
import { resolveTaskContext, resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
42
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
43
41
|
import { verifyTaskEvent } from './verification.ts';
|
|
42
|
+
import { releaseStaleShortIdRegistry } from './short-id.ts';
|
|
44
43
|
|
|
45
44
|
export type TaskControlExecutionContext =
|
|
46
45
|
| Readonly<{
|
|
@@ -215,7 +214,7 @@ async function applyLifecycleWithAuthority(
|
|
|
215
214
|
): Promise<TaskLifecycleResult | LifecycleRecoveryResult> {
|
|
216
215
|
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
217
216
|
const controlRoot = taskId ? controlRootForTask(context, taskId) : undefined;
|
|
218
|
-
const execute = async (
|
|
217
|
+
const execute = async (): Promise<TaskLifecycleResult | LifecycleRecoveryResult> => {
|
|
219
218
|
if (request.intent === 'recover-started') {
|
|
220
219
|
const adapter = isAgentClientId(request.agent)
|
|
221
220
|
? getAgentClientAdapter(request.agent).orchestrationAdapter
|
|
@@ -229,38 +228,13 @@ async function applyLifecycleWithAuthority(
|
|
|
229
228
|
}
|
|
230
229
|
return adapter.recoverStarted(request as LifecycleRecoveryRequest, { repoRoot: context.repoRoot });
|
|
231
230
|
}
|
|
232
|
-
return
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}, taskId!)
|
|
238
|
-
: applyTaskLifecycle(request, {
|
|
239
|
-
repoRoot: context.repoRoot,
|
|
240
|
-
...(controlRoot ? { controlRoot } : {}),
|
|
241
|
-
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
|
|
242
|
-
});
|
|
231
|
+
return applyTaskLifecycle(request, {
|
|
232
|
+
repoRoot: context.repoRoot,
|
|
233
|
+
...(controlRoot ? { controlRoot } : {}),
|
|
234
|
+
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
|
|
235
|
+
});
|
|
243
236
|
};
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
return await withTaskExecutionLock(
|
|
247
|
-
context.repoRoot,
|
|
248
|
-
taskId,
|
|
249
|
-
`task-lifecycle.${request.intent}`,
|
|
250
|
-
() => execute(true)
|
|
251
|
-
);
|
|
252
|
-
} catch (error) {
|
|
253
|
-
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
254
|
-
if (request.intent === 'recover-started') {
|
|
255
|
-
return {
|
|
256
|
-
status: 'owner-unknown', changed: false, targetState: 'active',
|
|
257
|
-
requestRef: request.taskRef, intent: 'recover-started', taskId,
|
|
258
|
-
receiptId: null, childId: null,
|
|
259
|
-
error: { code: error.code, message: error.message }
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
263
|
-
}
|
|
237
|
+
return await execute();
|
|
264
238
|
}
|
|
265
239
|
|
|
266
240
|
function controlRootForTask(context: TaskControlExecutionContext, taskId: string): string | undefined {
|
|
@@ -393,6 +367,26 @@ export function dispatchTaskControlOperation(
|
|
|
393
367
|
preflight: (request, options) => verifyTaskEvent(
|
|
394
368
|
{ ...request, event: 'complete-task.hard-preflight' }, options
|
|
395
369
|
)
|
|
370
|
+
}).then((result) => {
|
|
371
|
+
if (context.source !== 'direct-host' || !controlRoot || result.status !== 'completed' || !result.taskId) return result;
|
|
372
|
+
try {
|
|
373
|
+
if (fs.lstatSync(controlRoot, { throwIfNoEntry: false }) === undefined) {
|
|
374
|
+
const released = releaseStaleShortIdRegistry(context.repoRoot, result.taskId);
|
|
375
|
+
return { ...result, changed: result.changed || released.changed };
|
|
376
|
+
}
|
|
377
|
+
return result;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
return {
|
|
380
|
+
...result,
|
|
381
|
+
status: 'blocked',
|
|
382
|
+
result: 'blocked',
|
|
383
|
+
error: {
|
|
384
|
+
code: 'TASK_FINALIZATION_SHORT_ID_RELEASE_FAILED',
|
|
385
|
+
message: error instanceof Error ? error.message : String(error),
|
|
386
|
+
retryable: true
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
396
390
|
});
|
|
397
391
|
}
|
|
398
392
|
try {
|
|
@@ -4,7 +4,6 @@ import type { PlatformResult } from '../platform/types.ts';
|
|
|
4
4
|
import { VERSION } from '../version.ts';
|
|
5
5
|
import { applyWorkflowWarningIntent } from './workflow-warning-intents.ts';
|
|
6
6
|
import { createLocalTask, type TaskCreateCandidateV1 } from './create.ts';
|
|
7
|
-
import { TaskExecutionLockError } from './task-execution-lock.ts';
|
|
8
7
|
import { verifyTaskEvent } from './verification.ts';
|
|
9
8
|
|
|
10
9
|
type TaskCreateStatus = 'applied' | 'no-op' | 'degraded' | 'failed' | 'blocked';
|
|
@@ -201,9 +200,8 @@ async function createTask(value: unknown, options: CreateTaskOptions): Promise<T
|
|
|
201
200
|
});
|
|
202
201
|
} catch (error) {
|
|
203
202
|
const message = error instanceof Error ? error.message : String(error);
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
return taskCreateFailure({ code, message, retryable: lockBusy || code === 'TASK_CREATE_LOCK_TIMEOUT' });
|
|
203
|
+
const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
|
|
204
|
+
return taskCreateFailure({ code, message, retryable: false });
|
|
207
205
|
}
|
|
208
206
|
|
|
209
207
|
const operations: TaskCreateOperation[] = [{
|
package/lib/task/create.ts
CHANGED
|
@@ -5,13 +5,13 @@ import path from 'node:path';
|
|
|
5
5
|
|
|
6
6
|
import { isValidAgentInfraVersion, VERSION } from '../version.ts';
|
|
7
7
|
import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.ts';
|
|
8
|
-
import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
9
8
|
import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.ts';
|
|
10
9
|
import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.ts';
|
|
11
10
|
import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, parseTaskQualification } from './qualification-audit.ts';
|
|
12
11
|
import { enumerateAllTaskDirs } from './resolve-ref.ts';
|
|
13
12
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
14
13
|
import { AGENT_TOKEN_VALUES, type AgentToken } from '../agent-clients/tokens.ts';
|
|
14
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
15
15
|
|
|
16
16
|
const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'] as const;
|
|
17
17
|
const PRIORITIES = ['Urgent', 'High', 'Medium', 'Low'] as const;
|
|
@@ -348,42 +348,6 @@ function ensureRealDirectory(directory: string, code: string): void {
|
|
|
348
348
|
fs.mkdirSync(directory, { mode: 0o700 });
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
function createLockRoot(project: string): string {
|
|
352
|
-
const root = path.join(os.homedir(), '.agent-infra');
|
|
353
|
-
const directories = [root, path.join(root, 'run'), path.join(root, 'run', project), path.join(root, 'run', project, 'task-create-locks')];
|
|
354
|
-
for (const directory of directories) {
|
|
355
|
-
if (fs.existsSync(directory)) {
|
|
356
|
-
assertRealDirectory(directory, 'TASK_CREATE_LOCK_FAILED');
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
try {
|
|
360
|
-
fs.mkdirSync(directory, { mode: 0o700 });
|
|
361
|
-
} catch (error) {
|
|
362
|
-
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
363
|
-
assertRealDirectory(directory, 'TASK_CREATE_LOCK_FAILED');
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return directories.at(-1)!;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function withCreateLock<T>(repoRoot: string, project: string, operation: () => T): T {
|
|
370
|
-
const lockRoot = createLockRoot(project);
|
|
371
|
-
let callbackError: unknown;
|
|
372
|
-
try {
|
|
373
|
-
return withTaskExecutionLock(repoRoot, 'task-create', 'task-create', () => {
|
|
374
|
-
try { return operation(); }
|
|
375
|
-
catch (error) { callbackError = error; throw error; }
|
|
376
|
-
}, { lockRoot, identityScope: `task-create\0${project}` });
|
|
377
|
-
} catch (error) {
|
|
378
|
-
if (callbackError !== undefined) throw callbackError;
|
|
379
|
-
if (error instanceof TaskExecutionLockError && error.code === 'ORCHESTRATION_LOCK_BUSY') {
|
|
380
|
-
throw new Error('TASK_CREATE_LOCK_TIMEOUT');
|
|
381
|
-
}
|
|
382
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
383
|
-
throw new Error(`TASK_CREATE_LOCK_FAILED: ${message}`);
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
351
|
function recoverPublishedTask(repoRoot: string, keyDigest: string, candidateDigest: string): { taskId: string; shortId: string | null; state: 'active' | 'blocked' | 'completed' | 'archive' } | null {
|
|
388
352
|
for (const task of enumerateAllTaskDirs(repoRoot)) {
|
|
389
353
|
const taskMd = path.join(task.taskDir, 'task.md');
|
|
@@ -410,16 +374,22 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
410
374
|
throw new Error('TASK_CREATE_VERSION_INVALID: agentInfraVersion must be a valid v-prefixed semver');
|
|
411
375
|
}
|
|
412
376
|
const repoRoot = fs.realpathSync.native(options.repoRoot);
|
|
413
|
-
const workspaceRoot =
|
|
377
|
+
const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
414
378
|
const activeRoot = path.join(workspaceRoot, 'active');
|
|
415
379
|
ensureRealDirectory(path.join(repoRoot, '.agents'), 'TASK_CREATE_WORKSPACE_INVALID');
|
|
380
|
+
const home = path.resolve(process.env.HOME || os.homedir());
|
|
381
|
+
const managedRoots = [path.join(home, '.agent-infra'), path.join(home, '.agent-infra', 'task-workspaces')];
|
|
382
|
+
for (const root of managedRoots) {
|
|
383
|
+
if (fs.existsSync(root)) assertRealDirectory(root, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
384
|
+
else fs.mkdirSync(root, { mode: 0o700 });
|
|
385
|
+
}
|
|
416
386
|
ensureRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
417
387
|
ensureRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
|
|
418
388
|
const projectConfig = readProject(repoRoot);
|
|
419
389
|
const canonical = canonicalTaskCreateCandidate(candidate);
|
|
420
390
|
const candidateDigest = sha256(canonical);
|
|
421
391
|
const keyDigest = sha256(candidate.idempotencyKey);
|
|
422
|
-
function
|
|
392
|
+
function createOnce(): LocalTaskCreateResult {
|
|
423
393
|
const recovered = recoverPublishedTask(repoRoot, keyDigest, candidateDigest);
|
|
424
394
|
if (recovered) {
|
|
425
395
|
return { status: 'no-op', changed: false, task: { id: recovered.taskId, shortId: recovered.shortId, state: recovered.state } };
|
|
@@ -451,18 +421,7 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
|
|
|
451
421
|
return { status: 'applied', changed: true, task: { id: taskId, shortId: allocated.shortId, state: 'active' } };
|
|
452
422
|
}
|
|
453
423
|
|
|
454
|
-
return
|
|
455
|
-
let callbackError: unknown;
|
|
456
|
-
try {
|
|
457
|
-
return withRepositoryMutationLock(repoRoot, () => {
|
|
458
|
-
try { return createUnderRepositoryLock(); }
|
|
459
|
-
catch (error) { callbackError = error; throw error; }
|
|
460
|
-
});
|
|
461
|
-
} catch (error) {
|
|
462
|
-
if (callbackError !== undefined) throw callbackError;
|
|
463
|
-
throw error;
|
|
464
|
-
}
|
|
465
|
-
});
|
|
424
|
+
return createOnce();
|
|
466
425
|
|
|
467
426
|
}
|
|
468
427
|
|
|
@@ -263,7 +263,7 @@ function validateSealRequest(
|
|
|
263
263
|
|
|
264
264
|
function validateReviewerSealPaths(receipt: DelegationReceipt, changedPaths: readonly string[]): ReceiptFailure | null {
|
|
265
265
|
if (receipt.role !== 'reviewer') return null;
|
|
266
|
-
const taskRoot =
|
|
266
|
+
const taskRoot = `task-workspace/active/${receipt.taskId}/`;
|
|
267
267
|
const allowed = new Set([
|
|
268
268
|
`${taskRoot}${receipt.artifact}`,
|
|
269
269
|
`${taskRoot}task.md`,
|
package/lib/task/delivery.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
7
7
|
import { resolveDeliveryTarget, validateBaseRef, validateRemote } from './delivery-target.ts';
|
|
8
8
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
9
|
-
import { withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
10
9
|
|
|
11
10
|
export type DeliveryState = 'absent' | 'same' | 'known-old' | 'unknown-drift';
|
|
12
11
|
|
|
@@ -50,7 +49,7 @@ function failure(taskId: string | null, code: string, message: string, retryable
|
|
|
50
49
|
return { status, changed: false, taskId, remoteHead: null, localHead: null, state: null, error: { code, message, retryable } };
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
function
|
|
52
|
+
function deliverTaskBranchCore(
|
|
54
53
|
taskId: string,
|
|
55
54
|
taskDir: string,
|
|
56
55
|
repoRoot: string,
|
|
@@ -108,14 +107,7 @@ function deliverUnlocked(
|
|
|
108
107
|
function deliverTaskBranch(taskRef: string, options: { repoRoot?: string; agent: string; remote?: string; baseRef?: string; dryRun?: boolean }): TaskBranchDeliveryResult {
|
|
109
108
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
110
109
|
if (!resolved.ok) return failure(resolved.taskId, resolved.code, resolved.message);
|
|
111
|
-
|
|
112
|
-
return withRepositoryMutationLock(resolved.repoRoot, () => withTaskExecutionLock(
|
|
113
|
-
resolved.repoRoot, resolved.taskId, 'task-delivery',
|
|
114
|
-
() => deliverUnlocked(resolved.taskId, resolved.taskDir, resolved.repoRoot, options)
|
|
115
|
-
));
|
|
116
|
-
} catch (error) {
|
|
117
|
-
return failure(resolved.taskId, 'DELIVERY_LOCK_FAILED', error instanceof Error ? error.message : String(error), true, 'blocked');
|
|
118
|
-
}
|
|
110
|
+
return deliverTaskBranchCore(resolved.taskId, resolved.taskDir, resolved.repoRoot, options);
|
|
119
111
|
}
|
|
120
112
|
|
|
121
113
|
export { classifyDeliveryState, deliverTaskBranch };
|
package/lib/task/events.ts
CHANGED
|
@@ -31,7 +31,6 @@ import { findSectionHeading } from './sections.ts';
|
|
|
31
31
|
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
32
32
|
import { commitOrchestrationStageCompletion } from './orchestration.ts';
|
|
33
33
|
import type { OrchestrationStageCompletion } from './orchestration.ts';
|
|
34
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
35
34
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
36
35
|
import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
|
|
37
36
|
import { validateLocalArtifact } from './local-artifact-finalization.ts';
|
|
@@ -86,7 +85,6 @@ type TaskEventRequest = {
|
|
|
86
85
|
type TaskEventError = { code: TaskEventErrorCode; message: string };
|
|
87
86
|
type TaskEventOptions = TaskWriteOptions & {
|
|
88
87
|
commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
|
|
89
|
-
lockAlreadyHeld?: boolean;
|
|
90
88
|
};
|
|
91
89
|
type TaskEventResult = {
|
|
92
90
|
status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
|
|
@@ -519,6 +517,7 @@ function reworkIntentMutationForCompletion(
|
|
|
519
517
|
function buildCompletionReceipt(
|
|
520
518
|
content: string,
|
|
521
519
|
taskDir: string,
|
|
520
|
+
repositoryRoot: string | undefined,
|
|
522
521
|
family: EventFamily,
|
|
523
522
|
artifact: ArtifactIdentity,
|
|
524
523
|
completedAt: string,
|
|
@@ -527,7 +526,7 @@ function buildCompletionReceipt(
|
|
|
527
526
|
): { ok: true; receipts: readonly ArtifactReceipt[] } | { ok: false; message: string } | null {
|
|
528
527
|
if (!['analyze', 'plan', 'code'].includes(family) && !family.startsWith('review-')) return null;
|
|
529
528
|
if (family === 'review-code') {
|
|
530
|
-
const identity = inspectReviewIdentity(taskDir, fs.readFileSync(artifact.path, 'utf8'));
|
|
529
|
+
const identity = inspectReviewIdentity(taskDir, fs.readFileSync(artifact.path, 'utf8'), repositoryRoot);
|
|
531
530
|
if (identity.status !== 'matched') return { ok: false, message: `review-code identity is ${identity.status}: ${identity.message}` };
|
|
532
531
|
}
|
|
533
532
|
const event = family === 'analyze' ? 'analysis.completed'
|
|
@@ -627,7 +626,7 @@ function replaceCompletionFact(facts: readonly CompletionFact[], next: Completio
|
|
|
627
626
|
return [...facts.filter((fact) => !(fact.event === next.event && fact.output === next.output)), next];
|
|
628
627
|
}
|
|
629
628
|
|
|
630
|
-
function
|
|
629
|
+
function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {}): TaskEventResult {
|
|
631
630
|
const invalid = validateTaskEventRequest(request);
|
|
632
631
|
if (invalid) return failed(request, invalid);
|
|
633
632
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
@@ -757,7 +756,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
757
756
|
}
|
|
758
757
|
if (eventIdentity.phase === 'started') {
|
|
759
758
|
const trigger = eventTrigger(normalized, eventIdentity.family);
|
|
760
|
-
const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state);
|
|
759
|
+
const facts = buildLifecycleFacts(resolved.taskDir, content, resolved.state, false, resolved.repoRoot);
|
|
761
760
|
if (!facts.ok) return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${facts.code}: ${facts.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
762
761
|
const capability = canStart(lifecycleAction(eventIdentity.family), facts.facts, trigger);
|
|
763
762
|
const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
@@ -804,7 +803,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
804
803
|
let currentFact: CompletionFact | null = null;
|
|
805
804
|
let completionRework: ReturnType<typeof reworkIntentMutation> | null = null;
|
|
806
805
|
if (eventIdentity.phase === 'completed' && completedArtifact) {
|
|
807
|
-
const receipt = buildCompletionReceipt(content, resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter, normalized);
|
|
806
|
+
const receipt = buildCompletionReceipt(content, resolved.taskDir, resolved.repoRoot, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter, normalized);
|
|
808
807
|
if (receipt && !receipt.ok) {
|
|
809
808
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: receipt.message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
810
809
|
}
|
|
@@ -978,32 +977,6 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
978
977
|
};
|
|
979
978
|
}
|
|
980
979
|
|
|
981
|
-
function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {}): TaskEventResult {
|
|
982
|
-
const invalid = validateTaskEventRequest(request);
|
|
983
|
-
if (invalid) return failed(request, invalid);
|
|
984
|
-
const parts = eventParts(request.event);
|
|
985
|
-
if (options.lockAlreadyHeld) return applyTaskEventUnlocked(request, options);
|
|
986
|
-
if (request.dryRun || !['started', 'completed'].includes(parts.phase)) {
|
|
987
|
-
return applyTaskEventUnlocked(request, options);
|
|
988
|
-
}
|
|
989
|
-
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
990
|
-
if (!resolved.ok) return applyTaskEventUnlocked(request, options);
|
|
991
|
-
try {
|
|
992
|
-
return withTaskExecutionLock(
|
|
993
|
-
resolved.repoRoot,
|
|
994
|
-
resolved.taskId,
|
|
995
|
-
`task-event.${request.event}`,
|
|
996
|
-
() => applyTaskEventUnlocked(request, { ...options, lockAlreadyHeld: true })
|
|
997
|
-
);
|
|
998
|
-
} catch (error) {
|
|
999
|
-
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
1000
|
-
return failed(request, {
|
|
1001
|
-
code: 'EVENT_TRANSITION_INVALID',
|
|
1002
|
-
message: `${error.code}: ${error.message}`
|
|
1003
|
-
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
980
|
function successNoOp(
|
|
1008
981
|
request: TaskEventRequest, taskId: string, taskMdPath: string, currentStep: string,
|
|
1009
982
|
eventIdentity: ReturnType<typeof identity>, timestamp: string,
|
package/lib/task/finalization.ts
CHANGED
|
@@ -14,7 +14,6 @@ import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
|
14
14
|
import { recoverPlatformOperations } from './platform-operation-recovery.ts';
|
|
15
15
|
import {
|
|
16
16
|
applyTaskLifecycle,
|
|
17
|
-
applyTaskLifecycleWithinTaskLock,
|
|
18
17
|
inspectTaskLifecycleProgress,
|
|
19
18
|
type TaskLifecycleOptions,
|
|
20
19
|
type TaskLifecycleRequest,
|
|
@@ -24,7 +23,6 @@ import {
|
|
|
24
23
|
} from './lifecycle.ts';
|
|
25
24
|
import { enumerateAllTaskDirs, resolveTaskRef } from './resolve-ref.ts';
|
|
26
25
|
import { inspectShortIdRegistry } from './short-id.ts';
|
|
27
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
28
26
|
import { verifyTaskEvent } from './verification.ts';
|
|
29
27
|
import type { TaskVerificationResult } from './verification.ts';
|
|
30
28
|
import { projectFinalizationWarning } from './workflow-warning-intents.ts';
|
|
@@ -586,7 +584,7 @@ function mutationPatch(current: TaskFinalizationReceipt, mutation: FinalizationM
|
|
|
586
584
|
: { warningProjection: 'pending', lastError: mutation.error };
|
|
587
585
|
}
|
|
588
586
|
|
|
589
|
-
function
|
|
587
|
+
function mutateFinalizationReceipt(
|
|
590
588
|
repoRoot: string,
|
|
591
589
|
receipt: TaskFinalizationReceipt,
|
|
592
590
|
capability: FinalizationCapability,
|
|
@@ -615,9 +613,7 @@ function applyFinalizationReceiptMutation(
|
|
|
615
613
|
if (!hasFinalizationTaskDocument(repoRoot, receipt.taskId)) {
|
|
616
614
|
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires an active task or a completed task');
|
|
617
615
|
}
|
|
618
|
-
return
|
|
619
|
-
applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set<string>())
|
|
620
|
-
);
|
|
616
|
+
return mutateFinalizationReceipt(repoRoot, receipt, capability, mutation, new Set<string>());
|
|
621
617
|
}
|
|
622
618
|
|
|
623
619
|
function warningKey(warning: Pick<FinalizationWarning, 'step' | 'code' | 'target'>): string {
|
|
@@ -640,20 +636,20 @@ function reconcileWarningProjection(repoRoot: string, taskId: string, receipt: T
|
|
|
640
636
|
retryable: true
|
|
641
637
|
};
|
|
642
638
|
const capability = issueCapability(receipt, 'warning-projection');
|
|
643
|
-
return
|
|
639
|
+
return mutateFinalizationReceipt(repoRoot, receipt, capability, { scope: 'warning-projection', operation: 'failed', error: detail }, consumed);
|
|
644
640
|
}
|
|
645
641
|
} catch (error) {
|
|
646
642
|
const detail = errorOf(error, 'FINALIZATION_WARNING_PROJECTION_FAILED', true);
|
|
647
643
|
try {
|
|
648
644
|
const capability = issueCapability(receipt, 'warning-projection');
|
|
649
|
-
return
|
|
645
|
+
return mutateFinalizationReceipt(repoRoot, receipt, capability, { scope: 'warning-projection', operation: 'failed', error: detail }, consumed);
|
|
650
646
|
}
|
|
651
647
|
catch { return receipt; }
|
|
652
648
|
}
|
|
653
649
|
}
|
|
654
650
|
try {
|
|
655
651
|
const capability = issueCapability(receipt, 'warning-projection');
|
|
656
|
-
return
|
|
652
|
+
return mutateFinalizationReceipt(repoRoot, receipt, capability, { scope: 'warning-projection', operation: 'succeeded' }, consumed);
|
|
657
653
|
}
|
|
658
654
|
catch { return receipt; }
|
|
659
655
|
}
|
|
@@ -692,7 +688,7 @@ async function syncPendingTaskComment(input: {
|
|
|
692
688
|
if (result.status === 'applied' || result.status === 'no-op') {
|
|
693
689
|
const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
|
|
694
690
|
const capability = issueCapability(receipt, 'task-comment');
|
|
695
|
-
receipt =
|
|
691
|
+
receipt = mutateFinalizationReceipt(repoRoot, receipt, capability, {
|
|
696
692
|
scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
|
|
697
693
|
}, consumedCapabilities);
|
|
698
694
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
@@ -708,7 +704,7 @@ async function syncPendingTaskComment(input: {
|
|
|
708
704
|
}
|
|
709
705
|
const detail = step.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
|
|
710
706
|
const capability = issueCapability(receipt, 'task-comment');
|
|
711
|
-
receipt =
|
|
707
|
+
receipt = mutateFinalizationReceipt(repoRoot, receipt, capability, {
|
|
712
708
|
scope: 'task-comment', operation: 'failed', error: detail
|
|
713
709
|
}, consumedCapabilities);
|
|
714
710
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
@@ -717,7 +713,7 @@ async function syncPendingTaskComment(input: {
|
|
|
717
713
|
const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
|
|
718
714
|
try {
|
|
719
715
|
const capability = issueCapability(receipt, 'task-comment');
|
|
720
|
-
receipt =
|
|
716
|
+
receipt = mutateFinalizationReceipt(repoRoot, receipt, capability, {
|
|
721
717
|
scope: 'task-comment', operation: 'failed', error: detail
|
|
722
718
|
}, consumedCapabilities);
|
|
723
719
|
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
@@ -863,14 +859,14 @@ function preparedResult(
|
|
|
863
859
|
};
|
|
864
860
|
}
|
|
865
861
|
|
|
866
|
-
async function
|
|
862
|
+
async function prepareTaskFinalizationCore(
|
|
867
863
|
request: TaskFinalizationRequest,
|
|
868
864
|
taskId: string,
|
|
869
865
|
options: TaskFinalizationOptions
|
|
870
866
|
): Promise<TaskFinalizationResult> {
|
|
871
867
|
const repoRoot = path.resolve(options.repoRoot);
|
|
872
868
|
const lifecycle = options.lifecycle ?? ((lifecycleRequest, lifecycleOptions) => (
|
|
873
|
-
|
|
869
|
+
applyTaskLifecycle(lifecycleRequest, lifecycleOptions ?? { repoRoot })
|
|
874
870
|
));
|
|
875
871
|
const backfill = options.backfill ?? backfillCompletionComments;
|
|
876
872
|
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
@@ -1142,11 +1138,9 @@ async function prepareTaskFinalization(request: TaskFinalizationRequest, options
|
|
|
1142
1138
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot });
|
|
1143
1139
|
if (!resolved.ok) return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
1144
1140
|
try {
|
|
1145
|
-
return await
|
|
1141
|
+
return await prepareTaskFinalizationCore(request, resolved.taskId, options);
|
|
1146
1142
|
} catch (error) {
|
|
1147
|
-
const detail = error
|
|
1148
|
-
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
1149
|
-
: errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
1143
|
+
const detail = errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
1150
1144
|
return failed(resolved.taskId, detail);
|
|
1151
1145
|
}
|
|
1152
1146
|
}
|
|
@@ -1159,7 +1153,7 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
|
|
|
1159
1153
|
const resolved = resolveTaskRef(request.taskRef, { repoRoot });
|
|
1160
1154
|
if (!resolved.ok) return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
1161
1155
|
try {
|
|
1162
|
-
return await
|
|
1156
|
+
return await (async () => {
|
|
1163
1157
|
if (options.controlBinding && options.manifestPath) {
|
|
1164
1158
|
try {
|
|
1165
1159
|
const manifest = readSandboxControlManifest(options.manifestPath);
|
|
@@ -1223,7 +1217,7 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
|
|
|
1223
1217
|
};
|
|
1224
1218
|
const result = options.lifecycle
|
|
1225
1219
|
? options.lifecycle(lifecycleRequest, lifecycleOptions)
|
|
1226
|
-
:
|
|
1220
|
+
: applyTaskLifecycle(lifecycleRequest, lifecycleOptions);
|
|
1227
1221
|
const step = lifecycleStep(result);
|
|
1228
1222
|
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
1229
1223
|
const updated = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: step.error });
|
|
@@ -1305,11 +1299,9 @@ async function commitPreparedTaskFinalization(request: TaskFinalizationRequest,
|
|
|
1305
1299
|
});
|
|
1306
1300
|
}
|
|
1307
1301
|
return terminalResult(resolved.taskId, updated, { lifecycle: step, verification }, result.changed, detail);
|
|
1308
|
-
});
|
|
1302
|
+
})();
|
|
1309
1303
|
} catch (error) {
|
|
1310
|
-
const detail = error
|
|
1311
|
-
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
1312
|
-
: errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
1304
|
+
const detail = errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
1313
1305
|
return failed(resolved.taskId, detail);
|
|
1314
1306
|
}
|
|
1315
1307
|
}
|
package/lib/task/lifecycle.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { validateCurrentTaskContract } from './current-contract.ts';
|
|
|
18
18
|
import { isValidAgentInfraVersion } from '../version.ts';
|
|
19
19
|
import { taskIssueIdentity } from '../platform/task-identities.ts';
|
|
20
20
|
import { resourceIdentityEquals } from '../platform/resource-identity.ts';
|
|
21
|
-
import {
|
|
21
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
22
22
|
|
|
23
23
|
const lifecycleIntentCatalog = [
|
|
24
24
|
'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'
|
|
@@ -138,7 +138,7 @@ const DEFAULT_IO: LifecycleFileSystem = {
|
|
|
138
138
|
|
|
139
139
|
function isVirtiofsMountPoint(directory: string, mountInfo: string): boolean {
|
|
140
140
|
const decodeMountInfoPath = (value: string): string => value.replace(/\\([0-7]{3})/gu, (_match, octal: string) => String.fromCharCode(Number.parseInt(octal, 8)));
|
|
141
|
-
const expected =
|
|
141
|
+
const expected = fs.realpathSync.native(directory);
|
|
142
142
|
return mountInfo.split('\n').some((line) => {
|
|
143
143
|
if (!line) return false;
|
|
144
144
|
const fields = line.split(' ');
|
|
@@ -437,14 +437,14 @@ function validateRestoreStaging(request: Extract<TaskLifecycleRequest, { intent:
|
|
|
437
437
|
let workspace: string;
|
|
438
438
|
let stagingDir: string;
|
|
439
439
|
try {
|
|
440
|
-
workspace = fs.realpathSync.native(
|
|
440
|
+
workspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
441
441
|
stagingDir = fs.realpathSync.native(path.resolve(request.stagingDir));
|
|
442
442
|
} catch (error) {
|
|
443
443
|
return { code: 'LIFECYCLE_STAGING_INVALID', message: String(error) };
|
|
444
444
|
}
|
|
445
445
|
const relative = path.relative(workspace, stagingDir);
|
|
446
446
|
if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || path.basename(stagingDir) === taskId) {
|
|
447
|
-
return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside
|
|
447
|
+
return { code: 'LIFECYCLE_STAGING_INVALID', message: 'restore staging must be a non-final directory inside the project task workspace' };
|
|
448
448
|
}
|
|
449
449
|
let entries: fs.Dirent[];
|
|
450
450
|
try { entries = fs.readdirSync(stagingDir, { withFileTypes: true }); }
|
|
@@ -629,7 +629,7 @@ export function inspectTaskLifecycleProgress(
|
|
|
629
629
|
}
|
|
630
630
|
}
|
|
631
631
|
|
|
632
|
-
function
|
|
632
|
+
function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
|
|
633
633
|
if (requestInput.intent === 'recover-started') {
|
|
634
634
|
return failed(requestInput, { code: 'LIFECYCLE_RECOVERY_REQUIRES_AUTHORITY', message: 'recover-started must be dispatched through task control authority' });
|
|
635
635
|
}
|
|
@@ -671,7 +671,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
671
671
|
} else {
|
|
672
672
|
return failed(request, { code: 'LIFECYCLE_TASK_NOT_FOUND', message: `task ${taskId} has no lifecycle source` }, { taskId });
|
|
673
673
|
}
|
|
674
|
-
const targetPath = path.join(repoRoot,
|
|
674
|
+
const targetPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), spec.target, taskId);
|
|
675
675
|
const requiredSteps = stepsForIntent(request.intent);
|
|
676
676
|
const movingTaskDirectory = sourcePath !== targetPath;
|
|
677
677
|
const currentTaskFile = path.join(sourcePath, 'task.md');
|
|
@@ -709,7 +709,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
709
709
|
if (!contract.ok) return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
710
710
|
}
|
|
711
711
|
const registryRequest = executeShortIdCommand({
|
|
712
|
-
operation: 'list', activeDir: path.join(repoRoot, '
|
|
712
|
+
operation: 'list', activeDir: path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active'),
|
|
713
713
|
shortIdLength: configuredShortIdLength(repoRoot)
|
|
714
714
|
});
|
|
715
715
|
if (registryRequest.status === 'failed') {
|
|
@@ -880,7 +880,7 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
880
880
|
}
|
|
881
881
|
}
|
|
882
882
|
}
|
|
883
|
-
const activeTaskPath = path.join(repoRoot, '
|
|
883
|
+
const activeTaskPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
|
|
884
884
|
if (request.intent === 'complete' && fs.existsSync(activeTaskPath)) {
|
|
885
885
|
try {
|
|
886
886
|
let sourceIsVirtiofsMount = false;
|
|
@@ -925,50 +925,9 @@ function applyTaskLifecycleUnlocked(requestInput: TaskLifecycleRequest, options:
|
|
|
925
925
|
};
|
|
926
926
|
}
|
|
927
927
|
|
|
928
|
-
function applyTaskLifecycle(request: TaskLifecycleRequest, options: TaskLifecycleOptions = {}): TaskLifecycleResult {
|
|
929
|
-
const normalized = normalizedRequest(request);
|
|
930
|
-
if ('code' in normalized) return applyTaskLifecycleUnlocked(request, options);
|
|
931
|
-
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
932
|
-
const taskId = normalized.intent === 'restore'
|
|
933
|
-
? normalized.taskRef
|
|
934
|
-
: (() => {
|
|
935
|
-
const resolved = resolveTaskRef(normalized.taskRef, { repoRoot });
|
|
936
|
-
return resolved.ok ? resolved.taskId : null;
|
|
937
|
-
})();
|
|
938
|
-
if (!taskId) return applyTaskLifecycleUnlocked(request, options);
|
|
939
|
-
try {
|
|
940
|
-
return withTaskExecutionLock(repoRoot, taskId, 'task-lifecycle.move', () => applyTaskLifecycleUnlocked(request, options));
|
|
941
|
-
} catch (error) {
|
|
942
|
-
if (error instanceof TaskExecutionLockError) {
|
|
943
|
-
return failed(request, { code: error.code, message: error.message }, { taskId });
|
|
944
|
-
}
|
|
945
|
-
throw error;
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
function applyTaskLifecycleWithinTaskLock(
|
|
950
|
-
request: TaskLifecycleRequest,
|
|
951
|
-
options: TaskLifecycleOptions,
|
|
952
|
-
lockedTaskId: string
|
|
953
|
-
): TaskLifecycleResult {
|
|
954
|
-
const normalized = normalizedRequest(request);
|
|
955
|
-
if ('code' in normalized) return failed(request, normalized);
|
|
956
|
-
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
957
|
-
const resolved = normalized.intent === 'restore'
|
|
958
|
-
? { ok: true as const, taskId: normalized.taskRef }
|
|
959
|
-
: resolveTaskRef(normalized.taskRef, { repoRoot });
|
|
960
|
-
if (!resolved.ok || resolved.taskId !== lockedTaskId) {
|
|
961
|
-
return failed(request, {
|
|
962
|
-
code: 'LIFECYCLE_LOCK_SCOPE_MISMATCH',
|
|
963
|
-
message: 'task lifecycle request does not match the held task lock'
|
|
964
|
-
}, { taskId: resolved.ok ? resolved.taskId : resolved.taskId });
|
|
965
|
-
}
|
|
966
|
-
return applyTaskLifecycleUnlocked(request, options);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
928
|
export {
|
|
970
929
|
lifecycleIntentCatalog, lifecycleFailureCatalog, lifecycleProducerCatalog,
|
|
971
|
-
applyTaskLifecycle,
|
|
930
|
+
applyTaskLifecycle, previewTaskCompletion
|
|
972
931
|
};
|
|
973
932
|
export type {
|
|
974
933
|
TaskLifecycleIntent, TaskLifecycleRequest, TaskLifecycleResult, TaskLifecycleOptions,
|
|
@@ -52,7 +52,6 @@ type LocalArtifactFinalizationRequest = {
|
|
|
52
52
|
artifact: string;
|
|
53
53
|
repoRoot?: string;
|
|
54
54
|
requiredSections?: readonly string[];
|
|
55
|
-
lockAlreadyHeld?: boolean;
|
|
56
55
|
};
|
|
57
56
|
|
|
58
57
|
type LocalArtifactFinalizationResult = {
|
|
@@ -129,7 +128,6 @@ type LocalArtifactPreparation = Readonly<{
|
|
|
129
128
|
result: LocalArtifactFinalizationResult;
|
|
130
129
|
content: string;
|
|
131
130
|
repoRoot?: string;
|
|
132
|
-
lockAlreadyHeld?: boolean;
|
|
133
131
|
}>;
|
|
134
132
|
|
|
135
133
|
function prepareLocalArtifact(
|
|
@@ -180,8 +178,7 @@ function prepareLocalArtifact(
|
|
|
180
178
|
return {
|
|
181
179
|
result: validation.ok && !qualificationError ? { ...result, status: 'passed', error: null } : result,
|
|
182
180
|
content,
|
|
183
|
-
repoRoot: resolved.repoRoot
|
|
184
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
181
|
+
repoRoot: resolved.repoRoot
|
|
185
182
|
};
|
|
186
183
|
|
|
187
184
|
}
|