@fitlab-ai/agent-infra 0.9.8 → 0.9.10

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 (182) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +11 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +25 -11
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +127 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +63 -92
  10. package/dist/lib/agent-clients/adapters/codex-sandbox.js +72 -11
  11. package/dist/lib/agent-clients/adapters/index.js +3 -1
  12. package/dist/lib/agent-clients/adapters/traecli.js +66 -0
  13. package/dist/lib/agent-clients/tokens.js +2 -2
  14. package/dist/lib/agent-clients/types.js +2 -1
  15. package/dist/lib/defaults.json +5 -0
  16. package/dist/lib/git/workflow.js +10 -1
  17. package/dist/lib/internal/codex-lifecycle.js +20 -6
  18. package/dist/lib/internal/codex-sandbox-controller.js +9 -15
  19. package/dist/lib/internal/git-workflow.js +5 -7
  20. package/dist/lib/internal/platform-pr.js +20 -7
  21. package/dist/lib/internal/release-workflow.js +2 -1
  22. package/dist/lib/internal/sandbox-control.js +79 -2
  23. package/dist/lib/internal/task-finalization.js +54 -0
  24. package/dist/lib/internal/task-orchestration.js +156 -291
  25. package/dist/lib/{task/commands/validate.js → internal/task-validate.js} +75 -42
  26. package/dist/lib/platform/pr-summary.js +48 -10
  27. package/dist/lib/platform/pull-requests.js +205 -41
  28. package/dist/lib/run/tui.js +5 -2
  29. package/dist/lib/sandbox/commands/rm.js +316 -55
  30. package/dist/lib/sandbox/control/client.js +190 -6
  31. package/dist/lib/sandbox/control/controller-registration.js +275 -0
  32. package/dist/lib/sandbox/control/executor.js +140 -5
  33. package/dist/lib/sandbox/control/lifecycle.js +3 -1
  34. package/dist/lib/sandbox/control/protocol.js +67 -6
  35. package/dist/lib/sandbox/index.js +5 -3
  36. package/dist/lib/sandbox/managed-fs.js +5 -0
  37. package/dist/lib/sandbox/task-resolver.js +50 -6
  38. package/dist/lib/sandbox/workspace-identity.js +27 -0
  39. package/dist/lib/sandbox/worktree-safety.js +215 -2
  40. package/dist/lib/task/codex-orchestration.js +161 -39
  41. package/dist/lib/task/commands/status.js +59 -1
  42. package/dist/lib/task/commit-operation.js +335 -0
  43. package/dist/lib/task/commit-policy.js +18 -0
  44. package/dist/lib/task/delegation-receipts.js +248 -6
  45. package/dist/lib/task/finalization.js +593 -0
  46. package/dist/lib/task/index.js +0 -9
  47. package/dist/lib/task/operation-outcome.js +33 -0
  48. package/dist/lib/task/orchestration.js +187 -761
  49. package/dist/lib/task/short-id.js +59 -1
  50. package/dist/lib/task/task-execution-lock.js +6 -1
  51. package/dist/lib/task/verification-engine.js +110 -60
  52. package/dist/lib/task/verification.js +2 -1
  53. package/dist/lib/task/workflow-warning-intents.js +50 -1
  54. package/dist/lifecycle-build-manifest.json +3 -3
  55. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
  56. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +37 -10
  57. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
  58. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +163 -0
  59. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
  60. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +91 -118
  62. package/lib/agent-clients/adapters/codex-sandbox.ts +89 -14
  63. package/lib/agent-clients/adapters/index.ts +3 -1
  64. package/lib/agent-clients/adapters/traecli.ts +67 -0
  65. package/lib/agent-clients/tokens.ts +2 -2
  66. package/lib/agent-clients/types.ts +2 -1
  67. package/lib/defaults.json +5 -0
  68. package/lib/git/workflow.ts +11 -2
  69. package/lib/internal/codex-lifecycle.ts +19 -6
  70. package/lib/internal/codex-sandbox-controller.ts +9 -14
  71. package/lib/internal/git-workflow.ts +5 -6
  72. package/lib/internal/platform-pr.ts +14 -7
  73. package/lib/internal/release-workflow.ts +2 -1
  74. package/lib/internal/sandbox-control.ts +92 -2
  75. package/lib/internal/task-finalization.ts +68 -0
  76. package/lib/internal/task-orchestration.ts +20 -103
  77. package/lib/{task/commands/validate.ts → internal/task-validate.ts} +75 -40
  78. package/lib/platform/pr-summary.ts +55 -11
  79. package/lib/platform/pull-requests.ts +211 -41
  80. package/lib/run/tui.ts +5 -3
  81. package/lib/sandbox/commands/rm.ts +413 -61
  82. package/lib/sandbox/control/client.ts +255 -6
  83. package/lib/sandbox/control/controller-registration.ts +352 -0
  84. package/lib/sandbox/control/executor.ts +169 -5
  85. package/lib/sandbox/control/lifecycle.ts +2 -1
  86. package/lib/sandbox/control/protocol.ts +80 -7
  87. package/lib/sandbox/index.ts +5 -3
  88. package/lib/sandbox/managed-fs.ts +5 -0
  89. package/lib/sandbox/task-resolver.ts +58 -7
  90. package/lib/sandbox/workspace-identity.ts +50 -2
  91. package/lib/sandbox/workspace-view.ts +3 -3
  92. package/lib/sandbox/worktree-safety.ts +236 -2
  93. package/lib/task/codex-orchestration.ts +168 -40
  94. package/lib/task/commands/status.ts +78 -1
  95. package/lib/task/commit-operation.ts +430 -0
  96. package/lib/task/commit-policy.ts +30 -0
  97. package/lib/task/delegation-receipts.ts +263 -4
  98. package/lib/task/finalization.ts +761 -0
  99. package/lib/task/index.ts +0 -8
  100. package/lib/task/operation-outcome.ts +48 -0
  101. package/lib/task/orchestration.ts +218 -982
  102. package/lib/task/short-id.ts +65 -1
  103. package/lib/task/task-execution-lock.ts +10 -1
  104. package/lib/task/verification-engine.ts +115 -63
  105. package/lib/task/verification.ts +3 -2
  106. package/lib/task/workflow-warning-intents.ts +57 -2
  107. package/package.json +2 -2
  108. package/templates/.agents/QUICKSTART.en.md +3 -4
  109. package/templates/.agents/QUICKSTART.zh-CN.md +3 -4
  110. package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
  111. package/templates/.agents/rules/README.en.md +1 -0
  112. package/templates/.agents/rules/README.zh-CN.md +1 -0
  113. package/templates/.agents/rules/compatibility-policy.en.md +39 -0
  114. package/templates/.agents/rules/compatibility-policy.zh-CN.md +39 -0
  115. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -4
  116. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -4
  117. package/templates/.agents/rules/pr-sync.en.md +1 -1
  118. package/templates/.agents/rules/pr-sync.github.en.md +2 -1
  119. package/templates/.agents/rules/pr-sync.github.zh-CN.md +2 -0
  120. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -0
  121. package/templates/.agents/rules/review-method.en.md +1 -0
  122. package/templates/.agents/rules/review-method.zh-CN.md +1 -0
  123. package/templates/.agents/rules/task-management.en.md +2 -2
  124. package/templates/.agents/rules/task-management.zh-CN.md +2 -2
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
  127. package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
  128. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
  129. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  130. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  131. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
  132. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
  133. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
  134. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
  135. package/templates/.agents/skills/code-task/SKILL.en.md +1 -0
  136. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -0
  137. package/templates/.agents/skills/commit/SKILL.en.md +41 -125
  138. package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
  139. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  140. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  141. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
  142. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
  143. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  144. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  145. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
  146. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
  147. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
  148. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
  149. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  150. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  151. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  152. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  153. package/templates/.agents/skills/create-pr/SKILL.en.md +17 -9
  154. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +17 -9
  155. package/templates/.agents/skills/create-pr/reference/change-report.en.md +41 -0
  156. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +41 -0
  157. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  158. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  159. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -1
  160. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -1
  161. package/templates/.agents/skills/review-code/reference/migration-risks.en.md +8 -6
  162. package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +8 -6
  163. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +3 -3
  164. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +3 -3
  165. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +1 -1
  166. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +1 -1
  167. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
  168. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
  169. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  170. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  171. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  172. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  173. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +24 -0
  174. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  175. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  176. package/templates/.codex/hooks.json +4 -4
  177. package/templates/.trae/skills/update-agent-infra.en.md +8 -0
  178. package/templates/.trae/skills/update-agent-infra.zh-CN.md +8 -0
  179. package/dist/lib/task/commit-finalization.js +0 -305
  180. package/dist/lib/task/commit-intent.js +0 -165
  181. package/lib/task/commit-finalization.ts +0 -419
  182. package/lib/task/commit-intent.ts +0 -230
