@fitlab-ai/agent-infra 0.11.7 → 0.11.9

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 (128) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +8 -2
  4. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  5. package/dist/lib/agent-clients/config.js +11 -3
  6. package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
  7. package/dist/lib/agent-clients/next-steps.js +1 -1
  8. package/dist/lib/agent-clients/reconcile.js +3 -3
  9. package/dist/lib/agent-clients/schema.js +18 -10
  10. package/dist/lib/defaults.json +6 -3
  11. package/dist/lib/init.js +2 -2
  12. package/dist/lib/internal/agent-client.js +3 -3
  13. package/dist/lib/internal/cli-route-inventory.js +3 -0
  14. package/dist/lib/internal/platform-pr-review.js +20 -3
  15. package/dist/lib/internal/task-operation-registry.js +3 -1
  16. package/dist/lib/internal/task-platform-recovery.js +56 -0
  17. package/dist/lib/merge.js +25 -5
  18. package/dist/lib/platform/completion-backfill.js +2 -1
  19. package/dist/lib/platform/in-label-sync.js +12 -1
  20. package/dist/lib/platform/issue-comments.js +58 -4
  21. package/dist/lib/platform/issues.js +105 -5
  22. package/dist/lib/platform/operation-coordinator.js +74 -0
  23. package/dist/lib/platform/pr-review.js +107 -1
  24. package/dist/lib/platform/pr-summary.js +79 -57
  25. package/dist/lib/platform/pull-requests.js +96 -4
  26. package/dist/lib/sandbox/commands/create.js +6 -3
  27. package/dist/lib/sandbox/commands/ls.js +4 -3
  28. package/dist/lib/sandbox/commands/rm.js +1 -1
  29. package/dist/lib/sandbox/config.js +22 -4
  30. package/dist/lib/sandbox/git-metadata.js +50 -0
  31. package/dist/lib/sandbox/index.js +1 -1
  32. package/dist/lib/sandbox/mounts.js +12 -0
  33. package/dist/lib/sandbox/recovery.js +12 -22
  34. package/dist/lib/sandbox/removal.js +58 -20
  35. package/dist/lib/sandbox/tools.js +31 -7
  36. package/dist/lib/task/activity-intent.js +4 -3
  37. package/dist/lib/task/create-service.js +5 -3
  38. package/dist/lib/task/create.js +22 -5
  39. package/dist/lib/task/finalization-artifacts.js +4 -1
  40. package/dist/lib/task/finalization.js +243 -113
  41. package/dist/lib/task/lifecycle.js +38 -2
  42. package/dist/lib/task/platform-operation-journal.js +152 -0
  43. package/dist/lib/task/platform-operation-recovery.js +459 -0
  44. package/dist/lib/task/verification.js +0 -1
  45. package/dist/lib/task/workflow-warning-intents.js +7 -1
  46. package/dist/lib/update.js +2 -2
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapters/codex.ts +1 -1
  49. package/lib/agent-clients/config.ts +9 -3
  50. package/lib/agent-clients/custom-tool-invocations.ts +129 -0
  51. package/lib/agent-clients/next-steps.ts +3 -3
  52. package/lib/agent-clients/reconcile.ts +3 -3
  53. package/lib/agent-clients/schema.ts +18 -10
  54. package/lib/defaults.json +6 -3
  55. package/lib/init.ts +2 -2
  56. package/lib/internal/agent-client.ts +3 -3
  57. package/lib/internal/cli-route-inventory.ts +2 -0
  58. package/lib/internal/platform-pr-review.ts +14 -3
  59. package/lib/internal/task-operation-registry.ts +3 -1
  60. package/lib/internal/task-platform-recovery.ts +53 -0
  61. package/lib/merge.ts +26 -6
  62. package/lib/platform/completion-backfill.ts +2 -1
  63. package/lib/platform/in-label-sync.ts +12 -0
  64. package/lib/platform/issue-comments.ts +59 -3
  65. package/lib/platform/issues.ts +103 -5
  66. package/lib/platform/operation-coordinator.ts +96 -0
  67. package/lib/platform/pr-review.ts +113 -1
  68. package/lib/platform/pr-summary.ts +22 -1
  69. package/lib/platform/pull-requests.ts +105 -5
  70. package/lib/sandbox/commands/create.ts +7 -8
  71. package/lib/sandbox/commands/ls.ts +4 -3
  72. package/lib/sandbox/commands/rm.ts +1 -1
  73. package/lib/sandbox/config.ts +23 -5
  74. package/lib/sandbox/git-metadata.ts +66 -0
  75. package/lib/sandbox/index.ts +1 -1
  76. package/lib/sandbox/mounts.ts +14 -0
  77. package/lib/sandbox/recovery.ts +13 -25
  78. package/lib/sandbox/removal.ts +57 -19
  79. package/lib/sandbox/tool-types.ts +2 -1
  80. package/lib/sandbox/tools.ts +38 -7
  81. package/lib/task/activity-intent.ts +4 -3
  82. package/lib/task/create-service.ts +5 -3
  83. package/lib/task/create.ts +17 -5
  84. package/lib/task/finalization-artifacts.ts +4 -1
  85. package/lib/task/finalization.ts +242 -106
  86. package/lib/task/lifecycle.ts +35 -3
  87. package/lib/task/platform-operation-journal.ts +230 -0
  88. package/lib/task/platform-operation-recovery.ts +435 -0
  89. package/lib/task/verification.ts +1 -2
  90. package/lib/task/workflow-warning-intents.ts +9 -1
  91. package/lib/update.ts +2 -3
  92. package/package.json +1 -1
  93. package/templates/.agents/README.en.md +20 -66
  94. package/templates/.agents/README.zh-CN.md +19 -65
  95. package/templates/.agents/rules/README.en.md +1 -1
  96. package/templates/.agents/rules/README.zh-CN.md +1 -1
  97. package/templates/.agents/rules/next-step-output.en.md +1 -1
  98. package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
  99. package/templates/.agents/rules/task-management.en.md +12 -0
  100. package/templates/.agents/rules/task-management.zh-CN.md +12 -0
  101. package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
  102. package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -2
  103. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +4 -2
  104. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  105. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  106. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  107. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  108. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
  109. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
  110. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  111. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  112. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  113. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  114. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  115. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  116. package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
  117. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
  118. package/templates/.agents/skills/review-pr/SKILL.en.md +11 -11
  119. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +11 -11
  120. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
  121. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
  122. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
  123. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
  124. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +1 -1
  125. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +1 -1
  126. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +21 -274
  127. package/dist/lib/agent-clients/custom-tuis.js +0 -99
  128. package/lib/agent-clients/custom-tuis.ts +0 -149
