@fitlab-ai/agent-infra 0.11.8 → 0.11.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +0 -6
  4. package/dist/lib/agent-clients/custom-tool-invocations.js +92 -0
  5. package/dist/lib/agent-clients/next-steps.js +1 -1
  6. package/dist/lib/agent-clients/reconcile.js +3 -3
  7. package/dist/lib/agent-clients/schema.js +18 -10
  8. package/dist/lib/git/workflow.js +1 -1
  9. package/dist/lib/init.js +2 -2
  10. package/dist/lib/internal/agent-client.js +3 -3
  11. package/dist/lib/internal/cli-route-inventory.js +0 -3
  12. package/dist/lib/internal/sandbox-control.js +30 -5
  13. package/dist/lib/internal/task-finalization.js +14 -23
  14. package/dist/lib/internal/task-operation-registry.js +2 -3
  15. package/dist/lib/internal/task-platform-recovery.js +9 -4
  16. package/dist/lib/platform/completion-backfill.js +2 -1
  17. package/dist/lib/platform/context.js +4 -2
  18. package/dist/lib/platform/github-client.js +20 -17
  19. package/dist/lib/platform/github-provider.js +42 -22
  20. package/dist/lib/platform/github-release-notes.js +4 -2
  21. package/dist/lib/platform/in-label-sync.js +2 -1
  22. package/dist/lib/platform/issue-comments.js +13 -9
  23. package/dist/lib/platform/operation-coordinator.js +1 -1
  24. package/dist/lib/platform/pr-review.js +2 -1
  25. package/dist/lib/platform/provider-bridge.js +4 -5
  26. package/dist/lib/platform/provider-contract.d.ts +3 -6
  27. package/dist/lib/platform/provider-contract.js +3 -15
  28. package/dist/lib/platform/provider-validation.js +9 -4
  29. package/dist/lib/platform/pull-requests.js +4 -2
  30. package/dist/lib/sandbox/control/client.js +54 -6
  31. package/dist/lib/sandbox/control/executor.js +8 -3
  32. package/dist/lib/sandbox/recovery.js +1 -22
  33. package/dist/lib/sandbox/removal.js +0 -1
  34. package/dist/lib/sandbox/tools.js +2 -0
  35. package/dist/lib/task/artifact-command.js +11 -1
  36. package/dist/lib/task/artifact-lifecycle.js +119 -62
  37. package/dist/lib/task/artifact-selection.js +1 -1
  38. package/dist/lib/task/capabilities.js +55 -21
  39. package/dist/lib/task/commands/status.js +0 -27
  40. package/dist/lib/task/commit-identity.js +22 -7
  41. package/dist/lib/task/commit-intent.js +67 -26
  42. package/dist/lib/task/commit-operation.js +19 -8
  43. package/dist/lib/task/completion-facts.js +32 -10
  44. package/dist/lib/task/control-authority.js +2 -4
  45. package/dist/lib/task/current-contract.js +1 -5
  46. package/dist/lib/task/events.js +8 -274
  47. package/dist/lib/task/execution-inputs.js +14 -0
  48. package/dist/lib/task/finalization-envelope.js +4 -0
  49. package/dist/lib/task/finalization.js +100 -129
  50. package/dist/lib/task/frontmatter.js +1 -1
  51. package/dist/lib/task/ledger-intents.js +4 -7
  52. package/dist/lib/task/orchestration.js +11 -10
  53. package/dist/lib/task/platform-operation-journal.js +17 -10
  54. package/dist/lib/task/platform-operation-recovery.js +95 -28
  55. package/dist/lib/task/review-fingerprint.js +4 -26
  56. package/dist/lib/task/verification-engine.js +95 -6
  57. package/dist/lib/task/verification.js +0 -1
  58. package/dist/lib/task/write.js +0 -11
  59. package/dist/lib/update.js +2 -2
  60. package/dist/lifecycle-build-manifest.json +3 -3
  61. package/lib/agent-clients/custom-tool-invocations.ts +129 -0
  62. package/lib/agent-clients/next-steps.ts +3 -3
  63. package/lib/agent-clients/reconcile.ts +3 -3
  64. package/lib/agent-clients/schema.ts +18 -10
  65. package/lib/git/workflow.ts +1 -1
  66. package/lib/init.ts +2 -2
  67. package/lib/internal/agent-client.ts +3 -3
  68. package/lib/internal/cli-route-inventory.ts +0 -2
  69. package/lib/internal/sandbox-control.ts +39 -6
  70. package/lib/internal/task-finalization.ts +16 -24
  71. package/lib/internal/task-operation-registry.ts +2 -3
  72. package/lib/internal/task-platform-recovery.ts +9 -4
  73. package/lib/platform/completion-backfill.ts +2 -1
  74. package/lib/platform/context.ts +8 -4
  75. package/lib/platform/github-client.ts +24 -20
  76. package/lib/platform/github-provider.ts +47 -26
  77. package/lib/platform/github-release-notes.ts +5 -3
  78. package/lib/platform/in-label-sync.ts +2 -1
  79. package/lib/platform/issue-comments.ts +14 -9
  80. package/lib/platform/operation-coordinator.ts +1 -1
  81. package/lib/platform/pr-review.ts +2 -1
  82. package/lib/platform/provider-bridge.ts +10 -9
  83. package/lib/platform/provider-contract.ts +6 -21
  84. package/lib/platform/provider-loader.ts +7 -6
  85. package/lib/platform/provider-validation.ts +30 -8
  86. package/lib/platform/pull-requests.ts +4 -2
  87. package/lib/platform/repository-metadata.ts +3 -2
  88. package/lib/platform/security-alerts.ts +1 -1
  89. package/lib/platform/types.ts +2 -0
  90. package/lib/sandbox/control/client.ts +57 -6
  91. package/lib/sandbox/control/executor.ts +8 -3
  92. package/lib/sandbox/recovery.ts +0 -25
  93. package/lib/sandbox/removal.ts +2 -10
  94. package/lib/sandbox/tool-types.ts +1 -0
  95. package/lib/sandbox/tools.ts +2 -0
  96. package/lib/task/artifact-command.ts +10 -1
  97. package/lib/task/artifact-lifecycle.ts +100 -52
  98. package/lib/task/artifact-selection.ts +1 -1
  99. package/lib/task/capabilities.ts +47 -24
  100. package/lib/task/commands/status.ts +0 -31
  101. package/lib/task/commit-identity.ts +24 -7
  102. package/lib/task/commit-intent.ts +58 -24
  103. package/lib/task/commit-operation.ts +17 -8
  104. package/lib/task/completion-facts.ts +31 -9
  105. package/lib/task/control-authority.ts +1 -4
  106. package/lib/task/current-contract.ts +1 -6
  107. package/lib/task/events.ts +8 -247
  108. package/lib/task/execution-inputs.ts +25 -0
  109. package/lib/task/finalization-envelope.ts +21 -0
  110. package/lib/task/finalization.ts +101 -123
  111. package/lib/task/frontmatter.ts +1 -1
  112. package/lib/task/ledger-intents.ts +4 -7
  113. package/lib/task/orchestration.ts +11 -17
  114. package/lib/task/platform-operation-journal.ts +18 -11
  115. package/lib/task/platform-operation-recovery.ts +90 -31
  116. package/lib/task/review-fingerprint.ts +4 -26
  117. package/lib/task/verification-engine.ts +103 -9
  118. package/lib/task/verification.ts +1 -2
  119. package/lib/task/write.ts +0 -19
  120. package/lib/update.ts +2 -3
  121. package/package.json +1 -1
  122. package/templates/.agents/README.en.md +2 -50
  123. package/templates/.agents/README.zh-CN.md +2 -50
  124. package/templates/.agents/rules/commit-and-pr.en.md +5 -5
  125. package/templates/.agents/rules/commit-and-pr.zh-CN.md +5 -5
  126. package/templates/.agents/rules/decision-qualification.en.md +1 -1
  127. package/templates/.agents/rules/decision-qualification.zh-CN.md +1 -1
  128. package/templates/.agents/rules/next-step-output.en.md +1 -1
  129. package/templates/.agents/rules/next-step-output.zh-CN.md +1 -1
  130. package/templates/.agents/scripts/lib/agent-infra-package.js +0 -7
  131. package/templates/.agents/skills/code-task/SKILL.en.md +25 -9
  132. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +26 -9
  133. package/templates/.agents/skills/code-task/reference/code-rules.en.md +1 -1
  134. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +1 -1
  135. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +5 -16
  136. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +5 -16
  137. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  138. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  139. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/complete-task/SKILL.en.md +15 -12
  142. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -10
  143. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
  144. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
  145. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/review-code/reference/output-templates.en.md +15 -6
  148. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +15 -6
  149. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
  150. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
  151. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +2 -268
  152. package/templates/.agents/templates/task.zh-CN.md +0 -12
  153. package/templates/.github/workflows/pr-label.yml +1 -2
  154. package/dist/lib/agent-clients/custom-tuis.js +0 -99
  155. package/dist/lib/internal/task-invalidation.js +0 -63
  156. package/dist/lib/task/intermediate-cleanup.js +0 -327
  157. package/dist/lib/task/invalidation-command.js +0 -88
  158. package/dist/lib/task/invalidation.js +0 -242
  159. package/lib/agent-clients/custom-tuis.ts +0 -149
  160. package/lib/internal/task-invalidation.ts +0 -41
  161. package/lib/task/intermediate-cleanup.ts +0 -393
  162. package/lib/task/invalidation-command.ts +0 -88
  163. package/lib/task/invalidation.ts +0 -313
