@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
@@ -0,0 +1,39 @@
1
+ import { detectRepoRoot } from '../task/resolve-ref.ts';
2
+ import { resolveProjectTaskWorkspaceLayout, type ExecutionBoundary } from '../task/workspace-root.ts';
3
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
4
+
5
+ const USAGE = `Usage: agent-infra-internal task-workspace resolve [--boundary host|container]\n`;
6
+
7
+ function fail(code: string, message: string): void {
8
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code, message } })}\n`);
9
+ process.exitCode = 1;
10
+ }
11
+
12
+ function taskWorkspace(args: string[] = []): void {
13
+ if (!ensureInternalHandlerRoute('task-workspace', args)) return;
14
+ const operation = args[0] ?? '';
15
+ if (operation === '--help' || operation === '-h') { process.stdout.write(USAGE); return; }
16
+ if (!internalHandlerRoute('task-workspace', 'resolve', operation)) {
17
+ fail('TASK_WORKSPACE_PAYLOAD_INVALID', "operation must be 'resolve'");
18
+ return;
19
+ }
20
+ let boundary: ExecutionBoundary = 'host';
21
+ const options = args.slice(1);
22
+ if (options.length !== 0) {
23
+ if (options.length !== 2 || options[0] !== '--boundary') {
24
+ fail('TASK_WORKSPACE_PAYLOAD_INVALID', 'invalid or duplicate task-workspace option');
25
+ return;
26
+ }
27
+ boundary = options[1] as ExecutionBoundary;
28
+ }
29
+ if (boundary !== 'host' && boundary !== 'container') {
30
+ fail('TASK_WORKSPACE_PAYLOAD_INVALID', 'resolve accepts only --boundary host|container');
31
+ return;
32
+ }
33
+ try {
34
+ const layout = resolveProjectTaskWorkspaceLayout(detectRepoRoot(), boundary);
35
+ process.stdout.write(`${JSON.stringify({ status: 'resolved', changed: false, projectKey: layout.projectKey, boundary: layout.boundary, root: layout.root, error: null })}\n`);
36
+ } catch (error) { fail('TASK_WORKSPACE_OPERATION_FAILED', error instanceof Error ? error.message : String(error)); }
37
+ }
38
+
39
+ export { taskWorkspace };
package/lib/merge.ts CHANGED
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import crypto from 'node:crypto';
5
5
  import { removeDirRecursive } from './remove-dir.ts';
6
6
  import { validateCurrentTaskContract } from './task/current-contract.ts';
7
+ import { resolveProjectTaskWorkspaceRoot } from './task/workspace-root.ts';
7
8
 
8
9
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
9
10
  const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
@@ -988,7 +989,7 @@ function mergeArchiveSection(sourceArchive: string, localArchive: string, report
988
989
  }
989
990
 
990
991
  function printSection(lines: string[], name: MutableSection, counts: MutableCounts): void {
991
- const title = `${SECTION_LABELS[name].padEnd(9, ' ')} (.agents/workspace/${name}/):`;
992
+ const title = `${SECTION_LABELS[name].padEnd(9, ' ')} (task workspace/${name}/):`;
992
993
  lines.push(title);
993
994
 
994
995
  const allEntries: Array<[MutableAction, string]> = [
@@ -1013,7 +1014,7 @@ function printSection(lines: string[], name: MutableSection, counts: MutableCoun
1013
1014
  }
1014
1015
 
1015
1016
  function printArchiveSection(lines: string[], counts: ArchiveCounts): void {
1016
- const title = `${SECTION_LABELS.archive.padEnd(9, ' ')} (.agents/workspace/archive/):`;
1017
+ const title = `${SECTION_LABELS.archive.padEnd(9, ' ')} (task workspace/archive/):`;
1017
1018
  lines.push(title);
1018
1019
 
1019
1020
  if (counts.copied.length === 0 && counts.skipped.length === 0) {
@@ -1105,7 +1106,7 @@ async function cmdMerge(args: string[]): Promise<void> {
1105
1106
  }
1106
1107
 
1107
1108
  const repoRoot = process.cwd();
1108
- const workspaceDir = path.join(repoRoot, '.agents', 'workspace');
1109
+ const workspaceDir = resolveProjectTaskWorkspaceRoot(repoRoot);
1109
1110
  const archiveDir = path.join(workspaceDir, 'archive');
1110
1111
  const backupStamp = formatBackupTimestamp(new Date());
1111
1112
  const projectKey = resolveRecoveryProjectKey(repoRoot);
@@ -5,7 +5,6 @@ import { createHash } from 'node:crypto';
5
5
  import { enumerateArtifacts } from '../task/artifacts.ts';
6
6
  import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
7
7
  import { resolveTaskRef } from '../task/resolve-ref.ts';
8
- import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
9
8
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
10
9
  import type { PrDeliveryFact } from '../task/pr-delivery-fact.ts';
11
10
  import { resolvePlatformProviderContext } from './context.ts';
@@ -433,7 +432,7 @@ async function reportWrite(taskRef: string, options: ReportWriteOptions): Promis
433
432
  if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) return { ...context, report: null };
434
433
  let knownPrNumber: number | null = null;
435
434
  try {
436
- return await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, async () => {
435
+ return await (async () => {
437
436
  const { taskContent, boundFact, factError } = readBoundTaskSnapshot(resolved.taskMdPath);
438
437
  if (factError) return {
439
438
  ...basePlatformResult('failed', context, resolved.taskId, null, { code: factError.code, message: factError.message, retryable: false }),
@@ -502,18 +501,15 @@ async function reportWrite(taskRef: string, options: ReportWriteOptions): Promis
502
501
  operations: [{ name: 'change-report:write', status: 'applied', reasonCode: null }],
503
502
  report: { ...reportInfo, status: 'written' }
504
503
  };
505
- });
504
+ })();
506
505
  } catch (error) {
507
- const lockError = error instanceof TaskExecutionLockError
508
- ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
509
- : { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
510
- return { ...basePlatformResult(lockError.retryable ? 'blocked' : 'failed', context, resolved.taskId, knownPrNumber, lockError), report: null };
506
+ return { ...basePlatformResult('failed', context, resolved.taskId, knownPrNumber, { code: 'PR_CHANGE_REPORT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }), report: null };
511
507
  }
512
508
  }
513
509
 
514
510
  async function syncPullRequestSummary(
515
511
  taskRef: string,
516
- options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string; manualValidation?: ManualValidationSummaryOptions; lockAlreadyHeld?: boolean; skipQueue?: boolean }
512
+ options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string; manualValidation?: ManualValidationSummaryOptions; skipQueue?: boolean }
517
513
  ): Promise<PullRequestSummaryResult> {
518
514
  const warningResult = warningResultForPrimary(options.primaryResult);
519
515
  let knownPrNumber: number | null = null;
@@ -561,7 +557,7 @@ async function syncPullRequestSummary(
561
557
  const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
562
558
  const context = loaded.ok ? loaded.value.context : loaded.context;
563
559
  if (!loaded.ok || !context.platform.repository || !['no-op', 'degraded'].includes(context.status)) {
564
- // This diagnostic-only read preserves the known PR target when context resolution fails before the execution lock.
560
+ // Preserve the known PR target for diagnostics when provider context resolution fails.
565
561
  knownPrNumber = boundPrNumberForWarning(resolved.taskMdPath);
566
562
  return preserveFailure(context);
567
563
  }
@@ -780,14 +776,9 @@ async function syncPullRequestSummary(
780
776
  persistenceFailure: (error) => fail('failed', context, error, prNumber)
781
777
  });
782
778
  };
783
- return await (options.lockAlreadyHeld
784
- ? execute()
785
- : withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
779
+ return await execute();
786
780
  } catch (error) {
787
- const lockError = error instanceof TaskExecutionLockError
788
- ? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
789
- : { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
790
- return fail(lockError.retryable ? 'blocked' : 'failed', context, lockError);
781
+ return fail('failed', context, { code: 'PR_SUMMARY_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false });
791
782
  }
792
783
  }
793
784
 
@@ -21,7 +21,6 @@ import {
21
21
  import { platformResult } from './types.ts';
22
22
  import type { PlatformOperation, PlatformResult } from './types.ts';
23
23
  import { inspectCompletionArtifacts } from '../task/finalization-artifacts.ts';
24
- import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.ts';
25
24
  import { resolveDeliveryTarget } from '../task/delivery-target.ts';
26
25
  import { mergeOperationWarnings, type OperationWarning } from '../task/operation-outcome.ts';
27
26
  import {
@@ -1174,20 +1173,8 @@ async function createPlatformPullRequestImpl(taskRef: string, options: CreateOpt
1174
1173
  }), PRECONDITION_NOT_CREATED);
1175
1174
  }
1176
1175
  try {
1177
- return withTaskExecutionLock(
1178
- base.resolved.repoRoot,
1179
- base.resolved.taskId,
1180
- 'platform-pr.create',
1181
- () => createExternalPullRequest(taskRef, options, base)
1182
- );
1176
+ return await createExternalPullRequest(taskRef, options, base);
1183
1177
  } catch (error) {
1184
- if (error instanceof TaskExecutionLockError) {
1185
- return withCreation(result('blocked', base.resolved.taskId, base.issueNumber, null, {
1186
- platform: base.context.platform,
1187
- capabilities: base.context.capabilities,
1188
- error: { code: error.code, message: error.message, retryable: true }
1189
- }), PRECONDITION_NOT_CREATED);
1190
- }
1191
1178
  return withCreation(result('failed', base.resolved.taskId, base.issueNumber, null, {
1192
1179
  platform: base.context.platform,
1193
1180
  capabilities: base.context.capabilities,
@@ -1602,7 +1589,7 @@ function readProjectPrFlow(repoRoot: string): 'required' | 'disabled' | undefine
1602
1589
  }
1603
1590
  }
1604
1591
 
1605
- function skipPlatformPullRequestFactUnlocked(
1592
+ function applySkippedPullRequestFact(
1606
1593
  resolved: Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>,
1607
1594
  options: SkipFactOptions
1608
1595
  ): PullRequestResult {
@@ -1674,16 +1661,8 @@ async function skipPlatformPullRequestFact(taskRef: string, options: SkipFactOpt
1674
1661
  error: { code: 'TASK_STATE_INVALID', message: 'platform-pr skip requires an active task', retryable: false }
1675
1662
  });
1676
1663
  try {
1677
- return withTaskExecutionLock(
1678
- resolved.repoRoot,
1679
- resolved.taskId,
1680
- 'platform-pr.skip',
1681
- () => skipPlatformPullRequestFactUnlocked(resolved, options)
1682
- );
1664
+ return await applySkippedPullRequestFact(resolved, options);
1683
1665
  } catch (error) {
1684
- if (error instanceof TaskExecutionLockError) return result('blocked', resolved.taskId, null, null, {
1685
- error: { code: error.code, message: error.message, retryable: true }
1686
- });
1687
1666
  return result('failed', resolved.taskId, null, null, {
1688
1667
  error: { code: 'PR_SKIP_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
1689
1668
  });
@@ -5,6 +5,7 @@ import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
5
5
  import { readPrDeliveryFact } from '../task/pr-delivery-fact.ts';
6
6
  import { resourceIdentityEquals, resourceIdentityNumber } from '../platform/resource-identity.ts';
7
7
  import type { ResourceIdentity } from '../platform/resource-identity.ts';
8
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
8
9
  import { taskIssueIdentity } from '../platform/task-identities.ts';
9
10
 
10
11
  // ---------------------------------------------------------------------------
@@ -61,7 +62,7 @@ export function extractClosingIssueNumbers(body: string): number[] {
61
62
  }
62
63
 
63
64
  /**
64
- * Scan `.agents/workspace/active/{task-id}/task.md` files for tasks bound to the
65
+ * Scan active project task workspace task.md files for tasks bound to the
65
66
  * PR number or to one of the PR's closing issues. A task is emitted at most once;
66
67
  * a verified fact identity hit takes priority over the issue-identity reverse lookup.
67
68
  */
@@ -71,7 +72,7 @@ export function collectHostCandidates(input: {
71
72
  closingIssues: number[];
72
73
  workspaceRoot: string;
73
74
  }): HostCandidate[] {
74
- const activeRoot = path.join(input.workspaceRoot, '.agents', 'workspace', 'active');
75
+ const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(input.workspaceRoot), 'active');
75
76
  const candidates: HostCandidate[] = [];
76
77
  let entries: fs.Dirent[];
77
78
  try {
@@ -3,6 +3,7 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { detectRepoRoot } from '../task/resolve-ref.ts';
6
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
6
7
  import { acquireGitHubCheckpoint, commitGitHubCheckpoint, readGitHubCheckpoint, releaseGitHubCheckpoint } from './checkpoint.ts';
7
8
  import { materializeJson, materializeSnapshot } from './materialize.ts';
8
9
  import { normalizeResources } from './normalize.ts';
@@ -64,13 +65,13 @@ function dataRoot(repoRoot: string, requested?: string): string {
64
65
  input = path.join(boundary, '.agent-infra', 'data-store', config.project);
65
66
  }
66
67
  const resolved = path.resolve(repoRoot, input);
67
- const relative = path.relative(repoRoot, resolved);
68
- if (requested !== undefined && (!relative || relative.startsWith('..') || path.isAbsolute(relative))) throw new Error('data root must be inside the repository');
69
- const workspace = path.resolve(repoRoot, '.agents', 'workspace');
68
+ const workspace = resolveProjectTaskWorkspaceRoot(repoRoot);
70
69
  const taskRoots = ['active', 'blocked', 'completed', 'archive'].map((state) => path.join(workspace, state));
71
70
  if (resolved === workspace || taskRoots.some((taskRoot) => resolved === taskRoot || resolved.startsWith(`${taskRoot}${path.sep}`))) {
72
71
  throw new Error('data root cannot be the workspace root or a task directory');
73
72
  }
73
+ const relative = path.relative(repoRoot, resolved);
74
+ if (requested !== undefined && (!relative || relative.startsWith('..') || path.isAbsolute(relative))) throw new Error('data root must be inside the repository');
74
75
  let current = resolved;
75
76
  while (current !== boundary) {
76
77
  if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink()) throw new Error('data root cannot traverse a symlink');
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
 
4
4
  import { createGitHubClient } from '../platform/github-client.ts';
5
5
  import { enumerateAllTaskDirs } from '../task/resolve-ref.ts';
6
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
6
7
  import { captureText, inspectPrivacy, redactExcerpt } from './privacy.ts';
7
8
  import { canonicalJsonBytes, sha256 } from './store.ts';
8
9
 
@@ -157,6 +158,11 @@ function fetchRestCollection<T>(options: FetchRestCollectionOptions<T>): Process
157
158
  }
158
159
 
159
160
  function relativeIdentity(repoRoot: string, filePath: string): string {
161
+ const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
162
+ const taskRelative = path.relative(workspaceRoot, filePath);
163
+ if (taskRelative && !taskRelative.startsWith('..') && !path.isAbsolute(taskRelative)) {
164
+ return path.posix.join('task-workspace', taskRelative.split(path.sep).join('/'));
165
+ }
160
166
  const relative = path.relative(repoRoot, filePath);
161
167
  if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) throw new Error(`path escapes repository: ${filePath}`);
162
168
  return relative.split(path.sep).join('/');
package/lib/run/index.ts CHANGED
@@ -4,6 +4,7 @@ import { loadServerConfig } from '../server/config.ts';
4
4
  import { resolveSandboxTarget, type SandboxWorkspaceIdentity } from '../sandbox/workspace-identity.ts';
5
5
  import { createRunId, runInSandbox, type SandboxRunMetadata } from '../sandbox/capture.ts';
6
6
  import { loadShortIdByTaskId, normalizeShortIdInput } from '../task/short-id.ts';
7
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
7
8
  import { buildTuiCommand, renderPrompt, selectTui } from './tui.ts';
8
9
  import { getSkillRunSpec } from './skills.ts';
9
10
  import { runHostCommand, type RunProcessResult } from './host.ts';
@@ -157,7 +158,7 @@ function readShortIdLength(repoRoot: string): number {
157
158
  }
158
159
 
159
160
  function readActiveShortIdRegistry(repoRoot: string): Record<string, string> {
160
- const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', '.short-ids.json');
161
+ const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
161
162
  try {
162
163
  const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
163
164
  return data && typeof data === 'object' && data.ids && typeof data.ids === 'object' ? data.ids : {};
@@ -186,7 +187,7 @@ function resolveActiveTaskIdentity(taskRef: string, repoRoot: string): ActiveTas
186
187
  }
187
188
 
188
189
  if (!taskId) return null;
189
- const taskDir = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
190
+ const taskDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId);
190
191
  if (!fs.existsSync(path.join(taskDir, 'task.md'))) return null;
191
192
  return { taskId, taskDir, taskRef: resolvedTaskRef };
192
193
  }
@@ -1454,7 +1454,7 @@ export async function create(
1454
1454
  const completedTaskSource = target.workspace.mode === 'task-bound'
1455
1455
  ? prepareSandboxCompletedTaskMountSource(effectiveConfig.repoRoot, target.workspace.taskId)
1456
1456
  : null;
1457
- prepareSandboxWorkspaceMountTargets(worktree, target.workspace.mode === 'task-bound' ? target.workspace.taskId : undefined);
1457
+ prepareSandboxWorkspaceMountTargets(effectiveConfig.repoRoot);
1458
1458
  if (previousCutoverSnapshot) {
1459
1459
  assertSandboxControlCutoverSnapshot(previousCutoverSnapshot);
1460
1460
  if (previousManifest) {
@@ -1603,6 +1603,10 @@ export async function create(
1603
1603
  : []),
1604
1604
  '--label',
1605
1605
  `${sandboxRuntimeCapabilityLabel(effectiveConfig)}=${capabilityPlan.runtimeSignature}`,
1606
+ // Every task sandbox runs without seccomp and AppArmor filtering;
1607
+ // capabilities and namespace isolation remain unchanged.
1608
+ '--security-opt', 'seccomp=unconfined',
1609
+ '--security-opt', 'apparmor=unconfined',
1606
1610
  ...coreVolumes,
1607
1611
  ...buildClipboardVolumeArgs(engine, effectiveConfig.home),
1608
1612
  ...dotfilesMount,
@@ -31,7 +31,7 @@ import {
31
31
  const USAGE = `Usage: ai sandbox exec [--recreate] <branch | TASK-id | N> [cmd...]
