@fitlab-ai/agent-infra 0.11.15 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/bin/cli.ts +1 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/bin/internal-cli.js +6 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
  6. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
  7. package/dist/lib/decide.js +2 -7
  8. package/dist/lib/internal/cli-route-inventory.js +3 -0
  9. package/dist/lib/internal/manual-validation.js +8 -16
  10. package/dist/lib/internal/platform-comment.js +3 -2
  11. package/dist/lib/internal/task-activity.js +1 -12
  12. package/dist/lib/internal/task-event.js +1 -12
  13. package/dist/lib/internal/task-ledger.js +1 -12
  14. package/dist/lib/internal/task-operation-registry.js +2 -1
  15. package/dist/lib/internal/task-qualification.js +6 -15
  16. package/dist/lib/internal/task-review.js +3 -16
  17. package/dist/lib/internal/task-short-id.js +2 -1
  18. package/dist/lib/internal/task-verify.js +1 -11
  19. package/dist/lib/internal/task-warning.js +1 -12
  20. package/dist/lib/internal/task-workspace.js +43 -0
  21. package/dist/lib/merge.js +4 -3
  22. package/dist/lib/platform/pr-summary.js +6 -15
  23. package/dist/lib/platform/pull-requests.js +3 -15
  24. package/dist/lib/pr-review/evidence-grading.js +3 -2
  25. package/dist/lib/process-data/index.js +5 -4
  26. package/dist/lib/process-data/sources.js +6 -0
  27. package/dist/lib/run/index.js +3 -2
  28. package/dist/lib/sandbox/commands/create.js +5 -1
  29. package/dist/lib/sandbox/commands/enter.js +1 -1
  30. package/dist/lib/sandbox/commands/list-running.js +2 -1
  31. package/dist/lib/sandbox/control/client.js +8 -13
  32. package/dist/lib/sandbox/control/completed-reentry.js +2 -1
  33. package/dist/lib/sandbox/control/task-view.js +4 -2
  34. package/dist/lib/sandbox/mounts.js +4 -4
  35. package/dist/lib/sandbox/recovery.js +26 -16
  36. package/dist/lib/sandbox/removal.js +3 -10
  37. package/dist/lib/sandbox/task-resolver.js +9 -6
  38. package/dist/lib/sandbox/workspace-identity.js +6 -5
  39. package/dist/lib/sandbox/workspace-view.js +29 -22
  40. package/dist/lib/task/activity-intent.js +2 -12
  41. package/dist/lib/task/artifact-lifecycle.js +17 -11
  42. package/dist/lib/task/artifact-operations.js +41 -49
  43. package/dist/lib/task/capabilities.js +2 -2
  44. package/dist/lib/task/commands/ls.js +4 -3
  45. package/dist/lib/task/commands/status.js +1 -1
  46. package/dist/lib/task/commit-operation.js +6 -6
  47. package/dist/lib/task/control-authority.js +32 -32
  48. package/dist/lib/task/create-service.js +2 -4
  49. package/dist/lib/task/create.js +12 -65
  50. package/dist/lib/task/delegation-receipts.js +1 -1
  51. package/dist/lib/task/delivery.js +2 -8
  52. package/dist/lib/task/events.js +5 -31
  53. package/dist/lib/task/finalization.js +17 -22
  54. package/dist/lib/task/lifecycle.js +9 -48
  55. package/dist/lib/task/local-artifact-finalization.js +1 -2
  56. package/dist/lib/task/orchestration.js +24 -57
  57. package/dist/lib/task/platform-operation-recovery.js +1 -2
  58. package/dist/lib/task/resolve-ref.js +17 -12
  59. package/dist/lib/task/review-finalization.js +2 -35
  60. package/dist/lib/task/review-identity.js +3 -1
  61. package/dist/lib/task/short-id.js +10 -8
  62. package/dist/lib/task/verification-engine.js +19 -58
  63. package/dist/lib/task/workspace-root.js +59 -0
  64. package/dist/lib/task/workspace-snapshot.js +4 -3
  65. package/dist/lifecycle-build-manifest.json +3 -3
  66. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
  67. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
  68. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
  69. package/lib/decide.ts +2 -7
  70. package/lib/internal/cli-route-inventory.ts +2 -0
  71. package/lib/internal/manual-validation.ts +8 -19
  72. package/lib/internal/platform-comment.ts +3 -2
  73. package/lib/internal/task-activity.ts +1 -10
  74. package/lib/internal/task-event.ts +1 -10
  75. package/lib/internal/task-ledger.ts +1 -10
  76. package/lib/internal/task-operation-registry.ts +2 -1
  77. package/lib/internal/task-qualification.ts +5 -11
  78. package/lib/internal/task-review.ts +3 -14
  79. package/lib/internal/task-short-id.ts +2 -1
  80. package/lib/internal/task-verify.ts +1 -9
  81. package/lib/internal/task-warning.ts +1 -10
  82. package/lib/internal/task-workspace.ts +39 -0
  83. package/lib/merge.ts +4 -3
  84. package/lib/platform/pr-summary.ts +7 -16
  85. package/lib/platform/pull-requests.ts +3 -24
  86. package/lib/pr-review/evidence-grading.ts +3 -2
  87. package/lib/process-data/index.ts +4 -3
  88. package/lib/process-data/sources.ts +6 -0
  89. package/lib/run/index.ts +3 -2
  90. package/lib/sandbox/commands/create.ts +5 -1
  91. package/lib/sandbox/commands/enter.ts +1 -1
  92. package/lib/sandbox/commands/list-running.ts +2 -1
  93. package/lib/sandbox/control/client.ts +7 -12
  94. package/lib/sandbox/control/completed-reentry.ts +2 -1
  95. package/lib/sandbox/control/task-view.ts +4 -2
  96. package/lib/sandbox/mounts.ts +4 -4
  97. package/lib/sandbox/recovery.ts +26 -18
  98. package/lib/sandbox/removal.ts +3 -26
  99. package/lib/sandbox/task-resolver.ts +9 -6
  100. package/lib/sandbox/workspace-identity.ts +6 -5
  101. package/lib/sandbox/workspace-view.ts +28 -20
  102. package/lib/task/activity-intent.ts +3 -17
  103. package/lib/task/artifact-lifecycle.ts +39 -11
  104. package/lib/task/artifact-operations.ts +28 -35
  105. package/lib/task/capabilities.ts +2 -2
  106. package/lib/task/commands/ls.ts +4 -3
  107. package/lib/task/commands/status.ts +1 -1
  108. package/lib/task/commit-operation.ts +5 -6
  109. package/lib/task/control-authority.ts +29 -35
  110. package/lib/task/create-service.ts +2 -4
  111. package/lib/task/create.ts +10 -51
  112. package/lib/task/delegation-receipts.ts +1 -1
  113. package/lib/task/delivery.ts +2 -10
  114. package/lib/task/events.ts +5 -32
  115. package/lib/task/finalization.ts +16 -24
  116. package/lib/task/lifecycle.ts +9 -50
  117. package/lib/task/local-artifact-finalization.ts +1 -4
  118. package/lib/task/orchestration.ts +31 -80
  119. package/lib/task/platform-operation-recovery.ts +1 -2
  120. package/lib/task/resolve-ref.ts +18 -12
  121. package/lib/task/review-finalization.ts +2 -52
  122. package/lib/task/review-identity.ts +3 -1
  123. package/lib/task/short-id.ts +10 -8
  124. package/lib/task/verification-engine.ts +20 -62
  125. package/lib/task/workspace-root.ts +78 -0
  126. package/lib/task/workspace-snapshot.ts +4 -3
  127. package/package.json +1 -1
  128. package/templates/.agents/QUICKSTART.en.md +19 -8
  129. package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
  130. package/templates/.agents/README.en.md +10 -15
  131. package/templates/.agents/README.zh-CN.md +9 -13
  132. package/templates/.agents/rules/issue-sync.en.md +1 -1
  133. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  134. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  135. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  136. package/templates/.agents/rules/next-step-output.en.md +2 -2
  137. package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
  138. package/templates/.agents/rules/pr-sync.en.md +1 -1
  139. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
  140. package/templates/.agents/rules/release-commands.en.md +3 -0
  141. package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
  142. package/templates/.agents/rules/task-management.en.md +0 -2
  143. package/templates/.agents/rules/task-management.zh-CN.md +0 -2
  144. package/templates/.agents/rules/task-short-id.en.md +2 -2
  145. package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
  146. package/templates/.agents/scripts/task-short-id.js +2 -8
  147. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  148. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  149. package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
  150. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
  151. package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
  152. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
  153. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
  154. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
  156. package/templates/.agents/skills/block-task/config/verify.json +0 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -6
  158. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -6
  159. package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
  160. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  161. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  162. package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
  163. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
  164. package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
  165. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  166. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  167. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  168. package/templates/.agents/skills/commit/config/verify.json +0 -1
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
  170. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
  171. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  174. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  175. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
  176. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
  177. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  178. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  179. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -7
  180. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +8 -8
  181. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  182. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  183. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
  184. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
  185. package/templates/.agents/skills/create-pr/config/verify.json +0 -1
  186. package/templates/.agents/skills/create-pr/reference/change-report.en.md +1 -1
  187. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +1 -1
  188. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  189. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  190. package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
  191. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
  192. package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
  193. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
  194. package/templates/.agents/skills/create-task/config/verify.json +0 -1
  195. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  196. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  197. package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
  198. package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
  199. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  200. package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
  201. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  202. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  203. package/templates/.agents/skills/import-issue/config/verify.json +0 -1
  204. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  205. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  206. package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
  207. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
  208. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
  210. package/templates/.agents/skills/restore-task/config/verify.json +0 -1
  211. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  212. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  213. package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
  214. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
  215. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  216. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  217. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  218. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  219. package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
  220. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
  221. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  222. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  223. package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
  224. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
  225. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  226. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  227. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
  228. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
  229. package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
  230. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
  231. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
  232. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
  233. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
  234. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
  235. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  236. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  237. package/templates/.agents/skills/test/SKILL.en.md +0 -3
  238. package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
  239. package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
  240. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
  241. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
  242. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
  243. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
  244. package/templates/.agents/skills/watch-pr/SKILL.en.md +3 -3
  245. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +3 -3
  246. package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
  247. package/templates/.agents/templates/handoff.en.md +1 -1
  248. package/templates/.agents/templates/handoff.zh-CN.md +1 -1
  249. package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
  250. package/templates/.agents/workflows/feature-development.en.yaml +1 -1
  251. package/templates/.agents/workflows/refactoring.en.yaml +1 -1
  252. package/dist/lib/task/task-execution-lock.js +0 -250
  253. package/lib/task/task-execution-lock.ts +0 -320