package/README.md CHANGED
@@ -240,7 +240,7 @@ In-depth guides live under [`docs/en/`](./docs/en/README.md):
240
240
  - [Feishu Bridge](./docs/en/feishu-bridge.md) — configure the Feishu long-connection adapter, command routing, permissions, and TUI selection
241
241
  - [Built-in AI Skills](./docs/en/skills.md) — the full skill catalog by use case
242
242
  - [Custom Skills](./docs/en/custom-skills.md) — create and sync project-specific skills
243
- - [Custom TUI Configuration](./docs/en/custom-tui.md) — adapt agent-infra to non-built-in AI TUIs
243
+ - [Custom Tool Invocations](./docs/en/custom-tool-invocations.md) — configure custom-tool next-step commands
244
244
  - [Prebuilt Workflows](./docs/en/workflows.md) — the gated delivery lifecycle and example flow
245
245
  - [Configuration Reference](./docs/en/configuration.md) — `.agents/.airc.json`, external sources, version management
246
246
  - [File Management Strategies](./docs/en/file-management.md) — managed / merged / ejected update strategies
package/README.zh-CN.md CHANGED
@@ -239,7 +239,7 @@ my-project/
239
239
  - [飞书桥接](./docs/zh-CN/feishu-bridge.md) — 配置飞书长连接 adapter、命令路由、权限和 TUI 选择