32
32
 
33
33
  N references an active task short id from
34
- .agents/workspace/active/.short-ids.json. They resolve only via that
34
+ the read-only task-workspace mount at /task-workspace/active/.short-ids.json. They resolve only via that
35
35
  registry — they do not reference a container's row position in
36
36
  'ai sandbox ls' output. --recreate rebuilds the matched container while
37
37
  preserving its task or branch identity. The same token after the target is
@@ -5,6 +5,7 @@ import { runSafeEngine, runVerboseEngine } from '../shell.ts';
5
5
  import { resolveTaskBranch } from '../task-resolver.ts';
6
6
  import { isRemovedHashShortIdInput } from '../../task/short-id.ts';
7
7
  import { parseSandboxWorkspaceIdentity } from '../workspace-identity.ts';
8
+ import { resolveProjectTaskWorkspaceRoot } from '../../task/workspace-root.ts';
8
9
 
9
10
  export type SandboxRow = {
10
11
  name: string;
@@ -143,7 +144,7 @@ function tryResolveFromRegistry(arg: string, repoRoot: string): RegistryLookup {
143
144
  );
144
145
  }
145
146
  for (const sub of ['active', 'completed', 'blocked', 'archive']) {
146
- const taskMdPath = path.join(repoRoot, '.agents', 'workspace', sub, taskId, 'task.md');
147
+ const taskMdPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), sub, taskId, 'task.md');
147
148
  if (!fs.existsSync(taskMdPath)) continue;
