@fitlab-ai/agent-infra 0.11.6 → 0.11.8

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 (192) hide show
  1. package/dist/bin/internal-cli.js +8 -2
  2. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  3. package/dist/lib/agent-clients/config.js +11 -3
  4. package/dist/lib/defaults.json +6 -3
  5. package/dist/lib/git/primary-worktree.js +58 -0
  6. package/dist/lib/internal/cli-route-inventory.js +3 -0
  7. package/dist/lib/internal/platform-pr-review.js +20 -3
  8. package/dist/lib/internal/task-operation-registry.js +3 -1
  9. package/dist/lib/internal/task-platform-recovery.js +56 -0
  10. package/dist/lib/merge.js +25 -5
  11. package/dist/lib/platform/completion-backfill.js +2 -1
  12. package/dist/lib/platform/in-label-sync.js +12 -1
  13. package/dist/lib/platform/issue-comments.js +58 -4
  14. package/dist/lib/platform/issues.js +105 -5
  15. package/dist/lib/platform/operation-coordinator.js +74 -0
  16. package/dist/lib/platform/pr-review.js +107 -1
  17. package/dist/lib/platform/pr-summary.js +79 -57
  18. package/dist/lib/platform/pull-requests.js +125 -7
  19. package/dist/lib/sandbox/commands/create.js +6 -3
  20. package/dist/lib/sandbox/commands/ls.js +4 -3
  21. package/dist/lib/sandbox/commands/rm.js +1 -1
  22. package/dist/lib/sandbox/config.js +22 -4
  23. package/dist/lib/sandbox/control/server.js +6 -2
  24. package/dist/lib/sandbox/git-metadata.js +50 -0
  25. package/dist/lib/sandbox/index.js +1 -1
  26. package/dist/lib/sandbox/mounts.js +12 -0
  27. package/dist/lib/sandbox/recovery.js +11 -0
  28. package/dist/lib/sandbox/removal.js +58 -20
  29. package/dist/lib/sandbox/tools.js +29 -7
  30. package/dist/lib/server/config.js +7 -27
  31. package/dist/lib/server/daemon.js +4 -3
  32. package/dist/lib/server/process-control.js +4 -2
  33. package/dist/lib/server/runner.js +4 -3
  34. package/dist/lib/task/activity-intent.js +4 -3
  35. package/dist/lib/task/artifact-lifecycle.js +42 -10
  36. package/dist/lib/task/artifact-receipts.js +62 -22
  37. package/dist/lib/task/capabilities.js +22 -4
  38. package/dist/lib/task/completion-facts.js +56 -0
  39. package/dist/lib/task/create-service.js +11 -5
  40. package/dist/lib/task/create.js +60 -79
  41. package/dist/lib/task/events.js +268 -195
  42. package/dist/lib/task/finalization-artifacts.js +4 -1
  43. package/dist/lib/task/finalization-handoff.js +2 -1
  44. package/dist/lib/task/finalization.js +157 -24
  45. package/dist/lib/task/invalidation-command.js +4 -2
  46. package/dist/lib/task/invalidation.js +38 -9
  47. package/dist/lib/task/lifecycle.js +38 -2
  48. package/dist/lib/task/local-artifact-finalization.js +17 -3
  49. package/dist/lib/task/platform-operation-journal.js +152 -0
  50. package/dist/lib/task/platform-operation-recovery.js +459 -0
  51. package/dist/lib/task/qualification-audit.js +23 -105
  52. package/dist/lib/task/review-finalization.js +7 -0
  53. package/dist/lib/task/review-identity.js +69 -0
  54. package/dist/lib/task/sections.js +6 -5
  55. package/dist/lib/task/task-execution-lock.js +6 -3
  56. package/dist/lib/task/verification-engine.js +14 -63
  57. package/dist/lib/task/verification.js +35 -38
  58. package/dist/lib/task/workflow-warning-intents.js +7 -1
  59. package/dist/lifecycle-build-manifest.json +3 -3
  60. package/lib/agent-clients/adapters/codex.ts +1 -1
  61. package/lib/agent-clients/config.ts +9 -3
  62. package/lib/defaults.json +6 -3
  63. package/lib/git/primary-worktree.ts +62 -0
  64. package/lib/internal/cli-route-inventory.ts +2 -0
  65. package/lib/internal/platform-pr-review.ts +14 -3
  66. package/lib/internal/task-operation-registry.ts +3 -1
  67. package/lib/internal/task-platform-recovery.ts +53 -0
  68. package/lib/merge.ts +26 -6
  69. package/lib/platform/completion-backfill.ts +2 -1
  70. package/lib/platform/in-label-sync.ts +12 -0
  71. package/lib/platform/issue-comments.ts +59 -3
  72. package/lib/platform/issues.ts +103 -5
  73. package/lib/platform/operation-coordinator.ts +96 -0
  74. package/lib/platform/pr-review.ts +113 -1
  75. package/lib/platform/pr-summary.ts +22 -1
  76. package/lib/platform/pull-requests.ts +132 -8
  77. package/lib/sandbox/commands/create.ts +7 -8
  78. package/lib/sandbox/commands/ls.ts +4 -3
  79. package/lib/sandbox/commands/rm.ts +1 -1
  80. package/lib/sandbox/config.ts +23 -5
  81. package/lib/sandbox/control/server.ts +5 -2
  82. package/lib/sandbox/git-metadata.ts +66 -0
  83. package/lib/sandbox/index.ts +1 -1
  84. package/lib/sandbox/mounts.ts +14 -0
  85. package/lib/sandbox/recovery.ts +13 -0
  86. package/lib/sandbox/removal.ts +57 -19
  87. package/lib/sandbox/tool-types.ts +1 -1
  88. package/lib/sandbox/tools.ts +36 -7
  89. package/lib/server/config.ts +7 -29
  90. package/lib/server/daemon.ts +5 -3
  91. package/lib/server/process-control.ts +4 -2
  92. package/lib/server/runner.ts +7 -4
  93. package/lib/server/server.schema.json +0 -4
  94. package/lib/task/activity-intent.ts +4 -3
  95. package/lib/task/artifact-lifecycle.ts +35 -11
  96. package/lib/task/artifact-receipts.ts +55 -15
  97. package/lib/task/capabilities.ts +17 -3
  98. package/lib/task/completion-facts.ts +66 -0
  99. package/lib/task/create-service.ts +12 -6
  100. package/lib/task/create.ts +54 -69
  101. package/lib/task/events.ts +248 -182
  102. package/lib/task/finalization-artifacts.ts +4 -1
  103. package/lib/task/finalization-handoff.ts +2 -1
  104. package/lib/task/finalization.ts +154 -21
  105. package/lib/task/invalidation-command.ts +4 -2
  106. package/lib/task/invalidation.ts +35 -10
  107. package/lib/task/lifecycle.ts +35 -3
  108. package/lib/task/local-artifact-finalization.ts +13 -3
  109. package/lib/task/platform-operation-journal.ts +230 -0
  110. package/lib/task/platform-operation-recovery.ts +435 -0
  111. package/lib/task/qualification-audit.ts +21 -114
  112. package/lib/task/review-finalization.ts +7 -0
  113. package/lib/task/review-identity.ts +85 -0
  114. package/lib/task/sections.ts +6 -6
  115. package/lib/task/task-execution-lock.ts +7 -3
  116. package/lib/task/verification-engine.ts +13 -86
  117. package/lib/task/verification.ts +34 -38
  118. package/lib/task/workflow-warning-intents.ts +9 -1
  119. package/package.json +1 -1
  120. package/templates/.agents/README.en.md +19 -17
  121. package/templates/.agents/README.zh-CN.md +18 -16
  122. package/templates/.agents/rules/README.en.md +2 -1
  123. package/templates/.agents/rules/README.zh-CN.md +2 -1
  124. package/templates/.agents/rules/decision-qualification.en.md +4 -3
  125. package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
  126. package/templates/.agents/rules/task-management.en.md +12 -0
  127. package/templates/.agents/rules/task-management.zh-CN.md +12 -0
  128. package/templates/.agents/rules/validation-output.en.md +19 -0
  129. package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
  130. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
  131. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  132. package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
  133. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  134. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -2
  135. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -3
  136. package/templates/.agents/skills/code-task/SKILL.en.md +5 -3
  137. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  138. package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
  139. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
  140. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  141. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  142. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  143. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  144. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  145. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  146. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
  147. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  148. package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
  149. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  150. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
  151. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  152. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
  153. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  154. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
  155. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
  157. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  158. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  159. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  160. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
  161. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
  162. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
  163. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
  164. package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
  165. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
  166. package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
  167. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
  168. package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
  169. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
  170. package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
  171. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
  172. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
  173. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
  174. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
  175. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
  176. package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
  177. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
  178. package/templates/.agents/skills/review-pr/SKILL.en.md +13 -11
  179. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +13 -11
  180. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
  181. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
  182. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
  183. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
  184. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +19 -6
  189. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
  190. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  191. package/templates/.agents/templates/task.en.md +2 -2
  192. package/templates/.agents/templates/task.zh-CN.md +2 -2
