@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.
Files changed (253) hide show
  1. package/bin/cli.ts +1 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/bin/internal-cli.js +6 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
  6. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
  7. package/dist/lib/decide.js +2 -7
  8. package/dist/lib/internal/cli-route-inventory.js +3 -0
  9. package/dist/lib/internal/manual-validation.js +8 -16
  10. package/dist/lib/internal/platform-comment.js +3 -2
  11. package/dist/lib/internal/task-activity.js +1 -12
  12. package/dist/lib/internal/task-event.js +1 -12
  13. package/dist/lib/internal/task-ledger.js +1 -12
  14. package/dist/lib/internal/task-operation-registry.js +2 -1
  15. package/dist/lib/internal/task-qualification.js +6 -15
  16. package/dist/lib/internal/task-review.js +3 -16
  17. package/dist/lib/internal/task-short-id.js +2 -1
  18. package/dist/lib/internal/task-verify.js +1 -11
  19. package/dist/lib/internal/task-warning.js +1 -12
  20. package/dist/lib/internal/task-workspace.js +43 -0
  21. package/dist/lib/merge.js +4 -3
  22. package/dist/lib/platform/pr-summary.js +6 -15
  23. package/dist/lib/platform/pull-requests.js +3 -15
  24. package/dist/lib/pr-review/evidence-grading.js +3 -2
  25. package/dist/lib/process-data/index.js +5 -4
  26. package/dist/lib/process-data/sources.js +6 -0
  27. package/dist/lib/run/index.js +3 -2
  28. package/dist/lib/sandbox/commands/create.js +5 -1
  29. package/dist/lib/sandbox/commands/enter.js +1 -1
  30. package/dist/lib/sandbox/commands/list-running.js +2 -1
  31. package/dist/lib/sandbox/control/client.js +8 -13
  32. package/dist/lib/sandbox/control/completed-reentry.js +2 -1
  33. package/dist/lib/sandbox/control/task-view.js +4 -2
  34. package/dist/lib/sandbox/mounts.js +4 -4
  35. package/dist/lib/sandbox/recovery.js +26 -16
  36. package/dist/lib/sandbox/removal.js +3 -10
  37. package/dist/lib/sandbox/task-resolver.js +9 -6
  38. package/dist/lib/sandbox/workspace-identity.js +6 -5
  39. package/dist/lib/sandbox/workspace-view.js +29 -22
  40. package/dist/lib/task/activity-intent.js +2 -12
  41. package/dist/lib/task/artifact-lifecycle.js +17 -11
  42. package/dist/lib/task/artifact-operations.js +41 -49
  43. package/dist/lib/task/capabilities.js +2 -2
  44. package/dist/lib/task/commands/ls.js +4 -3
  45. package/dist/lib/task/commands/status.js +1 -1
  46. package/dist/lib/task/commit-operation.js +6 -6
  47. package/dist/lib/task/control-authority.js +32 -32
  48. package/dist/lib/task/create-service.js +2 -4
  49. package/dist/lib/task/create.js +12 -65
  50. package/dist/lib/task/delegation-receipts.js +1 -1
  51. package/dist/lib/task/delivery.js +2 -8
  52. package/dist/lib/task/events.js +5 -31
  53. package/dist/lib/task/finalization.js +17 -22
  54. package/dist/lib/task/lifecycle.js +9 -48
  55. package/dist/lib/task/local-artifact-finalization.js +1 -2
  56. package/dist/lib/task/orchestration.js +24 -57
  57. package/dist/lib/task/platform-operation-recovery.js +1 -2
  58. package/dist/lib/task/resolve-ref.js +17 -12
  59. package/dist/lib/task/review-finalization.js +2 -35
  60. package/dist/lib/task/review-identity.js +3 -1
  61. package/dist/lib/task/short-id.js +10 -8
  62. package/dist/lib/task/verification-engine.js +19 -58
  63. package/dist/lib/task/workspace-root.js +59 -0
  64. package/dist/lib/task/workspace-snapshot.js +4 -3
  65. package/dist/lifecycle-build-manifest.json +3 -3
  66. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
  67. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
  68. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
  69. package/lib/decide.ts +2 -7
  70. package/lib/internal/cli-route-inventory.ts +2 -0
  71. package/lib/internal/manual-validation.ts +8 -19
  72. package/lib/internal/platform-comment.ts +3 -2
  73. package/lib/internal/task-activity.ts +1 -10
  74. package/lib/internal/task-event.ts +1 -10
  75. package/lib/internal/task-ledger.ts +1 -10
  76. package/lib/internal/task-operation-registry.ts +2 -1
  77. package/lib/internal/task-qualification.ts +5 -11
  78. package/lib/internal/task-review.ts +3 -14
  79. package/lib/internal/task-short-id.ts +2 -1
  80. package/lib/internal/task-verify.ts +1 -9
  81. package/lib/internal/task-warning.ts +1 -10
  82. package/lib/internal/task-workspace.ts +39 -0
  83. package/lib/merge.ts +4 -3
  84. package/lib/platform/pr-summary.ts +7 -16
  85. package/lib/platform/pull-requests.ts +3 -24
  86. package/lib/pr-review/evidence-grading.ts +3 -2
  87. package/lib/process-data/index.ts +4 -3
  88. package/lib/process-data/sources.ts +6 -0
  89. package/lib/run/index.ts +3 -2
  90. package/lib/sandbox/commands/create.ts +5 -1
  91. package/lib/sandbox/commands/enter.ts +1 -1
  92. package/lib/sandbox/commands/list-running.ts +2 -1
  93. package/lib/sandbox/control/client.ts +7 -12
  94. package/lib/sandbox/control/completed-reentry.ts +2 -1
  95. package/lib/sandbox/control/task-view.ts +4 -2
  96. package/lib/sandbox/mounts.ts +4 -4
  97. package/lib/sandbox/recovery.ts +26 -18
  98. package/lib/sandbox/removal.ts +3 -26
  99. package/lib/sandbox/task-resolver.ts +9 -6
  100. package/lib/sandbox/workspace-identity.ts +6 -5
  101. package/lib/sandbox/workspace-view.ts +28 -20
  102. package/lib/task/activity-intent.ts +3 -17
  103. package/lib/task/artifact-lifecycle.ts +39 -11
  104. package/lib/task/artifact-operations.ts +28 -35
  105. package/lib/task/capabilities.ts +2 -2
  106. package/lib/task/commands/ls.ts +4 -3
  107. package/lib/task/commands/status.ts +1 -1
  108. package/lib/task/commit-operation.ts +5 -6
  109. package/lib/task/control-authority.ts +29 -35
  110. package/lib/task/create-service.ts +2 -4
  111. package/lib/task/create.ts +10 -51
  112. package/lib/task/delegation-receipts.ts +1 -1
  113. package/lib/task/delivery.ts +2 -10
  114. package/lib/task/events.ts +5 -32
  115. package/lib/task/finalization.ts +16 -24
  116. package/lib/task/lifecycle.ts +9 -50
  117. package/lib/task/local-artifact-finalization.ts +1 -4
  118. package/lib/task/orchestration.ts +31 -80
  119. package/lib/task/platform-operation-recovery.ts +1 -2
  120. package/lib/task/resolve-ref.ts +18 -12
  121. package/lib/task/review-finalization.ts +2 -52
  122. package/lib/task/review-identity.ts +3 -1
  123. package/lib/task/short-id.ts +10 -8
  124. package/lib/task/verification-engine.ts +20 -62
  125. package/lib/task/workspace-root.ts +78 -0
  126. package/lib/task/workspace-snapshot.ts +4 -3
  127. package/package.json +1 -1
  128. package/templates/.agents/QUICKSTART.en.md +19 -8
  129. package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
  130. package/templates/.agents/README.en.md +10 -15
  131. package/templates/.agents/README.zh-CN.md +9 -13
  132. package/templates/.agents/rules/issue-sync.en.md +1 -1
  133. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  134. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  135. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  136. package/templates/.agents/rules/next-step-output.en.md +2 -2
  137. package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
  138. package/templates/.agents/rules/pr-sync.en.md +1 -1
  139. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
  140. package/templates/.agents/rules/release-commands.en.md +3 -0
  141. package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
  142. package/templates/.agents/rules/task-management.en.md +0 -2
  143. package/templates/.agents/rules/task-management.zh-CN.md +0 -2
  144. package/templates/.agents/rules/task-short-id.en.md +2 -2
  145. package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
  146. package/templates/.agents/scripts/task-short-id.js +2 -8
  147. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  148. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  149. package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
  150. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
  151. package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
  152. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
  153. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
  154. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
  156. package/templates/.agents/skills/block-task/config/verify.json +0 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -6
  158. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -6
  159. package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
  160. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  161. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  162. package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
  163. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
  164. package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
  165. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  166. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  167. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  168. package/templates/.agents/skills/commit/config/verify.json +0 -1
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
  170. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
  171. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  174. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  175. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
  176. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
  177. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  178. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  179. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -7
  180. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +8 -8
  181. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  182. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  183. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
  184. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
  185. package/templates/.agents/skills/create-pr/config/verify.json +0 -1
  186. package/templates/.agents/skills/create-pr/reference/change-report.en.md +1 -1
  187. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +1 -1
  188. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  189. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  190. package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
  191. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
  192. package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
  193. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
  194. package/templates/.agents/skills/create-task/config/verify.json +0 -1
  195. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  196. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  197. package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
  198. package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
  199. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  200. package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
  201. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/config/verify.json +0 -1
  204. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  205. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  206. package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
  207. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
  208. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
  210. package/templates/.agents/skills/restore-task/config/verify.json +0 -1
  211. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  213. package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
  214. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
  215. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  216. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  217. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  218. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  219. package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
  220. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
  221. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  222. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  223. package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
  224. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
  225. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  226. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  227. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
  228. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
  229. package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
  230. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
  231. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
  232. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
  233. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
  234. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
  235. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  236. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  237. package/templates/.agents/skills/test/SKILL.en.md +0 -3
  238. package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
  239. package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
  240. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
  241. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
  242. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
  243. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
  244. package/templates/.agents/skills/watch-pr/SKILL.en.md +3 -3
  245. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +3 -3
  246. package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
  247. package/templates/.agents/templates/handoff.en.md +1 -1
  248. package/templates/.agents/templates/handoff.zh-CN.md +1 -1
  249. package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
  250. package/templates/.agents/workflows/feature-development.en.yaml +1 -1
  251. package/templates/.agents/workflows/refactoring.en.yaml +1 -1
  252. package/dist/lib/task/task-execution-lock.js +0 -250
  253. 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, '.agents', 'workspace', 'active', '.short-ids.json');
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, '.agents', 'workspace', 'active', taskId, 'task.md');
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, '.agents', 'workspace', 'active', taskId, 'task.md'), taskId), workspace };
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, '.agents', 'workspace', 'active', requestedRef, 'task.md'), requestedRef), workspace };
89
+ return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', requestedRef, 'task.md'), requestedRef), workspace };
89
90
  }
