@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
package/dist/lib/merge.js CHANGED
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import crypto from 'node:crypto';
5
5
  import { removeDirRecursive } from './remove-dir.js';
6
6
  import { validateCurrentTaskContract } from './task/current-contract.js';
7
+ import { resolveProjectTaskWorkspaceRoot } from './task/workspace-root.js';
7
8
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
8
9
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
9
10
  const TITLE_RE = /^# (.+)$/m;
@@ -800,7 +801,7 @@ function mergeArchiveSection(sourceArchive, localArchive, report) {
800
801
  return sourceTasks.length;
801
802
  }
802
803
  function printSection(lines, name, counts) {
803
- const title = `${SECTION_LABELS[name].padEnd(9, ' ')} (.agents/workspace/${name}/):`;
804
+ const title = `${SECTION_LABELS[name].padEnd(9, ' ')} (task workspace/${name}/):`;
804
805
  lines.push(title);
805
806
  const allEntries = [
806
807
  ['copied', '✓ Copied '],
@@ -820,7 +821,7 @@ function printSection(lines, name, counts) {
820
821
  lines.push('');
821
822
  }
822
823
  function printArchiveSection(lines, counts) {
823
- const title = `${SECTION_LABELS.archive.padEnd(9, ' ')} (.agents/workspace/archive/):`;
824
+ const title = `${SECTION_LABELS.archive.padEnd(9, ' ')} (task workspace/archive/):`;
824
825
  lines.push(title);
825
826
  if (counts.copied.length === 0 && counts.skipped.length === 0) {
826
827
  lines.push(' (no changes)', '');
@@ -891,7 +892,7 @@ async function cmdMerge(args) {
891
892
  throw new Error(`Source path is not a directory: ${sourcePath}`);
892
893
  }
893
894
  const repoRoot = process.cwd();
894
- const workspaceDir = path.join(repoRoot, '.agents', 'workspace');
895
+ const workspaceDir = resolveProjectTaskWorkspaceRoot(repoRoot);
895
896
  const archiveDir = path.join(workspaceDir, 'archive');
896
897
  const backupStamp = formatBackupTimestamp(new Date());
897
898
  const projectKey = resolveRecoveryProjectKey(repoRoot);
@@ -4,7 +4,6 @@ import { createHash } from 'node:crypto';
4
4
  import { enumerateArtifacts } from '../task/artifacts.js';
5
5
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
6
6
  import { resolveTaskRef } from '../task/resolve-ref.js';
7
- import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
8
7
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
9
8
  import { resolvePlatformProviderContext } from './context.js';
10
9
  import { normalizeCommentContent } from './issue-comments.js';
@@ -348,7 +347,7 @@ async function reportWrite(taskRef, options) {
348
347
  return { ...context, report: null };
349
348
  let knownPrNumber = null;
350
349
  try {
351
- return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
350
+ return await (async () => {
352
351
  const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
353
352
  if (factError)
354
353
  return {
@@ -433,13 +432,10 @@ async function reportWrite(taskRef, options) {
433
432
  operations: [{ name: 'change-report:write', status: 'applied', reasonCode: null }],
434
433
  report: { ...reportInfo, status: 'written' }
435
434
  };
436
- });
435
+ })();
437
436
  }
438
437
  catch (error) {
439
- const lockError = error instanceof TaskExecutionLockError
440
- ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
441
- : { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
442
- return { ...basePlatformResult(lockError.retryable ? 'blocked' : 'failed', context, resolved.taskId, knownPrNumber, lockError), report: null };
438
+ return { ...basePlatformResult('failed', context, resolved.taskId, knownPrNumber, { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
443
439
  }
444
440
  }
445
441
  async function syncPullRequestSummary(taskRef, options) {
@@ -492,7 +488,7 @@ async function syncPullRequestSummary(taskRef, options) {
492
488
  const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
493
489
  const context = loaded.ok ? loaded.value.context : loaded.context;
494
490
  if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) {
495
- // This diagnostic-only read preserves the known PR target when context resolution fails before the execution lock.
491
+ // Preserve the known PR target for diagnostics when provider context resolution fails.
496
492
  knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
497
493
  return preserveFailure(context);
498
494
  }
@@ -737,15 +733,10 @@ async function syncPullRequestSummary(taskRef, options) {
737
733
  persistenceFailure: (error) => fail('failed', context, error, prNumber)
738
734
  });
739
735
  };
740
- return await (options.lockAlreadyHeld
741
- ? execute()
742
- : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
736
+ return await execute();
743
737
  }
744
738
  catch (error) {
745
- const lockError = error instanceof TaskExecutionLockError
746
- ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
747
- : { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
748
- return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
739
+ return fail('failed', context, { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false });
749
740
  }
750
741
  }
751
742
  export { buildPullRequestSummary, reconcileSummaryComment, reportWrite, summaryCommentState, summaryContext, summaryMarker, syncPullRequestSummary, warningResultForPrimary };
@@ -14,7 +14,6 @@ import { planPullRequestMetadata } from './pull-request-metadata.js';
14
14
  import { planInLabelUpdate, validateInLabelMapping } from './in-label-sync.js';
15
15
  import { platformResult } from './types.js';
16
16
  import { inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
17
- import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
18
17
  import { resolveDeliveryTarget } from '../task/delivery-target.js';
19
18
  import { mergeOperationWarnings } from '../task/operation-outcome.js';
20
19
  import { buildBoundFact, buildSkippedFact, factFrontmatterMutation, readPrDeliveryFact } from '../task/pr-delivery-fact.js';
@@ -1007,16 +1006,9 @@ async function createPlatformPullRequestImpl(taskRef, options) {
1007
1006
  }), PRECONDITION_NOT_CREATED);
1008
1007
  }
1009
1008
  try {
1010
- return withTaskExecutionLock(base.resolved.repoRoot, base.resolved.taskId, 'platform-pr.create', () => createExternalPullRequest(taskRef, options, base));
1009
+ return await createExternalPullRequest(taskRef, options, base);
1011
1010
  }
1012
1011
  catch (error) {
1013
- if (error instanceof TaskExecutionLockError) {
1014
- return withCreation(result('blocked', base.resolved.taskId, base.issueNumber, null, {
1015
- platform: base.context.platform,
1016
- capabilities: base.context.capabilities,
1017
- error: { code: error.code, message: error.message, retryable: true }
1018
- }), PRECONDITION_NOT_CREATED);
1019
- }
1020
1012
  return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
1021
1013
  platform: base.context.platform,
1022
1014
  capabilities: base.context.capabilities,
@@ -1456,7 +1448,7 @@ function readProjectPrFlow(repoRoot) {
1456
1448
  return undefined;
1457
1449
  }
1458
1450
  }
1459
- function skipPlatformPullRequestFactUnlocked(resolved, options) {
1451
+ function applySkippedPullRequestFact(resolved, options) {
1460
1452
  let content;
1461
1453
  let frontmatter;
1462
1454
  try {
@@ -1533,13 +1525,9 @@ async function skipPlatformPullRequestFact(taskRef, options) {
1533
1525
  error: { code: 'TASK_STATE_INVALID', message: 'platform-pr skip requires an active task', retryable: false }
1534
1526
  });
1535
1527
  try {
1536
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'platform-pr.skip', () => skipPlatformPullRequestFactUnlocked(resolved, options));
1528
+ return await applySkippedPullRequestFact(resolved, options);
1537
1529
  }
1538
1530
  catch (error) {
1539
- if (error instanceof TaskExecutionLockError)
1540
- return result('blocked', resolved.taskId, null, null, {
1541
- error: { code: error.code, message: error.message, retryable: true }
1542
- });
1543
1531
  return result('failed', resolved.taskId, null, null, {
1544
1532
  error: { code: 'PR_SKIP_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
1545
1533
  });
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.js';
4
4
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.js';
5
5
  import { resourceIdentityEquals, resourceIdentityNumber } from '../platform/resource-identity.js';
6
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
6
7
  import { taskIssueIdentity } from '../platform/task-identities.js';
7
8
  export class HostResolutionError extends Error {
8
9
  code;
@@ -35,12 +36,12 @@ export function extractClosingIssueNumbers(body) {
35
36
  return result;
36
37
  }
37
38
  /**
38
- * Scan `.agents/workspace/active/{task-id}/task.md` files for tasks bound to the
39
+ * Scan active project task workspace task.md files for tasks bound to the
39
40
  * PR number or to one of the PR's closing issues. A task is emitted at most once;
40
41
  * a verified fact identity hit takes priority over the issue-identity reverse lookup.
41
42
  */
42
43
  export function collectHostCandidates(input) {
43
- const activeRoot = path.join(input.workspaceRoot, '.agents', 'workspace', 'active');
44
+ const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(input.workspaceRoot), 'active');
44
45
  const candidates = [];
45
46
  let entries;
46
47
  try {
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { detectRepoRoot } from '../task/resolve-ref.js';
5
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
5
6
  import { acquireGitHubCheckpoint, commitGitHubCheckpoint, readGitHubCheckpoint, releaseGitHubCheckpoint } from './checkpoint.js';
6
7
  import { materializeJson, materializeSnapshot } from './materialize.js';
7
8
  import { normalizeResources } from './normalize.js';
@@ -60,14 +61,14 @@ function dataRoot(repoRoot, requested) {
60
61
  input = path.join(boundary, '.agent-infra', 'data-store', config.project);
61
62
  }
62
63
  const resolved = path.resolve(repoRoot, input);
63
- const relative = path.relative(repoRoot, resolved);
64
- if (requested !== undefined && (!relative || relative.startsWith('..') || path.isAbsolute(relative)))
65
- throw new Error('data root must be inside the repository');
66
- const workspace = path.resolve(repoRoot, '.agents', 'workspace');
64
+ const workspace = resolveProjectTaskWorkspaceRoot(repoRoot);
67
65
  const taskRoots = ['active', 'blocked', 'completed', 'archive'].map((state) => path.join(workspace, state));
68
66
  if (resolved === workspace || taskRoots.some((taskRoot) => resolved === taskRoot || resolved.startsWith(`${taskRoot}${path.sep}`))) {
69
67
  throw new Error('data root cannot be the workspace root or a task directory');
70
68
  }
69
+ const relative = path.relative(repoRoot, resolved);
70
+ if (requested !== undefined && (!relative || relative.startsWith('..') || path.isAbsolute(relative)))
71
+ throw new Error('data root must be inside the repository');
71
72
  let current = resolved;
72
73
  while (current !== boundary) {
73
74
  if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink())
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createGitHubClient } from '../platform/github-client.js';
4
4
  import { enumerateAllTaskDirs } from '../task/resolve-ref.js';
5
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
5
6
  import { captureText, inspectPrivacy, redactExcerpt } from './privacy.js';
6
7
  import { canonicalJsonBytes, sha256 } from './store.js';
7
8
  function endpointForPage(endpoint, page, perPage) {
@@ -103,6 +104,11 @@ function fetchRestCollection(options) {
103
104
  };
104
105
  }
105
106
  function relativeIdentity(repoRoot, filePath) {
107
+ const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
108
+ const taskRelative = path.relative(workspaceRoot, filePath);
109
+ if (taskRelative && !taskRelative.startsWith('..') && !path.isAbsolute(taskRelative)) {
110
+ return path.posix.join('task-workspace', taskRelative.split(path.sep).join('/'));
111
+ }
106
112
  const relative = path.relative(repoRoot, filePath);
107
113
  if (!relative || relative.startsWith('..') || path.isAbsolute(relative))
108
114
  throw new Error(`path escapes repository: ${filePath}`);
@@ -4,6 +4,7 @@ import { loadServerConfig } from '../server/config.js';
4
4
  import { resolveSandboxTarget } from '../sandbox/workspace-identity.js';
5
5
  import { createRunId, runInSandbox } from '../sandbox/capture.js';
6
6
  import { loadShortIdByTaskId, normalizeShortIdInput } from '../task/short-id.js';
7
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
7
8
  import { buildTuiCommand, renderPrompt, selectTui } from './tui.js';
8
9
  import { getSkillRunSpec } from './skills.js';
9
10
  import { runHostCommand } from './host.js';
@@ -133,7 +134,7 @@ function readShortIdLength(repoRoot) {
133
134
  return 2;
134
135
  }
135
136
  function readActiveShortIdRegistry(repoRoot) {
136
- const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', '.short-ids.json');
137
+ const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
137
138
  try {
138
139
  const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
139
140
  return data && typeof data === 'object' && data.ids && typeof data.ids === 'object' ? data.ids : {};
@@ -157,7 +158,7 @@ function resolveActiveTaskIdentity(taskRef, repoRoot) {
157
158
  }
158
159
  if (!taskId)
159
160
  return null;
160
- const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
161
+ const taskDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
161
162
  if (!fs.existsSync(path.join(taskDir, 'task.md')))
162
163
  return null;
163
164
  return { taskId, taskDir, taskRef: resolvedTaskRef };
@@ -1040,7 +1040,7 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
1040
1040
  const completedTaskSource = target.workspace.mode === 'task-bound'
1041
1041
  ? prepareSandboxCompletedTaskMountSource(effectiveConfig.repoRoot, target.workspace.taskId)
1042
1042
  : null;
1043
- prepareSandboxWorkspaceMountTargets(worktree, target.workspace.mode === 'task-bound' ? target.workspace.taskId : undefined);
1043
+ prepareSandboxWorkspaceMountTargets(effectiveConfig.repoRoot);
1044
1044
  if (previousCutoverSnapshot) {
1045
1045
  assertSandboxControlCutoverSnapshot(previousCutoverSnapshot);
1046
1046
  if (previousManifest) {
@@ -1177,6 +1177,10 @@ export async function create(args, { runProjectInitCommand = true, workspace: wo
1177
1177
  : []),
1178
1178
  '--label',
1179
1179
  `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
1180
+ // Every task sandbox runs without seccomp and AppArmor filtering;
1181
+ // capabilities and namespace isolation remain unchanged.
1182
+ '--security-opt', 'seccomp=unconfined',
1183
+ '--security-opt', 'apparmor=unconfined',
1180
1184
  ...coreVolumes,
1181
1185
  ...buildClipboardVolumeArgs(engine, effectiveConfig.home),
1182
1186
  ...dotfilesMount,
@@ -13,7 +13,7 @@ import { resolveSandboxTarget } from '../workspace-identity.js';
13
13
  const USAGE = `Usage: ai sandbox exec [--recreate] <branch | TASK-id | N> [cmd...]
14
14
 
15
15
  N references an active task short id from
16
- .agents/workspace/active/.short-ids.json. They resolve only via that
16
+ the read-only task-workspace mount at /task-workspace/active/.short-ids.json. They resolve only via that
17
17
  registry — they do not reference a container's row position in
18
18
  'ai sandbox ls' output. --recreate rebuilds the matched container while
19
19
  preserving its task or branch identity. The same token after the target is
@@ -5,6 +5,7 @@ import { runSafeEngine, runVerboseEngine } from '../shell.js';
5
5
  import { resolveTaskBranch } from '../task-resolver.js';
6
6
  import { isRemovedHashShortIdInput } from '../../task/short-id.js';
7
7
  import { parseSandboxWorkspaceIdentity } from '../workspace-identity.js';
8
+ import { resolveProjectTaskWorkspaceRoot } from '../../task/workspace-root.js';
8
9
  export function containerListFormat() {
9
10
  return '{{.Names}}\t{{.Status}}\t{{.Labels}}';
10
11
  }
@@ -111,7 +112,7 @@ function tryResolveFromRegistry(arg, repoRoot) {
111
112
  throw new Error(`Registry returned malformed task id for '${arg}': ${JSON.stringify(taskId)}`);
112
113
  }
113
114
  for (const sub of ['active', 'completed', 'blocked', 'archive']) {
114
- const taskMdPath = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
115
+ const taskMdPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), sub, taskId, 'task.md');
115
116
  if (!fs.existsSync(taskMdPath))
116
117
  continue;
117
118
  const content = fs.readFileSync(taskMdPath, 'utf8');
@@ -1,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
+ import { resolveProjectTaskWorkspaceRoot } from '../../task/workspace-root.js';
5
+ import { detectRepoRoot } from '../../task/resolve-ref.js';
4
6
  import { SANDBOX_CONTROL_MAX_BYTES, SANDBOX_CONTROL_STATUS_STALE_MS, isSandboxControlFamily } from './protocol.js';
5
7
  import { readSandboxControlPayload, readSandboxControlStatus } from './state.js';
6
8
  import { accessSandboxTaskView, taskViewFromStatus } from './task-view.js';
@@ -127,19 +129,12 @@ const CONTROL_TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
127
129
  const CONTROL_SHORT_ID_RE = /^\d+$/;
128
130
  // Use the current short-id contract for the first visible active registry.
129
131
  export function resolveVisibleActiveShortId(ref) {
130
- let dir = process.cwd();
131
- for (let depth = 0; depth < 64; depth += 1) {
132
- const registryPath = path.join(dir, '.agents', 'workspace', 'active', '.short-ids.json');
133
- if (fs.existsSync(registryPath)) {
134
- const resolved = resolveShortIdReadOnly(ref, dir, { shortIdLength: configuredShortIdLength(dir) });
135
- return resolved.ok ? resolved.taskId : null;
136
- }
137
- const parent = path.dirname(dir);
138
- if (parent === dir)
139
- break;
140
- dir = parent;
141
- }
142
- return null;
132
+ const repoRoot = detectRepoRoot();
133
+ const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
134
+ if (!fs.existsSync(registryPath))
135
+ return null;
136
+ const resolved = resolveShortIdReadOnly(ref, repoRoot, { shortIdLength: configuredShortIdLength(repoRoot) });
137
+ return resolved.ok ? resolved.taskId : null;
143
138
  }
144
139
  // A request's leading argument is only a task reference for some families and
145
140
  // may be a documented short id. Compare canonical ids so a bound sandbox keeps
@@ -4,6 +4,7 @@ import { resolveTaskWorkspace } from '../task-resolver.js';
4
4
  import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
5
5
  import { parseTypedTaskFrontmatter } from '../../task/frontmatter.js';
6
6
  import { readTaskFinalizationReceipt } from '../../task/finalization.js';
7
+ import { resolveProjectTaskWorkspaceRoot } from '../../task/workspace-root.js';
7
8
  import { inspectSandboxControlContainer } from './container-identity.js';
8
9
  import { atomicWriteJson, readSandboxControlStatus } from './state.js';
9
10
  import { taskViewForManifest } from './task-view.js';
@@ -40,7 +41,7 @@ export async function prepareCompletedReentry(manifest, inspect = inspectSandbox
40
41
  || previous.receipt.requestId !== view.receipt.requestId
41
42
  || previous.receipt.revision > view.receipt.revision)
42
43
  throw invalid();
43
- const completedRoot = fs.realpathSync.native(path.join(manifest.repoRoot, '.agents', 'workspace', 'completed'));
44
+ const completedRoot = fs.realpathSync.native(path.join(resolveProjectTaskWorkspaceRoot(manifest.repoRoot), 'completed'));
44
45
  const source = fs.realpathSync.native(path.join(completedRoot, manifest.taskId));
45
46
  if (source !== path.join(completedRoot, manifest.taskId))
46
47
  throw invalid();
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { resolveProjectTaskWorkspaceRoot } from '../../task/workspace-root.js';
3
4
  export const SANDBOX_TASK_VIEW_STATES = ['not-applicable', 'current', 'finalized-stale', 'unknown'];
4
5
  export const SANDBOX_TASK_VIEW_SOURCES = ['active', 'completed', 'unknown'];
5
6
  function invalidView(message) {
@@ -71,8 +72,9 @@ export function taskViewForManifest(params) {
71
72
  if (params.mode !== 'task-bound' || !params.taskId) {
72
73
  return { state: 'not-applicable', taskId: null, observedSource: null, receipt: null, reasonCode: null };
73
74
  }
74
- const active = path.join(params.repoRoot, '.agents', 'workspace', 'active', params.taskId, 'task.md');
75
- const completed = path.join(params.repoRoot, '.agents', 'workspace', 'completed', params.taskId, 'task.md');
75
+ const taskRoot = resolveProjectTaskWorkspaceRoot(params.repoRoot);
76
+ const active = path.join(taskRoot, 'active', params.taskId, 'task.md');
77
+ const completed = path.join(taskRoot, 'completed', params.taskId, 'task.md');
76
78
  const activeExists = fs.existsSync(active);
77
79
  const completedExists = fs.existsSync(completed);
78
80
  if (activeExists === completedExists) {
@@ -11,12 +11,12 @@ function workspaceStateMounts(workspaceViewRoot, taskBound) {
11
11
  .map(({ state, hostPath }) => taskBound && state === 'active'
12
12
  ? {
13
13
  hostPaths: [path.join(hostPath, '.short-ids.json')],
14
- containerPath: '/workspace/.agents/workspace/active/.short-ids.json',
14
+ containerPath: '/task-workspace/active/.short-ids.json',
15
15
  readOnly: true
16
16
  }
17
17
  : {
18
18
  hostPaths: [hostPath],
19
- containerPath: path.posix.join('/workspace/.agents/workspace', state),
19
+ containerPath: path.posix.join('/task-workspace', state),
20
20
  readOnly: true
21
21
  });
22
22
  }
@@ -37,13 +37,13 @@ function taskWorkspaceMounts(taskSources, taskBound, overrides) {
37
37
  const taskId = overrides.taskId;
38
38
  const mounts = [{
39
39
  hostPaths: taskSources,
40
- containerPath: path.posix.join('/workspace/.agents/workspace/active', taskId),
40
+ containerPath: path.posix.join('/task-workspace/active', taskId),
41
41
  readOnly: false
42
42
  }];
43
43
  if (overrides.completedTaskSource)
44
44
  mounts.push({
45
45
  hostPaths: [overrides.completedTaskSource],
46
- containerPath: path.posix.join('/workspace/.agents/workspace/completed', taskId),
46
+ containerPath: path.posix.join('/task-workspace/completed', taskId),
47
47
  readOnly: false
48
48
  });
49
49
  return mounts;
@@ -11,6 +11,7 @@ import { sandboxCoreBindMounts } from './mounts.js';
11
11
  import { resolveSandboxGitMetadata } from './git-metadata.js';
12
12
  import { assertSandboxTaskSource, sandboxCompletedTaskMountSource, sandboxControlPaths, sandboxWorkspaceViewPaths } from './workspace-view.js';
13
13
  import { resolveTaskWorkspace } from './task-resolver.js';
14
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
14
15
  import { parseSandboxWorkspaceIdentity, sameSandboxWorkspaceIdentity } from './workspace-identity.js';
15
16
  import { inspectWorktree } from './worktree-safety.js';
16
17
  import { commandForEngine, runEngine, runOkEngine, runProbe, runVerboseEngine } from './shell.js';
@@ -277,11 +278,13 @@ export function classifySandboxRecovery(snapshot) {
277
278
  });
278
279
  }
279
280
  if (snapshot.workspaceTopology === 'legacy-parent') {
281
+ const legacyRoot = snapshot.mounts.some((mount) => mount.path === '/task-workspace')
282
+ ? '/task-workspace' : '/workspace/.agents/workspace';
280
283
  findings.push({
281
284
  repairKind: 'hard-failure',
282
285
  code: 'SANDBOX_WORKSPACE_TOPOLOGY_MISMATCH',
283
286
  message: 'Sandbox uses the legacy workspace parent-mount topology; recreate the container explicitly to migrate it.',
284
- path: '/workspace/.agents/workspace'
287
+ path: legacyRoot
285
288
  });
286
289
  }
287
290
  if (snapshot.taskView && !snapshot.taskView.readable) {
@@ -302,7 +305,8 @@ export function classifySandboxRecovery(snapshot) {
302
305
  }
303
306
  for (const mount of snapshot.mounts) {
304
307
  if (snapshot.workspaceTopology === 'legacy-parent'
305
- && mount.path.startsWith('/workspace/.agents/workspace'))
308
+ && (mount.path.startsWith('/workspace/.agents/workspace')
309
+ || mount.path.startsWith('/task-workspace')))
306
310
  continue;
307
311
  if (mount.actualType !== mount.expectedType
308
312
  || !mount.sourceMatches
@@ -386,14 +390,15 @@ function inspectContainer(engine, container, runFn) {
386
390
  return parsed[0];
387
391
  }
388
392
  function workspaceTopology(mountsByDestination) {
389
- if (mountsByDestination.has('/workspace/.agents/workspace'))
393
+ if (mountsByDestination.has('/workspace/.agents/workspace')
394
+ || mountsByDestination.has('/task-workspace'))
390
395
  return 'legacy-parent';
391
396
  const perState = [
392
- '/workspace/.agents/workspace/active',
393
- '/workspace/.agents/workspace/active/.short-ids.json',
394
- '/workspace/.agents/workspace/completed',
395
- '/workspace/.agents/workspace/blocked',
396
- '/workspace/.agents/workspace/archive'
397
+ '/task-workspace/active',
398
+ '/task-workspace/active/.short-ids.json',
399
+ '/task-workspace/completed',
400
+ '/task-workspace/blocked',
401
+ '/task-workspace/archive'
397
402
  ];
398
403
  return perState.some((destination) => mountsByDestination.has(destination))
399
404
  ? 'per-state'
@@ -421,8 +426,9 @@ function sourceCandidates(engine, hostPath) {
421
426
  return [...new Set(candidates.map((candidate) => normalizeMountSource(engine, candidate)))];
422
427
  }
423
428
  function recoveryTaskSources(repoRoot, taskId) {
424
- const activePath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
425
- const completedPath = path.join(repoRoot, '.agents', 'workspace', 'completed', taskId);
429
+ const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
430
+ const activePath = path.join(taskRoot, 'active', taskId);
431
+ const completedPath = path.join(taskRoot, 'completed', taskId);
426
432
  if (fs.existsSync(activePath) && !fs.existsSync(completedPath)) {
427
433
  const source = assertSandboxTaskSource(repoRoot, taskId);
428
434
  return { mountPaths: [source], accessiblePaths: [source] };
@@ -435,12 +441,12 @@ function recoveryTaskSources(repoRoot, taskId) {
435
441
  if (task.state !== 'completed') {
436
442
  throw new Error(`SANDBOX_TASK_SOURCE_STATE_UNSUPPORTED: task ${taskId} is ${task.state}`);
437
443
  }
438
- const completedRoot = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace', 'completed'));
444
+ const completedRoot = fs.realpathSync.native(path.join(taskRoot, 'completed'));
439
445
  const completedSource = fs.realpathSync.native(path.dirname(task.taskMd));
440
446
  if (path.dirname(completedSource) !== completedRoot) {
441
447
  throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${completedSource} is not under completed workspace`);
442
448
  }
443
- const historicalActivePath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
449
+ const historicalActivePath = path.join(taskRoot, 'active', taskId);
444
450
  const mountPaths = [completedSource, historicalActivePath];
445
451
  if (fs.existsSync(historicalActivePath)) {
446
452
  try {
@@ -456,7 +462,7 @@ function recoveryTaskSources(repoRoot, taskId) {
456
462
  export function startCompletedSandboxContainer(params) {
457
463
  const taskSources = recoveryTaskSources(params.config.repoRoot, params.taskId);
458
464
  const completedSource = taskSources.accessiblePaths[0];
459
- const activeRoot = path.join(params.config.repoRoot, '.agents', 'workspace', 'active');
465
+ const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(params.config.repoRoot), 'active');
460
466
  const historicalSource = path.join(activeRoot, params.taskId);
461
467
  fs.mkdirSync(activeRoot, { recursive: true });
462
468
  let replacedEmptyRoot = false;
@@ -559,11 +565,11 @@ function expectedMounts(params) {
559
565
  expectedType: 'bind',
560
566
  hostPaths: mount.hostPaths,
561
567
  ...(taskSources !== null && taskId !== null
562
- && mount.containerPath === `/workspace/.agents/workspace/active/${taskId}`
568
+ && mount.containerPath === `/task-workspace/active/${taskId}`
563
569
  ? { sourceAccessiblePaths: taskSources.accessiblePaths }
564
570
  : {}),
565
571
  ...(completedTaskSource !== null && taskId !== null
566
- && mount.containerPath === `/workspace/.agents/workspace/completed/${taskId}`
572
+ && mount.containerPath === `/task-workspace/completed/${taskId}`
567
573
  ? { sourceAccessiblePaths: [completedTaskSource] }
568
574
  : {}),
569
575
  expectedRW: !mount.readOnly
@@ -664,7 +670,7 @@ export function collectSandboxRecoverySnapshot(params) {
664
670
  const tools = [...capabilityPlan.tools];
665
671
  const topology = workspaceTopology(mountsByDestination);
666
672
  const taskViewPath = workspace.mode === 'task-bound'
667
- ? `/workspace/.agents/workspace/active/${workspace.taskId}/task.md`
673
+ ? `/task-workspace/active/${workspace.taskId}/task.md`
668
674
  : null;
669
675
  const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
670
676
  const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
@@ -856,6 +862,10 @@ function canRetryFreshReadiness(findings) {
856
862
  && finding.code !== 'SANDBOX_WORKSPACE_TOPOLOGY_MISMATCH'
857
863
  && finding.path !== undefined
858
864
  && (finding.path.startsWith('/workspace/.agents/workspace')
865
+ || finding.path.startsWith('/task-workspace/active')
866
+ || finding.path.startsWith('/task-workspace/blocked')
867
+ || finding.path.startsWith('/task-workspace/completed')
868
+ || finding.path.startsWith('/task-workspace/archive')
859
869
  || finding.path === '/home/devuser/.host-shell-config'
860
870
  || finding.path === '/home/devuser/.bash_aliases'
861
871
  || finding.path === '/run/agent-infra/runtime'));
@@ -17,7 +17,6 @@ import { acquireSandboxResourceLock } from './control/native-file-lock.js';
17
17
  import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.js';
18
18
  import { getProcessStartTime } from '../server/process-state.js';
19
19
  import { releaseStaleShortIdRegistry } from '../task/short-id.js';
20
- import { withRepositoryMutationLock } from '../task/task-execution-lock.js';
21
20
  import { fetchSandboxRows } from './commands/list-running.js';
22
21
  import { createSandboxControlBindingEvidence } from './task-cleanup.js';
23
22
  import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees, verifyWorktreePermit } from './worktree-safety.js';
@@ -665,12 +664,12 @@ async function authorizeWorktrees(worktrees, { allowDirtyDiscard, assumeYes }, {
665
664
  }
666
665
  return permits;
667
666
  }
668
- async function runRmOneUnderRepositoryLock(config, tools, branch, options = {}) {
667
+ async function runRmOne(config, tools, branch, options = {}) {
669
668
  const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
670
669
  return rmOneCore(config, tools, branch, { ...options, target });
671
670
  }
672
671
  async function rmOne(config, tools, branch, options = {}) {
673
- await withRepositoryMutationLock(config.repoRoot, () => runRmOneUnderRepositoryLock(config, tools, branch, options));
672
+ await runRmOne(config, tools, branch, options);
674
673
  }
675
674
  async function removeUncheckedSandbox(config, target, options) {
676
675
  const confirm = options.prompt?.confirm ?? p.confirm;
@@ -751,9 +750,6 @@ async function rmOneCore(config, tools, branch, options = {}) {
751
750
  return removeUncheckedSandbox(config, target, options);
752
751
  }
753
752
  async function rmPurge(config, tools, prompt = {}) {
754
- return withRepositoryMutationLock(config.repoRoot, () => rmPurgeCore(config, tools, prompt));
755
- }
756
- async function rmPurgeCore(config, tools, prompt = {}) {
757
753
  const engine = detectEngine(config);
758
754
  const confirm = prompt.confirm ?? p.confirm;
759
755
  const isCancel = prompt.isCancel ?? p.isCancel;
@@ -829,9 +825,6 @@ async function rmPurgeCore(config, tools, prompt = {}) {
829
825
  p.outro(pc.green('All project sandboxes removed'));
830
826
  }
831
827
  async function rmUnbound(config, tools, options) {
832
- return withRepositoryMutationLock(config.repoRoot, () => rmUnboundCore(config, tools, options));
833
- }
834
- async function rmUnboundCore(config, tools, options) {
835
828
  const engine = detectEngine(config);
836
829
  const listed = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
837
830
  const discovered = [...listed.running, ...listed.nonRunning];
@@ -892,7 +885,7 @@ async function rmUnboundCore(config, tools, options) {
892
885
  return;
893
886
  }
894
887
  for (const { row, cleanupTarget, target } of targets) {
895
- await runRmOneUnderRepositoryLock(config, tools, cleanupTarget.branch, {
888
+ await runRmOne(config, tools, cleanupTarget.branch, {
896
889
  assumeYes: options.assumeYes,
897
890
  target,
898
891
  cleanupTarget
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { isRemovedHashShortIdInput } from '../task/short-id.js';
5
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
5
6
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
6
7
  const SHORT_ID_RE = /^\d+$/;
7
8
  export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'];
@@ -20,10 +21,11 @@ function stripQuotes(value) {
20
21
  return value.replace(/^(["'])(.*)\1$/, '$2');
21
22
  }
22
23
  function readTaskContent(repoRoot, taskId) {
24
+ const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
23
25
  for (const state of TASK_WORKSPACE_STATES) {
24
26
  const taskPath = state === 'archive'
25
- ? path.join(repoRoot, '.agents', 'workspace', state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
26
- : path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
27
+ ? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
28
+ : path.join(taskRoot, state, taskId, 'task.md');
27
29
  if (fs.existsSync(taskPath)) {
28
30
  return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
29
31
  }
@@ -31,8 +33,9 @@ function readTaskContent(repoRoot, taskId) {
31
33
  throw new Error(`Task not found: ${taskId}`);
32
34
  }
33
35
  function taskWorkspaceCandidates(repoRoot, taskId) {
36
+ const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
34
37
  const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
35
- const taskMd = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
38
+ const taskMd = path.join(taskRoot, state, taskId, 'task.md');
36
39
  if (!fs.existsSync(taskMd))
37
40
  return [];
38
41
  const content = fs.readFileSync(taskMd, 'utf8');
@@ -46,7 +49,7 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
46
49
  const parts = findArchiveTaskParts(repoRoot, taskId);
47
50
  if (parts.length === 0)
48
51
  return hot;
49
- const taskMd = path.join(repoRoot, '.agents', 'workspace', 'archive', ...parts, 'local', 'task.md');
52
+ const taskMd = path.join(taskRoot, 'archive', ...parts, 'local', 'task.md');
50
53
  if (!fs.existsSync(taskMd))
51
54
  return hot;
52
55
  const content = fs.readFileSync(taskMd, 'utf8');
@@ -58,7 +61,7 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
58
61
  }];
59
62
  }
60
63
  function findArchiveTaskParts(repoRoot, taskId) {
61
- const archiveRoot = path.join(repoRoot, '.agents', 'workspace', 'archive');
64
+ const archiveRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'archive');
62
65
  if (!fs.existsSync(archiveRoot))
63
66
  return [];
64
67
  for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
@@ -113,7 +116,7 @@ export function resolveTaskWorkspace(taskId, repoRoot) {
113
116
  return matches[0];
114
117
  }
115
118
  export function listTaskWorkspaces(repoRoot, state) {
116
- const stateRoot = path.join(repoRoot, '.agents', 'workspace', state);
119
+ const stateRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
117
120
  if (state === 'archive') {
118
121
  if (!fs.existsSync(stateRoot))
119
122
  return [];