@@ -1,320 +0,0 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { createHash, randomUUID } from 'node:crypto';
5
-
6
- import { getProcessStartTime, processIdentityMatches } from '../server/process-state.ts';
7
- import type { ProcessIdentity } from '../server/process-state.ts';
8
-
9
- type TaskExecutionLockErrorCode =
10
- | 'ORCHESTRATION_LOCK_BUSY'
11
- | 'ORCHESTRATION_LOCK_UNSUPPORTED'
12
- | 'ORCHESTRATION_LOCK_FAILED';
13
-
14
- type TaskExecutionLockOwner = Readonly<{
15
- version: 2;
16
- pid: number;
17
- startTime: number;
18
- token: string;
19
- owner: string;
20
- canonicalRepoRoot: string;
21
- taskId: string;
22
- acquiredAt: string;
23
- }>;
24
-
25
- type LinkOperation = 'acquire' | 'quarantine';
26
- type LinkDisposition = 'exists' | 'missing' | TaskExecutionLockError;
27
-
28
- type TaskExecutionLockOptions = Readonly<{
29
- lockRoot?: string;
30
- identityScope?: string;
31
- token?: () => string;
32
- now?: () => string;
33
- getStartTime?: (pid: number) => number | null;
34
- identityMatches?: (identity: ProcessIdentity) => boolean;
35
- linkSync?: (existingPath: string, newPath: string) => void;
36
- }>;
37
-
38
- class TaskExecutionLockError extends Error {
39
- readonly code: TaskExecutionLockErrorCode;
40
- readonly detail: Readonly<{ operation: string; errno: string | null; key: string }>;
41
-
42
- constructor(
43
- code: TaskExecutionLockErrorCode,
44
- message: string,
45
- detail: Readonly<{ operation: string; errno: string | null; key: string }>
46
- ) {
47
- super(message);
48
- this.name = 'TaskExecutionLockError';
49
- this.code = code;
50
- this.detail = detail;
51
- }
52
- }
53
-
54
- function errno(error: unknown): string | null {
55
- const value = (error as NodeJS.ErrnoException | null)?.code;
56
- return typeof value === 'string' ? value : null;
57
- }
58
-
59
- function mapLinkError(operation: LinkOperation, error: unknown, key: string): LinkDisposition {
60
- const code = errno(error);
61
- if (code === 'EEXIST') return 'exists';
62
- if (operation === 'quarantine' && code === 'ENOENT') return 'missing';
63
- const detail = { operation, errno: code, key };
64
- if (['EPERM', 'EACCES', 'ENOSYS', 'ENOTSUP', 'EOPNOTSUPP', 'EXDEV'].includes(code ?? '')) {
65
- return new TaskExecutionLockError(
66
- 'ORCHESTRATION_LOCK_UNSUPPORTED',
67
- `lifecycle lock hard-link operation '${operation}' is unsupported`,
68
- detail
69
- );
70
- }
71
- return new TaskExecutionLockError(
72
- 'ORCHESTRATION_LOCK_FAILED',
73
- `lifecycle lock hard-link operation '${operation}' failed`,
74
- detail
75
- );
76
- }
77
-
78
- function lockKey(repoRoot: string, taskId: string, identityScope?: string): Readonly<{ canonicalRepoRoot: string; key: string }> {
79
- const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
80
- const identity = identityScope === undefined
81
- ? `${canonicalRepoRoot}\0${taskId}`
82
- : `${identityScope}\0${taskId}`;
83
- const key = createHash('sha256').update(identity).digest('hex');
84
- return { canonicalRepoRoot, key };
85
- }
86
-
87
- function parseOwner(raw: string, key: string): TaskExecutionLockOwner {
88
- try {
89
- const value = JSON.parse(raw) as Partial<TaskExecutionLockOwner> | null;
90
- if (
91
- value !== null
92
- && value.version === 2
93
- && Number.isSafeInteger(value.pid)
94
- && (value.pid ?? 0) > 0
95
- && typeof value.startTime === 'number'
96
- && Number.isSafeInteger(value.startTime)
97
- && value.startTime >= 0
98
- && typeof value.token === 'string'
99
- && value.token.length > 0
100
- && typeof value.owner === 'string'
101
- && typeof value.canonicalRepoRoot === 'string'
102
- && typeof value.taskId === 'string'
103
- && typeof value.acquiredAt === 'string'
104
- ) {
105
- return value as TaskExecutionLockOwner;
106
- }
107
- } catch {
108
- // Fall through to the stable lock failure below.
109
- }
110
- throw new TaskExecutionLockError(
111
- 'ORCHESTRATION_LOCK_FAILED',
112
- 'lifecycle lock owner record is invalid',
113
- { operation: 'read', errno: null, key }
114
- );
115
- }
116
-
117
- function unlinkIfPresent(file: string): void {
118
- try {
119
- fs.unlinkSync(file);
120
- } catch (error) {
121
- if (errno(error) !== 'ENOENT') throw error;
122
- }
123
- }
124
-
125
- function sameInode(left: fs.Stats, right: fs.Stats): boolean {
126
- return left.dev === right.dev && left.ino === right.ino;
127
- }
128
-
129
- function reclaimStaleLock(
130
- fixed: string,
131
- quarantine: string,
132
- snapshot: string,
133
- key: string,
134
- link: (existingPath: string, newPath: string) => void
135
- ): void {
136
- let linked = false;
137
- try {
138
- link(fixed, quarantine);
139
- linked = true;
140
- } catch (error) {
141
- const disposition = mapLinkError('quarantine', error, key);
142
- if (disposition === 'missing') return;
143
- if (disposition === 'exists') {
144
- try {
145
- const quarantineSnapshot = fs.readFileSync(quarantine, 'utf8');
146
- const fixedStat = fs.statSync(fixed);
147
- const quarantineStat = fs.statSync(quarantine);
148
- if (quarantineSnapshot === snapshot && sameInode(fixedStat, quarantineStat)) {
149
- unlinkIfPresent(quarantine);
150
- }
151
- } catch (readError) {
152
- if (errno(readError) !== 'ENOENT') throw readError;
153
- }
154
- return;
155
- }
156
- if (disposition instanceof TaskExecutionLockError) throw disposition;
157
- }
158
- try {
159
- const fixedSnapshot = fs.readFileSync(fixed, 'utf8');
160
- const fixedStat = fs.statSync(fixed);
161
- const quarantineStat = fs.statSync(quarantine);
162
- if (fixedSnapshot === snapshot && sameInode(fixedStat, quarantineStat)) unlinkIfPresent(fixed);
163
- } catch (error) {
164
- if (errno(error) !== 'ENOENT') throw error;
165
- } finally {
166
- if (linked) unlinkIfPresent(quarantine);
167
- }
168
- }
169
-
170
- function cleanStaleCandidates(
171
- lockRoot: string,
172
- key: string,
173
- identityMatches: (identity: ProcessIdentity) => boolean
174
- ): void {
175
- const prefix = `${key}.candidate.`;
176
- for (const name of fs.readdirSync(lockRoot)) {
177
- if (!name.startsWith(prefix)) continue;
178
- const candidate = path.join(lockRoot, name);
179
- try {
180
- const owner = parseOwner(fs.readFileSync(candidate, 'utf8'), key);
181
- if (!identityMatches(owner)) unlinkIfPresent(candidate);
182
- } catch {
183
- // A malformed candidate never owns the fixed lock and is left for manual diagnosis.
184
- }
185
- }
186
- }
187
-
188
- function withTaskExecutionLock<T>(
189
- repoRoot: string,
190
- taskId: string,
191
- ownerName: string,
192
- callback: () => T,
193
- options: TaskExecutionLockOptions = {}
194
- ): T {
195
- let identity: Readonly<{ canonicalRepoRoot: string; key: string }>;
196
- try {
197
- identity = lockKey(repoRoot, taskId, options.identityScope);
198
- } catch (error) {
199
- throw new TaskExecutionLockError(
200
- 'ORCHESTRATION_LOCK_FAILED',
201
- 'failed to canonicalize the repository for lifecycle locking',
202
- { operation: 'canonicalize', errno: errno(error), key: 'unresolved' }
203
- );
204
- }
205
- const { canonicalRepoRoot, key } = identity;
206
- const lockRoot = options.lockRoot ?? path.join(os.tmpdir(), 'agent-infra-lifecycle-locks');
207
- const token = (options.token ?? randomUUID)();
208
- const candidate = path.join(lockRoot, `${key}.candidate.${process.pid}.${token}`);
209
- const fixed = path.join(lockRoot, `${key}.lock`);
210
- const link = options.linkSync ?? fs.linkSync;
211
- const startTime = (options.getStartTime ?? getProcessStartTime)(process.pid);
212
- if (startTime === null) {
213
- throw new TaskExecutionLockError(
214
- 'ORCHESTRATION_LOCK_FAILED',
215
- 'current process start time is unavailable',
216
- { operation: 'identity', errno: null, key }
217
- );
218
- }
219
- const owner: TaskExecutionLockOwner = {
220
- version: 2,
221
- pid: process.pid,
222
- startTime,
223
- token,
224
- owner: ownerName,
225
- canonicalRepoRoot,
226
- taskId,
227
- acquiredAt: (options.now ?? (() => new Date().toISOString()))()
228
- };
229
- const serialized = `${JSON.stringify(owner)}\n`;
230
- let ownsFixed = false;
231
- let deferRelease = false;
232
- const release = (): void => {
233
- try {
234
- unlinkIfPresent(candidate);
235
- } catch {
236
- // Candidate cleanup is best-effort and must not replace the primary result.
237
- }
238
- if (ownsFixed) {
239
- try {
240
- const current = parseOwner(fs.readFileSync(fixed, 'utf8'), key);
241
- if (current.token === token) unlinkIfPresent(fixed);
242
- } catch {
243
- // A missing or replaced fixed lock is not owned by this invocation.
244
- }
245
- }
246
- };
247
- try {
248
- fs.mkdirSync(lockRoot, { recursive: true });
249
- cleanStaleCandidates(lockRoot, key, options.identityMatches ?? processIdentityMatches);
250
- const descriptor = fs.openSync(candidate, 'wx', 0o600);
251
- try {
252
- fs.writeFileSync(descriptor, serialized);
253
- fs.fsyncSync(descriptor);
254
- } finally {
255
- fs.closeSync(descriptor);
256
- }
257
- for (let attempt = 0; attempt < 16; attempt += 1) {
258
- try {
259
- link(candidate, fixed);
260
- ownsFixed = true;
261
- break;
262
- } catch (error) {
263
- const disposition = mapLinkError('acquire', error, key);
264
- if (disposition instanceof TaskExecutionLockError) throw disposition;
265
- let snapshot: string;
266
- let current: TaskExecutionLockOwner;
267
- try {
268
- snapshot = fs.readFileSync(fixed, 'utf8');
269
- current = parseOwner(snapshot, key);
270
- } catch (readError) {
271
- if (errno(readError) === 'ENOENT') continue;
272
- throw readError;
273
- }
274
- if ((options.identityMatches ?? processIdentityMatches)(current)) {
275
- throw new TaskExecutionLockError(
276
- 'ORCHESTRATION_LOCK_BUSY',
277
- 'another lifecycle operation holds the task lock',
278
- { operation: 'acquire', errno: 'EEXIST', key }
279
- );
280
- }
281
- reclaimStaleLock(fixed, `${fixed}.quarantine.${current.token}`, snapshot, key, link);
282
- }
283
- }
284
- if (!ownsFixed) {
285
- throw new TaskExecutionLockError(
286
- 'ORCHESTRATION_LOCK_BUSY',
287
- 'lifecycle task lock could not be acquired after stale recovery',
288
- { operation: 'acquire', errno: 'EEXIST', key }
289
- );
290
- }
291
- unlinkIfPresent(candidate);
292
- const value = callback();
293
- if (value && typeof (value as { then?: unknown }).then === 'function') {
294
- deferRelease = true;
295
- return (value as unknown as Promise<unknown>).finally(release) as T;
296
- }
297
- return value;
298
- } catch (error) {
299
- if (error instanceof TaskExecutionLockError) throw error;
300
- if (error instanceof Error && ['OrchestrationStateError', 'ArtifactRecoveryError'].includes(error.name)) throw error;
301
- throw new TaskExecutionLockError(
302
- 'ORCHESTRATION_LOCK_FAILED',
303
- 'lifecycle task lock operation failed',
304
- { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key }
305
- );
306
- } finally {
307
- if (!deferRelease) release();
308
- }
309
- }
310
-
311
- function withRepositoryMutationLock<T>(
312
- repoRoot: string,
313
- callback: () => T,
314
- options: TaskExecutionLockOptions = {}
315
- ): T {
316
- return withTaskExecutionLock(repoRoot, '__repository_mutation__', 'repository-mutation', callback, options);
317
- }
318
-
319
- export { TaskExecutionLockError, lockKey, mapLinkError, withRepositoryMutationLock, withTaskExecutionLock };
320
- export type { LinkDisposition, LinkOperation, TaskExecutionLockErrorCode, TaskExecutionLockOptions, TaskExecutionLockOwner };