90
- const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
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 activeSource = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
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(path.join(repoRoot, '.agents', 'workspace'));
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(path.join(repoRoot, '.agents', 'workspace'));
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(worktreeRoot, taskId) {
140
- const resolvedWorktreeRoot = path.resolve(worktreeRoot);
141
- const canonicalWorktreeRoot = fs.realpathSync.native(resolvedWorktreeRoot);
142
- const workspaceRoot = path.resolve(resolvedWorktreeRoot, '.agents', 'workspace');
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 completedTaskTarget = taskId ? path.join(workspaceRoot, 'completed', taskId) : null;
146
- if (taskId && !/^TASK-\d{8}-\d{6}$/.test(taskId))
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.closeSync(fs.openSync(registryTarget, 'a', 0o600));
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 = path.resolve(repoRoot, '.agents', 'workspace');
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, '.agents', 'workspace', 'completed', taskId);
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(path.join(repoRoot, '.agents', 'workspace'));
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 (workspaceRelative.startsWith('..') || path.isAbsolute(workspaceRelative)
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 applyLocked(intent, normalizedAgent, options) {
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
- if (options.lockAlreadyHeld)
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 resolveArtifactContext(taskRef, family, options = {}) {
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 resolveCodeContext(inventory, options) {
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
- return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.init', () => {
243
- try {
244
- const existing = fs.lstatSync(target);
245
- if (existing.isSymbolicLink() || !existing.isFile())
246
- return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
247
- return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
248
- }
249
- catch (error) {
250
- if (error.code !== 'ENOENT')
251
- return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
252
- }
253
- const skeleton = renderArtifactSkeleton({
254
- taskId: request.taskId,
255
- family: request.family,
256
- artifact: request.artifact,
257
- ...(request.locale ? { locale: request.locale } : {})
258
- });
259
- const content = skeleton.trimEnd();
260
- const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
261
- try {
262
- fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
263
- try {
264
- fs.lstatSync(target);
265
- fs.unlinkSync(tempPath);
266
- return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently');
267
- }
268
- catch (error) {
269
- if (error.code !== 'ENOENT')
270
- throw error;
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
- return resultFailure('ARTIFACT_INIT_LOCK_FAILED', String(error));
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 .agents/workspace/. Defaults to active tasks only.
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, '.agents', 'workspace', state);
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 .agents/workspace/${selection.join('|')}\n`);
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 executeUnlocked(input, task, mode) {
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 = error instanceof TaskExecutionLockError ? error.code : 'COMMIT_OPERATION_FAILED';
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, applyTaskLifecycleWithinTaskLock, taskLifecycleFailure } from './lifecycle.js';
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 (underTaskLock = false) => {
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 underTaskLock
130
- ? applyTaskLifecycleWithinTaskLock(request, {
131
- repoRoot: context.repoRoot,
132
- ...(controlRoot ? { controlRoot } : {}),
133
- ...(context.source === 'direct-host' && context.project ? { controlProject: context.project } : {})
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
- if (!taskId)
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 lockBusy = error instanceof TaskExecutionLockError && error.code === 'ORCHESTRATION_LOCK_BUSY';
156
- const code = lockBusy ? error.code : /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
157
- return taskCreateFailure({ code, message, retryable: lockBusy || code === 'TASK_CREATE_LOCK_TIMEOUT' });
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