148
149
  const content = fs.readFileSync(taskMdPath, 'utf8');
149
150
  const fm = content.match(/^branch:\s*(.+)$/m);
@@ -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.ts';
5
+ import { detectRepoRoot } from '../../task/resolve-ref.ts';
4
6
  import {
5
7
  SANDBOX_CONTROL_MAX_BYTES,
6
8
  SANDBOX_CONTROL_STATUS_STALE_MS,
@@ -158,18 +160,11 @@ type CanonicalRequestTask =
158
160
 
159
161
  // Use the current short-id contract for the first visible active registry.
160
162
  export function resolveVisibleActiveShortId(ref: string): string | null {
161
- let dir = process.cwd();
162
- for (let depth = 0; depth < 64; depth += 1) {
163
- const registryPath = path.join(dir, '.agents', 'workspace', 'active', '.short-ids.json');
164
- if (fs.existsSync(registryPath)) {
165
- const resolved = resolveShortIdReadOnly(ref, dir, { shortIdLength: configuredShortIdLength(dir) });
166
- return resolved.ok ? resolved.taskId : null;
167
- }
168
- const parent = path.dirname(dir);
169
- if (parent === dir) break;
170
- dir = parent;
171
- }
172
- return null;
163
+ const repoRoot = detectRepoRoot();
164
+ const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
165
+ if (!fs.existsSync(registryPath)) return null;
166
+ const resolved = resolveShortIdReadOnly(ref, repoRoot, { shortIdLength: configuredShortIdLength(repoRoot) });
167
+ return resolved.ok ? resolved.taskId : null;
173
168
  }
174
169
 
175
170
  // A request's leading argument is only a task reference for some families and
@@ -4,6 +4,7 @@ import { resolveTaskWorkspace } from '../task-resolver.ts';
4
4
  import { assertGitWorktreeBinding } from '../../git/worktree-identity.ts';
5
5
  import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
6
6
  import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
7
+ import { resolveProjectTaskWorkspaceRoot } from '../../task/workspace-root.ts';
7
8
  import { inspectSandboxControlContainer } from './container-identity.ts';
8
9
  import type { SandboxControlManifest } from './protocol.ts';
9
10
  import { atomicWriteJson, readSandboxControlStatus } from './state.ts';
@@ -53,7 +54,7 @@ export async function prepareCompletedReentry(
53
54
  || previous.receipt.generation !== view.receipt.generation
54
55
  || previous.receipt.requestId !== view.receipt.requestId
55
56
  || previous.receipt.revision > view.receipt.revision) throw invalid();
56
- const completedRoot = fs.realpathSync.native(path.join(manifest.repoRoot, '.agents', 'workspace', 'completed'));
57
+ const completedRoot = fs.realpathSync.native(path.join(resolveProjectTaskWorkspaceRoot(manifest.repoRoot), 'completed'));
57
58
  const source = fs.realpathSync.native(path.join(completedRoot, manifest.taskId));
58
59
  if (source !== path.join(completedRoot, manifest.taskId)) throw invalid();
59
60
  const stat = fs.statSync(source, { bigint: true });
@@ -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.ts';
3
4
 
4
5
  export const SANDBOX_TASK_VIEW_STATES = ['not-applicable', 'current', 'finalized-stale', 'unknown'] as const;
5
6
  export const SANDBOX_TASK_VIEW_SOURCES = ['active', 'completed', 'unknown'] as const;
@@ -119,8 +120,9 @@ export function taskViewForManifest(params: Readonly<{
119
120
  if (params.mode !== 'task-bound' || !params.taskId) {
120
121
  return { state: 'not-applicable', taskId: null, observedSource: null, receipt: null, reasonCode: null };
121
122
  }
122
- const active = path.join(params.repoRoot, '.agents', 'workspace', 'active', params.taskId, 'task.md');
123
- const completed = path.join(params.repoRoot, '.agents', 'workspace', 'completed', params.taskId, 'task.md');
123
+ const taskRoot = resolveProjectTaskWorkspaceRoot(params.repoRoot);
124
+ const active = path.join(taskRoot, 'active', params.taskId, 'task.md');
125
+ const completed = path.join(taskRoot, 'completed', params.taskId, 'task.md');
124
126
  const activeExists = fs.existsSync(active);
125
127
  const completedExists = fs.existsSync(completed);
126
128
  if (activeExists === completedExists) {
@@ -24,12 +24,12 @@ function workspaceStateMounts(workspaceViewRoot: string, taskBound: boolean): Sa
24
24
  .map(({ state, hostPath }) => taskBound && state === 'active'
25
25
  ? {
26
26
  hostPaths: [path.join(hostPath, '.short-ids.json')],
27
- containerPath: '/workspace/.agents/workspace/active/.short-ids.json',
27
+ containerPath: '/task-workspace/active/.short-ids.json',
28
28
  readOnly: true
29
29
  }
30
30
  : {
31
31
  hostPaths: [hostPath],
32
- containerPath: path.posix.join('/workspace/.agents/workspace', state),
32
+ containerPath: path.posix.join('/task-workspace', state),
33
33
  readOnly: true
34
34
  }
35
35
  );
@@ -60,12 +60,12 @@ function taskWorkspaceMounts(taskSources: string[], taskBound: boolean, override
60
60
  const taskId = overrides.taskId!;
61
61
  const mounts: SandboxBindMountDeclaration[] = [{
62
62
  hostPaths: taskSources,
63
- containerPath: path.posix.join('/workspace/.agents/workspace/active', taskId),
63
+ containerPath: path.posix.join('/task-workspace/active', taskId),
64
64
  readOnly: false
65
65
  }];
66
66
  if (overrides.completedTaskSource) mounts.push({
67
67
  hostPaths: [overrides.completedTaskSource],
68
- containerPath: path.posix.join('/workspace/.agents/workspace/completed', taskId),
68
+ containerPath: path.posix.join('/task-workspace/completed', taskId),
69
69
  readOnly: false
70
70
  });
71
71
  return mounts;
@@ -30,6 +30,7 @@ import {
30
30
  sandboxWorkspaceViewPaths
31
31
  } from './workspace-view.ts';
32
32
  import { resolveTaskWorkspace } from './task-resolver.ts';
33
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
33
34
  import {
34
35
  parseSandboxWorkspaceIdentity,
35
36
  sameSandboxWorkspaceIdentity,
@@ -450,11 +451,13 @@ export function classifySandboxRecovery(snapshot: SandboxRecoverySnapshot): Sand
450
451
  });
451
452
  }
452
453
  if (snapshot.workspaceTopology === 'legacy-parent') {
454
+ const legacyRoot = snapshot.mounts.some((mount) => mount.path === '/task-workspace')
455
+ ? '/task-workspace' : '/workspace/.agents/workspace';
453
456
  findings.push({
454
457
  repairKind: 'hard-failure',
455
458
  code: 'SANDBOX_WORKSPACE_TOPOLOGY_MISMATCH',
456
459
  message: 'Sandbox uses the legacy workspace parent-mount topology; recreate the container explicitly to migrate it.',
457
- path: '/workspace/.agents/workspace'
460
+ path: legacyRoot
458
461
  });
459
462
  }
460
463
  if (snapshot.taskView && !snapshot.taskView.readable) {
@@ -476,7 +479,8 @@ export function classifySandboxRecovery(snapshot: SandboxRecoverySnapshot): Sand
476
479
  for (const mount of snapshot.mounts) {
477
480
  if (
478
481
  snapshot.workspaceTopology === 'legacy-parent'
479
- && mount.path.startsWith('/workspace/.agents/workspace')
482
+ && (mount.path.startsWith('/workspace/.agents/workspace')
483
+ || mount.path.startsWith('/task-workspace'))
480
484
  ) continue;
481
485
  if (
482
486
  mount.actualType !== mount.expectedType
@@ -571,13 +575,14 @@ function inspectContainer(
571
575
  }
572
576
 
573
577
  function workspaceTopology(mountsByDestination: ReadonlyMap<string, DockerMount>): SandboxRecoverySnapshot['workspaceTopology'] {
574
- if (mountsByDestination.has('/workspace/.agents/workspace')) return 'legacy-parent';
578
+ if (mountsByDestination.has('/workspace/.agents/workspace')
579
+ || mountsByDestination.has('/task-workspace')) return 'legacy-parent';
575
580
  const perState = [
576
- '/workspace/.agents/workspace/active',
577
- '/workspace/.agents/workspace/active/.short-ids.json',
578
- '/workspace/.agents/workspace/completed',
579
- '/workspace/.agents/workspace/blocked',
580
- '/workspace/.agents/workspace/archive'
581
+ '/task-workspace/active',
582
+ '/task-workspace/active/.short-ids.json',
583
+ '/task-workspace/completed',
584
+ '/task-workspace/blocked',
585
+ '/task-workspace/archive'
581
586
  ];
582
587
  return perState.some((destination) => mountsByDestination.has(destination))
583
588
  ? 'per-state'
@@ -618,8 +623,9 @@ function recoveryTaskSources(repoRoot: string, taskId: string): {
618
623
  mountPaths: string[];
619
624
  accessiblePaths: string[];
620
625
  } {
621
- const activePath = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
622
- const completedPath = path.join(repoRoot, '.agents', 'workspace', 'completed', taskId);
626
+ const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
627
+ const activePath = path.join(taskRoot, 'active', taskId);
628
+ const completedPath = path.join(taskRoot, 'completed', taskId);
623
629
  if (fs.existsSync(activePath) && !fs.existsSync(completedPath)) {
624
630
  const source = assertSandboxTaskSource(repoRoot, taskId);
625
631
  return { mountPaths: [source], accessiblePaths: [source] };
@@ -635,7 +641,7 @@ function recoveryTaskSources(repoRoot: string, taskId: string): {
635
641
  }
636
642
 
637
643
  const completedRoot = fs.realpathSync.native(
638
- path.join(repoRoot, '.agents', 'workspace', 'completed')
644
+ path.join(taskRoot, 'completed')
639
645
  );
640
646
  const completedSource = fs.realpathSync.native(path.dirname(task.taskMd));
641
647
  if (path.dirname(completedSource) !== completedRoot) {
@@ -643,9 +649,7 @@ function recoveryTaskSources(repoRoot: string, taskId: string): {
643
649
  }
644
650
 
645
651
  const historicalActivePath = path.join(
646
- repoRoot,
647
- '.agents',
648
- 'workspace',
652
+ taskRoot,
649
653
  'active',
650
654
  taskId
651
655
  );
@@ -670,7 +674,7 @@ export function startCompletedSandboxContainer(params: {
670
674
  }): void {
671
675
  const taskSources = recoveryTaskSources(params.config.repoRoot, params.taskId);
672
676
  const completedSource = taskSources.accessiblePaths[0]!;
673
- const activeRoot = path.join(params.config.repoRoot, '.agents', 'workspace', 'active');
677
+ const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(params.config.repoRoot), 'active');
674
678
  const historicalSource = path.join(activeRoot, params.taskId);
675
679
  fs.mkdirSync(activeRoot, { recursive: true });
676
680
  let replacedEmptyRoot = false;
@@ -791,11 +795,11 @@ function expectedMounts(params: {
791
795
  expectedType: 'bind' as const,
792
796
  hostPaths: mount.hostPaths,
793
797
  ...(taskSources !== null && taskId !== null
794
- && mount.containerPath === `/workspace/.agents/workspace/active/${taskId}`
798
+ && mount.containerPath === `/task-workspace/active/${taskId}`
795
799
  ? { sourceAccessiblePaths: taskSources!.accessiblePaths }
796
800
  : {}),
797
801
  ...(completedTaskSource !== null && taskId !== null
798
- && mount.containerPath === `/workspace/.agents/workspace/completed/${taskId}`
802
+ && mount.containerPath === `/task-workspace/completed/${taskId}`
799
803
  ? { sourceAccessiblePaths: [completedTaskSource] }
800
804
  : {}),
801
805
  expectedRW: !mount.readOnly
@@ -929,7 +933,7 @@ export function collectSandboxRecoverySnapshot(params: {
929
933
  const tools = [...capabilityPlan.tools];
930
934
  const topology = workspaceTopology(mountsByDestination);
931
935
  const taskViewPath = workspace.mode === 'task-bound'
932
- ? `/workspace/.agents/workspace/active/${workspace.taskId}/task.md`
936
+ ? `/task-workspace/active/${workspace.taskId}/task.md`
933
937
  : null;
934
938
  const seeds = declaredTmpfsSeedEntries(tools).map((seed) => {
935
939
  const mounted = mountsByDestination.get(seed.stagingPath)?.Type === 'bind';
@@ -1215,6 +1219,10 @@ function canRetryFreshReadiness(findings: SandboxRecoveryFinding[]): boolean {
1215
1219
  && finding.path !== undefined
1216
1220
  && (
1217
1221
  finding.path.startsWith('/workspace/.agents/workspace')
1222
+ || finding.path.startsWith('/task-workspace/active')
1223
+ || finding.path.startsWith('/task-workspace/blocked')
1224
+ || finding.path.startsWith('/task-workspace/completed')
1225
+ || finding.path.startsWith('/task-workspace/archive')
1218
1226
  || finding.path === '/home/devuser/.host-shell-config'
1219
1227
  || finding.path === '/home/devuser/.bash_aliases'
1220
1228
  || finding.path === '/run/agent-infra/runtime'
@@ -48,7 +48,6 @@ import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.ts';
48
48
  import type { SandboxTool } from './tools.ts';
49
49
  import { getProcessStartTime } from '../server/process-state.ts';
50
50
  import { releaseStaleShortIdRegistry } from '../task/short-id.ts';
51
- import { withRepositoryMutationLock } from '../task/task-execution-lock.ts';
52
51
  import { fetchSandboxRows, type SandboxRow } from './commands/list-running.ts';
53
52
  import { createSandboxControlBindingEvidence } from './task-cleanup.ts';
54
53
  import {
@@ -933,7 +932,7 @@ async function authorizeWorktrees(
933
932
  return permits;
934
933
  }
935
934
 
936
- async function runRmOneUnderRepositoryLock(
935
+ async function runRmOne(
937
936
  config: SandboxConfig,
938
937
  tools: SandboxTool[],
939
938
  branch: string,
@@ -953,10 +952,7 @@ async function rmOne(
953
952
  branch: string,
954
953
  options: RmOneOptions = {}
955
954
  ): Promise<void> {
956
- await withRepositoryMutationLock(
957
- config.repoRoot,
958
- () => runRmOneUnderRepositoryLock(config, tools, branch, options)
959
- );
955
+ await runRmOne(config, tools, branch, options);
960
956
  }
961
957
 
962
958
  async function removeUncheckedSandbox(
@@ -1060,14 +1056,6 @@ async function rmPurge(
1060
1056
  config: SandboxConfig,
1061
1057
  tools: SandboxTool[],
1062
1058
  prompt: PromptDependencies = {}
1063
- ): Promise<void> {
1064
- return withRepositoryMutationLock(config.repoRoot, () => rmPurgeCore(config, tools, prompt));
1065
- }
1066
-
1067
- async function rmPurgeCore(
1068
- config: SandboxConfig,
1069
- tools: SandboxTool[],
1070
- prompt: PromptDependencies = {}
1071
1059
  ): Promise<void> {
1072
1060
  const engine = detectEngine(config);
1073
1061
  const confirm = prompt.confirm ?? p.confirm;
@@ -1142,17 +1130,6 @@ async function rmUnbound(
1142
1130
  config: SandboxConfig,
1143
1131
  tools: SandboxTool[],
1144
1132
  options: { dryRun: boolean; assumeYes: boolean }
1145
- ): Promise<void> {
1146
- return withRepositoryMutationLock(
1147
- config.repoRoot,
1148
- () => rmUnboundCore(config, tools, options)
1149
- );
1150
- }
1151
-
1152
- async function rmUnboundCore(
1153
- config: SandboxConfig,
1154
- tools: SandboxTool[],
1155
- options: { dryRun: boolean; assumeYes: boolean }
1156
1133
  ): Promise<void> {
1157
1134
  const engine = detectEngine(config);
1158
1135
  const listed = fetchSandboxRows(
@@ -1215,7 +1192,7 @@ async function rmUnboundCore(
1215
1192
  return;
1216
1193
  }
1217
1194
  for (const { row, cleanupTarget, target } of targets) {
1218
- await runRmOneUnderRepositoryLock(config, tools, cleanupTarget.branch, {
1195
+ await runRmOne(config, tools, cleanupTarget.branch, {
1219
1196
  assumeYes: options.assumeYes,
1220
1197
  target,
1221
1198
  cleanupTarget