@@ -0,0 +1,335 @@
1
+ import fs from 'node:fs';
2
+ import { execFileSync } from 'node:child_process';
3
+ import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.js';
4
+ import { parseTaskFrontmatter } from './frontmatter.js';
5
+ import { resolveTaskContext, resolveTaskRef } from './resolve-ref.js';
6
+ import { commitPushDecision } from './commit-policy.js';
7
+ import { mergeOperationWarnings } from './operation-outcome.js';
8
+ import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.js';
9
+ import { appendActivityEntry, locateActivityLog } from './activity-log.js';
10
+ import { captureTaskWriteMetadata, writeTask } from './write.js';
11
+ import { commitOrchestrationStageCompletion, planOrchestrationStageCompletion } from './orchestration.js';
12
+ const HEAD_REF = /^refs\/heads\/(.+)$/;
13
+ function isCommitExecutionMode(value) {
14
+ return value === 'direct' || value === 'orchestrated';
15
+ }
16
+ function gitText(repoRoot, args) {
17
+ return execFileSync('git', [...args], {
18
+ cwd: repoRoot,
19
+ encoding: 'utf8',
20
+ stdio: ['ignore', 'pipe', 'pipe']
21
+ }).trim();
22
+ }
23
+ function canonicalRepositoryRoot(cwd) {
24
+ return gitText(cwd, ['rev-parse', '--show-toplevel']);
25
+ }
26
+ function failure(input, mode, taskId, code, message, status = 'failed', snapshot = null) {
27
+ return {
28
+ status,
29
+ changed: false,
30
+ result: status,
31
+ taskId,
32
+ mode,
33
+ warnings: [],
34
+ snapshot: snapshot ?? inspectGitWorkflow(input.cwd).snapshot,
35
+ operations: [],
36
+ error: { code, message }
37
+ };
38
+ }
39
+ function warning(code, message, retryable, step, target, severity) {
40
+ return { code, message, retryable, step, target, severity };
41
+ }
42
+ function resolveBoundTask(input) {
43
+ const mode = input.mode ?? 'direct';
44
+ if (input.taskRef !== undefined) {
45
+ const resolved = resolveTaskRef(input.taskRef, { repoRoot: input.cwd });
46
+ if (!resolved.ok)
47
+ return {
48
+ kind: 'error',
49
+ result: failure(input, mode, resolved.taskId, resolved.code, resolved.message)
50
+ };
51
+ return boundTask(input, resolved.taskId, resolved.taskDir, resolved.taskMdPath, resolved.repoRoot, resolved.state);
52
+ }
53
+ const context = resolveTaskContext(undefined, { repoRoot: input.cwd });
54
+ if (context.ok)
55
+ return boundTask(input, context.taskId, context.taskDir, context.taskMdPath, context.repoRoot, context.state);
56
+ if (context.code === 'TASK_CONTEXT_NOT_FOUND')
57
+ return { kind: 'none' };
58
+ return {
59
+ kind: 'error',
60
+ result: failure(input, mode, context.taskId, context.code, context.message)
61
+ };
62
+ }
63
+ function boundTask(input, taskId, taskDir, taskMdPath, repoRoot, state) {
64
+ let branch;
65
+ try {
66
+ const frontmatter = parseTaskFrontmatter(fs.readFileSync(taskMdPath, 'utf8'));
67
+ branch = frontmatter.branch ?? '';
68
+ }
69
+ catch (error) {
70
+ return {
71
+ kind: 'error',
72
+ result: failure(input, input.mode ?? 'direct', taskId, 'TASK_CONTEXT_UNREADABLE', error instanceof Error ? error.message : String(error))
73
+ };
74
+ }
75
+ if (!branch)
76
+ return {
77
+ kind: 'error',
78
+ result: failure(input, input.mode ?? 'direct', taskId, 'TASK_CONTEXT_UNREADABLE', 'task branch is missing')
79
+ };
80
+ return { kind: 'bound', task: { repoRoot, taskId, taskDir, taskMdPath, state, branch } };
81
+ }
82
+ function validateCommon(input, branch) {
83
+ if (!input.message.trim())
84
+ return { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Commit message is required' };
85
+ if (input.paths.length === 0 && !input.push)
86
+ return { code: 'GIT_COMMIT_INPUT_INVALID', message: 'At least one explicit path is required unless this is a push-only retry' };
87
+ if (!/^[a-f0-9]{40,64}$/.test(input.expectedHead))
88
+ return { code: 'GIT_HEAD_EXPECTATION_REQUIRED', message: 'A valid expected HEAD is required' };
89
+ if (!/^[a-f0-9]{40,64}$/.test(input.expectedTree))
90
+ return { code: 'GIT_TREE_EXPECTATION_REQUIRED', message: 'A valid expected tree is required' };
91
+ if (input.paths.some((candidate) => !candidate || candidate.startsWith('/') || candidate.split(/[\\/]/).includes('..'))) {
92
+ return { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Commit paths must stay inside the repository' };
93
+ }
94
+ if (input.paths.some((candidate) => /(^|\/)(?:\.env|credentials\.json|id_rsa)(?:$|\/)/i.test(candidate))) {
95
+ return { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Sensitive paths cannot be committed' };
96
+ }
97
+ if (branch !== null && branch === '')
98
+ return { code: 'GIT_BRANCH_INVALID', message: 'A named branch is required' };
99
+ if (input.push) {
100
+ if (!input.push.remote || input.push.refs.length !== 1)
101
+ return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push requires one remote and one ref' };
102
+ const ref = input.push.refs[0] ?? '';
103
+ if (!HEAD_REF.test(ref) || HEAD_REF.exec(ref)?.[1] !== branch) {
104
+ return { code: 'GIT_PUSH_INPUT_INVALID', message: 'Push ref must be the current full heads ref' };
105
+ }
106
+ }
107
+ return null;
108
+ }
109
+ function validateOrchestrated(input, task) {
110
+ if (!input.agent)
111
+ return { error: { code: 'ORCHESTRATION_PROVENANCE_MISMATCH', message: 'orchestrated commit requires an agent' } };
112
+ const planned = planOrchestrationStageCompletion(task.taskId, {
113
+ stage: 'commit',
114
+ round: 1,
115
+ artifact: 'commit',
116
+ role: 'executor',
117
+ agent: input.agent
118
+ }, { repoRoot: task.repoRoot });
119
+ const run = planned.result.run;
120
+ if (!planned.plan
121
+ || !run
122
+ || run.status !== 'running'
123
+ || !run.commitAuthorization.issuedAt
124
+ || run.commitAuthorization.consumedAt)
125
+ return {
126
+ error: {
127
+ code: planned.result.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH',
128
+ message: planned.result.error?.message ?? 'orchestrated commit requires one matching activated commit delegation'
129
+ }
130
+ };
131
+ return { plan: planned.plan };
132
+ }
133
+ function syncTaskCommit(input, task, committedHead) {
134
+ let content;
135
+ try {
136
+ content = fs.readFileSync(task.taskMdPath, 'utf8');
137
+ const activity = locateActivityLog(content);
138
+ if (!activity)
139
+ return warning('TASK_STATUS_SYNC_FAILED', 'task has no unique Activity Log section', true, 'task', task.taskId, 'ACTION_REQUIRED');
140
+ const metadata = captureTaskWriteMetadata();
141
+ const commitNote = gitText(task.repoRoot, ['show', '-s', '--format=%h %s', committedHead]);
142
+ const alreadyLogged = activity.entries.some((entry) => entry.step === 'Commit' && entry.note === commitNote);
143
+ const mutations = [];
144
+ if (!alreadyLogged)
145
+ mutations.push({
146
+ kind: 'section',
147
+ aliases: ['活动日志', 'Activity Log'],
148
+ heading: activity.heading,
149
+ body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Commit', agent: input.agent, note: commitNote })
150
+ });
151
+ const frontmatter = { assigned_to: input.agent };
152
+ mutations.push({ kind: 'frontmatter', set: frontmatter });
153
+ if (mutations.length === 0)
154
+ return null;
155
+ const written = writeTask({
156
+ taskRef: task.taskId,
157
+ expectedState: 'active',
158
+ mutations
159
+ }, { repoRoot: task.repoRoot, metadataProvider: () => metadata });
160
+ if (written.status === 'failed')
161
+ return warning('TASK_STATUS_SYNC_FAILED', written.error.message, true, 'task', task.taskId, 'ACTION_REQUIRED');
162
+ return null;
163
+ }
164
+ catch (error) {
165
+ return warning('TASK_STATUS_SYNC_FAILED', error instanceof Error ? error.message : String(error), true, 'task', task.taskId, 'ACTION_REQUIRED');
166
+ }
167
+ }
168
+ function executeUnlocked(input, task, mode) {
169
+ const inspected = inspectGitWorkflow(input.cwd);
170
+ if (!inspected.snapshot)
171
+ return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
172
+ const branch = inspected.snapshot.branch;
173
+ const commonError = validateCommon(input, branch);
174
+ if (commonError)
175
+ return failure(input, mode, task?.taskId ?? null, commonError.code, commonError.message, 'blocked', inspected.snapshot);
176
+ if (task && task.state !== 'active')
177
+ return failure(input, mode, task.taskId, 'TASK_STATE_INVALID', 'task-bound commit requires an active task', 'blocked', inspected.snapshot);
178
+ if (task && task.branch !== branch)
179
+ return failure(input, mode, task.taskId, 'GIT_BRANCH_MISMATCH', `Task branch '${task.branch}' does not match current branch '${branch}'`, 'blocked', inspected.snapshot);
180
+ if (task && !input.agent)
181
+ return failure(input, mode, task.taskId, 'COMMIT_AGENT_REQUIRED', 'task-bound commit requires an agent', 'blocked', inspected.snapshot);
182
+ const orchestrationValidation = mode === 'orchestrated'
183
+ ? (task
184
+ ? validateOrchestrated(input, task)
185
+ : { error: { code: 'ORCHESTRATION_TASK_REQUIRED', message: 'orchestrated commit requires a task context' } })
186
+ : null;
187
+ if (orchestrationValidation && 'error' in orchestrationValidation) {
188
+ const error = orchestrationValidation.error;
189
+ return failure(input, mode, task?.taskId ?? null, error.code, error.message, 'blocked', inspected.snapshot);
190
+ }
191
+ const orchestrationCompletion = orchestrationValidation && 'plan' in orchestrationValidation
192
+ ? orchestrationValidation.plan
193
+ : null;
194
+ const committed = input.paths.length === 0
195
+ ? (() => {
196
+ if (inspected.snapshot.worktree.length > 0 || inspected.snapshot.staged.length > 0)
197
+ return {
198
+ status: 'failed',
199
+ changed: false,
200
+ snapshot: inspected.snapshot,
201
+ operations: [],
202
+ error: { code: 'GIT_COMMIT_INPUT_INVALID', message: 'Push-only retry requires a clean working tree' }
203
+ };
204
+ if (inspected.snapshot.head !== input.expectedHead)
205
+ return {
206
+ status: 'failed',
207
+ changed: false,
208
+ snapshot: inspected.snapshot,
209
+ operations: [],
210
+ error: { code: 'GIT_HEAD_MISMATCH', message: `Expected HEAD ${input.expectedHead}, received ${inspected.snapshot.head}` }
211
+ };
212
+ const currentTree = gitText(input.cwd, ['rev-parse', 'HEAD^{tree}']);
213
+ if (currentTree !== input.expectedTree)
214
+ return {
215
+ status: 'failed',
216
+ changed: false,
217
+ snapshot: inspected.snapshot,
218
+ operations: [],
219
+ error: { code: 'GIT_TREE_MISMATCH', message: `Expected current tree ${input.expectedTree}, received ${currentTree}` }
220
+ };
221
+ return {
222
+ status: 'no-op',
223
+ changed: false,
224
+ snapshot: inspected.snapshot,
225
+ operations: [{ name: 'commit', status: 'no-op' }],
226
+ error: null
227
+ };
228
+ })()
229
+ : commitExplicitPaths({
230
+ cwd: input.cwd,
231
+ paths: input.paths,
232
+ message: input.message,
233
+ expectedHead: input.expectedHead,
234
+ expectedTree: input.expectedTree
235
+ });
236
+ if (committed.status === 'failed')
237
+ return {
238
+ ...committed,
239
+ result: 'failed',
240
+ taskId: task?.taskId ?? null,
241
+ mode,
242
+ warnings: [],
243
+ snapshot: committed.snapshot
244
+ };
245
+ const warnings = [];
246
+ if (orchestrationCompletion) {
247
+ try {
248
+ commitOrchestrationStageCompletion(orchestrationCompletion);
249
+ }
250
+ catch (error) {
251
+ warnings.push(warning('ORCHESTRATION_COMPLETION_FAILED', error instanceof Error ? error.message : String(error), true, 'orchestration', task?.taskId ?? 'orchestration', 'ACTION_REQUIRED'));
252
+ }
253
+ }
254
+ let status = committed.status;
255
+ let result = committed.status === 'applied' ? 'committed' : 'no_op';
256
+ if (input.push) {
257
+ const target = `${input.push.remote}:${input.push.refs[0]}`;
258
+ const decision = commitPushDecision({ branch, automatic: input.push.automatic }, target);
259
+ if (!decision.shouldPush) {
260
+ warnings.push(decision.warning);
261
+ }
262
+ else {
263
+ const pushed = pushGitRefs({
264
+ cwd: input.cwd,
265
+ remote: input.push.remote,
266
+ refs: input.push.refs,
267
+ expectedSha: committed.snapshot?.head ?? gitText(input.cwd, ['rev-parse', 'HEAD'])
268
+ });
269
+ if (pushed.status !== 'applied')
270
+ warnings.push(warning('COMMIT_PUSH_FAILED', pushed.error?.message ?? 'Git push failed', true, 'push', target, 'ACTION_REQUIRED'));
271
+ }
272
+ }
273
+ if (warnings.length > 0) {
274
+ status = 'applied';
275
+ result = 'committed_with_warnings';
276
+ }
277
+ if (task && (committed.status === 'applied' || committed.status === 'no-op')) {
278
+ const taskWarning = syncTaskCommit(input, task, committed.snapshot?.head ?? gitText(task.repoRoot, ['rev-parse', 'HEAD']));
279
+ if (taskWarning) {
280
+ warnings.push(taskWarning);
281
+ status = 'applied';
282
+ result = 'committed_with_warnings';
283
+ }
284
+ }
285
+ return {
286
+ status,
287
+ changed: committed.changed,
288
+ result,
289
+ taskId: task?.taskId ?? null,
290
+ mode,
291
+ warnings: mergeOperationWarnings(warnings),
292
+ snapshot: committed.snapshot,
293
+ operations: committed.operations,
294
+ error: null
295
+ };
296
+ }
297
+ function executeCommitOperation(input) {
298
+ if (input.mode !== undefined && !isCommitExecutionMode(input.mode))
299
+ return {
300
+ status: 'blocked',
301
+ changed: false,
302
+ result: 'blocked',
303
+ taskId: null,
304
+ mode: 'direct',
305
+ warnings: [],
306
+ snapshot: null,
307
+ operations: [],
308
+ error: { code: 'COMMIT_MODE_INVALID', message: 'commit mode must be direct or orchestrated' }
309
+ };
310
+ const mode = input.mode ?? 'direct';
311
+ let normalizedInput;
312
+ try {
313
+ normalizedInput = { ...input, cwd: canonicalRepositoryRoot(input.cwd) };
314
+ }
315
+ catch (error) {
316
+ return failure(input, mode, null, 'GIT_INSPECT_FAILED', error instanceof Error ? error.message : 'Unable to resolve Git repository root', 'blocked');
317
+ }
318
+ const resolved = resolveBoundTask(normalizedInput);
319
+ if (resolved.kind === 'error')
320
+ return resolved.result;
321
+ const task = resolved.kind === 'bound' ? resolved.task : null;
322
+ if (mode === 'orchestrated' && !task)
323
+ return failure(normalizedInput, mode, null, 'ORCHESTRATION_TASK_REQUIRED', 'orchestrated commit requires a task context', 'blocked');
324
+ try {
325
+ return task
326
+ ? withRepositoryMutationLock(normalizedInput.cwd, () => withTaskExecutionLock(normalizedInput.cwd, task.taskId, 'commit-operation', () => executeUnlocked(normalizedInput, task, mode)))
327
+ : withRepositoryMutationLock(normalizedInput.cwd, () => executeUnlocked(normalizedInput, null, mode));
328
+ }
329
+ catch (error) {
330
+ const code = error instanceof TaskExecutionLockError ? error.code : 'COMMIT_OPERATION_FAILED';
331
+ return failure(normalizedInput, mode, task?.taskId ?? null, code, error instanceof Error ? error.message : String(error), 'blocked');
332
+ }
333
+ }
334
+ export { executeCommitOperation };
335
+ //# sourceMappingURL=commit-operation.js.map
@@ -0,0 +1,18 @@
1
+ function commitPushDecision(policy, target) {
2
+ const branch = policy.branch.trim();
3
+ if (policy.automatic && (branch === 'main' || branch === 'master'))
4
+ return {
5
+ shouldPush: false,
6
+ warning: {
7
+ code: 'COMMIT_AUTOPUSH_PROTECTED_BRANCH',
8
+ message: `Automatic commit push is disabled for protected branch '${branch}'`,
9
+ retryable: false,
10
+ step: 'push',
11
+ target,
12
+ severity: 'IMPORTANT'
13
+ }
14
+ };
15
+ return { shouldPush: true, warning: null };
16
+ }
17
+ export { commitPushDecision };
18
+ //# sourceMappingURL=commit-policy.js.map
@@ -1,5 +1,249 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { isAgentClientId } from '../agent-clients/types.js';
2
3
  import { normalizeAgentToken } from '../agent-clients/tokens.js';
4
+ function isRecord(value) {
5
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
6
+ }
7
+ function hasExactKeys(value, required, optional = []) {
8
+ if (!isRecord(value))
9
+ return false;
10
+ const keys = Object.keys(value);
11
+ const allowed = new Set([...required, ...optional]);
12
+ return required.every((key) => Object.prototype.hasOwnProperty.call(value, key))
13
+ && keys.every((key) => allowed.has(key));
14
+ }
15
+ function exactText(value) {
16
+ return typeof value === 'string' && value.length > 0 && value.trim() === value;
17
+ }
18
+ function validCodexBuildMetadata(value) {
19
+ return Number.isSafeInteger(value.protocolVersion) && value.protocolVersion > 0
20
+ && exactText(value.packageVersion)
21
+ && exactText(value.internalExecutableBuildHash)
22
+ && exactText(value.lifecycleContractHash);
23
+ }
24
+ function nullableText(value) {
25
+ return value === null || exactText(value);
26
+ }
27
+ function nullableSafeInteger(value) {
28
+ return value === null || (Number.isSafeInteger(value) && value >= 0);
29
+ }
30
+ const LIFECYCLE_PROVENANCE_KEYS = [
31
+ 'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
32
+ 'hookDefinitionHash', 'hookSource', 'hookSourcePathDigest', 'hookSourceHash',
33
+ 'capabilitySessionId', 'capabilityTurnId', 'capabilityToolUseId',
34
+ 'controllerInstanceDigest', 'controlGeneration'
35
+ ];
36
+ function isDelegationLifecycleProvenance(value) {
37
+ if (!hasExactKeys(value, LIFECYCLE_PROVENANCE_KEYS))
38
+ return false;
39
+ return Number.isSafeInteger(value.protocolVersion)
40
+ && value.protocolVersion > 0
41
+ && exactText(value.packageVersion)
42
+ && exactText(value.internalExecutableBuildHash)
43
+ && exactText(value.lifecycleContractHash)
44
+ && exactText(value.hookDefinitionHash)
45
+ && ['project', 'managed', 'isolated-user'].includes(value.hookSource)
46
+ && exactText(value.hookSourcePathDigest)
47
+ && exactText(value.hookSourceHash)
48
+ && exactText(value.capabilitySessionId)
49
+ && exactText(value.capabilityTurnId)
50
+ && exactText(value.capabilityToolUseId)
51
+ && nullableText(value.controllerInstanceDigest)
52
+ && nullableText(value.controlGeneration);
53
+ }
54
+ const HOST_EVIDENCE_REQUIRED_KEYS = [
55
+ 'kind', 'hookDefinitionHash', 'startRevision', 'stopRevision', 'consumer', 'consumedAt'
56
+ ];
57
+ const HOST_EVIDENCE_OPTIONAL_KEYS = [
58
+ 'protocolVersion', 'packageVersion', 'internalExecutableBuildHash', 'lifecycleContractHash',
59
+ 'hookSource', 'hookSourcePathDigest', 'hookSourceHash', 'capabilitySessionId',
60
+ 'capabilityTurnId', 'capabilityToolUseId', 'spawnToolUseId', 'spawnObservedAt',
61
+ 'controllerInstanceDigest', 'controlGeneration'
62
+ ];
63
+ function isDelegationHostEvidence(value) {
64
+ if (!hasExactKeys(value, HOST_EVIDENCE_REQUIRED_KEYS, HOST_EVIDENCE_OPTIONAL_KEYS))
65
+ return false;
66
+ if (!['codex-lifecycle-v1', 'codex-lifecycle-v2'].includes(value.kind))
67
+ return false;
68
+ if (!exactText(value.hookDefinitionHash)
69
+ || !Number.isSafeInteger(value.startRevision) || value.startRevision < 1
70
+ || !nullableSafeInteger(value.stopRevision)
71
+ || !nullableText(value.consumer)
72
+ || !nullableText(value.consumedAt))
73
+ return false;
74
+ if (value.kind === 'codex-lifecycle-v1')
75
+ return true;
76
+ return Number.isSafeInteger(value.protocolVersion)
77
+ && value.protocolVersion > 0
78
+ && exactText(value.packageVersion)
79
+ && exactText(value.internalExecutableBuildHash)
80
+ && exactText(value.lifecycleContractHash)
81
+ && ['project', 'managed', 'isolated-user'].includes(value.hookSource)
82
+ && exactText(value.hookSourcePathDigest)
83
+ && exactText(value.hookSourceHash)
84
+ && exactText(value.capabilitySessionId)
85
+ && exactText(value.capabilityTurnId)
86
+ && exactText(value.spawnToolUseId)
87
+ && exactText(value.spawnObservedAt)
88
+ && nullableText(value.controllerInstanceDigest)
89
+ && nullableText(value.controlGeneration);
90
+ }
91
+ function hasCurrentCodexEvidence(receipt) {
92
+ const provenance = receipt.lifecycleProvenance;
93
+ if (!provenance)
94
+ return false;
95
+ const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
96
+ if (!activated)
97
+ return receipt.hostEvidence === null;
98
+ const host = receipt.hostEvidence;
99
+ if (host?.kind !== 'codex-lifecycle-v2'
100
+ || host.protocolVersion !== provenance.protocolVersion
101
+ || host.hookDefinitionHash !== provenance.hookDefinitionHash
102
+ || host.hookSource !== provenance.hookSource
103
+ || host.hookSourcePathDigest !== provenance.hookSourcePathDigest
104
+ || host.hookSourceHash !== provenance.hookSourceHash
105
+ || host.capabilitySessionId !== provenance.capabilitySessionId
106
+ || host.capabilityTurnId !== provenance.capabilityTurnId
107
+ || host.controllerInstanceDigest !== provenance.controllerInstanceDigest
108
+ || host.controlGeneration !== provenance.controlGeneration
109
+ || receipt.parentId !== provenance.capabilitySessionId
110
+ || host.spawnToolUseId === provenance.capabilityToolUseId)
111
+ return false;
112
+ const spawnObservedAt = Date.parse(host.spawnObservedAt ?? '');
113
+ const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt ?? '');
114
+ const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt ?? '');
115
+ if (!Number.isFinite(spawnObservedAt)
116
+ || !Number.isFinite(spawnDispatchedAt)
117
+ || !Number.isFinite(activationDeadlineAt)
118
+ || spawnObservedAt < spawnDispatchedAt
119
+ || spawnObservedAt > activationDeadlineAt)
120
+ return false;
121
+ if (['sealed', 'consumed'].includes(receipt.status)) {
122
+ return Number.isSafeInteger(host.stopRevision)
123
+ && host.stopRevision > host.startRevision
124
+ && host.consumer === receipt.id
125
+ && exactText(host.consumedAt);
126
+ }
127
+ return host.stopRevision === null && host.consumer === null && host.consumedAt === null;
128
+ }
129
+ const RECEIPT_KEYS = [
130
+ 'id', 'taskId', 'runId', 'role', 'stage', 'round', 'artifact', 'client',
131
+ 'requestedModel', 'requestedReasoningEffort', 'actualModel', 'actualReasoningEffort',
132
+ 'modelFallbackReason', 'reasoningEffortFallbackReason', 'parentId', 'childId',
133
+ 'spawnMode', 'agent', 'status', 'workspaceSnapshotScope', 'lifecycleProvenance',
134
+ 'hostEvidence', 'beforeFingerprint', 'afterFingerprint', 'changedPaths', 'createdAt',
135
+ 'preparedMonotonicMs', 'spawnDispatchMonotonicMs', 'activationDeadlineMonotonicMs',
136
+ 'spawnDispatchedAt', 'activationDeadlineAt', 'startEvidenceMonotonicMs',
137
+ 'activatedMonotonicMs', 'activatedAt', 'sealedAt', 'consumedAt'
138
+ ];
139
+ function hasStatusBoundEvidence(receipt) {
140
+ const dispatchFields = [
141
+ receipt.spawnDispatchMonotonicMs,
142
+ receipt.activationDeadlineMonotonicMs,
143
+ receipt.spawnDispatchedAt,
144
+ receipt.activationDeadlineAt
145
+ ];
146
+ const dispatchEmpty = dispatchFields.every((field) => field === null);
147
+ const dispatchComplete = dispatchFields.every((field) => field !== null);
148
+ if (!dispatchEmpty && !dispatchComplete)
149
+ return false;
150
+ const beforeActivation = ['prepared', 'aborted', 'expired'].includes(receipt.status);
151
+ if (beforeActivation) {
152
+ return receipt.parentId === null
153
+ && receipt.childId === null
154
+ && receipt.spawnMode === null
155
+ && receipt.actualModel === null
156
+ && receipt.actualReasoningEffort === null
157
+ && receipt.modelFallbackReason === null
158
+ && receipt.reasoningEffortFallbackReason === null
159
+ && receipt.agent === null
160
+ && receipt.hostEvidence === null
161
+ && receipt.startEvidenceMonotonicMs === null
162
+ && receipt.activatedMonotonicMs === null
163
+ && receipt.activatedAt === null
164
+ && receipt.afterFingerprint === null
165
+ && receipt.changedPaths.length === 0
166
+ && receipt.sealedAt === null
167
+ && receipt.consumedAt === null;
168
+ }
169
+ if (!dispatchComplete
170
+ || !exactText(receipt.parentId)
171
+ || !exactText(receipt.childId)
172
+ || receipt.parentId === receipt.childId
173
+ || receipt.startEvidenceMonotonicMs === null
174
+ || receipt.activatedMonotonicMs === null
175
+ || !exactText(receipt.activatedAt))
176
+ return false;
177
+ if (receipt.status === 'activated') {
178
+ return receipt.agent === null
179
+ && receipt.afterFingerprint === null
180
+ && receipt.changedPaths.length === 0
181
+ && receipt.sealedAt === null
182
+ && receipt.consumedAt === null;
183
+ }
184
+ if (!exactText(receipt.agent)
185
+ || normalizeAgentToken(receipt.agent) !== normalizeAgentToken(receipt.client))
186
+ return false;
187
+ if (receipt.status === 'stage-completed') {
188
+ return receipt.afterFingerprint === null
189
+ && receipt.changedPaths.length === 0
190
+ && receipt.sealedAt === null
191
+ && receipt.consumedAt === null;
192
+ }
193
+ if (!exactText(receipt.afterFingerprint) || !exactText(receipt.sealedAt))
194
+ return false;
195
+ return receipt.status === 'sealed'
196
+ ? receipt.consumedAt === null
197
+ : receipt.status === 'consumed' && exactText(receipt.consumedAt);
198
+ }
199
+ function isDelegationReceipt(value) {
200
+ if (!hasExactKeys(value, RECEIPT_KEYS))
201
+ return false;
202
+ const structurallyValid = exactText(value.id)
203
+ && exactText(value.taskId)
204
+ && exactText(value.runId)
205
+ && ['executor', 'reviewer'].includes(value.role)
206
+ && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code', 'commit'].includes(value.stage)
207
+ && Number.isSafeInteger(value.round)
208
+ && value.round > 0
209
+ && exactText(value.artifact)
210
+ && isAgentClientId(value.client)
211
+ && exactText(value.requestedModel)
212
+ && exactText(value.requestedReasoningEffort)
213
+ && nullableText(value.actualModel)
214
+ && nullableText(value.actualReasoningEffort)
215
+ && nullableText(value.modelFallbackReason)
216
+ && nullableText(value.reasoningEffortFallbackReason)
217
+ && nullableText(value.parentId)
218
+ && nullableText(value.childId)
219
+ && nullableText(value.spawnMode)
220
+ && nullableText(value.agent)
221
+ && ['prepared', 'activated', 'stage-completed', 'sealed', 'consumed', 'aborted', 'expired'].includes(value.status)
222
+ && value.workspaceSnapshotScope === 'task'
223
+ && (value.lifecycleProvenance === null || isDelegationLifecycleProvenance(value.lifecycleProvenance))
224
+ && (value.hostEvidence === null || isDelegationHostEvidence(value.hostEvidence))
225
+ && exactText(value.beforeFingerprint)
226
+ && nullableText(value.afterFingerprint)
227
+ && Array.isArray(value.changedPaths)
228
+ && value.changedPaths.every(exactText)
229
+ && exactText(value.createdAt)
230
+ && nullableSafeInteger(value.preparedMonotonicMs)
231
+ && value.preparedMonotonicMs !== null
232
+ && nullableSafeInteger(value.spawnDispatchMonotonicMs)
233
+ && nullableSafeInteger(value.activationDeadlineMonotonicMs)
234
+ && nullableText(value.spawnDispatchedAt)
235
+ && nullableText(value.activationDeadlineAt)
236
+ && nullableSafeInteger(value.startEvidenceMonotonicMs)
237
+ && nullableSafeInteger(value.activatedMonotonicMs)
238
+ && nullableText(value.activatedAt)
239
+ && nullableText(value.sealedAt)
240
+ && nullableText(value.consumedAt);
241
+ if (!structurallyValid)
242
+ return false;
243
+ const receipt = value;
244
+ return hasStatusBoundEvidence(receipt)
245
+ && (receipt.client !== 'codex' || hasCurrentCodexEvidence(receipt));
246
+ }
3
247
  const MANAGED_AGENTS = {
4
248
  'agent-infra-lifecycle-executor': 'executor',
5
249
  'agent-infra-lifecycle-reviewer': 'reviewer'
@@ -53,7 +297,7 @@ function dispatchDelegation(receipt, options = {}) {
53
297
  }
54
298
  const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))();
