@fitlab-ai/agent-infra 0.9.3 → 0.9.5

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 (203) hide show
  1. package/README.md +3 -1
  2. package/README.zh-CN.md +3 -1
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +144 -123
  6. package/dist/lib/agent-clients/adapter.js +94 -4
  7. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  8. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  9. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  10. package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +382 -0
  12. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +163 -0
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -0
  14. package/dist/lib/agent-clients/adapters/codex.js +2 -2
  15. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  16. package/dist/lib/agent-clients/adapters/opencode.js +15 -3
  17. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  18. package/dist/lib/agent-clients/registry.js +9 -1
  19. package/dist/lib/git/worktree-identity.js +64 -0
  20. package/dist/lib/internal/codex-lifecycle.js +202 -0
  21. package/dist/lib/internal/sandbox-control.js +24 -0
  22. package/dist/lib/internal/task-activity.js +45 -13
  23. package/dist/lib/internal/task-create.js +48 -0
  24. package/dist/lib/internal/task-ledger.js +12 -3
  25. package/dist/lib/internal/task-lifecycle.js +19 -1
  26. package/dist/lib/internal/task-orchestration.js +170 -27
  27. package/dist/lib/platform/github-client.js +19 -2
  28. package/dist/lib/platform/pr-checks.js +1 -1
  29. package/dist/lib/platform/pull-requests.js +47 -1
  30. package/dist/lib/platform/releases.js +13 -5
  31. package/dist/lib/process-data/index.js +239 -0
  32. package/dist/lib/process-data/normalize.js +198 -0
  33. package/dist/lib/process-data/privacy.js +33 -0
  34. package/dist/lib/process-data/reconcile.js +95 -0
  35. package/dist/lib/process-data/repair.js +58 -0
  36. package/dist/lib/process-data/sources.js +345 -0
  37. package/dist/lib/process-data/store.js +282 -0
  38. package/dist/lib/process-data/types.js +2 -0
  39. package/dist/lib/run/index.js +4 -3
  40. package/dist/lib/run/tui.js +7 -8
  41. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  42. package/dist/lib/sandbox/capture.js +7 -2
  43. package/dist/lib/sandbox/commands/create.js +110 -228
  44. package/dist/lib/sandbox/commands/enter.js +15 -47
  45. package/dist/lib/sandbox/commands/list-running.js +16 -6
  46. package/dist/lib/sandbox/commands/ls.js +10 -6
  47. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  48. package/dist/lib/sandbox/commands/refresh.js +4 -84
  49. package/dist/lib/sandbox/commands/rm.js +24 -3
  50. package/dist/lib/sandbox/commands/show.js +12 -3
  51. package/dist/lib/sandbox/commands/start.js +7 -4
  52. package/dist/lib/sandbox/config.js +2 -0
  53. package/dist/lib/sandbox/constants.js +6 -0
  54. package/dist/lib/sandbox/control/client.js +58 -0
  55. package/dist/lib/sandbox/control/protocol.js +54 -0
  56. package/dist/lib/sandbox/control/server.js +144 -0
  57. package/dist/lib/sandbox/dockerfile.js +36 -13
  58. package/dist/lib/sandbox/mounts.js +17 -4
  59. package/dist/lib/sandbox/recovery.js +128 -5
  60. package/dist/lib/sandbox/redaction.js +12 -0
  61. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  62. package/dist/lib/sandbox/workspace-identity.js +95 -0
  63. package/dist/lib/sandbox/workspace-view.js +109 -0
  64. package/dist/lib/task/activity-intent.js +257 -51
  65. package/dist/lib/task/activity-log.js +32 -5
  66. package/dist/lib/task/commit-finalization.js +278 -0
  67. package/dist/lib/task/commit-intent.js +165 -0
  68. package/dist/lib/task/create-service.js +137 -0
  69. package/dist/lib/task/create.js +333 -0
  70. package/dist/lib/task/decision-intents.js +31 -17
  71. package/dist/lib/task/delegation-receipts.js +2 -2
  72. package/dist/lib/task/implementation-inputs.js +54 -7
  73. package/dist/lib/task/ledger-intents.js +35 -1
  74. package/dist/lib/task/orchestration.js +505 -34
  75. package/dist/lib/task/resolve-ref.js +41 -1
  76. package/dist/lib/task/review-fingerprint.js +4 -0
  77. package/dist/lib/task/workspace-snapshot.js +78 -3
  78. package/lib/agent-clients/adapter.ts +130 -6
  79. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  80. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  81. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  82. package/lib/agent-clients/adapters/claude-code.ts +21 -3
  83. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +442 -0
  84. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +307 -0
  85. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +250 -0
  86. package/lib/agent-clients/adapters/codex.ts +2 -2
  87. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  88. package/lib/agent-clients/adapters/opencode.ts +18 -3
  89. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  90. package/lib/agent-clients/registry.ts +9 -1
  91. package/lib/git/worktree-identity.ts +86 -0
  92. package/lib/internal/codex-lifecycle.ts +204 -0
  93. package/lib/internal/sandbox-control.ts +24 -0
  94. package/lib/internal/task-activity.ts +50 -14
  95. package/lib/internal/task-create.ts +49 -0
  96. package/lib/internal/task-ledger.ts +12 -3
  97. package/lib/internal/task-lifecycle.ts +22 -1
  98. package/lib/internal/task-orchestration.ts +117 -21
  99. package/lib/platform/github-client.ts +20 -2
  100. package/lib/platform/pr-checks.ts +1 -1
  101. package/lib/platform/pull-requests.ts +49 -1
  102. package/lib/platform/releases.ts +16 -4
  103. package/lib/process-data/index.ts +224 -0
  104. package/lib/process-data/normalize.ts +202 -0
  105. package/lib/process-data/privacy.ts +41 -0
  106. package/lib/process-data/reconcile.ts +110 -0
  107. package/lib/process-data/repair.ts +64 -0
  108. package/lib/process-data/sources.ts +354 -0
  109. package/lib/process-data/store.ts +315 -0
  110. package/lib/process-data/types.ts +135 -0
  111. package/lib/run/index.ts +5 -3
  112. package/lib/run/tui.ts +11 -8
  113. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  114. package/lib/sandbox/capture.ts +9 -2
  115. package/lib/sandbox/commands/create.ts +127 -260
  116. package/lib/sandbox/commands/enter.ts +13 -59
  117. package/lib/sandbox/commands/list-running.ts +23 -6
  118. package/lib/sandbox/commands/ls.ts +22 -6
  119. package/lib/sandbox/commands/rebuild.ts +4 -3
  120. package/lib/sandbox/commands/refresh.ts +4 -126
  121. package/lib/sandbox/commands/rm.ts +24 -3
  122. package/lib/sandbox/commands/show.ts +29 -3
  123. package/lib/sandbox/commands/start.ts +6 -4
  124. package/lib/sandbox/config.ts +4 -0
  125. package/lib/sandbox/constants.ts +8 -0
  126. package/lib/sandbox/control/client.ts +79 -0
  127. package/lib/sandbox/control/protocol.ts +110 -0
  128. package/lib/sandbox/control/server.ts +155 -0
  129. package/lib/sandbox/dockerfile.ts +66 -13
  130. package/lib/sandbox/mounts.ts +24 -4
  131. package/lib/sandbox/recovery.ts +163 -6
  132. package/lib/sandbox/redaction.ts +14 -0
  133. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  134. package/lib/sandbox/tool-types.ts +16 -0
  135. package/lib/sandbox/workspace-identity.ts +123 -0
  136. package/lib/sandbox/workspace-view.ts +150 -0
  137. package/lib/task/activity-intent.ts +350 -61
  138. package/lib/task/activity-log.ts +46 -7
  139. package/lib/task/commit-finalization.ts +376 -0
  140. package/lib/task/commit-intent.ts +230 -0
  141. package/lib/task/create-service.ts +171 -0
  142. package/lib/task/create.ts +372 -0
  143. package/lib/task/decision-intents.ts +32 -12
  144. package/lib/task/delegation-receipts.ts +2 -2
  145. package/lib/task/implementation-inputs.ts +62 -8
  146. package/lib/task/ledger-intents.ts +43 -3
  147. package/lib/task/orchestration.ts +673 -41
  148. package/lib/task/resolve-ref.ts +46 -1
  149. package/lib/task/review-fingerprint.ts +9 -0
  150. package/lib/task/workspace-snapshot.ts +98 -4
  151. package/package.json +7 -7
  152. package/templates/.agents/hooks/lifecycle-delegation.js +47 -3
  153. package/templates/.agents/rules/README.en.md +1 -1
  154. package/templates/.agents/rules/README.zh-CN.md +1 -1
  155. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  156. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  157. package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
  158. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  159. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  160. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
  161. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  162. package/templates/.agents/rules/task-management.en.md +3 -0
  163. package/templates/.agents/rules/task-management.zh-CN.md +3 -0
  164. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  165. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  166. package/templates/.agents/skills/commit/SKILL.en.md +21 -17
  167. package/templates/.agents/skills/commit/SKILL.zh-CN.md +28 -16
  168. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +50 -0
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +56 -0
  170. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  171. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  172. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  173. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  174. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  175. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  176. package/templates/.agents/skills/create-task/SKILL.en.md +26 -43
  177. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +26 -43
  178. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  179. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  180. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  181. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  182. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  183. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  184. package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
  185. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
  186. package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
  187. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
  188. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
  189. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  190. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  191. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  192. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  193. package/templates/.agents/templates/task.en.md +1 -1
  194. package/templates/.agents/templates/task.zh-CN.md +1 -1
  195. package/templates/.claude/commands/watch-pr.en.md +1 -1
  196. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  197. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  198. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  199. package/templates/.codex/hooks.json +50 -0
  200. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  201. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  202. package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
  203. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