240
240
  - [内置 AI Skills](./docs/zh-CN/skills.md) — 按使用场景分组的完整 skill 清单
241
241
  - [自定义 Skills](./docs/zh-CN/custom-skills.md) — 创建并同步项目专属 skill
242
- - [自定义 TUI 配置](./docs/zh-CN/custom-tui.md) — 适配非内置的 AI TUI
242
+ - [自定义工具调用](./docs/zh-CN/custom-tool-invocations.md) — 配置自定义工具的下一步命令
243
243
  - [预置工作流](./docs/zh-CN/workflows.md) — 分阶段交付链路与示例流程
244
244
  - [配置参考](./docs/zh-CN/configuration.md) — `.agents/.airc.json`、外部源、版本管理
245
245
  - [文件管理策略](./docs/zh-CN/file-management.md) — managed / merged / ejected 更新策略
@@ -13,7 +13,6 @@ const taskWorkflowCommand = command === 'task-artifact'
13
13
  || command === 'task-review'
14
14
  || command === 'task-event'
15
15
  || command === 'task-ledger'
16
- || command === 'task-invalidation'
17
16
  || command === 'task-warning';
18
17
  const taskPlatformRecoveryCommand = command === 'task-platform-recovery';
19
18
  const manualValidationWorkflowCommand = command === 'manual-validation';
@@ -231,11 +230,6 @@ if (!controlRouted && !taskViewGuardFailed && internalRouteRegistered)
231
230
  await taskEvent(process.argv.slice(3));
232
231
  break;
233
232
  }
