@fitlab-ai/agent-infra 0.11.14 → 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-finalization.js +2 -2
- 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-change-report.js +10 -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 +18 -6
- 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/lifecycle.js +2 -21
- 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 +36 -20
- 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 +38 -49
- 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 +12 -10
- 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-finalization.ts +2 -2
- 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-change-report.ts +9 -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 +17 -5
- 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/lifecycle.ts +4 -21
- 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 +42 -18
- 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 +40 -51
- 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 +15 -10
- 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 +17 -9
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +18 -10
- 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 +13 -3
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +13 -3
- 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/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -5
- 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-manual-validation/reference/discovery-and-execution.en.md +13 -2
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +13 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/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 +21 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +22 -5
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +14 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +15 -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/runtime/agent-runtime.js +0 -33
- package/dist/lib/task/runtime-paths.js +0 -10
- package/dist/lib/task/task-execution-lock.js +0 -250
- package/lib/runtime/agent-runtime.ts +0 -41
- package/lib/task/runtime-paths.ts +0 -12
- package/lib/task/task-execution-lock.ts +0 -320
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
} from './artifact-lifecycle.ts';
|
|
11
11
|
import { parseArtifactName } from './artifact-name.ts';
|
|
12
12
|
import type { ArtifactIdentity } from './artifact-lifecycle.ts';
|
|
13
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
14
|
-
import type { TaskExecutionLockOptions } from './task-execution-lock.ts';
|
|
15
13
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
16
14
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
17
15
|
|
|
@@ -95,7 +93,7 @@ export type PrReviewInspectionResult = {
|
|
|
95
93
|
error: { code: string; message: string } | null;
|
|
96
94
|
};
|
|
97
95
|
|
|
98
|
-
type ActivityIntentOptions = TaskWriteOptions
|
|
96
|
+
type ActivityIntentOptions = TaskWriteOptions;
|
|
99
97
|
type ArtifactRecord = { identity: ArtifactIdentity; head: string; status: PrReviewArtifactStatus };
|
|
100
98
|
type RoundState = ArtifactRecord & {
|
|
101
99
|
startedAgent: string | null;
|
|
@@ -299,7 +297,7 @@ function terminalNote(intent: PrReviewCompleteIntent | PrReviewTerminateIntent):
|
|
|
299
297
|
return `Outcome: ${outcomeLabel(intent.outcome)}, reason: ${intent.reason} → ${intent.artifact}`;
|
|
300
298
|
}
|
|
301
299
|
|
|
302
|
-
function
|
|
300
|
+
function applyActivityIntent(intent: PrReviewActivityIntent, normalizedAgent: string, options: ActivityIntentOptions): PrReviewActivityResult {
|
|
303
301
|
const state = readSnapshot(intent.taskRef, options.repoRoot);
|
|
304
302
|
if (!state.ok) return failed(intent, state.code, state.message, state.taskId);
|
|
305
303
|
const parsed = parseArtifactName(intent.artifact)!;
|
|
@@ -401,19 +399,7 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
|
|
|
401
399
|
if (resolved.state !== 'active') {
|
|
402
400
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
403
401
|
}
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
return withTaskExecutionLock(
|
|
407
|
-
resolved.repoRoot,
|
|
408
|
-
resolved.taskId,
|
|
409
|
-
`task-activity:${intent.kind}`,
|
|
410
|
-
() => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }),
|
|
411
|
-
options.lockOptions
|
|
412
|
-
);
|
|
413
|
-
} catch (error) {
|
|
414
|
-
if (error instanceof TaskExecutionLockError) return failed(intent, error.code, error.message, resolved.taskId);
|
|
415
|
-
return failed(intent, 'ACTIVITY_LOCK_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
416
|
-
}
|
|
402
|
+
return applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
417
403
|
}
|
|
418
404
|
|
|
419
405
|
export type { ActivityIntentOptions };
|
|
@@ -134,6 +134,8 @@ type InspectOptions = {
|
|
|
134
134
|
sourceSha256?: string;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
type ResolvedTaskRef = Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>;
|
|
138
|
+
|
|
137
139
|
const BLOCKING_DIAGNOSTICS = new Set<ArtifactDiagnosticCode>([
|
|
138
140
|
'NONCANONICAL_NAME', 'ROUND_OUT_OF_RANGE', 'MISSING_BASE', 'ROUND_GAP',
|
|
139
141
|
'DUPLICATE_LOGICAL_ROUND', 'NON_REGULAR_FILE', 'SYMBOLIC_LINK',
|
|
@@ -160,10 +162,15 @@ function diagnostic(code: ArtifactDiagnosticCode, family: ArtifactFamily, name:
|
|
|
160
162
|
return { code, family, name, message };
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
function inspectTaskArtifacts(
|
|
165
|
+
function inspectTaskArtifacts(
|
|
166
|
+
taskRef: string,
|
|
167
|
+
family: string,
|
|
168
|
+
options: InspectOptions = {},
|
|
169
|
+
resolvedTask?: ResolvedTaskRef
|
|
170
|
+
): ArtifactInventoryResult {
|
|
164
171
|
const spec = familySpec(family);
|
|
165
172
|
if (!spec) return failure(taskRef, family, { code: 'ARTIFACT_FAMILY_UNKNOWN', message: `unknown artifact family '${family}'` });
|
|
166
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
173
|
+
const resolved = resolvedTask ?? resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
167
174
|
if (!resolved.ok) return failure(taskRef, family, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
168
175
|
return inspectArtifactDirectory(resolved.taskDir, spec.family, {
|
|
169
176
|
requestRef: taskRef, taskId: resolved.taskId, taskState: resolved.state
|
|
@@ -452,8 +459,13 @@ function attachArtifactSelection(
|
|
|
452
459
|
}
|
|
453
460
|
}
|
|
454
461
|
|
|
455
|
-
function
|
|
456
|
-
|
|
462
|
+
function resolveArtifactContextFromTask(
|
|
463
|
+
taskRef: string,
|
|
464
|
+
family: string,
|
|
465
|
+
options: InspectOptions,
|
|
466
|
+
resolvedTask?: ResolvedTaskRef
|
|
467
|
+
): ArtifactContextResult {
|
|
468
|
+
const inventory = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
457
469
|
if (inventory.status === 'failed') return { ...inventory, inputs: [], codeMode: null };
|
|
458
470
|
const writableError = assertWritableInventory(inventory);
|
|
459
471
|
if (writableError) return { ...inventory, status: 'failed', inputs: [], codeMode: null, error: writableError };
|
|
@@ -462,7 +474,7 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
462
474
|
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
463
475
|
}
|
|
464
476
|
if (inventory.family === 'code') {
|
|
465
|
-
const code = resolveCodeContext(inventory, options);
|
|
477
|
+
const code = resolveCodeContext(inventory, options, resolvedTask);
|
|
466
478
|
const selected = attachArtifactSelection(code, pathState, options);
|
|
467
479
|
if (selected.status !== 'ready' || !selected.selection) return selected;
|
|
468
480
|
if (selected.selection.disposition === 'reuse') return selected;
|
|
@@ -496,7 +508,7 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
496
508
|
const required = REQUIRED_INPUT[inventory.family as ArtifactFamily];
|
|
497
509
|
const inputs: ArtifactIdentity[] = [];
|
|
498
510
|
if (required) {
|
|
499
|
-
const input = inspectTaskArtifacts(taskRef, required, options);
|
|
511
|
+
const input = inspectTaskArtifacts(taskRef, required, options, resolvedTask);
|
|
500
512
|
if (input.status === 'failed' || !input.latest) {
|
|
501
513
|
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
|
|
502
514
|
}
|
|
@@ -505,7 +517,7 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
505
517
|
const optional = OPTIONAL_CONTEXT[inventory.family as ArtifactFamily];
|
|
506
518
|
if (optional) {
|
|
507
519
|
for (const family of optional) {
|
|
508
|
-
const context = inspectTaskArtifacts(taskRef, family, options);
|
|
520
|
+
const context = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
509
521
|
if (context.status === 'ready' && context.latest) inputs.push(context.latest);
|
|
510
522
|
}
|
|
511
523
|
}
|
|
@@ -515,14 +527,30 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
515
527
|
return attachArtifactSelection({ ...inventory, inputs, codeMode: null }, pathState, options);
|
|
516
528
|
}
|
|
517
529
|
|
|
518
|
-
function
|
|
530
|
+
function resolveArtifactContext(taskRef: string, family: string, options: InspectOptions = {}): ArtifactContextResult {
|
|
531
|
+
return resolveArtifactContextFromTask(taskRef, family, options);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function resolveArtifactContextForResolvedTask(
|
|
535
|
+
resolvedTask: ResolvedTaskRef,
|
|
536
|
+
family: string,
|
|
537
|
+
options: InspectOptions = {}
|
|
538
|
+
): ArtifactContextResult {
|
|
539
|
+
return resolveArtifactContextFromTask(resolvedTask.taskId, family, options, resolvedTask);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function resolveCodeContext(
|
|
543
|
+
inventory: ArtifactInventoryResult,
|
|
544
|
+
options: InspectOptions,
|
|
545
|
+
resolvedTask?: ResolvedTaskRef
|
|
546
|
+
): ArtifactContextResult {
|
|
519
547
|
const taskRef = inventory.requestRef;
|
|
520
548
|
const pathState = lifecyclePathState(inventory.taskDir!);
|
|
521
549
|
if (pathState.status !== 'valid') return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
522
550
|
const inputFamily = pathState.decision.path === 'streamlined' ? 'analysis' : 'plan';
|
|
523
|
-
const source = inspectTaskArtifacts(taskRef, inputFamily, options);
|
|
551
|
+
const source = inspectTaskArtifacts(taskRef, inputFamily, options, resolvedTask);
|
|
524
552
|
if (source.status === 'failed' || !source.latest) return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest ${inputFamily} artifact is required`);
|
|
525
|
-
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options);
|
|
553
|
+
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options, resolvedTask);
|
|
526
554
|
try {
|
|
527
555
|
const taskContent = fs.readFileSync(path.join(inventory.taskDir!, 'task.md'), 'utf8');
|
|
528
556
|
const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
|
|
@@ -538,7 +566,7 @@ function resolveCodeContext(inventory: ArtifactInventoryResult, options: Inspect
|
|
|
538
566
|
const reviewMax = reviewCode.latest?.round ?? 0;
|
|
539
567
|
const inputs = [source.latest];
|
|
540
568
|
const sourceReviewFamily = inputFamily === 'analysis' ? 'review-analysis' : 'review-plan';
|
|
541
|
-
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options);
|
|
569
|
+
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options, resolvedTask);
|
|
542
570
|
if (sourceReview.status === 'ready' && sourceReview.latest && sourceReview.reviewedInput?.name === source.latest.name) inputs.push(sourceReview.latest);
|
|
543
571
|
if (inventory.openRounds.length > 0) {
|
|
544
572
|
try {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
getArtifactSchema,
|
|
10
10
|
renderArtifactSkeleton
|
|
11
11
|
} from './artifact-schema.ts';
|
|
12
|
-
import { withTaskExecutionLock } from './task-execution-lock.ts';
|
|
13
12
|
import type {
|
|
14
13
|
ArtifactSchema,
|
|
15
14
|
ArtifactSchemaFamily,
|
|
@@ -352,42 +351,36 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
|
|
|
352
351
|
const target = path.join(request.taskDir, request.artifact);
|
|
353
352
|
if (path.dirname(path.resolve(target)) !== path.resolve(request.taskDir)) return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be inside the current task directory');
|
|
354
353
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
375
|
-
fs.renameSync(tempPath, target);
|
|
376
|
-
} catch (error) {
|
|
377
|
-
try { fs.unlinkSync(tempPath); } catch { /* preserve primary error */ }
|
|
378
|
-
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
status: 'applied',
|
|
382
|
-
changed: true,
|
|
383
|
-
artifactSha256: sha256Content(content),
|
|
384
|
-
semanticDigest: canonicalSemanticDigest(content),
|
|
385
|
-
error: null
|
|
386
|
-
};
|
|
387
|
-
});
|
|
354
|
+
const existing = fs.lstatSync(target);
|
|
355
|
+
if (existing.isSymbolicLink() || !existing.isFile()) return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
|
|
356
|
+
return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
|
|
357
|
+
} catch (error) {
|
|
358
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
359
|
+
}
|
|
360
|
+
const skeleton = renderArtifactSkeleton({
|
|
361
|
+
taskId: request.taskId,
|
|
362
|
+
family: request.family,
|
|
363
|
+
artifact: request.artifact,
|
|
364
|
+
...(request.locale ? { locale: request.locale } : {})
|
|
365
|
+
});
|
|
366
|
+
const content = skeleton.trimEnd();
|
|
367
|
+
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
368
|
+
try {
|
|
369
|
+
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
370
|
+
try { fs.lstatSync(target); fs.unlinkSync(tempPath); return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently'); }
|
|
371
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
372
|
+
fs.renameSync(tempPath, target);
|
|
388
373
|
} catch (error) {
|
|
389
|
-
|
|
374
|
+
try { fs.unlinkSync(tempPath); } catch { /* preserve primary error */ }
|
|
375
|
+
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
390
376
|
}
|
|
377
|
+
return {
|
|
378
|
+
status: 'applied',
|
|
379
|
+
changed: true,
|
|
380
|
+
artifactSha256: sha256Content(content),
|
|
381
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
382
|
+
error: null
|
|
383
|
+
};
|
|
391
384
|
}
|
|
392
385
|
|
|
393
386
|
export {
|
package/lib/task/capabilities.ts
CHANGED
|
@@ -302,7 +302,7 @@ function recommendNext(facts: LifecycleFacts): LifecycleRecommendation {
|
|
|
302
302
|
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
function buildLifecycleFacts(taskDir: string, content: string, taskState = 'active', executionBusy = false): LifecycleFactsResult {
|
|
305
|
+
function buildLifecycleFacts(taskDir: string, content: string, taskState = 'active', executionBusy = false, repositoryRoot?: string): LifecycleFactsResult {
|
|
306
306
|
try {
|
|
307
307
|
const metadata = parseTypedTaskFrontmatter(content);
|
|
308
308
|
const rework = parseReworkIntentDocument(content);
|
|
@@ -393,7 +393,7 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
|
|
|
393
393
|
&& artifactHashes[input] === receipt.inputSha256
|
|
394
394
|
) reviewedInputs[family] = input;
|
|
395
395
|
if (family === 'review-code') {
|
|
396
|
-
const identity = inspectReviewIdentity(taskDir, reviewContent);
|
|
396
|
+
const identity = inspectReviewIdentity(taskDir, reviewContent, repositoryRoot);
|
|
397
397
|
reviewCodeIdentityValid = identity.status === 'matched'
|
|
398
398
|
&& hasArtifactCompletionFact(content, path.join(taskDir, latest), 'review-code.completed')
|
|
399
399
|
&& hasArtifactCompletionLog(content, latest, 'review-code.completed');
|
package/lib/task/commands/ls.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { execFileSync } from 'node:child_process';
|
|
|
4
4
|
import { formatTable } from '../../table.ts';
|
|
5
5
|
import { parseTaskFrontmatter, extractTitle } from '../frontmatter.ts';
|
|
6
6
|
import { loadShortIdByTaskId } from '../short-id.ts';
|
|
7
|
+
import { resolveProjectTaskWorkspaceRoot } from '../workspace-root.ts';
|
|
7
8
|
|
|
8
9
|
const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
|
|
9
10
|
|
|
10
|
-
Lists tasks under .
|
|
11
|
+
Lists tasks under the project task workspace. Defaults to active tasks only.
|
|
11
12
|
--all Include active + blocked + completed (excludes archive)
|
|
12
13
|
--blocked Only blocked tasks
|
|
13
14
|
--completed Only completed tasks
|
|
@@ -73,7 +74,7 @@ type TaskRow = {
|
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
function collectTasks(repoRoot: string, state: 'active' | 'blocked' | 'completed'): TaskRow[] {
|
|
76
|
-
const dir = path.join(repoRoot,
|
|
77
|
+
const dir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
|
|
77
78
|
if (!fs.existsSync(dir)) return [];
|
|
78
79
|
const shortIdByTaskId = state === 'active' || state === 'completed'
|
|
79
80
|
? loadShortIdByTaskId(repoRoot)
|
|
@@ -117,7 +118,7 @@ function ls(args: string[] = []): void {
|
|
|
117
118
|
rows.push(...collectTasks(repoRoot, state));
|
|
118
119
|
}
|
|
119
120
|
if (rows.length === 0) {
|
|
120
|
-
process.stdout.write(`No tasks under
|
|
121
|
+
process.stdout.write(`No tasks under project task workspace state ${selection.join('|')}\n`);
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
123
124
|
const tableRows = rows.map((r, i) => [
|
|
@@ -521,7 +521,7 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
|
521
521
|
const run = input.run ?? makeRunner(input.repoRoot);
|
|
522
522
|
const artifacts = enumerateArtifacts(input.taskDir);
|
|
523
523
|
const workflow = collectWorkflow(content, input.now);
|
|
524
|
-
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
|
|
524
|
+
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active', false, input.repoRoot);
|
|
525
525
|
const next = facts.ok ? recommendNext(facts.facts) : null;
|
|
526
526
|
const recommendation = next?.action ?? DASH;
|
|
527
527
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { execFileSync } from 'node:child_process';
|
|
3
4
|
|
|
4
5
|
import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.ts';
|
|
@@ -7,7 +8,6 @@ import { checkpointCommitMatches } from './commit-identity.ts';
|
|
|
7
8
|
import { resolveTaskContext, resolveTaskRef } from './resolve-ref.ts';
|
|
8
9
|
import { commitPushDecision } from './commit-policy.ts';
|
|
9
10
|
import { mergeOperationWarnings, type OperationWarning } from './operation-outcome.ts';
|
|
10
|
-
import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
11
11
|
import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
12
12
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
13
13
|
import type { TaskMutation } from './write.ts';
|
|
@@ -135,6 +135,7 @@ function resolveBoundTask(input: CommitOperationInput):
|
|
|
135
135
|
};
|
|
136
136
|
return boundTask(input, resolved.taskId, resolved.taskDir, resolved.taskMdPath, resolved.repoRoot, resolved.state);
|
|
137
137
|
}
|
|
138
|
+
if (!fs.existsSync(path.join(input.cwd, '.agents', '.airc.json'))) return { kind: 'none' };
|
|
138
139
|
const context = resolveTaskContext(undefined, { repoRoot: input.cwd });
|
|
139
140
|
if (context.ok) return boundTask(input, context.taskId, context.taskDir, context.taskMdPath, context.repoRoot, context.state);
|
|
140
141
|
if (context.code === 'TASK_CONTEXT_NOT_FOUND') return { kind: 'none' };
|
|
@@ -280,7 +281,7 @@ function checkpointTimestamp(): string {
|
|
|
280
281
|
return new Date().toISOString();
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
function
|
|
284
|
+
function executeCommitOperationCore(input: CommitOperationInput, task: BoundTask | null, mode: CommitExecutionMode): CommitOperationResult {
|
|
284
285
|
const inspected = inspectGitWorkflow(input.cwd);
|
|
285
286
|
if (!inspected.snapshot) return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
|
|
286
287
|
if (mode === 'orchestrated') return failure(
|
|
@@ -516,11 +517,9 @@ function executeCommitOperation(input: CommitOperationInput): CommitOperationRes
|
|
|
516
517
|
const task = resolved.kind === 'bound' ? resolved.task : null;
|
|
517
518
|
if (mode === 'orchestrated' && !task) return failure(normalizedInput, mode, null, 'ORCHESTRATION_TASK_REQUIRED', 'orchestrated commit requires a task context', 'blocked');
|
|
518
519
|
try {
|
|
519
|
-
return task
|
|
520
|
-
? withRepositoryMutationLock(normalizedInput.cwd, () => withTaskExecutionLock(normalizedInput.cwd, task.taskId, 'commit-operation', () => executeUnlocked(normalizedInput, task, mode)))
|
|
521
|
-
: withRepositoryMutationLock(normalizedInput.cwd, () => executeUnlocked(normalizedInput, null, mode));
|
|
520
|
+
return executeCommitOperationCore(normalizedInput, task, mode);
|
|
522
521
|
} catch (error) {
|
|
523
|
-
const code =
|
|
522
|
+
const code = 'COMMIT_OPERATION_FAILED';
|
|
524
523
|
return failure(normalizedInput, mode, task?.taskId ?? null, code, error instanceof Error ? error.message : String(error), 'blocked');
|
|
525
524
|
}
|
|
526
525
|
}
|
|
@@ -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 };
|