@@ -0,0 +1,333 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
5
+ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
6
+ const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'];
7
+ const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'];
8
+ const PRIORITIES = ['Urgent', 'High', 'Medium', 'Low'];
9
+ const EFFORTS = ['High', 'Medium', 'Low'];
10
+ const TASK_INPUT_KEYS = [
11
+ 'sources', 'facts', 'constraints', 'decisions', 'alternatives',
12
+ 'acceptanceCriteria', 'openQuestions'
13
+ ];
14
+ function payloadError(message) {
15
+ throw new Error(`TASK_CREATE_PAYLOAD_INVALID: ${message}`);
16
+ }
17
+ function exactKeys(value, expected, label) {
18
+ const actual = Object.keys(value).sort();
19
+ const wanted = [...expected].sort();
20
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
21
+ payloadError(`${label} has unknown or missing fields`);
22
+ }
23
+ }
24
+ function cleanString(value, label, maxBytes, oneLine = false) {
25
+ if (typeof value !== 'string' || value.length === 0 || Buffer.byteLength(value, 'utf8') > maxBytes) {
26
+ payloadError(`${label} must be a non-empty string within ${maxBytes} bytes`);
27
+ }
28
+ if (/\0|[\u0001-\u0008\u000b\u000c\u000e-\u001f\u007f]/u.test(value) || (oneLine && /[\r\n]/u.test(value))) {
29
+ payloadError(`${label} contains unsupported control characters`);
30
+ }
31
+ return value;
32
+ }
33
+ function enumValue(value, allowed, label) {
34
+ if (typeof value !== 'string' || !allowed.includes(value))
35
+ payloadError(`${label} is invalid`);
36
+ return value;
37
+ }
38
+ function validateTaskCreateCandidate(value) {
39
+ if (!value || typeof value !== 'object' || Array.isArray(value))
40
+ payloadError('candidate must be an object');
41
+ const candidate = value;
42
+ exactKeys(candidate, [
43
+ 'version', 'idempotencyKey', 'agent', 'title', 'type', 'branchSlug',
44
+ 'priority', 'effort', 'description', 'taskInput'
45
+ ], 'candidate');
46
+ if (candidate.version !== 1)
47
+ payloadError('version must be 1');
48
+ const idempotencyKey = cleanString(candidate.idempotencyKey, 'idempotencyKey', 64, true);
49
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(idempotencyKey)) {
50
+ payloadError('idempotencyKey must be a UUID v4');
51
+ }
52
+ const title = cleanString(candidate.title, 'title', 400, true);
53
+ if ([...title].length > 100)
54
+ payloadError('title exceeds 100 Unicode code points');
55
+ const branchSlug = cleanString(candidate.branchSlug, 'branchSlug', 64, true);
56
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)+$/.test(branchSlug) || branchSlug.length < 3) {
57
+ payloadError('branchSlug must be lowercase ASCII kebab-case with length 3-64');
58
+ }
59
+ const description = cleanString(candidate.description, 'description', 8 * 1024);
60
+ if (!candidate.taskInput || typeof candidate.taskInput !== 'object' || Array.isArray(candidate.taskInput)) {
61
+ payloadError('taskInput must be an object');
62
+ }
63
+ const rawInput = candidate.taskInput;
64
+ exactKeys(rawInput, TASK_INPUT_KEYS, 'taskInput');
65
+ const taskInput = Object.fromEntries(TASK_INPUT_KEYS.map((key) => {
66
+ const list = rawInput[key];
67
+ if (!Array.isArray(list) || list.length > 50)
68
+ payloadError(`taskInput.${key} must contain at most 50 items`);
69
+ return [key, list.map((item, index) => cleanString(item, `taskInput.${key}[${index}]`, 2 * 1024, true))];
70
+ }));
71
+ return {
72
+ version: 1,
73
+ idempotencyKey,
74
+ agent: enumValue(candidate.agent, AGENTS, 'agent'),
75
+ title,
76
+ type: enumValue(candidate.type, TYPES, 'type'),
77
+ branchSlug,
78
+ priority: enumValue(candidate.priority, PRIORITIES, 'priority'),
79
+ effort: enumValue(candidate.effort, EFFORTS, 'effort'),
80
+ description,
81
+ taskInput
82
+ };
83
+ }
84
+ function canonicalTaskCreateCandidate(candidate) {
85
+ const valid = validateTaskCreateCandidate(candidate);
86
+ return JSON.stringify({
87
+ version: valid.version,
88
+ idempotencyKey: valid.idempotencyKey,
89
+ agent: valid.agent,
90
+ title: valid.title,
91
+ type: valid.type,
92
+ branchSlug: valid.branchSlug,
93
+ priority: valid.priority,
94
+ effort: valid.effort,
95
+ description: valid.description,
96
+ taskInput: Object.fromEntries(TASK_INPUT_KEYS.map((key) => [key, valid.taskInput[key]]))
97
+ });
98
+ }
99
+ function sha256(value) {
100
+ return createHash('sha256').update(value).digest('hex');
101
+ }
102
+ function formatTimestamp(date) {
103
+ const pad = (value) => String(value).padStart(2, '0');
104
+ const offsetMinutes = -date.getTimezoneOffset();
105
+ const sign = offsetMinutes >= 0 ? '+' : '-';
106
+ const offset = Math.abs(offsetMinutes);
107
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}${sign}${pad(Math.floor(offset / 60))}:${pad(offset % 60)}`;
108
+ }
109
+ function taskIdFor(date) {
110
+ const pad = (value) => String(value).padStart(2, '0');
111
+ return `TASK-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
112
+ }
113
+ function yamlString(value) {
114
+ return JSON.stringify(value);
115
+ }
116
+ function markdownBlock(value) {
117
+ return value.split(/\r?\n/).map((line) => /^(?:#{1,6}\s|```)/.test(line) ? `\\${line}` : line).join('\n');
118
+ }
119
+ function renderList(items) {
120
+ return items.map((item) => `- ${item}`).join('\n');
121
+ }
122
+ function replaceEmptySubsection(content, heading, items) {
123
+ if (items.length === 0)
124
+ return content;
125
+ return content.replace(`${heading}\n`, `${heading}\n\n${renderList(items)}\n`);
126
+ }
127
+ function renderTask(params) {
128
+ const { candidate } = params;
129
+ const workflow = candidate.type === 'bugfix' ? 'bug-fix' : candidate.type === 'refactor' ? 'refactoring' : 'feature-development';
130
+ const branch = `${params.project}-${candidate.type}-${candidate.branchSlug}`;
131
+ const frontmatter = [
132
+ '---',
133
+ `id: ${params.taskId}`,
134
+ `type: ${candidate.type}`,
135
+ `branch: ${branch}`,
136
+ `workflow: ${workflow}`,
137
+ 'status: active',
138
+ `created_at: ${params.timestamp}`,
139
+ `updated_at: ${params.timestamp}`,
140
+ `agent_infra_version: ${params.agentInfraVersion}`,
141
+ `priority: ${candidate.priority}`,
142
+ `effort: ${candidate.effort}`,
143
+ 'start_date:',
144
+ 'target_date:',
145
+ 'current_step: requirement-analysis',
146
+ `assigned_to: ${candidate.agent}`,
147
+ 'pr_status: pending',
148
+ `task_create_key_digest: ${params.keyDigest}`,
149
+ `task_create_candidate_digest: ${params.candidateDigest}`,
150
+ '---'
151
+ ].join('\n');
152
+ let content = params.template.replace(/^---\n[\s\S]*?\n---/, frontmatter);
153
+ content = content.replace('# 任务:[标题]', `# 任务:${candidate.title}`);
154
+ content = content.replace('[清晰简洁地描述任务。]', markdownBlock(candidate.description));
155
+ const sections = [
156
+ ['### 来源', 'sources'], ['### 已确认事实与证据', 'facts'], ['### 约束', 'constraints'],
157
+ ['### 已确认决策', 'decisions'], ['### 候选与否决方案', 'alternatives'],
158
+ ['### 验收标准', 'acceptanceCriteria'], ['### 未决事项', 'openQuestions']
159
+ ];
160
+ for (const [heading, key] of sections)
161
+ content = replaceEmptySubsection(content, heading, candidate.taskInput[key]);
162
+ content = content.replace('- **关联 Issue**:#XXX', '- **关联 Issue**:N/A');
163
+ content = content.replace('- **关联 PR**:#XXX', '- **关联 PR**:N/A');
164
+ content = content.replace('- **分支**:`feature/xxx`', `- **分支**:\`${branch}\``);
165
+ const log = [
166
+ `- ${params.timestamp} — **Create Task [started]** by ${candidate.agent} — started`,
167
+ `- ${params.timestamp} — **Create Task** by ${candidate.agent} — Task created from structured candidate`
168
+ ].join('\n');
169
+ content = content.replace('\n## 完成检查清单', `\n${log}\n\n## 完成检查清单`);
170
+ return `${content.trimEnd()}\n`;
171
+ }
172
+ function readProject(repoRoot) {
173
+ const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
174
+ if (typeof config.project !== 'string' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(config.project)) {
175
+ throw new Error('TASK_CREATE_CONFIG_INVALID: project is missing or invalid');
176
+ }
177
+ return config.project;
178
+ }
179
+ function assertRealDirectory(directory, code) {
180
+ const stat = fs.lstatSync(directory);
181
+ if (!stat.isDirectory() || stat.isSymbolicLink())
182
+ throw new Error(code);
183
+ }
184
+ function withCreateLock(repoRoot, workspaceRoot, operation) {
185
+ const lockRoot = path.join(workspaceRoot, '.task-create.lock');
186
+ fs.mkdirSync(lockRoot, { recursive: true, mode: 0o700 });
187
+ assertRealDirectory(lockRoot, 'TASK_CREATE_LOCK_FAILED');
188
+ let callbackError;
189
+ try {
190
+ return withTaskExecutionLock(repoRoot, 'task-create', 'task-create', () => {
191
+ try {
192
+ return operation();
193
+ }
194
+ catch (error) {
195
+ callbackError = error;
196
+ throw error;
197
+ }
198
+ }, { lockRoot });
199
+ }
200
+ catch (error) {
201
+ if (callbackError !== undefined)
202
+ throw callbackError;
203
+ if (error instanceof TaskExecutionLockError && error.code === 'ORCHESTRATION_LOCK_BUSY') {
204
+ throw new Error('TASK_CREATE_LOCK_TIMEOUT');
205
+ }
206
+ const message = error instanceof Error ? error.message : String(error);
207
+ throw new Error(`TASK_CREATE_LOCK_FAILED: ${message}`);
208
+ }
209
+ }
210
+ function writeReceipt(receiptPath, value) {
211
+ const temporary = `${receiptPath}.tmp.${process.pid}.${randomUUID()}`;
212
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { flag: 'wx', mode: 0o600 });
213
+ try {
214
+ fs.renameSync(temporary, receiptPath);
215
+ }
216
+ catch (error) {
217
+ try {
218
+ fs.unlinkSync(temporary);
219
+ }
220
+ catch { /* best effort */ }
221
+ throw error;
222
+ }
223
+ }
224
+ function recoverPublishedTask(activeRoot, repoRoot, keyDigest, candidateDigest) {
225
+ for (const name of fs.readdirSync(activeRoot)) {
226
+ if (!/^TASK-\d{8}-\d{6}$/.test(name))
227
+ continue;
228
+ const taskDir = path.join(activeRoot, name);
229
+ const stat = fs.lstatSync(taskDir);
230
+ if (!stat.isDirectory() || stat.isSymbolicLink())
231
+ continue;
232
+ const taskMd = path.join(taskDir, 'task.md');
233
+ if (!fs.existsSync(taskMd))
234
+ continue;
235
+ const taskStat = fs.lstatSync(taskMd);
236
+ if (!taskStat.isFile() || taskStat.isSymbolicLink())
237
+ continue;
238
+ const content = fs.readFileSync(taskMd, 'utf8');
239
+ if (!content.includes(`task_create_key_digest: ${keyDigest}`))
240
+ continue;
241
+ if (!content.includes(`task_create_candidate_digest: ${candidateDigest}`)) {
242
+ throw new Error('TASK_CREATE_IDEMPOTENCY_CONFLICT');
243
+ }
244
+ const shortId = loadShortIdByTaskId(repoRoot).get(name)
245
+ ?? mutateShortIdRegistry(repoRoot, name, 'alloc').shortId;
246
+ if (!shortId)
247
+ throw new Error('TASK_CREATE_RECEIPT_INVALID');
248
+ return { taskId: name, shortId };
249
+ }
250
+ return null;
251
+ }
252
+ function createLocalTask(value, options) {
253
+ const candidate = validateTaskCreateCandidate(value);
254
+ const repoRoot = fs.realpathSync.native(options.repoRoot);
255
+ const workspaceRoot = path.join(repoRoot, '.agents', 'workspace');
256
+ const activeRoot = path.join(workspaceRoot, 'active');
257
+ assertRealDirectory(workspaceRoot, 'TASK_CREATE_WORKSPACE_INVALID');
258
+ assertRealDirectory(activeRoot, 'TASK_CREATE_WORKSPACE_INVALID');
259
+ const project = readProject(repoRoot);
260
+ const canonical = canonicalTaskCreateCandidate(candidate);
261
+ const candidateDigest = sha256(canonical);
262
+ const keyDigest = sha256(candidate.idempotencyKey);
263
+ const receipts = path.join(workspaceRoot, '.task-create');
264
+ return withCreateLock(repoRoot, workspaceRoot, () => {
265
+ if (fs.existsSync(receipts))
266
+ assertRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
267
+ else
268
+ fs.mkdirSync(receipts, { mode: 0o700 });
269
+ const receiptPath = path.join(receipts, `${keyDigest}.json`);
270
+ if (fs.existsSync(receiptPath)) {
271
+ const stat = fs.lstatSync(receiptPath);
272
+ if (!stat.isFile() || stat.isSymbolicLink())
273
+ throw new Error('TASK_CREATE_RECEIPT_INVALID');
274
+ const receipt = JSON.parse(fs.readFileSync(receiptPath, 'utf8'));
275
+ if (receipt.candidateDigest !== candidateDigest)
276
+ throw new Error('TASK_CREATE_IDEMPOTENCY_CONFLICT');
277
+ if (!receipt.taskId || !receipt.shortId || !fs.existsSync(path.join(activeRoot, receipt.taskId, 'task.md'))) {
278
+ throw new Error('TASK_CREATE_RECEIPT_INVALID');
279
+ }
280
+ return { status: 'no-op', changed: false, task: { id: receipt.taskId, shortId: receipt.shortId } };
281
+ }
282
+ const recovered = recoverPublishedTask(activeRoot, repoRoot, keyDigest, candidateDigest);
283
+ if (recovered) {
284
+ writeReceipt(receiptPath, {
285
+ version: 1, candidateDigest, taskId: recovered.taskId, shortId: recovered.shortId, status: 'recovered'
286
+ });
287
+ return { status: 'no-op', changed: false, task: { id: recovered.taskId, shortId: recovered.shortId } };
288
+ }
289
+ const base = options.now?.() ?? new Date();
290
+ let taskDate = new Date(base.getTime());
291
+ let taskId = taskIdFor(taskDate);
292
+ while (fs.existsSync(path.join(activeRoot, taskId))) {
293
+ taskDate = new Date(taskDate.getTime() + 1_000);
294
+ taskId = taskIdFor(taskDate);
295
+ }
296
+ const timestamp = formatTimestamp(base);
297
+ const templatePath = path.join(repoRoot, '.agents', 'templates', 'task.md');
298
+ const templateStat = fs.lstatSync(templatePath);
299
+ if (!templateStat.isFile() || templateStat.isSymbolicLink())
300
+ throw new Error('TASK_CREATE_TEMPLATE_INVALID');
301
+ const rendered = renderTask({
302
+ template: fs.readFileSync(templatePath, 'utf8'), candidate, taskId, project, timestamp,
303
+ agentInfraVersion: options.agentInfraVersion ?? 'unknown', keyDigest, candidateDigest
304
+ });
305
+ const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
306
+ fs.mkdirSync(temporary, { mode: 0o700 });
307
+ fs.writeFileSync(path.join(temporary, 'task.md'), rendered, { flag: 'wx', mode: 0o600 });
308
+ const destination = path.join(activeRoot, taskId);
309
+ fs.renameSync(temporary, destination);
310
+ let shortId;
311
+ try {
312
+ const allocated = mutateShortIdRegistry(repoRoot, taskId, 'alloc');
313
+ if (!allocated.shortId)
314
+ throw new Error('TASK_CREATE_SHORT_ID_FAILED');
315
+ shortId = allocated.shortId;
316
+ }
317
+ catch (error) {
318
+ fs.rmSync(destination, { recursive: true, force: true });
319
+ throw error;
320
+ }
321
+ try {
322
+ writeReceipt(receiptPath, { version: 1, candidateDigest, taskId, shortId, status: 'created' });
323
+ }
324
+ catch (error) {
325
+ mutateShortIdRegistry(repoRoot, taskId, 'release');
326
+ fs.rmSync(destination, { recursive: true, force: true });
327
+ throw error;
328
+ }
329
+ return { status: 'applied', changed: true, task: { id: taskId, shortId } };
330
+ });
331
+ }
332
+ export { canonicalTaskCreateCandidate, createLocalTask, validateTaskCreateCandidate };
333
+ //# sourceMappingURL=create.js.map
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { appendActivityEntry, locateActivityLog } from './activity-log.js';
3
3
  import { isDecisionItem, listDecisionItems, selectDecisionItem } from './decision-items.js';
4
- import { createImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
4
+ import { createImplementationInput, finalizeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs, selectDeclaredImplementationInput } from './implementation-inputs.js';
5
5
  import { LEDGER_COLUMNS, LEDGER_HEADINGS, parseLedger } from './ledger.js';
6
6
  import { resolveTaskContext } from './resolve-ref.js';
7
7
  import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
@@ -45,7 +45,7 @@ function existingDecision(content, row, decision, needsImplementation) {
45
45
  ? { recordId, implementationInputId: null }
46
46
  : { conflict: '--needs-implementation is only valid for code-stage decisions' };
47
47
  const inputs = parseImplementationInputs(content).rows.filter((input) => input.ledgerId === row.id && input.decisionEvidence === row.evidence);
48
- if (inputs.length !== 1 || inputs[0].needsImplementation !== needsImplementation) {
48
+ if (inputs.length !== 1 || (needsImplementation !== undefined && inputs[0].needsImplementation !== needsImplementation)) {
49
49
  return { conflict: `implementation intent for '${row.id}' conflicts with the existing decision` };
50
50
  }
51
51
  return { recordId, implementationInputId: inputs[0].id };
@@ -87,10 +87,29 @@ function applyHumanDecision(request, options = {}) {
87
87
  return failed('DECISION_TARGET_INVALID', message, resolved.taskId);
88
88
  }
89
89
  const row = selected.row;
90
- if (row.stage === 'code' && request.needsImplementation === undefined)
91
- return failed('DECISION_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false', resolved.taskId, row.id);
92
90
  if (row.stage !== 'code' && request.needsImplementation !== undefined)
93
91
  return failed('DECISION_PAYLOAD_INVALID', '--needs-implementation is only valid for code-stage decisions', resolved.taskId, row.id);
92
+ let parsedInputs = null;
93
+ let declaredInput = null;
94
+ let effectiveNeedsImplementation = request.needsImplementation;
95
+ if (row.stage === 'code') {
96
+ try {
97
+ parsedInputs = parseImplementationInputs(content);
98
+ declaredInput = selectDeclaredImplementationInput(parsedInputs.rows, row.id, row.evidence);
99
+ }
100
+ catch (error) {
101
+ return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
102
+ }
103
+ if (declaredInput) {
104
+ if (request.needsImplementation !== undefined && request.needsImplementation !== declaredInput.needsImplementation) {
105
+ return failed('DECISION_CONFLICT', `implementation intent for '${row.id}' conflicts with its declaration`, resolved.taskId, row.id);
106
+ }
107
+ effectiveNeedsImplementation = declaredInput.needsImplementation;
108
+ }
109
+ else if (effectiveNeedsImplementation === undefined) {
110
+ return failed('DECISION_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false when no implementation intent was declared', resolved.taskId, row.id);
111
+ }
112
+ }
94
113
  let metadata;
95
114
  try {
96
115
  metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
@@ -119,22 +138,17 @@ function applyHumanDecision(request, options = {}) {
119
138
  ];
120
139
  let implementationInputId = null;
121
140
  if (row.stage === 'code') {
122
- let parsed;
123
- try {
124
- parsed = parseImplementationInputs(content);
125
- }
126
- catch (error) {
127
- return failed('DECISION_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, row.id);
128
- }
129
- const input = createImplementationInput(parsed.rows, {
130
- ledgerId: row.id, decisionEvidence: evidence,
131
- needsImplementation: request.needsImplementation, decidedAt: metadata.timestamp
132
- });
133
- implementationInputId = input.id;
141
+ const nextInputs = declaredInput
142
+ ? finalizeImplementationInput(parsedInputs.rows, declaredInput.id, evidence, metadata.timestamp)
143
+ : [...parsedInputs.rows, createImplementationInput(parsedInputs.rows, {
144
+ ledgerId: row.id, decisionEvidence: evidence,
145
+ needsImplementation: effectiveNeedsImplementation, decidedAt: metadata.timestamp
146
+ })];
147
+ implementationInputId = declaredInput?.id ?? nextInputs[nextInputs.length - 1].id;
134
148
  mutations.push({
135
149
  kind: 'section', aliases: IMPLEMENTATION_INPUT_ALIASES,
136
150
  heading: findSectionHeading(content, [...IMPLEMENTATION_INPUT_ALIASES]),
137
- body: renderImplementationInputs([...parsed.rows, input])
151
+ body: renderImplementationInputs(nextInputs)
138
152
  });
139
153
  }
140
154
  mutations.push({
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
+ import { normalizeAgentToken } from '../agent-clients/tokens.js';
2
3
  const MANAGED_AGENTS = {
3
4
  'agent-infra-lifecycle-executor': 'executor',
4
5
  'agent-infra-lifecycle-reviewer': 'reviewer'
@@ -83,8 +84,7 @@ function completeDelegationStage(receipt, event) {
83
84
  if (event.stage !== receipt.stage || event.round !== receipt.round || event.artifact !== receipt.artifact) {
84
85
  return fail('DELEGATION_STAGE_MISMATCH', 'stage completion identity does not match the active delegation');
85
86
  }
86
- const acceptedAgents = receipt.client === 'claude-code' ? ['claude', 'claude-code'] : [receipt.client];
87
- if (!acceptedAgents.includes(event.agent)) {
87
+ if (normalizeAgentToken(event.agent) !== normalizeAgentToken(receipt.client)) {
88
88
  return fail('DELEGATION_AGENT_MISMATCH', `stage agent '${event.agent}' does not match client '${receipt.client}'`);
89
89
  }
90
90
  return { ok: true, receipt: Object.freeze({ ...receipt, status: 'stage-completed', agent: event.agent }) };
@@ -13,12 +13,11 @@ function validateInput(row) {
13
13
  throw new Error(`implementation input ${row.id} has incomplete decision identity`);
14
14
  if (row.stage !== 'code')
15
15
  throw new Error(`implementation input ${row.id} has invalid stage`);
16
- if (!TIMESTAMP_RE.test(row.decidedAt) || !Number.isFinite(Date.parse(row.decidedAt.replace(' ', 'T')))) {
17
- throw new Error(`implementation input ${row.id} has invalid decided_at`);
18
- }
19
- const valid = (row.needsImplementation && row.status === 'pending' && row.consumedBy === '') ||
20
- (!row.needsImplementation && row.status === 'not-required' && row.consumedBy === '') ||
21
- (row.needsImplementation && row.status === 'consumed' && ARTIFACT_RE.test(row.consumedBy));
16
+ const hasValidTimestamp = TIMESTAMP_RE.test(row.decidedAt) && Number.isFinite(Date.parse(row.decidedAt.replace(' ', 'T')));
17
+ const valid = (row.status === 'declared' && row.decidedAt === '' && row.consumedBy === '') ||
18
+ (hasValidTimestamp && row.needsImplementation && row.status === 'pending' && row.consumedBy === '') ||
19
+ (hasValidTimestamp && !row.needsImplementation && row.status === 'not-required' && row.consumedBy === '') ||
20
+ (hasValidTimestamp && row.needsImplementation && row.status === 'consumed' && ARTIFACT_RE.test(row.consumedBy));
22
21
  if (!valid)
23
22
  throw new Error(`implementation input ${row.id} has an invalid state combination`);
24
23
  }
@@ -67,6 +66,54 @@ function createImplementationInput(rows, draft) {
67
66
  validateInput(row);
68
67
  return row;
69
68
  }
69
+ function declareImplementationInput(rows, draft) {
70
+ const existing = rows.filter((row) => row.ledgerId === draft.ledgerId && row.status === 'declared');
71
+ if (existing.length > 1)
72
+ throw new Error(`implementation input declaration for '${draft.ledgerId}' conflicts with existing rows`);
73
+ if (existing.length === 1) {
74
+ const row = existing[0];
75
+ if (row.decisionEvidence !== draft.decisionEvidence || row.needsImplementation !== draft.needsImplementation) {
76
+ throw new Error(`implementation input declaration for '${draft.ledgerId}' conflicts with the existing row`);
77
+ }
78
+ return row;
79
+ }
80
+ const row = {
81
+ id: nextId(rows), ledgerId: draft.ledgerId, decisionEvidence: draft.decisionEvidence,
82
+ stage: 'code', needsImplementation: draft.needsImplementation, decidedAt: '',
83
+ status: 'declared', consumedBy: ''
84
+ };
85
+ validateInput(row);
86
+ return row;
87
+ }
88
+ function selectDeclaredImplementationInput(rows, ledgerId, decisionEvidence) {
89
+ const forLedger = rows.filter((row) => row.ledgerId === ledgerId && row.status === 'declared');
90
+ if (forLedger.length > 1)
91
+ throw new Error(`implementation input declaration for '${ledgerId}' is ambiguous`);
92
+ if (forLedger.length === 0)
93
+ return null;
94
+ if (forLedger[0].decisionEvidence !== decisionEvidence) {
95
+ throw new Error(`implementation input declaration for '${ledgerId}' has conflicting evidence`);
96
+ }
97
+ return forLedger[0];
98
+ }
99
+ function finalizeImplementationInput(rows, id, decisionEvidence, decidedAt) {
100
+ let found = false;
101
+ const next = rows.map((row) => {
102
+ if (row.id !== id)
103
+ return row;
104
+ found = true;
105
+ if (row.status !== 'declared')
106
+ throw new Error(`implementation input ${id} is not declared`);
107
+ return {
108
+ ...row, decisionEvidence, decidedAt,
109
+ status: row.needsImplementation ? 'pending' : 'not-required'
110
+ };
111
+ });
112
+ if (!found)
113
+ throw new Error(`implementation input ${id} was not found`);
114
+ next.forEach(validateInput);
115
+ return next;
116
+ }
70
117
  function selectPendingImplementationInput(rows, reviewCompletedAt) {
71
118
  const reviewEpoch = Date.parse(reviewCompletedAt.replace(' ', 'T'));
72
119
  if (!Number.isFinite(reviewEpoch))
@@ -113,5 +160,5 @@ function renderImplementationInputs(rows) {
113
160
  })
114
161
  ].join('\n');
115
162
  }
116
- export { SECTION_ALIASES as IMPLEMENTATION_INPUT_ALIASES, createImplementationInput, consumeImplementationInput, parseImplementationInputs, renderImplementationInputs, selectPendingImplementationInput };
163
+ export { SECTION_ALIASES as IMPLEMENTATION_INPUT_ALIASES, createImplementationInput, consumeImplementationInput, declareImplementationInput, finalizeImplementationInput, parseImplementationInputs, renderImplementationInputs, selectDeclaredImplementationInput, selectPendingImplementationInput };
117
164
  //# sourceMappingURL=implementation-inputs.js.map
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { declareImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
3
4
  import { LEDGER_COLUMNS, LEDGER_HEADINGS, nextHdId, parseLedger, validateLedgerRows } from './ledger.js';
4
5
  import { resolveTaskRef } from './resolve-ref.js';
5
6
  import { writeTask } from './write.js';
@@ -66,6 +67,14 @@ function ledgerSectionMutation(content) {
66
67
  body: `| ${LEDGER_COLUMNS.join(' | ')} |\n|----|-------|-------|----------|--------|----------|`
67
68
  }];
68
69
  }
70
+ function implementationInputMutation(content, rows) {
71
+ const english = /^##\s+Activity Log\s*$/m.test(content);
72
+ return {
73
+ kind: 'section', aliases: IMPLEMENTATION_INPUT_ALIASES,
74
+ heading: english ? 'Implementation Inputs' : '实现输入',
75
+ body: renderImplementationInputs(rows)
76
+ };
77
+ }
69
78
  function mapWrite(intent, entityId, before, after, result) {
70
79
  if (result.status === 'failed')
71
80
  return failed(intent, result.error.code, result.error.message, result.taskId, entityId);
@@ -174,9 +183,34 @@ function applyLedgerIntent(intent, options = {}) {
174
183
  }
175
184
  }
176
185
  }
186
+ let implementationMutation = null;
187
+ if (intent.kind === 'decision-upsert' || intent.kind === 'finding-review') {
188
+ const stage = intent.kind === 'decision-upsert' ? intent.stage : after.stage;
189
+ const escalates = intent.kind === 'decision-upsert' || intent.status === 'needs-human-decision';
190
+ if (stage === 'code' && escalates && intent.needsImplementation === undefined) {
191
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'code-stage decisions require --needs-implementation true|false', resolved.taskId, after.id);
192
+ }
193
+ if ((stage !== 'code' || !escalates) && intent.needsImplementation !== undefined) {
194
+ return failed(intent, 'LEDGER_PAYLOAD_INVALID', '--needs-implementation is only valid for code-stage escalation', resolved.taskId, after.id);
195
+ }
196
+ if (stage === 'code' && escalates) {
197
+ try {
198
+ const parsed = parseImplementationInputs(content);
199
+ const declaration = declareImplementationInput(parsed.rows, {
200
+ ledgerId: after.id, decisionEvidence: after.evidence,
201
+ needsImplementation: intent.needsImplementation
202
+ });
203
+ const nextRows = parsed.rows.includes(declaration) ? parsed.rows : [...parsed.rows, declaration];
204
+ implementationMutation = implementationInputMutation(content, nextRows);
205
+ }
206
+ catch (error) {
207
+ return failed(intent, 'LEDGER_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId, after.id);
208
+ }
209
+ }
210
+ }
177
211
  const result = writeTask({
178
212
  taskRef: intent.taskRef, expectedState: 'active',
179
- mutations: [...ledgerSectionMutation(content), rowMutation(after)],
213
+ mutations: [...ledgerSectionMutation(content), rowMutation(after), ...(implementationMutation ? [implementationMutation] : [])],
180
214
  dryRun: 'dryRun' in intent ? intent.dryRun : false
181
215
  }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
182
216
  return mapWrite(intent, after.id, before, after, result);