234
- case 'task-invalidation': {
235
- const { taskInvalidation } = await import('../lib/internal/task-invalidation.js');
236
- taskInvalidation(process.argv.slice(3));
237
- break;
238
- }
239
233
  case 'task-lifecycle': {
240
234
  const { taskLifecycle } = await import('../lib/internal/task-lifecycle.js');
241
235
  await taskLifecycle(process.argv.slice(3));
@@ -0,0 +1,92 @@
1
+ const ALLOWED_PLACEHOLDERS = Object.freeze(['skillName', 'projectName']);
2
+ function isRecord(value) {
3
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
4
+ }
5
+ function isNonEmptySingleLine(value) {
6
+ return typeof value === 'string'
7
+ && value.trim() !== ''
8
+ && !/[\r\n]/.test(value);
9
+ }
10
+ function hasValidPlaceholders(invocation) {
11
+ const placeholders = [
12
+ ...invocation.matchAll(/\$\{([^}]+)\}/g)
13
+ ].map((match) => match[1]);
14
+ return placeholders.includes('skillName')
15
+ && placeholders.every((placeholder) => ALLOWED_PLACEHOLDERS.includes(placeholder))
16
+ && !invocation
17
+ .replaceAll('${skillName}', '')
18
+ .replaceAll('${projectName}', '')
19
+ .includes('${');
20
+ }
21
+ function normalizeCustomToolInvocations(config) {
22
+ const items = [];
23
+ const diagnostics = [];
24
+ const root = isRecord(config) ? config : {};
25
+ const sandbox = isRecord(root.sandbox) ? root.sandbox : {};
26
+ const sandboxTools = isRecord(sandbox.tools) ? sandbox.tools : { ids: ['agent-infra'] };
27
+ const definitions = sandboxTools.definitions ?? {};
28
+ const selectedIds = sandboxTools.ids;
29
+ if (!Array.isArray(selectedIds)) {
30
+ return Object.freeze({
31
+ items: Object.freeze([]),
32
+ diagnostics: Object.freeze([
33
+ Object.freeze({
34
+ code: 'INVALID_CUSTOM_TOOL_CONFIG',
35
+ path: 'sandbox.tools.ids'
36
+ })
37
+ ])
38
+ });
39
+ }
40
+ if (!isRecord(definitions)) {
41
+ return Object.freeze({
42
+ items: Object.freeze([]),
43
+ diagnostics: Object.freeze([
44
+ Object.freeze({
45
+ code: 'INVALID_CUSTOM_TOOL_CONFIG',
46
+ path: 'sandbox.tools.definitions'
47
+ })
48
+ ])
49
+ });
50
+ }
51
+ for (const [index, id] of selectedIds.entries()) {
52
+ if (typeof id !== 'string') {
53
+ diagnostics.push({
54
+ code: 'INVALID_CUSTOM_TOOL_INVOCATION',
55
+ path: `sandbox.tools.ids[${index}]`
56
+ });
57
+ continue;
58
+ }
59
+ const candidate = definitions[id];
60
+ if (!isRecord(candidate))
61
+ continue;
62
+ if (candidate.invoke === undefined)
63
+ continue;
64
+ const base = `sandbox.tools.definitions.${id}`;
65
+ const name = candidate.name === undefined ? id : candidate.name;
66
+ if (!isNonEmptySingleLine(name)) {
67
+ diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.name` });
68
+ continue;
69
+ }
70
+ if (!isNonEmptySingleLine(candidate.invoke)) {
71
+ diagnostics.push({ code: 'INVALID_CUSTOM_TOOL_INVOCATION', path: `${base}.invoke` });
72
+ continue;
73
+ }
74
+ if (!hasValidPlaceholders(candidate.invoke)) {
75
+ diagnostics.push({
76
+ code: 'INVALID_CUSTOM_TOOL_INVOCATION_PLACEHOLDER',
77
+ path: `${base}.invoke`
78
+ });
79
+ continue;
80
+ }
81
+ items.push(Object.freeze({
82
+ name,
83
+ invocation: candidate.invoke
84
+ }));
85
+ }
86
+ return Object.freeze({
87
+ items: Object.freeze(items),
88
+ diagnostics: Object.freeze(diagnostics.map((diagnostic) => Object.freeze(diagnostic)))
89
+ });
90
+ }
91
+ export { normalizeCustomToolInvocations };
92
+ //# sourceMappingURL=custom-tool-invocations.js.map
@@ -37,7 +37,7 @@ function renderNextStepCommands(input) {
37
37
  args
38
38
  })
39
39
  }));
40
- const custom = input.customTUIs.map((tool) => Object.freeze({
40
+ const custom = input.customToolInvocations.map((tool) => Object.freeze({
41
41
  source: 'custom',
42
42
  displayName: tool.name,
43
43
  command: renderAgentClientInvocation(tool.invocation, {
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import defaults from '../defaults.json' with { type: 'json' };
5
5
  import { normalizeAgentClients, serializeAgentClients } from './config.js';
6
- import { normalizeCustomTUIs } from './custom-tuis.js';
6
+ import { normalizeCustomToolInvocations } from './custom-tool-invocations.js';
7
7
  import { renderNextStepCommands } from './next-steps.js';
8
8
  import { planAgentClientProjectAssets } from './project-assets.js';
9
9
  import { listAgentClientAdapters, listEnabledAgentClientAdapters } from './registry.js';
@@ -141,11 +141,11 @@ function planAgentClientReconciliation(input) {
141
141
  if (Object.keys(baselines).length === 0) {
142
142
  delete nextConfig.files.managedBaselines;
143
143
  }
144
- const custom = normalizeCustomTUIs(input.projectRoot, input.config.customTUIs ?? []);
144
+ const custom = normalizeCustomToolInvocations(input.config);
145
145
  const nextSteps = renderNextStepCommands({
146
146
  projectName,
147
147
  state: desired,
148
- customTUIs: custom.items,
148
+ customToolInvocations: custom.items,
149
149
  skillName: 'update-agent-infra'
150
150
  });
151
151
  const actionableSeed = seedOperations.some((operation) => operation.kind === 'write' || operation.kind === 'remove');
@@ -38,16 +38,24 @@ const AGENT_CLIENTS_SCHEMA = {
38
38
  }
39
39
  }))
40
40
  },
41
- customTUIs: {
42
- type: 'array',
43
- items: {
44
- type: 'object',
45
- additionalProperties: true,
46
- required: ['name', 'dir', 'invoke'],
47
- properties: {
48
- name: { type: 'string' },
49
- dir: { type: 'string' },
50
- invoke: { type: 'string' }
41
+ sandbox: {
42
+ type: 'object',
43
+ properties: {
44
+ tools: {
45
+ type: 'object',
46
+ properties: {
47
+ ids: { type: 'array', items: { type: 'string' } },
48
+ definitions: {
49
+ type: 'object',
50
+ additionalProperties: {
51
+ type: 'object',
52
+ properties: {
53
+ name: { type: 'string' },
54
+ invoke: { type: 'string' }
55
+ }
56
+ }
57
+ }
58
+ }
51
59
  }
52
60
  }
53
61
  }
@@ -103,7 +103,7 @@ function commitExplicitPaths(input, runner = defaultRunner) {
103
103
  if (tree !== input.expectedTree)
104
104
  return { status: 'failed', changed: false, snapshot: inspectGitWorkflow(input.cwd, runner).snapshot, operations: [{ name: 'stage', status: 'applied' }], error: { code: 'GIT_TREE_MISMATCH', message: `Expected staged tree ${input.expectedTree}, received ${tree ?? 'unavailable'}` } };
105
105
  }
106
- const commit = run(runner, input.cwd, ['commit', '-m', input.message]);
106
+ const commit = run(runner, input.cwd, ['commit', '--cleanup=verbatim', '-m', input.message]);
107
107
  const after = inspectGitWorkflow(input.cwd, runner);
108
108
  if (commit.status !== 0)
109
109
  return { status: 'failed', changed: false, snapshot: after.snapshot, operations: [{ name: 'commit', status: 'failed', message: commit.stderr.trim() }], error: { code: 'GIT_COMMIT_FAILED', message: commit.stderr.trim() } };
package/dist/lib/init.js CHANGED
@@ -228,8 +228,8 @@ async function cmdInit() {
228
228
  console.log(' npm install -g @fitlab-ai/agent-infra');
229
229
  console.log('');
230
230
  if (workflowPlan.nextSteps.length === 0) {
231
- console.log(' No Agent Client project integration enabled.');
232
- console.log(` Configure "customTUIs" in ${configPath} before running update-agent-infra.`);
231
+ console.log(' No Agent Client or sandbox custom tool integration enabled.');
232
+ console.log(` Configure a custom tool in "sandbox.tools.definitions" and select it in "sandbox.tools.ids" in ${configPath}.`);
233
233
  console.log('');
234
234
  }
235
235
  else {
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { AgentClientConfigError, normalizeAgentClients } from '../agent-clients/config.js';
4
- import { normalizeCustomTUIs } from '../agent-clients/custom-tuis.js';
4
+ import { normalizeCustomToolInvocations } from '../agent-clients/custom-tool-invocations.js';
5
5
  import { renderNextStepCommands } from '../agent-clients/next-steps.js';
6
6
  import { getAgentClientModelSelection } from '../agent-clients/registry.js';
7
7
  import { isAgentClientId } from '../agent-clients/types.js';
@@ -139,11 +139,11 @@ function agentClient(args = []) {
139
139
  }
140
140
  try {
141
141
  const clients = normalizeAgentClients(config);
142
- const custom = normalizeCustomTUIs(process.cwd(), config.customTUIs ?? []);
142
+ const custom = normalizeCustomToolInvocations(config);
143
143
  const commands = renderNextStepCommands({
144
144
  projectName: String(config.project ?? ''),
145
145
  state: clients.state,
146
- customTUIs: custom.items,
146
+ customToolInvocations: custom.items,
147
147
  skillName: parsed.skillName,
148
148
  ...(parsed.taskRef ? { taskRef: parsed.taskRef } : {}),
149
149
  ...(parsed.version ? { version: parsed.version } : {})
@@ -57,7 +57,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
57
57
  'task-orchestration': ['status', 'progress'],
58
58
  'task-review': ['preflight', 'finalize-summary'],
59
59
  'task-event': ['event'],
60
- 'task-invalidation': ['reconcile'],
61
60
  'task-lifecycle': ['intent'],
62
61
  'task-finalization': ['complete'],
63
62
  'task-platform-recovery': ['inspect', 'recover'],
@@ -105,8 +104,6 @@ export function internalRouteSelector(command, args) {
105
104
  return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
106
105
  if (command === 'task-review')
107
106
  return args[1] ?? '';
108
- if (command === 'task-invalidation')
109
- return args[1] ?? '';
110
107
  if (command === 'task-artifact')
111
108
  return args[1] ?? '';
112
109
  if (command === 'platform-pr-review' && first(args) === 'publish') {
@@ -4,8 +4,9 @@ import { serveSandboxControl } from '../sandbox/control/server.js';
4
4
  import { runSandboxControlExecutor } from '../sandbox/control/executor.js';
5
5
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
6
6
  import { parseTaskCreateResult, taskCreateExitCode } from '../task/create-service.js';
7
- function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null) {
8
- process.stdout.write(`${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`);
7
+ import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.js';
8
+ function writeFinalizationEnvelope(status, accepted, error, changed = false, result = null, requestId = null) {
9
+ process.stdout.write(serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error }));
9
10
  }
10
11
  function finalizationErrorStatus(error) {
11
12
  if (error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN')
@@ -30,6 +31,28 @@ function recoveredExitCode(response) {
30
31
  }
31
32
  return response.exitCode ?? 1;
32
33
  }
34
+ function finalizationResponseOutput(response) {
35
+ try {
36
+ const value = JSON.parse(response.stdout);
37
+ if (value.version === 2
38
+ && value.status
39
+ && ['completed', 'failed', 'blocked', 'unknown'].includes(value.status)
40
+ && (typeof value.changed === 'boolean' || value.changed === null)) {
41
+ return serializeTaskFinalizationEnvelope({
42
+ status: value.status,
43
+ changed: value.changed,
44
+ accepted: true,
45
+ requestId: response.id,
46
+ result: value.result ?? null,
47
+ error: value.error ?? null
48
+ });
49
+ }
50
+ }
51
+ catch {
52
+ // Preserve non-envelope output for diagnostics from the executor.
53
+ }
54
+ return response.stdout;
55
+ }
33
56
  function sandboxFinalizationClient(args) {
34
57
  if (args.length !== 4 || !args[0] || args[1] !== 'complete' || args[2] !== '--agent' || !args[3]) {
35
58
  const error = { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'task ref, complete intent, and --agent are required', retryable: false };
@@ -52,7 +75,8 @@ function sandboxFinalizationClient(args) {
52
75
  catch (error) {
53
76
  if (!(error instanceof SandboxControlClientError))
54
77
  throw error;
55
- writeFinalizationEnvelope(finalizationErrorStatus(error.detail), error.accepted, error.detail);
78
+ const status = finalizationErrorStatus(error.detail);
79
+ writeFinalizationEnvelope(status, error.accepted, error.detail, status === 'unknown' ? null : false, null, error.requestId);
56
80
  writeClientError(error);
57
81
  process.exitCode = error.detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.detail.retryable ? 2 : 1;
58
82
  return;
@@ -63,12 +87,13 @@ function sandboxFinalizationClient(args) {
63
87
  message: 'sandbox control rejected the finalization request',
64
88
  retryable: false
65
89
  };
66
- writeFinalizationEnvelope(finalizationErrorStatus(error), error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error);
90
+ const status = finalizationErrorStatus(error);
91
+ writeFinalizationEnvelope(status, error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN', error, status === 'unknown' ? null : false, null, response.id);
67
92
  process.stderr.write(`${error.message}\n`);
68
93
  process.exitCode = error.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN' ? 1 : error.retryable ? 2 : 1;
69
94
  return;
70
95
  }
71
- process.stdout.write(response.stdout);
96
+ process.stdout.write(finalizationResponseOutput(response));
72
97
  process.stderr.write(response.stderr);
73
98
  process.exitCode = response.exitCode ?? 1;
74
99
  }
@@ -1,14 +1,13 @@
1
1
  import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../task/control-authority.js';
2
- import { applyTaskFinalization, bindTaskFinalizationReceipt, prepareTaskFinalization } from '../task/finalization.js';
3
- import { publishTaskFinalizationHandoff } from '../task/finalization-handoff.js';
2
+ import { applyTaskFinalization } from '../task/finalization.js';
4
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
5
- import { verifyTaskEvent } from '../task/verification.js';
6
4
  import { resolveSandboxControlTransport } from './task-operation-registry.js';
7
5
  import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.js';
6
+ import { serializeTaskFinalizationEnvelope } from '../task/finalization-envelope.js';
8
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
9
8
  const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
10
- function envelope(status, changed, accepted, result, error) {
11
- return `${JSON.stringify({ version: 1, status, changed, accepted, result, error })}\n`;
9
+ function envelope(status, changed, accepted, result, error, requestId = null) {
10
+ return serializeTaskFinalizationEnvelope({ status, changed, accepted, requestId, result, error });
12
11
  }
13
12
  function exitCode(status) {
14
13
  return status === 'completed' ? 0 : status === 'blocked' ? 2 : 1;
@@ -65,22 +64,9 @@ async function taskFinalization(args = []) {
65
64
  };
66
65
  const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
67
66
  if (transport.kind === 'sandbox-local') {
68
- const prepared = await prepareTaskFinalization(boundOperation.request, {
69
- repoRoot,
70
- preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
71
- });
72
- if (prepared.status !== 'prepared') {
73
- process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
74
- process.exitCode = exitCode(prepared.status);
75
- return;
76
- }
77
67
  try {
78
68
  const response = requestSandboxTaskFinalization({
79
- agent: boundOperation.request.agent,
80
- prepareHandoff: (binding) => {
81
- const receipt = bindTaskFinalizationReceipt(repoRoot, boundOperation.request.taskRef, binding);
82
- return publishTaskFinalizationHandoff('/share/branch', receipt, binding);
83
- }
69
+ agent: boundOperation.request.agent
84
70
  });
85
71
  process.stdout.write(response.stdout);
86
72
  process.stderr.write(response.stderr);
@@ -88,11 +74,16 @@ async function taskFinalization(args = []) {
88
74
  return;
89
75
  }
90
76
  catch (error) {
91
- const detail = error instanceof SandboxControlClientError
92
- ? error.detail
77
+ const clientError = error instanceof SandboxControlClientError ? error : null;
78
+ const detail = clientError
79
+ ? clientError.detail
93
80
  : { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
94
- process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
95
- process.exitCode = detail.retryable ? 2 : 1;
81
+ const status = detail.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN'
82
+ ? 'unknown'
83
+ : detail.retryable ? 'blocked' : 'failed';
84
+ const changed = status === 'unknown' ? null : false;
85
+ process.stdout.write(envelope(status, changed, clientError?.accepted ?? false, null, detail, clientError?.requestId ?? null));
86
+ process.exitCode = exitCode(status);
96
87
  return;
97
88
  }
98
89
  }
@@ -102,9 +102,8 @@ function internalTaskRoutes() {
102
102
  descriptor('internal', 'task-review', 'preflight', 'task-bound', 'artifact-write'),
103
103
  descriptor('internal', 'task-review', 'finalize-summary', 'task-bound', 'progress'),
104
104
  descriptor('internal', 'task-event', 'event', 'task-bound', 'progress'),
105
- descriptor('internal', 'task-invalidation', 'reconcile', 'task-bound', 'progress'),
106
105
  descriptor('internal', 'task-lifecycle', 'intent', 'task-bound', 'progress'),
107
- descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'progress'),
106
+ descriptor('internal', 'task-finalization', 'complete', 'task-bound', 'recovery'),
108
107
  descriptor('internal', 'task-platform-recovery', 'inspect', 'task-bound', 'diagnostic'),
109
108
  descriptor('internal', 'task-platform-recovery', 'recover', 'task-bound', 'recovery'),
110
109
  descriptor('internal', 'task-short-id', 'list', 'conditional', 'diagnostic'),
@@ -179,7 +178,7 @@ export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
179
178
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
180
179
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
181
180
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
182
- 'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
181
+ 'task-orchestration', 'task-review', 'task-event', 'task-lifecycle',
183
182
  'task-finalization', 'task-platform-recovery', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
184
183
  ]);
185
184
  export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
@@ -2,7 +2,7 @@ import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
2
2
  import { readPlatformOperationJournal } from '../task/platform-operation-journal.js';
3
3
  import { recoverPlatformOperations } from '../task/platform-operation-recovery.js';
4
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';
5
+ const USAGE = 'Usage: agent-infra-internal task-platform-recovery <N | TASK-id> <inspect | recover> [--agent <agent>] [--selection required|deferred|all] [--attempts <positive-integer>]\n';
6
6
  function option(args, name) {
7
7
  const index = args.indexOf(name);
8
8
  return index >= 0 ? args[index + 1] : undefined;
@@ -41,14 +41,19 @@ async function taskPlatformRecovery(args = []) {
41
41
  }
42
42
  const agent = option(args, '--agent');
43
43
  const selection = option(args, '--selection') ?? 'all';
44
+ const attemptsInput = option(args, '--attempts');
45
+ const attempts = attemptsInput === undefined ? undefined : Number(attemptsInput);
46
+ const validAttempts = attemptsInput === undefined
47
+ || (attempts !== undefined && Number.isSafeInteger(attempts) && attempts > 0);
44
48
  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`);
49
+ || !['required', 'deferred', 'all'].includes(selection)
50
+ || !validAttempts) {
51
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'recover requires a valid --agent, --selection, and positive --attempts when supplied' } })}\n`);
47
52
  process.stderr.write(USAGE);
48
53
  process.exitCode = 1;
49
54
  return;
50
55
  }
51
- const result = await recoverPlatformOperations(task.taskId, selection, { agent, cwd: repoRoot });
56
+ const result = await recoverPlatformOperations(task.taskId, selection, { agent, cwd: repoRoot, ...(attempts === undefined ? {} : { attempts }) });
52
57
  process.stdout.write(`${JSON.stringify(result)}\n`);
53
58
  process.exitCode = result.status === 'applied' || result.status === 'no-op' ? 0 : result.status === 'blocked' ? 2 : 1;
54
59
  }
@@ -8,6 +8,7 @@ import { getOpenWorkflowWarnings } from '../task/workflow-warnings.js';
8
8
  import { applyWorkflowWarningIntent } from '../task/workflow-warning-intents.js';
9
9
  import { captureTaskWriteMetadata } from '../task/write.js';
10
10
  import { syncPlatformComment } from './issue-comments.js';
11
+ import { providerErrorRetryable } from './provider-validation.js';
11
12
  import { platformResult } from './types.js';
12
13
  import { taskIssueIdentity } from './task-identities.js';
13
14
  function result(base, artifacts, warnings) {
@@ -124,7 +125,7 @@ async function backfillCompletionComments(taskRef, options) {
124
125
  if (latest.status === 'failed' || latest.status === 'blocked')
125
126
  return result({ ...latest, operations }, artifacts, []);
126
127
  if (latest.error)
127
- return result(platformResult(latest.error.retryable ? 'blocked' : 'failed', {
128
+ return result(platformResult(latest.error.retryable || providerErrorRetryable(latest.error.code) ? 'blocked' : 'failed', {
128
129
  platform: latest.platform,
129
130
  resource: latest.resource,
130
131
  capabilities: latest.capabilities,
@@ -1,18 +1,20 @@
1
1
  import path from 'node:path';
2
2
  import { loadPlatformProvider } from './provider-loader.js';
3
3
  import { defaultGitRemote, parseGitHubRemote } from './github-provider.js';
4
+ import { providerErrorRetryable } from './provider-validation.js';
4
5
  import { platformResult } from './types.js';
5
6
  function errorStatus(error) {
6
- if (error.retryable || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING')
7
+ if (error.retryable || providerErrorRetryable(error.code) || error.code === 'AUTH_REQUIRED' || error.code === 'PLATFORM_DEPENDENCY_MISSING')
7
8
  return 'blocked';
8
9
  if (error.code === 'REMOTE_MISSING' || error.code === 'PLATFORM_UNSUPPORTED')
9
10
  return 'no-op';
10
11
  return 'failed';
11
12
  }
12
13
  function contextError(providerType, error, repository = null) {
14
+ const retryable = error.retryable ?? providerErrorRetryable(error.code);
13
15
  return platformResult(errorStatus(error), {
14
16
  platform: { type: providerType || null, repository, currentUser: null },
15
- error: { code: error.code, message: error.message, retryable: error.retryable }
17
+ error: { code: error.code, message: error.message, retryable }
16
18
  });
17
19
  }
18
20
  function contextFromSnapshot(providerType, snapshot) {
@@ -59,30 +59,33 @@ function classifyGitHubFailure(result) {
59
59
  return {
60
60
  code: 'PLATFORM_OUTPUT_TOO_LARGE',
61
61
  message: 'GitHub CLI output exceeded the configured limit',
62
- retryable: false
62
+ requestRetryable: false
63
63
  };
64
64
  }
65
65
  const detail = boundedFailureDetail(result);
66
66
  const lower = detail.toLowerCase();
67
67
  if (/\b401\b|bad credentials|authentication required|not logged into/.test(lower)) {
68
- return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', retryable: false };
68
+ return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', requestRetryable: false };
69
69
  }
70
70
  if (/\b429\b|rate limit|secondary rate|\b5\d\d\b|timeout|timed out|econnreset|enotfound|dns|tls|socket|network/.test(lower)) {
71
- return { code: 'NETWORK_TRANSIENT', message: detail || 'GitHub request failed temporarily', retryable: true };
71
+ return { code: 'NETWORK_TRANSIENT', message: detail || 'GitHub request failed temporarily', requestRetryable: true };
72
72
  }
73
73
  if (/\b403\b|resource not accessible|permission denied/.test(lower)) {
74
- return { code: 'PERMISSION_DENIED', message: detail || 'GitHub permission denied', retryable: false };
74
+ return { code: 'PERMISSION_DENIED', message: detail || 'GitHub permission denied', requestRetryable: false };
75
75
  }
76
76
  if (/\b404\b/.test(lower)) {
77
- return { code: 'RESOURCE_NOT_FOUND', message: detail || 'GitHub resource not found', retryable: false };
77
+ return { code: 'RESOURCE_NOT_FOUND', message: detail || 'GitHub resource not found', requestRetryable: false };
78
78
  }
79
79
  if (/\b422\b|validation failed/.test(lower)) {
80
- return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', retryable: false };
80
+ return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', requestRetryable: false };
81
81
  }
82
82
  if (errorCode === 'ENOENT') {
83
- return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', retryable: false };
83
+ return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', requestRetryable: false };
84
84
  }
85
- return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', retryable: false };
85
+ return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', requestRetryable: false };
86
+ }
87
+ function clientError(error) {
88
+ return { code: error.code, message: error.message };
86
89
  }
87
90
  function responseUrl(args) {
88
91
  const endpoint = args.find((arg, index) => index > 0 && !arg.startsWith('-')) || '';
@@ -132,14 +135,14 @@ function createGitHubClient(options = {}) {
132
135
  if (result.status === 0)
133
136
  return { ok: true, value: result.stdout };
134
137
  const error = classifyGitHubFailure(result);
135
- if (!retryableMethod || !error.retryable)
136
- return { ok: false, error };
138
+ if (!retryableMethod || !error.requestRetryable)
139
+ return { ok: false, error: clientError(error) };
137
140
  if (attempt >= delays.length) {
138
141
  return {
139
142
  ok: false,
140
143
  error: error.code === 'NETWORK_TRANSIENT'
141
- ? { ...error, code: 'NETWORK_RETRY_EXHAUSTED', message: `${error.message} (retry exhausted)` }
142
- : error
144
+ ? { ...clientError(error), code: 'NETWORK_RETRY_EXHAUSTED', message: `${error.message} (retry exhausted)` }
145
+ : clientError(error)
143
146
  };
144
147
  }
145
148
  sleep(delays[attempt]);
@@ -150,12 +153,12 @@ function createGitHubClient(options = {}) {
150
153
  version(request = {}) {
151
154
  const result = runner(['--version'], request);
152
155
  if (result.status !== 0)
153
- return { ok: false, error: classifyGitHubFailure(result) };
156
+ return { ok: false, error: clientError(classifyGitHubFailure(result)) };
154
157
  const version = result.stdout.match(/^gh version (\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\s|$)/m)?.[1];
155
158
  if (!version || !semver.valid(version)) {
156
159
  return {
157
160
  ok: false,
158
- error: { code: 'GH_CLI_VERSION_INVALID', message: 'GitHub CLI returned an invalid version', retryable: false }
161
+ error: { code: 'GH_CLI_VERSION_INVALID', message: 'GitHub CLI returned an invalid version' }
159
162
  };
160
163
  }
161
164
  return { ok: true, value: version };
@@ -174,7 +177,7 @@ function createGitHubClient(options = {}) {
174
177
  catch {
175
178
  return {
176
179
  ok: false,
177
- error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub returned invalid JSON', retryable: true }
180
+ error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub returned invalid JSON' }
178
181
  };
179
182
  }
180
183
  },
@@ -189,12 +192,12 @@ function createGitHubClient(options = {}) {
189
192
  if (!parsed) {
190
193
  return {
191
194
  ok: false,
192
- error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub response metadata or JSON is invalid', retryable: true }
195
+ error: { code: 'INVALID_PLATFORM_RESPONSE', message: 'GitHub response metadata or JSON is invalid' }
193
196
  };
194
197
  }
195
198
  return { ok: true, value: parsed };
196
199
  }
197
200
  };
198
201
  }
199
- export { classifyGitHubFailure, createGitHubClient, MINIMUM_GITHUB_CLI_VERSION, parseIncludedResponse };
202
+ export { createGitHubClient, MINIMUM_GITHUB_CLI_VERSION, parseIncludedResponse };
200
203
  //# sourceMappingURL=github-client.js.map