@@ -85,6 +85,16 @@ function asOptionalNonEmptyString(value: unknown, field: string, context: string
85
85
  return value;
86
86
  }
87
87
 
88
+ function asOptionalBoolean(value: unknown, field: string, context: string): boolean | undefined {
89
+ if (value === undefined) {
90
+ return undefined;
91
+ }
92
+ if (typeof value !== 'boolean') {
93
+ throw new Error(`${context}: field "${field}" must be a boolean when provided`);
94
+ }
95
+ return value;
96
+ }
97
+
88
98
  function asStringRecord(value: unknown, field: string, context: string): Record<string, string> | undefined {
89
99
  if (value === undefined) {
90
100
  return undefined;
@@ -193,18 +203,20 @@ function parseTmpfs(value: unknown, context: string): SandboxTool['tmpfs'] {
193
203
  if (!isPlainObject(value)) {
194
204
  throw new Error(`${context}: field "tmpfs" must be an object when provided`);
195
205
  }
206
+ const exec = asOptionalBoolean(value.exec, 'tmpfs.exec', context);
196
207
  return {
197
208
  size: asOptionalNonEmptyString(value.size, 'tmpfs.size', context),
198
- seed: asStringArray(value.seed, 'tmpfs.seed', context)
209
+ seed: asStringArray(value.seed, 'tmpfs.seed', context),
210
+ ...(exec === undefined ? {} : { exec })
199
211
  };
200
212
  }
201
213
 
202
214
  export function parseCustomTool(
203
215
  entry: unknown,
204
216
  index: number,
205
- options: { home: string }
217
+ options: { home: string },
218
+ context = `customTools[${index}]`
206
219
  ): SandboxTool {
207
- const context = `customTools[${index}]`;
208
220
  if (!isPlainObject(entry)) {
209
221
  throw new Error(`${context} must be an object`);
210
222
  }
@@ -219,6 +231,7 @@ export function parseCustomTool(
219
231
  if (!containerMount.startsWith('/')) {
220
232
  throw new Error(`${context}: field "containerMount" must be an absolute path`);
221
233
  }
234
+ const invoke = asOptionalNonEmptyString(entry.invoke, 'invoke', context);
222
235
 
223
236
  const tool: SandboxTool = {
224
237
  id,
@@ -229,6 +242,7 @@ export function parseCustomTool(
229
242
  versionCmd: asOptionalNonEmptyString(entry.versionCmd, 'versionCmd', context) ?? `which ${id}`,
230
243
  setupHint: asOptionalNonEmptyString(entry.setupHint, 'setupHint', context)
231
244
  ?? `Run \`${id}\` inside the container to set up.`,
245
+ ...(invoke === undefined ? {} : { invoke }),
232
246
  envVars: asStringRecord(entry.envVars, 'envVars', context),
233
247
  hostPreSeedFiles: parseHostPreSeedFiles(entry.hostPreSeedFiles, context),
234
248
  hostPreSeedDirs: parseHostPreSeedDirs(entry.hostPreSeedDirs, context),
@@ -243,13 +257,30 @@ export function parseCustomTool(
243
257
  }
244
258
 
245
259
  export function parseCustomTools(value: unknown, options: { home: string }): SandboxTool[] {
246
- if (value === undefined || value === null) {
260
+ if (value === undefined) {
247
261
  return [];
248
262
  }
249
- if (!Array.isArray(value)) {
250
- throw new Error('sandbox: "customTools" must be an array');
263
+ if (!isPlainObject(value)) {
264
+ throw new Error('sandbox.tools.definitions must be an object');
251
265
  }
252
- return value.map((entry, index) => parseCustomTool(entry, index, options));
266
+ return Object.entries(value).map(([id, definition], index) => {
267
+ const context = `sandbox.tools.definitions.${id}`;
268
+ if (!TOOL_ID_PATTERN.test(id)) {
269
+ throw new Error(`${context}: tool id must match ${TOOL_ID_PATTERN.source}`);
270
+ }
271
+ if (!isPlainObject(definition)) {
272
+ throw new Error(`${context} must be an object`);
273
+ }
274
+ if (Object.hasOwn(definition, 'id')) {
275
+ throw new Error(`${context}.id is not allowed; the definition key is the tool id`);
276
+ }
277
+ return parseCustomTool(
278
+ { ...definition, id },
279
+ index,
280
+ options,
281
+ context
282
+ );
283
+ });
253
284
  }
254
285
 
255
286
  export function resolveTools(config: ToolsConfig): SandboxTool[] {
@@ -108,6 +108,7 @@ const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
108
108
  const VERDICTS: ReadonlySet<string> = new Set(['approved', 'changes-requested', 'commented']);
109
109
  const OUTCOMES: ReadonlySet<string> = new Set(['aborted', 'superseded']);
110
110
  const SUCCESS_STATUSES: ReadonlySet<PrReviewArtifactStatus> = new Set(['applied', 'no-op']);
111
+ const LOCAL_COMPLETION_STATUSES: ReadonlySet<PrReviewArtifactStatus> = new Set(['applied', 'no-op', 'blocked', 'failed']);
111
112
  const ARTIFACT_STATUSES: ReadonlySet<string> = new Set([
112
113
  'pending', 'applied', 'no-op', 'blocked', 'failed', 'aborted', 'superseded'
113
114
  ]);
@@ -209,7 +210,7 @@ function readSnapshot(taskRef: string, repoRoot?: string):
209
210
  if (terminal.length === 1) {
210
211
  const note = terminal[0]!.note;
211
212
  const terminalMatchesStatus =
212
- (note.startsWith('Verdict: ') && SUCCESS_STATUSES.has(parsed.status))
213
+ (note.startsWith('Verdict: ') && LOCAL_COMPLETION_STATUSES.has(parsed.status))
213
214
  || (note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
214
215
  || (note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
215
216
  || (note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
@@ -338,8 +339,8 @@ function applyLocked(intent: PrReviewActivityIntent, normalizedAgent: string, op
338
339
  if (roundState.startedAgent === null) {
339
340
  return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${base} has no matching started row`, state.taskId);
340
341
  }
341
- if (intent.kind === 'pr-review-complete' && !SUCCESS_STATUSES.has(roundState.status)) {
342
- return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} must be applied or no-op before completion`, state.taskId);
342
+ if (intent.kind === 'pr-review-complete' && !LOCAL_COMPLETION_STATUSES.has(roundState.status)) {
343
+ return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} must record a publication outcome before completion`, state.taskId);
343
344
  }
344
345
  if (intent.kind === 'pr-review-terminate' && roundState.status !== intent.outcome) {
345
346
  return failed(intent, 'ACTIVITY_STATE_CONFLICT', `${intent.artifact} status must match outcome ${intent.outcome}`, state.taskId);
@@ -4,6 +4,7 @@ import type { PlatformResult } from '../platform/types.ts';
4
4
  import { VERSION } from '../version.ts';
5
5
  import { applyWorkflowWarningIntent } from './workflow-warning-intents.ts';
6
6
  import { createLocalTask, type TaskCreateCandidateV1 } from './create.ts';
7
+ import { TaskExecutionLockError } from './task-execution-lock.ts';
7
8
  import { verifyTaskEvent } from './verification.ts';
8
9
 
9
10
  type TaskCreateStatus = 'applied' | 'no-op' | 'degraded' | 'failed' | 'blocked';
@@ -200,8 +201,9 @@ async function createTask(value: unknown, options: CreateTaskOptions): Promise<T
200
201
  });
201
202
  } catch (error) {
202
203
  const message = error instanceof Error ? error.message : String(error);
203
- const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
204
- return taskCreateFailure({ code, message, retryable: code === 'TASK_CREATE_LOCK_TIMEOUT' });
204
+ const lockBusy = error instanceof TaskExecutionLockError && error.code === 'ORCHESTRATION_LOCK_BUSY';
205
+ const code = lockBusy ? error.code : /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
206
+ return taskCreateFailure({ code, message, retryable: lockBusy || code === 'TASK_CREATE_LOCK_TIMEOUT' });
205
207
  }
206
208
 
207
209
  const operations: TaskCreateOperation[] = [{
@@ -250,7 +252,7 @@ async function createTask(value: unknown, options: CreateTaskOptions): Promise<T
250
252
  task: local.task, issue, operations, warnings: [], error: verified.error
251
253
  };
252
254
  return {
253
- status: platformFailure.status === 'blocked' ? 'blocked' : 'degraded',
255
+ status: 'degraded',
254
256
  changed: local.changed,
255
257
  task: local.task,
256
258
  issue,
@@ -5,7 +5,7 @@ import path from 'node:path';
5
5
 
6
6
  import { isValidAgentInfraVersion, VERSION } from '../version.ts';
7
7
  import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.ts';
8
- import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
8
+ import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
9
9
  import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.ts';
10
10
  import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.ts';
11
11
  import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, parseTaskQualification } from './qualification-audit.ts';
@@ -419,7 +419,7 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
419
419
  const canonical = canonicalTaskCreateCandidate(candidate);
420
420
  const candidateDigest = sha256(canonical);
421
421
  const keyDigest = sha256(candidate.idempotencyKey);
422
- return withCreateLock(repoRoot, projectConfig.project, () => {
422
+ function createUnderRepositoryLock(): LocalTaskCreateResult {
423
423
  const recovered = recoverPublishedTask(repoRoot, keyDigest, candidateDigest);
424
424
  if (recovered) {
425
425
  return { status: 'no-op', changed: false, task: { id: recovered.taskId, shortId: recovered.shortId, state: recovered.state } };
@@ -446,12 +446,24 @@ function createLocalTask(value: unknown, options: LocalTaskCreateOptions): Local
446
446
  fs.writeFileSync(path.join(temporary, 'task.md'), rendered, { flag: 'wx', mode: 0o600 });
447
447
  const destination = path.join(activeRoot, taskId);
448
448
  fs.renameSync(temporary, destination);
449
- let shortId: string;
450
449
  const allocated = mutateShortIdRegistry(repoRoot, taskId, 'alloc');
451
450
  if (!allocated.shortId) throw new Error('TASK_CREATE_SHORT_ID_FAILED');
452
- shortId = allocated.shortId;
453
- return { status: 'applied', changed: true, task: { id: taskId, shortId, state: 'active' } };
451
+ return { status: 'applied', changed: true, task: { id: taskId, shortId: allocated.shortId, state: 'active' } };
452
+ }
453
+
454
+ return withCreateLock(repoRoot, projectConfig.project, () => {
455
+ let callbackError: unknown;
456
+ try {
457
+ return withRepositoryMutationLock(repoRoot, () => {
458
+ try { return createUnderRepositoryLock(); }
459
+ catch (error) { callbackError = error; throw error; }
460
+ });
461
+ } catch (error) {
462
+ if (callbackError !== undefined) throw callbackError;
463
+ throw error;
464
+ }
454
465
  });
466
+
455
467
  }
456
468
 
457
469
  export {
@@ -15,7 +15,10 @@ const COMPLETION_BACKFILL_FAMILIES = [
15
15
  'plan',
16
16
  'review-plan',
17
17
  'code',
18
- 'review-code'
18
+ 'review-code',
19
+ 'manual-validation',
20
+ 'validation-run',
21
+ 'pr-review'
19
22
  ] as const satisfies readonly ArtifactFamily[];
20
23
 
21
24
  type CompletionArtifactResult = {