@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,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { configuredShortIdLength, inspectShortIdRegistry, isRemovedHashShortIdInput, normalizeShortIdInput } from '../task/short-id.js';
|
|
4
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
|
|
4
5
|
import { TASK_WORKSPACE_STATES, listTaskWorkspaces, resolveTaskWorkspace } from './task-resolver.js';
|
|
5
6
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
7
|
const SHORT_ID_RE = /^\d+$/;
|
|
@@ -16,7 +17,7 @@ function taskBranch(taskMd, taskId) {
|
|
|
16
17
|
return stripQuotes(value);
|
|
17
18
|
}
|
|
18
19
|
function activeRegistry(repoRoot) {
|
|
19
|
-
const registryPath = path.join(repoRoot, '
|
|
20
|
+
const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
|
|
20
21
|
if (!fs.existsSync(registryPath))
|
|
21
22
|
return {};
|
|
22
23
|
const value = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
@@ -29,7 +30,7 @@ function activeRegistry(repoRoot) {
|
|
|
29
30
|
return Object.fromEntries(Object.entries(ids).filter((entry) => SHORT_ID_RE.test(entry[0]) && typeof entry[1] === 'string' && TASK_ID_RE.test(entry[1])));
|
|
30
31
|
}
|
|
31
32
|
function taskIdentity(repoRoot, taskId, registry) {
|
|
32
|
-
const taskMd = path.join(repoRoot, '
|
|
33
|
+
const taskMd = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md');
|
|
33
34
|
if (!fs.existsSync(taskMd))
|
|
34
35
|
throw new Error(`Active task not found: ${taskId}`);
|
|
35
36
|
const shortIds = Object.entries(registry).filter(([, id]) => id === taskId).map(([shortId]) => shortId);
|
|
@@ -81,13 +82,13 @@ export function resolveSandboxTarget(requestedRef, repoRoot) {
|
|
|
81
82
|
if (!taskId)
|
|
82
83
|
throw new Error(`Short id '${requestedRef}' not found in active task registry`);
|
|
83
84
|
const workspace = taskIdentity(repoRoot, taskId, registry);
|
|
84
|
-
return { requestedRef, branch: taskBranch(path.join(repoRoot, '
|
|
85
|
+
return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md'), taskId), workspace };
|
|
85
86
|
}
|
|
86
87
|
if (TASK_ID_RE.test(requestedRef)) {
|
|
87
88
|
const workspace = taskIdentity(repoRoot, requestedRef, registry);
|
|
88
|
-
return { requestedRef, branch: taskBranch(path.join(repoRoot, '
|
|
89
|
+
return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', requestedRef, 'task.md'), requestedRef), workspace };
|
|
89
90
|
}
|
|
90
|
-
const activeRoot = path.join(repoRoot, '
|
|
91
|
+
const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active');
|
|
91
92
|
const matches = fs.existsSync(activeRoot)
|
|
92
93
|
? fs.readdirSync(activeRoot, { withFileTypes: true }).flatMap((entry) => {
|
|
93
94
|
if (!entry.isDirectory() || !TASK_ID_RE.test(entry.name))
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
5
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
|
|
4
6
|
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
5
7
|
import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
|
|
6
8
|
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
@@ -101,7 +103,8 @@ function assertSafeMountTargetPath(target, worktreeRoot, canonicalWorktreeRoot)
|
|
|
101
103
|
export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
102
104
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
103
105
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
104
|
-
const
|
|
106
|
+
const workspaceRootPath = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
107
|
+
const activeSource = path.join(workspaceRootPath, 'active', taskId);
|
|
105
108
|
try {
|
|
106
109
|
const stat = fs.lstatSync(activeSource);
|
|
107
110
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
@@ -109,7 +112,7 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
|
109
112
|
}
|
|
110
113
|
const canonical = fs.realpathSync.native(activeSource);
|
|
111
114
|
const activeRoot = fs.realpathSync.native(path.dirname(activeSource));
|
|
112
|
-
const workspaceRoot = fs.realpathSync.native(
|
|
115
|
+
const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
|
|
113
116
|
const relative = path.relative(workspaceRoot, canonical);
|
|
114
117
|
if (path.dirname(canonical) !== activeRoot || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
115
118
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${activeSource} escapes the task workspace`);
|
|
@@ -129,39 +132,46 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
|
129
132
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} must be a real directory`);
|
|
130
133
|
}
|
|
131
134
|
const canonical = fs.realpathSync.native(source);
|
|
132
|
-
const workspaceRoot = fs.realpathSync.native(
|
|
135
|
+
const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
|
|
133
136
|
const relative = path.relative(workspaceRoot, canonical);
|
|
134
137
|
if (path.basename(canonical) !== taskId || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
135
138
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} escapes the task workspace`);
|
|
136
139
|
}
|
|
137
140
|
return canonical;
|
|
138
141
|
}
|
|
139
|
-
export function prepareSandboxWorkspaceMountTargets(
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const workspaceRoot = path.resolve(
|
|
142
|
+
export function prepareSandboxWorkspaceMountTargets(repoRoot) {
|
|
143
|
+
const resolvedHome = path.resolve(process.env.HOME || os.homedir());
|
|
144
|
+
const canonicalHome = fs.realpathSync.native(resolvedHome);
|
|
145
|
+
const workspaceRoot = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
146
|
+
const relativeRoot = path.relative(resolvedHome, workspaceRoot);
|
|
147
|
+
if (relativeRoot.startsWith('..') || path.isAbsolute(relativeRoot)) {
|
|
148
|
+
throw new Error(`Sandbox workspace mount target escapes the host home: ${workspaceRoot}`);
|
|
149
|
+
}
|
|
143
150
|
const statePaths = sandboxWorkspaceViewStatePaths(workspaceRoot);
|
|
144
151
|
const registryTarget = path.join(workspaceRoot, 'active', '.short-ids.json');
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
148
|
-
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget, ...(completedTaskTarget ? [completedTaskTarget] : [])]) {
|
|
149
|
-
assertSafeMountTargetPath(target, resolvedWorktreeRoot, canonicalWorktreeRoot);
|
|
152
|
+
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget]) {
|
|
153
|
+
assertSafeMountTargetPath(target, resolvedHome, canonicalHome);
|
|
150
154
|
}
|
|
151
155
|
fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 });
|
|
152
156
|
for (const { hostPath } of statePaths) {
|
|
153
157
|
fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
|
|
154
158
|
fs.chmodSync(hostPath, 0o700);
|
|
155
159
|
}
|
|
156
|
-
if (completedTaskTarget)
|
|
157
|
-
fs.mkdirSync(completedTaskTarget, { recursive: true, mode: 0o700 });
|
|
158
160
|
fs.chmodSync(workspaceRoot, 0o700);
|
|
159
|
-
fs.
|
|
161
|
+
const registryFd = fs.openSync(registryTarget, 'a', 0o600);
|
|
162
|
+
try {
|
|
163
|
+
if (fs.fstatSync(registryFd).size === 0) {
|
|
164
|
+
fs.writeFileSync(registryFd, `${JSON.stringify({ version: 1, ids: {} })}\n`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
fs.closeSync(registryFd);
|
|
169
|
+
}
|
|
160
170
|
}
|
|
161
171
|
export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
162
172
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
163
173
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
164
|
-
const workspaceRoot =
|
|
174
|
+
const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
165
175
|
fs.mkdirSync(path.join(workspaceRoot, 'completed'), { recursive: true, mode: 0o700 });
|
|
166
176
|
const completedRoot = path.join(workspaceRoot, 'completed');
|
|
167
177
|
const completedStat = fs.lstatSync(completedRoot);
|
|
@@ -184,17 +194,14 @@ export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
|
184
194
|
export function sandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
185
195
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
186
196
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
187
|
-
const source = path.resolve(repoRoot, '
|
|
197
|
+
const source = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot), 'completed', taskId);
|
|
188
198
|
const stat = fs.lstatSync(source);
|
|
189
199
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
190
200
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
191
|
-
const canonicalWorkspace = fs.realpathSync.native(
|
|
201
|
+
const canonicalWorkspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
192
202
|
const canonicalSource = fs.realpathSync.native(source);
|
|
193
|
-
const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
|
|
194
|
-
const workspaceRelative = path.relative(canonicalRepoRoot, canonicalWorkspace);
|
|
195
203
|
const relative = path.relative(canonicalWorkspace, canonicalSource);
|
|
196
|
-
if (
|
|
197
|
-
|| path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
204
|
+
if (path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
198
205
|
|| relative.startsWith('..') || path.isAbsolute(relative))
|
|
199
206
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
200
207
|
return canonicalSource;
|
|
@@ -4,7 +4,6 @@ import { resolveTaskRef } from './resolve-ref.js';
|
|
|
4
4
|
import { locateActivityLog, appendActivityEntry } from './activity-log.js';
|
|
5
5
|
import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts } from './artifact-lifecycle.js';
|
|
6
6
|
import { parseArtifactName } from './artifact-name.js';
|
|
7
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
8
7
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
9
8
|
const HEAD_RE = /^[0-9a-f]{40}$/;
|
|
10
9
|
const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
@@ -192,7 +191,7 @@ function terminalNote(intent) {
|
|
|
192
191
|
}
|
|
193
192
|
return `Outcome: ${outcomeLabel(intent.outcome)}, reason: ${intent.reason} → ${intent.artifact}`;
|
|
194
193
|
}
|
|
195
|
-
function
|
|
194
|
+
function applyActivityIntent(intent, normalizedAgent, options) {
|
|
196
195
|
const state = readSnapshot(intent.taskRef, options.repoRoot);
|
|
197
196
|
if (!state.ok)
|
|
198
197
|
return failed(intent, state.code, state.message, state.taskId);
|
|
@@ -297,15 +296,6 @@ export function applyPrReviewActivityIntent(intent, options = {}) {
|
|
|
297
296
|
if (resolved.state !== 'active') {
|
|
298
297
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
299
298
|
}
|
|
300
|
-
|
|
301
|
-
return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
302
|
-
try {
|
|
303
|
-
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity:${intent.kind}`, () => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }), options.lockOptions);
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
if (error instanceof TaskExecutionLockError)
|
|
307
|
-
return failed(intent, error.code, error.message, resolved.taskId);
|
|
308
|
-
return failed(intent, 'ACTIVITY_LOCK_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
309
|
-
}
|
|
299
|
+
return applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
310
300
|
}
|
|
311
301
|
//# sourceMappingURL=activity-intent.js.map
|
|
@@ -79,11 +79,11 @@ function failure(requestRef, family, error, extra = {}) {
|
|
|
79
79
|
function diagnostic(code, family, name, message) {
|
|
80
80
|
return { code, family, name, message };
|
|
81
81
|
}
|
|
82
|
-
function inspectTaskArtifacts(taskRef, family, options = {}) {
|
|
82
|
+
function inspectTaskArtifacts(taskRef, family, options = {}, resolvedTask) {
|
|
83
83
|
const spec = familySpec(family);
|
|
84
84
|
if (!spec)
|
|
85
85
|
return failure(taskRef, family, { code: 'ARTIFACT_FAMILY_UNKNOWN', message: `unknown artifact family '${family}'` });
|
|
86
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
86
|
+
const resolved = resolvedTask ?? resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
87
87
|
if (!resolved.ok)
|
|
88
88
|
return failure(taskRef, family, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
89
89
|
return inspectArtifactDirectory(resolved.taskDir, spec.family, {
|
|
@@ -399,8 +399,8 @@ function attachArtifactSelection(context, pathState, options) {
|
|
|
399
399
|
return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
|
-
function
|
|
403
|
-
const inventory = inspectTaskArtifacts(taskRef, family, options);
|
|
402
|
+
function resolveArtifactContextFromTask(taskRef, family, options, resolvedTask) {
|
|
403
|
+
const inventory = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
404
404
|
if (inventory.status === 'failed')
|
|
405
405
|
return { ...inventory, inputs: [], codeMode: null };
|
|
406
406
|
const writableError = assertWritableInventory(inventory);
|
|
@@ -411,7 +411,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
411
411
|
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
412
412
|
}
|
|
413
413
|
if (inventory.family === 'code') {
|
|
414
|
-
const code = resolveCodeContext(inventory, options);
|
|
414
|
+
const code = resolveCodeContext(inventory, options, resolvedTask);
|
|
415
415
|
const selected = attachArtifactSelection(code, pathState, options);
|
|
416
416
|
if (selected.status !== 'ready' || !selected.selection)
|
|
417
417
|
return selected;
|
|
@@ -447,7 +447,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
447
447
|
const required = REQUIRED_INPUT[inventory.family];
|
|
448
448
|
const inputs = [];
|
|
449
449
|
if (required) {
|
|
450
|
-
const input = inspectTaskArtifacts(taskRef, required, options);
|
|
450
|
+
const input = inspectTaskArtifacts(taskRef, required, options, resolvedTask);
|
|
451
451
|
if (input.status === 'failed' || !input.latest) {
|
|
452
452
|
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
|
|
453
453
|
}
|
|
@@ -456,7 +456,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
456
456
|
const optional = OPTIONAL_CONTEXT[inventory.family];
|
|
457
457
|
if (optional) {
|
|
458
458
|
for (const family of optional) {
|
|
459
|
-
const context = inspectTaskArtifacts(taskRef, family, options);
|
|
459
|
+
const context = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
460
460
|
if (context.status === 'ready' && context.latest)
|
|
461
461
|
inputs.push(context.latest);
|
|
462
462
|
}
|
|
@@ -466,16 +466,22 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
466
466
|
}
|
|
467
467
|
return attachArtifactSelection({ ...inventory, inputs, codeMode: null }, pathState, options);
|
|
468
468
|
}
|
|
469
|
-
function
|
|
469
|
+
function resolveArtifactContext(taskRef, family, options = {}) {
|
|
470
|
+
return resolveArtifactContextFromTask(taskRef, family, options);
|
|
471
|
+
}
|
|
472
|
+
export function resolveArtifactContextForResolvedTask(resolvedTask, family, options = {}) {
|
|
473
|
+
return resolveArtifactContextFromTask(resolvedTask.taskId, family, options, resolvedTask);
|
|
474
|
+
}
|
|
475
|
+
function resolveCodeContext(inventory, options, resolvedTask) {
|
|
470
476
|
const taskRef = inventory.requestRef;
|
|
471
477
|
const pathState = lifecyclePathState(inventory.taskDir);
|
|
472
478
|
if (pathState.status !== 'valid')
|
|
473
479
|
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
474
480
|
const inputFamily = pathState.decision.path === 'streamlined' ? 'analysis' : 'plan';
|
|
475
|
-
const source = inspectTaskArtifacts(taskRef, inputFamily, options);
|
|
481
|
+
const source = inspectTaskArtifacts(taskRef, inputFamily, options, resolvedTask);
|
|
476
482
|
if (source.status === 'failed' || !source.latest)
|
|
477
483
|
return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest ${inputFamily} artifact is required`);
|
|
478
|
-
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options);
|
|
484
|
+
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options, resolvedTask);
|
|
479
485
|
try {
|
|
480
486
|
const taskContent = fs.readFileSync(path.join(inventory.taskDir, 'task.md'), 'utf8');
|
|
481
487
|
const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
|
|
@@ -492,7 +498,7 @@ function resolveCodeContext(inventory, options) {
|
|
|
492
498
|
const reviewMax = reviewCode.latest?.round ?? 0;
|
|
493
499
|
const inputs = [source.latest];
|
|
494
500
|
const sourceReviewFamily = inputFamily === 'analysis' ? 'review-analysis' : 'review-plan';
|
|
495
|
-
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options);
|
|
501
|
+
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options, resolvedTask);
|
|
496
502
|
if (sourceReview.status === 'ready' && sourceReview.latest && sourceReview.reviewedInput?.name === source.latest.name)
|
|
497
503
|
inputs.push(sourceReview.latest);
|
|
498
504
|
if (inventory.openRounds.length > 0) {
|
|
@@ -5,7 +5,6 @@ import { readStableFileSync } from './secure-fs.js';
|
|
|
5
5
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
6
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
7
7
|
import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
|
|
8
|
-
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
8
|
function sha256Content(content) {
|
|
10
9
|
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
11
10
|
}
|
|
@@ -239,57 +238,50 @@ function initializeArtifactSkeleton(request) {
|
|
|
239
238
|
if (path.dirname(path.resolve(target)) !== path.resolve(request.taskDir))
|
|
240
239
|
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be inside the current task directory');
|
|
241
240
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
fs.renameSync(tempPath, target);
|
|
273
|
-
}
|
|
274
|
-
catch (error) {
|
|
275
|
-
try {
|
|
276
|
-
fs.unlinkSync(tempPath);
|
|
277
|
-
}
|
|
278
|
-
catch { /* preserve primary error */ }
|
|
279
|
-
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
280
|
-
}
|
|
281
|
-
return {
|
|
282
|
-
status: 'applied',
|
|
283
|
-
changed: true,
|
|
284
|
-
artifactSha256: sha256Content(content),
|
|
285
|
-
semanticDigest: canonicalSemanticDigest(content),
|
|
286
|
-
error: null
|
|
287
|
-
};
|
|
288
|
-
});
|
|
241
|
+
const existing = fs.lstatSync(target);
|
|
242
|
+
if (existing.isSymbolicLink() || !existing.isFile())
|
|
243
|
+
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
|
|
244
|
+
return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (error.code !== 'ENOENT')
|
|
248
|
+
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
249
|
+
}
|
|
250
|
+
const skeleton = renderArtifactSkeleton({
|
|
251
|
+
taskId: request.taskId,
|
|
252
|
+
family: request.family,
|
|
253
|
+
artifact: request.artifact,
|
|
254
|
+
...(request.locale ? { locale: request.locale } : {})
|
|
255
|
+
});
|
|
256
|
+
const content = skeleton.trimEnd();
|
|
257
|
+
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
258
|
+
try {
|
|
259
|
+
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
260
|
+
try {
|
|
261
|
+
fs.lstatSync(target);
|
|
262
|
+
fs.unlinkSync(tempPath);
|
|
263
|
+
return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently');
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (error.code !== 'ENOENT')
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
fs.renameSync(tempPath, target);
|
|
289
270
|
}
|
|
290
271
|
catch (error) {
|
|
291
|
-
|
|
272
|
+
try {
|
|
273
|
+
fs.unlinkSync(tempPath);
|
|
274
|
+
}
|
|
275
|
+
catch { /* preserve primary error */ }
|
|
276
|
+
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
292
277
|
}
|
|
278
|
+
return {
|
|
279
|
+
status: 'applied',
|
|
280
|
+
changed: true,
|
|
281
|
+
artifactSha256: sha256Content(content),
|
|
282
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
283
|
+
error: null
|
|
284
|
+
};
|
|
293
285
|
}
|
|
294
286
|
export { canonicalSemanticDigest, initializeArtifactSkeleton, inspectArtifactContract, inspectArtifactPatterns, inspectArtifactStructure, sha256Content };
|
|
295
287
|
//# sourceMappingURL=artifact-operations.js.map
|
|
@@ -275,7 +275,7 @@ function recommendNext(facts) {
|
|
|
275
275
|
}
|
|
276
276
|
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
|
|
277
277
|
}
|
|
278
|
-
function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false) {
|
|
278
|
+
function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false, repositoryRoot) {
|
|
279
279
|
try {
|
|
280
280
|
const metadata = parseTypedTaskFrontmatter(content);
|
|
281
281
|
const rework = parseReworkIntentDocument(content);
|
|
@@ -387,7 +387,7 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
387
387
|
&& artifactHashes[input] === receipt.inputSha256)
|
|
388
388
|
reviewedInputs[family] = input;
|
|
389
389
|
if (family === 'review-code') {
|
|
390
|
-
const identity = inspectReviewIdentity(taskDir, reviewContent);
|
|
390
|
+
const identity = inspectReviewIdentity(taskDir, reviewContent, repositoryRoot);
|
|
391
391
|
reviewCodeIdentityValid = identity.status === 'matched'
|
|
392
392
|
&& hasArtifactCompletionFact(content, path.join(taskDir, latest), 'review-code.completed')
|
|
393
393
|
&& hasArtifactCompletionLog(content, latest, 'review-code.completed');
|
|
@@ -4,9 +4,10 @@ import { execFileSync } from 'node:child_process';
|
|
|
4
4
|
import { formatTable } from '../../table.js';
|
|
5
5
|
import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
|
|
6
6
|
import { loadShortIdByTaskId } from '../short-id.js';
|
|
7
|
+
import { resolveProjectTaskWorkspaceRoot } from '../workspace-root.js';
|
|
7
8
|
const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
|
|
8
9
|
|
|
9
|
-
Lists tasks under .
|
|
10
|
+
Lists tasks under the project task workspace. Defaults to active tasks only.
|
|
10
11
|
--all Include active + blocked + completed (excludes archive)
|
|
11
12
|
--blocked Only blocked tasks
|
|
12
13
|
--completed Only completed tasks
|
|
@@ -55,7 +56,7 @@ function parseSelection(args) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
function collectTasks(repoRoot, state) {
|
|
58
|
-
const dir = path.join(repoRoot,
|
|
59
|
+
const dir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
|
|
59
60
|
if (!fs.existsSync(dir))
|
|
60
61
|
return [];
|
|
61
62
|
const shortIdByTaskId = state === 'active' || state === 'completed'
|
|
@@ -101,7 +102,7 @@ function ls(args = []) {
|
|
|
101
102
|
rows.push(...collectTasks(repoRoot, state));
|
|
102
103
|
}
|
|
103
104
|
if (rows.length === 0) {
|
|
104
|
-
process.stdout.write(`No tasks under
|
|
105
|
+
process.stdout.write(`No tasks under project task workspace state ${selection.join('|')}\n`);
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
const tableRows = rows.map((r, i) => [
|
|
@@ -388,7 +388,7 @@ function buildFromResolved(input) {
|
|
|
388
388
|
const run = input.run ?? makeRunner(input.repoRoot);
|
|
389
389
|
const artifacts = enumerateArtifacts(input.taskDir);
|
|
390
390
|
const workflow = collectWorkflow(content, input.now);
|
|
391
|
-
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
|
|
391
|
+
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active', false, input.repoRoot);
|
|
392
392
|
const next = facts.ok ? recommendNext(facts.facts) : null;
|
|
393
393
|
const recommendation = next?.action ?? DASH;
|
|
394
394
|
return {
|
|
@@ -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
|
import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.js';
|
|
4
5
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
@@ -6,7 +7,6 @@ import { checkpointCommitMatches } from './commit-identity.js';
|
|
|
6
7
|
import { resolveTaskContext, resolveTaskRef } from './resolve-ref.js';
|
|
7
8
|
import { commitPushDecision } from './commit-policy.js';
|
|
8
9
|
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
9
|
-
import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
|
|
10
10
|
import { appendActivityEntry, locateActivityLog } from './activity-log.js';
|
|
11
11
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
12
12
|
import { checkpointIntentDigest, readCheckpointIntent, removeCheckpointIntent, sameCheckpointIntent, updateCheckpointIntent, writeCheckpointIntent } from './commit-intent.js';
|
|
@@ -58,6 +58,8 @@ function resolveBoundTask(input) {
|
|
|
58
58
|
};
|
|
59
59
|
return boundTask(input, resolved.taskId, resolved.taskDir, resolved.taskMdPath, resolved.repoRoot, resolved.state);
|
|
60
60
|
}
|
|
61
|
+
if (!fs.existsSync(path.join(input.cwd, '.agents', '.airc.json')))
|
|
62
|
+
return { kind: 'none' };
|
|
61
63
|
const context = resolveTaskContext(undefined, { repoRoot: input.cwd });
|
|
62
64
|
if (context.ok)
|
|
63
65
|
return boundTask(input, context.taskId, context.taskDir, context.taskMdPath, context.repoRoot, context.state);
|
|
@@ -181,7 +183,7 @@ function checkpointIdentity(input, task, delivery) {
|
|
|
181
183
|
function checkpointTimestamp() {
|
|
182
184
|
return new Date().toISOString();
|
|
183
185
|
}
|
|
184
|
-
function
|
|
186
|
+
function executeCommitOperationCore(input, task, mode) {
|
|
185
187
|
const inspected = inspectGitWorkflow(input.cwd);
|
|
186
188
|
if (!inspected.snapshot)
|
|
187
189
|
return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
|
|
@@ -432,12 +434,10 @@ function executeCommitOperation(input) {
|
|
|
432
434
|
if (mode === 'orchestrated' && !task)
|
|
433
435
|
return failure(normalizedInput, mode, null, 'ORCHESTRATION_TASK_REQUIRED', 'orchestrated commit requires a task context', 'blocked');
|
|
434
436
|
try {
|
|
435
|
-
return task
|
|
436
|
-
? withRepositoryMutationLock(normalizedInput.cwd, () => withTaskExecutionLock(normalizedInput.cwd, task.taskId, 'commit-operation', () => executeUnlocked(normalizedInput, task, mode)))
|
|
437
|
-
: withRepositoryMutationLock(normalizedInput.cwd, () => executeUnlocked(normalizedInput, null, mode));
|
|
437
|
+
return executeCommitOperationCore(normalizedInput, task, mode);
|
|
438
438
|
}
|
|
439
439
|
catch (error) {
|
|
440
|
-
const code =
|
|
440
|
+
const code = 'COMMIT_OPERATION_FAILED';
|
|
441
441
|
return failure(normalizedInput, mode, task?.taskId ?? null, code, error instanceof Error ? error.message : String(error), 'blocked');
|
|
442
442
|
}
|
|
443
443
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
3
4
|
import { isAgentClientId } from '../agent-clients/types.js';
|
|
@@ -5,10 +6,10 @@ import { sandboxControlPaths } from '../sandbox/workspace-view.js';
|
|
|
5
6
|
import { getAgentClientAdapter } from '../agent-clients/registry.js';
|
|
6
7
|
import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, awaitOrchestrationDelegationActivation, beginOrResumeOrchestration, dispatchOrchestrationDelegation, OrchestrationStateError, pauseOrchestration, prepareOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from './orchestration.js';
|
|
7
8
|
import { applyTaskFinalization } from './finalization.js';
|
|
8
|
-
import { applyTaskLifecycle
|
|
9
|
+
import { applyTaskLifecycle } from './lifecycle.js';
|
|
9
10
|
import { resolveTaskContext, resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
10
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
11
11
|
import { verifyTaskEvent } from './verification.js';
|
|
12
|
+
import { releaseStaleShortIdRegistry } from './short-id.js';
|
|
12
13
|
export function taskControlOperationKey(operation) {
|
|
13
14
|
if (operation.family === 'task-finalization')
|
|
14
15
|
return 'complete';
|
|
@@ -112,7 +113,7 @@ function lifecycleTaskId(request, repoRoot) {
|
|
|
112
113
|
async function applyLifecycleWithAuthority(context, request) {
|
|
113
114
|
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
114
115
|
const controlRoot = taskId ? controlRootForTask(context, taskId) : undefined;
|
|
115
|
-
const execute = async (
|
|
116
|
+
const execute = async () => {
|
|
116
117
|
if (request.intent === 'recover-started') {
|
|
117
118
|
const adapter = isAgentClientId(request.agent)
|
|
118
119
|
? getAgentClientAdapter(request.agent).orchestrationAdapter
|
|
@@ -126,36 +127,13 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
126
127
|
}
|
|
127
128
|
return adapter.recoverStarted(request, { repoRoot: context.repoRoot });
|
|
128
129
|
}
|
|
129
|
-
return
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}, taskId)
|
|
135
|
-
: applyTaskLifecycle(request, {
|
|
136
|
-
repoRoot: context.repoRoot,
|
|
137
|
-
...(controlRoot ? { controlRoot } : {}),
|
|
138
|
-
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
|
|
139
|
-
});
|
|
130
|
+
return applyTaskLifecycle(request, {
|
|
131
|
+
repoRoot: context.repoRoot,
|
|
132
|
+
...(controlRoot ? { controlRoot } : {}),
|
|
133
|
+
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
|
|
134
|
+
});
|
|
140
135
|
};
|
|
141
|
-
|
|
142
|
-
return await execute();
|
|
143
|
-
try {
|
|
144
|
-
return await withTaskExecutionLock(context.repoRoot, taskId, `task-lifecycle.${request.intent}`, () => execute(true));
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
if (!(error instanceof TaskExecutionLockError))
|
|
148
|
-
throw error;
|
|
149
|
-
if (request.intent === 'recover-started') {
|
|
150
|
-
return {
|
|
151
|
-
status: 'owner-unknown', changed: false, targetState: 'active',
|
|
152
|
-
requestRef: request.taskRef, intent: 'recover-started', taskId,
|
|
153
|
-
receiptId: null, childId: null,
|
|
154
|
-
error: { code: error.code, message: error.message }
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
158
|
-
}
|
|
136
|
+
return await execute();
|
|
159
137
|
}
|
|
160
138
|
function controlRootForTask(context, taskId) {
|
|
161
139
|
if (context.source === 'sandbox-executor')
|
|
@@ -251,6 +229,28 @@ export function dispatchTaskControlOperation(context, operation) {
|
|
|
251
229
|
...(controlRoot ? { controlRoot } : {}),
|
|
252
230
|
...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {}),
|
|
253
231
|
preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
|
|
232
|
+
}).then((result) => {
|
|
233
|
+
if (context.source !== 'direct-host' || !controlRoot || result.status !== 'completed' || !result.taskId)
|
|
234
|
+
return result;
|
|
235
|
+
try {
|
|
236
|
+
if (fs.lstatSync(controlRoot, { throwIfNoEntry: false }) === undefined) {
|
|
237
|
+
const released = releaseStaleShortIdRegistry(context.repoRoot, result.taskId);
|
|
238
|
+
return { ...result, changed: result.changed || released.changed };
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
...result,
|
|
245
|
+
status: 'blocked',
|
|
246
|
+
result: 'blocked',
|
|
247
|
+
error: {
|
|
248
|
+
code: 'TASK_FINALIZATION_SHORT_ID_RELEASE_FAILED',
|
|
249
|
+
message: error instanceof Error ? error.message : String(error),
|
|
250
|
+
retryable: true
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
254
|
});
|
|
255
255
|
}
|
|
256
256
|
try {
|
|
@@ -3,7 +3,6 @@ import { createPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
|
3
3
|
import { VERSION } from '../version.js';
|
|
4
4
|
import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
|
|
5
5
|
import { createLocalTask } from './create.js';
|
|
6
|
-
import { TaskExecutionLockError } from './task-execution-lock.js';
|
|
7
6
|
import { verifyTaskEvent } from './verification.js';
|
|
8
7
|
const DEFAULT_DEPENDENCIES = {
|
|
9
8
|
createIssue: createPlatformIssue,
|
|
@@ -152,9 +151,8 @@ async function createTask(value, options) {
|
|
|
152
151
|
}
|
|
153
152
|
catch (error) {
|
|
154
153
|
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
return taskCreateFailure({ code, message, retryable: lockBusy || code === 'TASK_CREATE_LOCK_TIMEOUT' });
|
|
154
|
+
const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
|
|
155
|
+
return taskCreateFailure({ code, message, retryable: false });
|
|
158
156
|
}
|
|
159
157
|
const operations = [{
|
|
160
158
|
name: 'task:local', status: local.status, reasonCode: null
|