55
299
  const dispatchedAt = (options.now ?? (() => new Date().toISOString()))();
56
- const window = options.activationWindowMs ?? 15_000;
300
+ const window = options.activationWindowMs ?? 60_000;
57
301
  return { ok: true, receipt: Object.freeze({
58
302
  ...receipt,
59
303
  spawnDispatchMonotonicMs: monotonic,
@@ -138,9 +382,6 @@ function activateDelegation(receipt, event, options = {}) {
138
382
  const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
139
383
  if (!expected
140
384
  || event.hostEvidence.protocolVersion !== expected.protocolVersion
141
- || event.hostEvidence.packageVersion !== expected.packageVersion
142
- || event.hostEvidence.internalExecutableBuildHash !== expected.internalExecutableBuildHash
143
- || event.hostEvidence.lifecycleContractHash !== expected.lifecycleContractHash
144
385
  || event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
145
386
  || event.hostEvidence.hookSource !== expected.hookSource
146
387
  || event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
@@ -154,7 +395,8 @@ function activateDelegation(receipt, event, options = {}) {
154
395
  || spawnObservedAt > activationDeadlineAt
155
396
  || event.parentId !== expected.capabilitySessionId
156
397
  || (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
157
- || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration)
398
+ || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration
399
+ || !validCodexBuildMetadata(event.hostEvidence))
158
400
  return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
159
401
  }
160
402
  return { ok: true, receipt: Object.freeze({
@@ -252,5 +494,5 @@ function consumeDelegation(receipt, options = {}) {
252
494
  consumedAt: (options.now ?? (() => new Date().toISOString()))()
253
495
  }) };
254
496
  }
255
- export { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, managedDelegationRole, prepareDelegation, sealDelegation };
497
+ export { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation };
256
498
  //# sourceMappingURL=delegation-receipts.js.map