@@ -15,6 +15,7 @@ const taskWorkflowCommand = command === 'task-artifact'
15
15
  || command === 'task-ledger'
16
16
  || command === 'task-invalidation'
17
17
  || command === 'task-warning';
18
+ const taskPlatformRecoveryCommand = command === 'task-platform-recovery';
18
19
  const manualValidationWorkflowCommand = command === 'manual-validation';
19
20
  const taskControlMarkers = [
20
21
  'AGENT_INFRA_TASK_ID', 'AGENT_INFRA_CONTROL_TOKEN', 'AGENT_INFRA_CONTROL_GENERATION',
@@ -48,9 +49,9 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
48
49
  process.exit(1);
49
50
  }
50
51
  let controlRouted = false;
51
- if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
52
+ if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand || taskPlatformRecoveryCommand)) {
52
53
  const transport = resolveSandboxControlTransport(process.env, {
53
- localWorkflow: command === 'task-orchestration' || command === 'task-finalization' || taskWorkflowCommand || manualValidationWorkflowCommand
54
+ localWorkflow: command === 'task-orchestration' || command === 'task-finalization' || taskWorkflowCommand || manualValidationWorkflowCommand || taskPlatformRecoveryCommand
54
55
  });
55
56
  switch (transport.kind) {
56
57
  case 'fail-closed': {
@@ -90,6 +91,11 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
90
91
  await taskFinalization(process.argv.slice(3));
91
92
  break;
92
93
  }
94
+ case 'task-platform-recovery': {
95
+ const { taskPlatformRecovery } = await import('../lib/internal/task-platform-recovery.js');
96
+ await taskPlatformRecovery(process.argv.slice(3));
97
+ break;
98
+ }
93
99
  case 'task-create': {
94
100
  const { taskCreate } = await import('../lib/internal/task-create.js');
95
101
  await taskCreate(process.argv.slice(3));
@@ -50,7 +50,7 @@ const codexAdapter = defineAgentClientAdapter({
50
50
  containerMount: '/home/devuser/.codex',
51
51
  versionCmd: 'codex --version',
52
52
  setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
53
- tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
53
+ tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
54
54
  hostLiveMounts: [
55
55
  {
56
56
  hostPath: hostJoin(home, '.codex', 'auth.json'),
@@ -114,10 +114,18 @@ function normalizeAgentClients(input) {
114
114
  if (hasOwn(input, 'tuis'))
115
115
  fail('INVALID_AGENT_CLIENTS', 'tuis');
116
116
  const sandbox = isRecord(input.sandbox) ? input.sandbox : undefined;
117
- if (Array.isArray(sandbox?.tools)) {
118
- for (const [index, tool] of sandbox.tools.entries()) {
117
+ if (sandbox && hasOwn(sandbox, 'customTools')) {
118
+ fail('INVALID_AGENT_CLIENTS', 'sandbox.customTools');
119
+ }
120
+ if (sandbox && hasOwn(sandbox, 'tools')) {
121
+ if (!isRecord(sandbox.tools))
122
+ fail('INVALID_AGENT_CLIENTS', 'sandbox.tools');
123
+ const tools = sandbox.tools;
124
+ if (!Array.isArray(tools.ids))
125
+ fail('INVALID_AGENT_CLIENTS', 'sandbox.tools.ids');
126
+ for (const [index, tool] of tools.ids.entries()) {
119
127
  if (isAgentClientId(tool)) {
120
- fail('INVALID_AGENT_CLIENTS', `sandbox.tools[${index}]`);
128
+ fail('INVALID_AGENT_CLIENTS', `sandbox.tools.ids[${index}]`);
121
129
  }
122
130
  }
123
131
  }
@@ -34,9 +34,12 @@
34
34
  "runtimes": [
35
35
  "node22"
36
36
  ],
37
- "tools": [
38
- "agent-infra"
39
- ],
37
+ "tools": {
38
+ "ids": [
39
+ "agent-infra"
40
+ ],
41
+ "definitions": {}
42
+ },
40
43
  "refreshIntervalDays": 7,
41
44
  "dockerfile": null,
42
45
  "vm": {
@@ -0,0 +1,58 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import { sameFilesystemEntry } from './worktree-identity.js';
4
+ function gitText(repositoryRoot, args) {
5
+ return execFileSync('git', ['-C', repositoryRoot, ...args], {
6
+ encoding: 'utf8',
7
+ stdio: ['ignore', 'pipe', 'pipe']
8
+ }).replace(/\r?\n$/, '');
9
+ }
10
+ function isPrimaryWorktree(candidate, expectedCommonDirectory) {
11
+ try {
12
+ const root = fs.realpathSync.native(candidate);
13
+ if (!fs.statSync(root).isDirectory())
14
+ return false;
15
+ if (gitText(root, ['rev-parse', '--show-prefix']) !== '')
16
+ return false;
17
+ const commonDirectory = gitText(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
18
+ const gitDirectory = gitText(root, ['rev-parse', '--absolute-git-dir']);
19
+ return sameFilesystemEntry(commonDirectory, expectedCommonDirectory) &&
20
+ sameFilesystemEntry(gitDirectory, expectedCommonDirectory);
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ function worktreePaths(repositoryRoot) {
27
+ const output = execFileSync('git', ['-C', repositoryRoot, 'worktree', 'list', '--porcelain', '-z'], {
28
+ encoding: 'utf8',
29
+ stdio: ['ignore', 'pipe', 'pipe']
30
+ });
31
+ return output
32
+ .split('\0\0')
33
+ .flatMap((record) => record.split('\0'))
34
+ .filter((field) => field.startsWith('worktree '))
35
+ .map((field) => field.slice('worktree '.length));
36
+ }
37
+ export function resolvePrimaryWorktreeRoot(repositoryRoot) {
38
+ try {
39
+ const root = fs.realpathSync.native(repositoryRoot);
40
+ if (!fs.statSync(root).isDirectory() || gitText(root, ['rev-parse', '--show-prefix']) !== '') {
41
+ throw new Error('repository root is not a worktree root');
42
+ }
43
+ const commonDirectory = gitText(root, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
44
+ const gitDirectory = gitText(root, ['rev-parse', '--absolute-git-dir']);
45
+ if (sameFilesystemEntry(gitDirectory, commonDirectory))
46
+ return root;
47
+ const matches = worktreePaths(root).filter((candidate) => isPrimaryWorktree(candidate, commonDirectory));
48
+ if (matches.length !== 1) {
49
+ throw new Error(matches.length === 0 ? 'primary worktree metadata was not found' : 'primary worktree metadata is ambiguous');
50
+ }
51
+ return fs.realpathSync.native(matches[0]);
52
+ }
53
+ catch (error) {
54
+ const detail = error instanceof Error ? error.message : String(error);
55
+ throw new Error(`server: unable to locate primary worktree: ${detail}`);
56
+ }
57
+ }
58
+ //# sourceMappingURL=primary-worktree.js.map
@@ -60,6 +60,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
60
60
  'task-invalidation': ['reconcile'],
61
61
  'task-lifecycle': ['intent'],
62
62
  'task-finalization': ['complete'],
63
+ 'task-platform-recovery': ['inspect', 'recover'],
63
64
  'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
64
65
  'task-snapshot': ['snapshot'],
65
66
  'task-verify': ['event'],
@@ -88,6 +89,8 @@ export function internalRouteSelector(command, args) {
88
89
  return args[1] ? 'intent' : '';
89
90
  if (command === 'task-finalization')
90
91
  return args[1] === 'complete' ? 'complete' : '';
92
+ if (command === 'task-platform-recovery')
93
+ return args[1] ?? '';
91
94
  if (command === 'task-event')
92
95
  return args[1] ? 'event' : '';
93
96
  if (command === 'task-verify')
@@ -3,12 +3,13 @@ import path from 'node:path';
3
3
  import { inspectPlatformPullRequestByNumber } from '../platform/pull-requests.js';
4
4
  import { listPrReviews, publishPrReview } from '../platform/pr-review.js';
5
5
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
6
+ import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
6
7
  const USAGE = `Usage: agent-infra-internal platform-pr-review inspect --pr <token> [--cwd <path>]
7
8
  agent-infra-internal platform-pr-review list --pr <token> [--cwd <path>]
8
- agent-infra-internal platform-pr-review publish --pr <token> --scope <taskId|pr{N}> --round <N> --commit <sha> --event <COMMENT|APPROVE|REQUEST_CHANGES> --body-file <path|-> [--dry-run] [--cwd <path>]
9
+ agent-infra-internal platform-pr-review publish --pr <token> --scope <taskId|pr{N}> --round <N> --commit <sha> --event <COMMENT|APPROVE|REQUEST_CHANGES> --body-file <path|-> [--artifact <pr-review-artifact>] [--agent <agent>] [--dry-run] [--cwd <path>]
9
10
  `;
10
11
  const BOOLEAN_FLAGS = new Set(['--dry-run']);
11
- const VALUE_FLAGS = new Set(['--cwd', '--pr', '--scope', '--round', '--commit', '--event', '--body-file']);
12
+ const VALUE_FLAGS = new Set(['--cwd', '--pr', '--scope', '--round', '--commit', '--event', '--body-file', '--artifact', '--agent']);
12
13
  function key(flag) {
13
14
  return flag.slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
14
15
  }
@@ -74,7 +75,7 @@ async function platformPrReview(args = []) {
74
75
  const allowed = {
75
76
  inspect: ['cwd', 'pr'],
76
77
  list: ['cwd', 'pr'],
77
- publish: ['cwd', 'pr', 'scope', 'round', 'commit', 'event', 'bodyFile', 'dryRun']
78
+ publish: ['cwd', 'pr', 'scope', 'round', 'commit', 'event', 'bodyFile', 'artifact', 'agent', 'dryRun']
78
79
  };
79
80
  const unexpected = Object.keys(values).find((name) => !allowed[operation].includes(name));
80
81
  if (unexpected) {
@@ -99,6 +100,8 @@ async function platformPrReview(args = []) {
99
100
  const round = Number(values.round);
100
101
  const commit = typeof values.commit === 'string' ? values.commit : '';
101
102
  const event = values.event;
103
+ const rawAgent = typeof values.agent === 'string' ? values.agent : '';
104
+ const agent = rawAgent ? normalizeAgentToken(rawAgent) : null;
102
105
  if (!scope || !/^(?:pr\d+|TASK-\d{8}-\d{6})$/.test(scope)) {
103
106
  fail('publish requires --scope <taskId|pr{N}>');
104
107
  return;
@@ -124,6 +127,18 @@ async function platformPrReview(args = []) {
124
127
  fail('publish requires --body-file');
125
128
  return;
126
129
  }
130
+ if (/^TASK-\d{8}-\d{6}$/u.test(scope) && typeof values.artifact !== 'string') {
131
+ fail('task-scoped publish requires --artifact <pr-review-artifact>');
132
+ return;
133
+ }
134
+ if (/^TASK-\d{8}-\d{6}$/u.test(scope) && !agent) {
135
+ fail(`task-scoped publish requires --agent <agent>${rawAgent ? `: invalid '${rawAgent}'` : ''}: ${AGENT_USAGE_HINT}`);
136
+ return;
137
+ }
138
+ if (rawAgent && !agent) {
139
+ fail(`invalid --agent '${rawAgent}': ${AGENT_USAGE_HINT}`);
140
+ return;
141
+ }
127
142
  let body;
128
143
  try {
129
144
  body = readBodyFile(values.bodyFile, cwd);
@@ -138,6 +153,8 @@ async function platformPrReview(args = []) {
138
153
  identity: { scope, round, commitSha: commit },
139
154
  event: event,
140
155
  body,
156
+ ...(typeof values.artifact === 'string' ? { recoveryArtifact: values.artifact } : {}),
157
+ ...(agent ? { agent } : {}),
141
158
  dryRun: values.dryRun === true
142
159
  }));
143
160
  }
@@ -105,6 +105,8 @@ function internalTaskRoutes() {
105
105
  descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
106
106
  descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
107
107
  descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
108
+ descriptor('internal', 'task-platform-recovery', 'inspect', 'task-bound', 'diagnostic'),
109
+ descriptor('internal', 'task-platform-recovery', 'recover', 'task-bound', 'recovery'),
108
110
  descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
109
111
  descriptor('internal', 'task-short-id', 'list-verify', 'conditional', 'diagnostic'),
110
112
  descriptor('internal', 'task-short-id', 'alloc', 'task-bound', 'progress'),
@@ -178,7 +180,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
178
180
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
179
181
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
180
182
  'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
181
- 'task-finalization', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
183
+ 'task-finalization', 'task-platform-recovery', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
182
184
  ]);
183
185
  export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
184
186
  'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
@@ -0,0 +1,56 @@
1
+ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
2
+ import { readPlatformOperationJournal } from '../task/platform-operation-journal.js';
3
+ import { recoverPlatformOperations } from '../task/platform-operation-recovery.js';
4
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
+ const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all]\n';
6
+ function option(args, name) {
7
+ const index = args.indexOf(name);
8
+ return index >= 0 ? args[index + 1] : undefined;
9
+ }
10
+ async function taskPlatformRecovery(args = []) {
11
+ if (args[0] === '--help' || args[0] === '-h') {
12
+ process.stdout.write(USAGE);
13
+ return;
14
+ }
15
+ if (!ensureInternalHandlerRoute('task-platform-recovery', args))
16
+ return;
17
+ const action = args[1];
18
+ const registered = action === 'inspect'
19
+ ? internalHandlerRoute('task-platform-recovery', 'inspect', action)
20
+ : action === 'recover' && internalHandlerRoute('task-platform-recovery', 'recover', action);
21
+ if (!registered)
22
+ return;
23
+ const repoRoot = detectRepoRoot();
24
+ const task = resolveTaskRef(args[0] ?? '', { repoRoot });
25
+ if (!task.ok) {
26
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: task.code, message: task.message } })}\n`);
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+ if (action === 'inspect') {
31
+ try {
32
+ const journal = readPlatformOperationJournal(task.taskId, repoRoot);
33
+ process.stdout.write(`${JSON.stringify({ status: 'ready', changed: false, taskId: task.taskId, journal })}\n`);
34
+ }
35
+ catch (error) {
36
+ const value = error;
37
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error) } })}\n`);
38
+ process.exitCode = 1;
39
+ }
40
+ return;
41
+ }
42
+ const agent = option(args, '--agent');
43
+ const selection = option(args, '--selection') ?? 'all';
44
+ if (!agent || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(agent)
45
+ || !['required', 'deferred', 'all'].includes(selection)) {
46
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'recover requires a valid --agent and --selection' } })}\n`);
47
+ process.stderr.write(USAGE);
48
+ process.exitCode = 1;
49
+ return;
50
+ }
51
+ const result = await recoverPlatformOperations(task.taskId, selection, { agent, cwd: repoRoot });
52
+ process.stdout.write(`${JSON.stringify(result)}\n`);
53
+ process.exitCode = result.status === 'applied' || result.status === 'no-op' ? 0 : result.status === 'blocked' ? 2 : 1;
54
+ }
55
+ export { taskPlatformRecovery };
56
+ //# sourceMappingURL=task-platform-recovery.js.map
package/dist/lib/merge.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import os from 'node:os';
2
3
  import path from 'node:path';
3
4
  import crypto from 'node:crypto';
4
5
  import { removeDirRecursive } from './remove-dir.js';
@@ -305,6 +306,23 @@ function formatBackupTimestamp(date) {
305
306
  String(date.getDate()).padStart(2, '0')
306
307
  ].join('') + `-${String(date.getHours()).padStart(2, '0')}${String(date.getMinutes()).padStart(2, '0')}${String(date.getSeconds()).padStart(2, '0')}`;
307
308
  }
309
+ function resolveRecoveryProjectKey(repoRoot) {
310
+ let projectKey;
311
+ try {
312
+ const config = JSON.parse(fs.readFileSync(path.join(repoRoot, '.agents', '.airc.json'), 'utf8'));
313
+ if (typeof config.project === 'string' && config.project.trim() !== '') {
314
+ projectKey = config.project.trim();
315
+ }
316
+ }
317
+ catch {
318
+ // Fall back to the repository directory when project config is unavailable.
319
+ }
320
+ projectKey ??= path.basename(repoRoot);
321
+ if (!/^[A-Za-z0-9_.@-]+$/.test(projectKey) || projectKey === '.' || projectKey === '..') {
322
+ throw new Error(`Invalid project name for recovery backups: ${projectKey}`);
323
+ }
324
+ return projectKey;
325
+ }
308
326
  function toPosixPath(relativePath) {
309
327
  return relativePath.split(path.sep).join('/');
310
328
  }
@@ -400,7 +418,7 @@ function buildWorkspaceIndex(workspaceDir) {
400
418
  function backupTaskDir(backupRoot, section, taskDir, taskId) {
401
419
  const backupDir = path.join(backupRoot, section, taskId);
402
420
  fs.mkdirSync(path.dirname(backupDir), { recursive: true });
403
- fs.cpSync(taskDir, backupDir, { recursive: true });
421
+ fs.cpSync(taskDir, backupDir, { recursive: true, verbatimSymlinks: true });
404
422
  return backupDir;
405
423
  }
406
424
  function copyTaskToSection(sourceTask, workspaceDir) {
@@ -872,12 +890,14 @@ async function cmdMerge(args) {
872
890
  if (!fs.lstatSync(resolvedSource).isDirectory()) {
873
891
  throw new Error(`Source path is not a directory: ${sourcePath}`);
874
892
  }
875
- const workspaceDir = path.join(process.cwd(), '.agents', 'workspace');
893
+ const repoRoot = process.cwd();
894
+ const workspaceDir = path.join(repoRoot, '.agents', 'workspace');
876
895
  const archiveDir = path.join(workspaceDir, 'archive');
877
896
  const backupStamp = formatBackupTimestamp(new Date());
878
- const backupRootRelative = `.agents/workspace/.merge-backup/${backupStamp}/`;
879
- const backupRoot = path.join(workspaceDir, '.merge-backup', backupStamp);
880
- const report = createReport(resolvedSource, backupRootRelative);
897
+ const projectKey = resolveRecoveryProjectKey(repoRoot);
898
+ const recoveryRoot = path.join(os.homedir(), '.agent-infra', 'recovery-backups', projectKey, 'workspace-merge');
899
+ const backupRoot = path.join(recoveryRoot, backupStamp);
900
+ const report = createReport(resolvedSource, backupRoot);
881
901
  detectSourceMode(resolvedSource);
882
902
  validateSourceWorkspace(resolvedSource);
883
903
  validateArchiveSection(workspaceDir, new Map());
@@ -19,7 +19,7 @@ function referencedBackfillArtifact(taskDir, message) {
19
19
  if (names.length !== 1)
20
20
  return { status: 'ignored', artifact: null, error: null };
21
21
  const parsed = parseArtifactName(names[0]);
22
- if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family))) {
22
+ if (!parsed || !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family)) {
23
23
  return { status: 'ignored', artifact: null, error: null };
24
24
  }
25
25
  const artifactPath = path.join(taskDir, parsed.name);
@@ -112,6 +112,7 @@ async function backfillCompletionComments(taskRef, options) {
112
112
  artifact: artifact.name,
113
113
  agent: options.agent,
114
114
  backfill: true,
115
+ dependency: 'required',
115
116
  cwd: resolved.repoRoot,
116
117
  client: options.client
117
118
  });
@@ -1,4 +1,5 @@
1
1
  import { computeInLabels } from './metadata-labels.js';
2
+ import { createHash } from 'node:crypto';
2
3
  function inLabels(labels) {
3
4
  return [...new Set(labels.filter((label) => label.startsWith('in:')))].sort();
4
5
  }
@@ -26,6 +27,16 @@ function validateInLabelMapping(value) {
26
27
  }
27
28
  return { ok: true, value: normalized };
28
29
  }
30
+ function inLabelMappingDigest(value) {
31
+ const mapping = validateInLabelMapping(value);
32
+ if (!mapping.ok)
33
+ return mapping;
34
+ const canonical = Object.fromEntries(Object.keys(mapping.value).sort().map((name) => [
35
+ name,
36
+ [...new Set(mapping.value[name])].sort()
37
+ ]));
38
+ return { ok: true, digest: createHash('sha256').update(JSON.stringify(canonical)).digest('hex') };
39
+ }
29
40
  function repositoryLabelPages(value) {
30
41
  if (!Array.isArray(value))
31
42
  return null;
@@ -139,5 +150,5 @@ function extractPullRequestFileNames(value) {
139
150
  function resourceSnapshot(kind, number, labels) {
140
151
  return { kind, number, labels: [...labels].sort() };
141
152
  }
142
- export { extractPullRequestFileNames, extractRepositoryLabelNames, flattenPages, inLabels, labelDelta, mergeInLabels, planInLabelUpdate, resourceSnapshot, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload };
153
+ export { extractPullRequestFileNames, extractRepositoryLabelNames, flattenPages, inLabels, inLabelMappingDigest, labelDelta, mergeInLabels, planInLabelUpdate, resourceSnapshot, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload };
143
154
  //# sourceMappingURL=in-label-sync.js.map
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
5
5
  import { resolveTaskRef } from '../task/resolve-ref.js';
6
+ import { coordinatePlatformWrite } from './operation-coordinator.js';
6
7
  import { resolvePlatformProviderContext } from './context.js';
7
8
  import { platformResult } from './types.js';
8
9
  import { providerError, providerOperationContext, providerStatus, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
@@ -29,6 +30,8 @@ const ARTIFACT_TITLES = {
29
30
  'review-plan': '技术方案审查',
30
31
  code: '实现报告',
31
32
  'review-code': '代码审查',
33
+ 'manual-validation': '人工验证',
34
+ 'validation-run': '验证运行证据',
32
35
  'pr-review': 'PR 审查报告'
33
36
  };
34
37
  function normalizeCommentContent(content) {
@@ -98,7 +101,7 @@ function taskCommentLanguage(repoRoot) {
98
101
  }
99
102
  function artifactIdentity(artifact) {
100
103
  const stem = path.basename(artifact, '.md');
101
- const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|pr-review)(?:-r(\d+))?$/);
104
+ const match = stem.match(/^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r(\d+))?$/);
102
105
  if (!match)
103
106
  throw new Error(`unsupported artifact '${artifact}'`);
104
107
  const base = ARTIFACT_TITLES[match[1]];
@@ -430,7 +433,7 @@ function writeComment(client, repo, issue, cwd, body, id) {
430
433
  const endpoint = id ? `repos/${repo}/issues/comments/${id}` : `repos/${repo}/issues/${issue}/comments`;
431
434
  return client.json(['api', endpoint, '-X', id ? 'PATCH' : 'POST', '--input', '-'], { cwd, method: id ? 'PATCH' : 'POST', input: JSON.stringify({ body }) });
432
435
  }
433
- async function syncPlatformComment(taskRef, options) {
436
+ async function syncPlatformCommentImpl(taskRef, options) {
434
437
  const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
435
438
  if (!resolved.ok) {
436
439
  return platformResult('failed', {
@@ -452,7 +455,12 @@ async function syncPlatformComment(taskRef, options) {
452
455
  }
453
456
  let desired;
454
457
  try {
455
- desired = expectedComments(resolved.taskId, taskContent, resolved.taskDir, resolved.repoRoot, options);
458
+ const projectedContent = options.kind === 'task' && options.taskProjection ? options.taskProjection.content : taskContent;
459
+ if (options.kind === 'task' && options.taskProjection
460
+ && createHash('sha256').update(projectedContent).digest('hex') !== options.taskProjection.sha256) {
461
+ throw Object.assign(new Error('finalization task projection digest does not match content'), { code: 'FINALIZATION_PROJECTION_INVALID' });
462
+ }
463
+ desired = expectedComments(resolved.taskId, projectedContent, resolved.taskDir, resolved.repoRoot, options);
456
464
  }
457
465
  catch (error) {
458
466
  return platformResult('failed', {
@@ -644,6 +652,52 @@ async function syncPlatformComment(taskRef, options) {
644
652
  });
645
653
  return result;
646
654
  }
655
+ async function syncPlatformComment(taskRef, options) {
656
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
657
+ if (!resolved.ok)
658
+ return syncPlatformCommentImpl(taskRef, options);
659
+ const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
660
+ if (!taskIssueIdentity(parseTaskFrontmatter(taskContent)))
661
+ return syncPlatformCommentImpl(taskRef, options);
662
+ let operation;
663
+ try {
664
+ const sourceContent = options.kind === 'task' && options.taskProjection ? options.taskProjection.content : taskContent;
665
+ const desired = expectedComments(resolved.taskId, sourceContent, resolved.taskDir, resolved.repoRoot, options);
666
+ const content = desired.map((chunk) => chunk.content).join('\0');
667
+ const kind = options.kind === 'task' ? 'task-comment' : options.kind === 'summary' ? 'summary-comment'
668
+ : options.kind === 'cancel' ? 'cancel-comment' : 'artifact-comment';
669
+ const target = options.kind === 'artifact' ? options.artifact : options.kind;
670
+ operation = { kind, target, expectedDigest: createHash('sha256').update(content).digest('hex') };
671
+ }
672
+ catch {
673
+ return syncPlatformCommentImpl(taskRef, options);
674
+ }
675
+ const execute = () => syncPlatformCommentImpl(taskRef, options);
676
+ if (options.skipQueue)
677
+ return execute();
678
+ return coordinatePlatformWrite({
679
+ operation: { taskRef: resolved.taskId, cwd: resolved.repoRoot, ...operation, dependency: options.dependency ?? 'deferred' },
680
+ agent: options.agent,
681
+ execute,
682
+ block: (error) => platformResult('blocked', { error }),
683
+ persistenceFailure: (error) => platformResult('failed', { error })
684
+ });
685
+ }
686
+ function inspectPlatformCommentOperation(taskRef, options) {
687
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
688
+ if (!resolved.ok)
689
+ return null;
690
+ const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
691
+ if (!taskIssueIdentity(parseTaskFrontmatter(taskContent)))
692
+ return null;
693
+ const sourceContent = options.kind === 'task' && options.taskProjection ? options.taskProjection.content : taskContent;
694
+ const desired = expectedComments(resolved.taskId, sourceContent, resolved.taskDir, resolved.repoRoot, options);
695
+ const kind = options.kind === 'task' ? 'task-comment' : options.kind === 'summary' ? 'summary-comment'
696
+ : options.kind === 'cancel' ? 'cancel-comment' : 'artifact-comment';
697
+ const target = options.kind === 'artifact' ? options.artifact : options.kind;
698
+ const expectedDigest = createHash('sha256').update(desired.map((chunk) => chunk.content).join('\0')).digest('hex');
699
+ return { kind, target, expectedDigest, id: createHash('sha256').update(`${kind}\0${target}\0${expectedDigest}`).digest('hex') };
700
+ }
647
701
  async function listPlatformComments(issue, cwd = process.cwd(), client) {
648
702
  const loaded = await resolvePlatformProviderContext({ cwd, client });
649
703
  const context = loaded.ok ? loaded.value.context : loaded.context;
@@ -708,5 +762,5 @@ async function checkPlatformCommentOwner(taskRef, options = {}) {
708
762
  }
709
763
  return platformResult('no-op', { ...contextFields(context), resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity }, error: null });
710
764
  }
711
- export { COMMENT_BYTE_LIMIT, MARKERS, chunkArtifactComment, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, renderTaskCommentResult, isTaskCommentTooLarge, syncPlatformComment, validateRelatedMarkerSet, writeComment };
765
+ export { COMMENT_BYTE_LIMIT, MARKERS, chunkArtifactComment, inspectPlatformCommentOperation, findMarkerComments, listRemoteComments, checkPlatformCommentOwner, listPlatformComments, normalizeCommentContent, renderTaskComment, renderTaskCommentResult, isTaskCommentTooLarge, syncPlatformComment, validateRelatedMarkerSet, writeComment };
712
766
  //# sourceMappingURL=issue-comments.js.map