@fitlab-ai/agent-infra 0.11.10 → 0.11.12

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 (219) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +27 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +222 -210
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex-sandbox.js +13 -0
  17. package/dist/lib/agent-clients/adapters/codex.js +44 -1
  18. package/dist/lib/agent-clients/adapters/index.js +2 -10
  19. package/dist/lib/agent-clients/tokens.js +3 -2
  20. package/dist/lib/internal/agent-client.js +132 -72
  21. package/dist/lib/internal/cli-route-inventory.js +2 -2
  22. package/dist/lib/internal/manual-validation.js +2 -2
  23. package/dist/lib/internal/platform-pr.js +4 -5
  24. package/dist/lib/internal/task-artifact.js +1 -1
  25. package/dist/lib/internal/task-create.js +15 -1
  26. package/dist/lib/internal/task-finalization.js +1 -3
  27. package/dist/lib/internal/task-operation-registry.js +5 -139
  28. package/dist/lib/internal/task-validate.js +1 -1
  29. package/dist/lib/platform/issues.js +18 -6
  30. package/dist/lib/platform/pr-summary.js +45 -8
  31. package/dist/lib/platform/pull-requests.js +79 -14
  32. package/dist/lib/platform/verification-sync.js +17 -14
  33. package/dist/lib/runtime/agent-runtime.js +6 -29
  34. package/dist/lib/sandbox/commands/control-status.js +1 -0
  35. package/dist/lib/sandbox/commands/create.js +19 -2
  36. package/dist/lib/sandbox/commands/enter.js +47 -33
  37. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  38. package/dist/lib/sandbox/control/client.js +68 -161
  39. package/dist/lib/sandbox/control/executor.js +13 -87
  40. package/dist/lib/sandbox/control/host-recovery.js +218 -0
  41. package/dist/lib/sandbox/control/protocol.js +15 -44
  42. package/dist/lib/sandbox/control/server.js +57 -51
  43. package/dist/lib/sandbox/environment.js +150 -0
  44. package/dist/lib/sandbox/mounts.js +2 -1
  45. package/dist/lib/sandbox/recovery.js +11 -1
  46. package/dist/lib/sandbox/removal.js +9 -5
  47. package/dist/lib/sandbox/workspace-identity.js +0 -14
  48. package/dist/lib/sandbox/workspace-view.js +41 -23
  49. package/dist/lib/task/artifact-command.js +7 -2
  50. package/dist/lib/task/artifact-lifecycle.js +2 -1
  51. package/dist/lib/task/capabilities.js +4 -1
  52. package/dist/lib/task/control-authority.js +54 -266
  53. package/dist/lib/task/control-recovery.js +1 -1
  54. package/dist/lib/task/create.js +2 -2
  55. package/dist/lib/task/delegation-receipts.js +74 -161
  56. package/dist/lib/task/events.js +89 -45
  57. package/dist/lib/task/finalization.js +58 -60
  58. package/dist/lib/task/frontmatter.js +24 -1
  59. package/dist/lib/task/ledger-intents.js +2 -1
  60. package/dist/lib/task/lifecycle.js +84 -97
  61. package/dist/lib/task/orchestration.js +182 -129
  62. package/dist/lib/task/platform-operation-journal.js +12 -0
  63. package/dist/lib/task/platform-operation-recovery.js +14 -7
  64. package/dist/lib/task/resolve-ref.js +8 -3
  65. package/dist/lib/task/runtime-paths.js +10 -0
  66. package/dist/lib/task/verification-engine.js +28 -15
  67. package/dist/lib/task/workspace-snapshot.js +1 -1
  68. package/dist/lifecycle-build-manifest.json +3 -3
  69. package/lib/agent-clients/adapter.ts +61 -0
  70. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  71. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  72. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  73. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  74. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  75. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  76. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  77. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  78. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  80. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  81. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  82. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  83. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +272 -223
  84. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  85. package/lib/agent-clients/adapters/codex-sandbox.ts +13 -0
  86. package/lib/agent-clients/adapters/codex.ts +48 -1
  87. package/lib/agent-clients/adapters/index.ts +7 -20
  88. package/lib/agent-clients/tokens.ts +4 -3
  89. package/lib/internal/agent-client.ts +142 -73
  90. package/lib/internal/cli-route-inventory.ts +2 -2
  91. package/lib/internal/manual-validation.ts +2 -2
  92. package/lib/internal/platform-pr.ts +3 -4
  93. package/lib/internal/task-artifact.ts +1 -1
  94. package/lib/internal/task-create.ts +15 -2
  95. package/lib/internal/task-finalization.ts +1 -3
  96. package/lib/internal/task-operation-registry.ts +5 -151
  97. package/lib/internal/task-validate.ts +1 -1
  98. package/lib/platform/issues.ts +18 -6
  99. package/lib/platform/pr-summary.ts +43 -7
  100. package/lib/platform/pull-requests.ts +74 -13
  101. package/lib/platform/verification-sync.ts +18 -14
  102. package/lib/runtime/agent-runtime.ts +6 -41
  103. package/lib/sandbox/commands/control-status.ts +1 -0
  104. package/lib/sandbox/commands/create.ts +25 -2
  105. package/lib/sandbox/commands/enter.ts +42 -39
  106. package/lib/sandbox/control/agent-operation.ts +49 -0
  107. package/lib/sandbox/control/client.ts +86 -210
  108. package/lib/sandbox/control/executor.ts +17 -96
  109. package/lib/sandbox/control/host-recovery.ts +195 -0
  110. package/lib/sandbox/control/protocol.ts +24 -55
  111. package/lib/sandbox/control/server.ts +54 -52
  112. package/lib/sandbox/environment.ts +162 -0
  113. package/lib/sandbox/mounts.ts +2 -1
  114. package/lib/sandbox/recovery.ts +13 -1
  115. package/lib/sandbox/removal.ts +12 -5
  116. package/lib/sandbox/tool-types.ts +1 -0
  117. package/lib/sandbox/workspace-identity.ts +0 -26
  118. package/lib/sandbox/workspace-view.ts +42 -24
  119. package/lib/task/artifact-command.ts +9 -2
  120. package/lib/task/artifact-lifecycle.ts +2 -1
  121. package/lib/task/capabilities.ts +5 -2
  122. package/lib/task/control-authority.ts +65 -382
  123. package/lib/task/control-recovery.ts +2 -2
  124. package/lib/task/create.ts +3 -3
  125. package/lib/task/delegation-receipts.ts +87 -231
  126. package/lib/task/events.ts +101 -43
  127. package/lib/task/finalization.ts +57 -58
  128. package/lib/task/frontmatter.ts +25 -1
  129. package/lib/task/ledger-intents.ts +2 -1
  130. package/lib/task/lifecycle.ts +82 -87
  131. package/lib/task/orchestration.ts +237 -176
  132. package/lib/task/platform-operation-journal.ts +29 -2
  133. package/lib/task/platform-operation-recovery.ts +12 -6
  134. package/lib/task/resolve-ref.ts +8 -3
  135. package/lib/task/runtime-paths.ts +12 -0
  136. package/lib/task/verification-engine.ts +27 -16
  137. package/lib/task/workspace-snapshot.ts +1 -1
  138. package/package.json +1 -1
  139. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  140. package/templates/.agents/rules/README.en.md +2 -2
  141. package/templates/.agents/rules/README.zh-CN.md +2 -2
  142. package/templates/.agents/rules/issue-sync.en.md +1 -1
  143. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  144. package/templates/.agents/rules/next-step-output.en.md +6 -19
  145. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  146. package/templates/.agents/rules/no-mid-flow-questions.en.md +19 -0
  147. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +19 -0
  148. package/templates/.agents/rules/review-handshake.en.md +3 -0
  149. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -0
  150. package/templates/.agents/rules/task-management.en.md +1 -1
  151. package/templates/.agents/rules/task-management.zh-CN.md +1 -1
  152. package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
  153. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  154. package/templates/.agents/skills/block-task/SKILL.en.md +1 -1
  155. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
  157. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  158. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  159. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  160. package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -0
  161. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -0
  162. package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -0
  163. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -0
  164. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  165. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  166. package/templates/.agents/skills/code-task/reference/branch-management.en.md +1 -1
  167. package/templates/.agents/skills/code-task/reference/branch-management.zh-CN.md +1 -1
  168. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +2 -0
  169. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +2 -0
  170. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +1 -1
  171. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +1 -1
  172. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -2
  173. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -2
  174. package/templates/.agents/skills/complete-task/SKILL.en.md +0 -3
  175. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +0 -3
  176. package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
  177. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  178. package/templates/.agents/skills/create-pr/reference/branch-strategy.en.md +2 -2
  179. package/templates/.agents/skills/create-pr/reference/branch-strategy.zh-CN.md +2 -2
  180. package/templates/.agents/skills/create-task/SKILL.en.md +3 -5
  181. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +3 -4
  182. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  183. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  184. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/plan-task/SKILL.en.md +1 -1
  189. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -1
  190. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  191. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  192. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  193. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  194. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  195. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  196. package/templates/.agents/skills/review-pr/SKILL.en.md +1 -1
  197. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -1
  198. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  199. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  200. package/templates/.agents/skills/test/SKILL.en.md +4 -1
  201. package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -1
  202. package/templates/.agents/skills/test-integration/SKILL.en.md +5 -2
  203. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +5 -2
  204. package/templates/.agents/skills/update-agent-infra/SKILL.en.md +4 -1
  205. package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +4 -1
  206. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +5 -2
  207. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +5 -2
  208. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  211. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  212. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  213. package/dist/lib/internal/codex-lifecycle.js +0 -385
  214. package/dist/lib/task/codex-orchestration.js +0 -206
  215. package/dist/lib/task/finalization-handoff.js +0 -93
  216. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  217. package/lib/internal/codex-lifecycle.ts +0 -390
  218. package/lib/task/codex-orchestration.ts +0 -276
  219. package/lib/task/finalization-handoff.ts +0 -105
@@ -3,10 +3,10 @@ import path from 'node:path';
3
3
  import { AgentClientConfigError, normalizeAgentClients } from '../agent-clients/config.js';
4
4
  import { normalizeCustomToolInvocations } from '../agent-clients/custom-tool-invocations.js';
5
5
  import { renderNextStepCommands } from '../agent-clients/next-steps.js';
6
- import { getAgentClientModelSelection } from '../agent-clients/registry.js';
6
+ import { getAgentClientAdapter, getAgentClientModelSelection } from '../agent-clients/registry.js';
7
7
  import { isAgentClientId } from '../agent-clients/types.js';
8
8
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
9
- const USAGE = 'Usage: agent-infra-internal agent-client <next-steps|model-selection> [options]\n';
9
+ const USAGE = 'Usage: agent-infra-internal agent-client <next-steps|model-selection|launch-carrier> [options]\n';
10
10
  function failure(code, message) {
11
11
  process.stdout.write(`${JSON.stringify({
12
12
  status: 'failed',
@@ -72,71 +72,113 @@ function parseArgs(args) {
72
72
  format
73
73
  };
74
74
  }
75
- function agentClient(args = []) {
76
- if (!ensureInternalHandlerRoute('agent-client', args))
77
- return;
78
- if (internalHandlerRoute('agent-client', 'model-selection', args[0] ?? '')) {
79
- const values = {};
80
- const seen = new Set();
81
- for (let index = 1; index < args.length; index += 1) {
82
- const flag = args[index];
83
- if (!['--client', '--format'].includes(flag) || seen.has(flag)) {
84
- failure('AGENT_CLIENT_PAYLOAD_INVALID', `invalid or duplicate option '${flag}'`);
85
- return;
86
- }
87
- const value = args[++index];
88
- if (!value || value.startsWith('--')) {
89
- failure('AGENT_CLIENT_PAYLOAD_INVALID', `option '${flag}' requires a value`);
90
- return;
91
- }
92
- seen.add(flag);
93
- values[flag] = value;
75
+ function parseLaunchCarrierArgs(args) {
76
+ const allowed = new Set(['--client', '--adapter-context', '--stage', '--round', '--role', '--format']);
77
+ const values = {};
78
+ const seen = new Set();
79
+ for (let index = 1; index < args.length; index += 1) {
80
+ const flag = args[index];
81
+ if (!allowed.has(flag) || seen.has(flag)) {
82
+ failure('AGENT_CLIENT_PAYLOAD_INVALID', `invalid or duplicate option '${flag}'`);
83
+ return null;
94
84
  }
95
- const client = values['--client'];
96
- const format = values['--format'] ?? 'text';
97
- if (!isAgentClientId(client) || !['text', 'json'].includes(format)) {
98
- failure('AGENT_CLIENT_PAYLOAD_INVALID', 'model-selection requires a known --client and text|json format');
99
- return;
85
+ const value = args[++index];
86
+ if (!value || value.startsWith('--')) {
87
+ failure('AGENT_CLIENT_PAYLOAD_INVALID', `option '${flag}' requires a value`);
88
+ return null;
100
89
  }
101
- const context = getAgentClientModelSelection(client);
102
- if (format === 'json') {
103
- process.stdout.write(`${JSON.stringify({
104
- status: 'resolved', changed: false, client, context, error: null
105
- })}\n`);
90
+ seen.add(flag);
91
+ values[flag] = value;
92
+ }
93
+ const round = Number(values['--round']);
94
+ if (!isAgentClientId(values['--client']) || !values['--adapter-context'] || !values['--stage'] || !values['--role']
95
+ || !Number.isSafeInteger(round) || round < 1 || (values['--format'] ?? 'json') !== 'json') {
96
+ failure('AGENT_CLIENT_PAYLOAD_INVALID', 'launch-carrier requires client, adapter context, stage, positive round, role, and json format');
97
+ return null;
98
+ }
99
+ return {
100
+ client: values['--client'],
101
+ adapterContext: values['--adapter-context'],
102
+ stage: values['--stage'],
103
+ round,
104
+ role: values['--role']
105
+ };
106
+ }
107
+ function launchCarrier(args) {
108
+ const parsed = parseLaunchCarrierArgs(args);
109
+ if (!parsed)
110
+ return;
111
+ const createCarrier = getAgentClientAdapter(parsed.client).orchestrationAdapter?.createLaunchCarrier;
112
+ if (!createCarrier) {
113
+ process.stdout.write(`${JSON.stringify({
114
+ status: 'failed', changed: false, client: parsed.client, carrier: null,
115
+ error: { code: 'AGENT_CLIENT_LAUNCH_CARRIER_UNSUPPORTED', message: `Agent Client '${parsed.client}' does not support lifecycle launch carriers` }
116
+ })}\n`);
117
+ process.exitCode = 1;
118
+ return;
119
+ }
120
+ try {
121
+ const carrier = createCarrier(parsed.adapterContext, { stage: parsed.stage, round: parsed.round, role: parsed.role });
122
+ process.stdout.write(`${JSON.stringify({ status: 'ready', changed: false, client: parsed.client, carrier, error: null })}\n`);
123
+ }
124
+ catch (error) {
125
+ process.stdout.write(`${JSON.stringify({
126
+ status: 'failed', changed: false, client: parsed.client, carrier: null,
127
+ error: { code: error instanceof Error && error.name ? error.name : 'AGENT_CLIENT_LAUNCH_CARRIER_INVALID', message: error instanceof Error ? error.message : String(error) }
128
+ })}\n`);
129
+ process.exitCode = 1;
130
+ }
131
+ }
132
+ function modelSelection(args) {
133
+ const values = {};
134
+ const seen = new Set();
135
+ for (let index = 1; index < args.length; index += 1) {
136
+ const flag = args[index];
137
+ if (!['--client', '--format'].includes(flag) || seen.has(flag)) {
138
+ failure('AGENT_CLIENT_PAYLOAD_INVALID', `invalid or duplicate option '${flag}'`);
106
139
  return;
107
140
  }
108
- if (context.kind === 'interactive-only') {
109
- process.stdout.write(`Model selection: interactive-only\nCommand: ${context.command}\n${context.guidance}\n`);
110
- }
111
- else {
112
- process.stdout.write(`Model selection: ${context.completeness} catalog\nSource: ${context.source}\n`);
113
- for (const model of context.models) {
114
- const efforts = model.reasoningEfforts?.length
115
- ? ` (${model.reasoningEfforts.join(', ')})`
116
- : '';
117
- process.stdout.write(`- ${model.id}${efforts}\n`);
118
- }
119
- if (context.guidance)
120
- process.stdout.write(`${context.guidance}\n`);
141
+ const value = args[++index];
142
+ if (!value || value.startsWith('--')) {
143
+ failure('AGENT_CLIENT_PAYLOAD_INVALID', `option '${flag}' requires a value`);
144
+ return;
121
145
  }
146
+ seen.add(flag);
147
+ values[flag] = value;
148
+ }
149
+ const client = values['--client'];
150
+ const format = values['--format'] ?? 'text';
151
+ if (!isAgentClientId(client) || !['text', 'json'].includes(format)) {
152
+ failure('AGENT_CLIENT_PAYLOAD_INVALID', 'model-selection requires a known --client and text|json format');
122
153
  return;
123
154
  }
124
- const parsed = parseArgs(args);
125
- if (!parsed || process.exitCode)
155
+ const context = getAgentClientModelSelection(client);
156
+ if (format === 'json') {
157
+ process.stdout.write(`${JSON.stringify({ status: 'resolved', changed: false, client, context, error: null })}\n`);
126
158
  return;
127
- let config;
128
- try {
129
- const configPath = path.join(process.cwd(), '.agents', '.airc.json');
130
- const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
131
- if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
132
- throw new Error('configuration root must be an object');
133
- }
134
- config = raw;
135
159
  }
136
- catch (error) {
137
- failure('AGENT_CLIENT_CONFIG_INVALID', String(error));
160
+ renderModelSelection(context);
161
+ }
162
+ function renderModelSelection(context) {
163
+ if (context.kind === 'interactive-only') {
164
+ process.stdout.write(`Model selection: interactive-only\nCommand: ${context.command}\n${context.guidance}\n`);
138
165
  return;
139
166
  }
167
+ process.stdout.write(`Model selection: ${context.completeness} catalog\nSource: ${context.source}\n`);
168
+ for (const model of context.models) {
169
+ const efforts = model.reasoningEfforts?.length ? ` (${model.reasoningEfforts.join(', ')})` : '';
170
+ process.stdout.write(`- ${model.id}${efforts}\n`);
171
+ }
172
+ if (context.guidance)
173
+ process.stdout.write(`${context.guidance}\n`);
174
+ }
175
+ function renderNextSteps(args) {
176
+ const parsed = parseArgs(args);
177
+ if (!parsed || process.exitCode)
178
+ return;
179
+ const config = readAgentClientConfig();
180
+ if (!config)
181
+ return;
140
182
  try {
141
183
  const clients = normalizeAgentClients(config);
142
184
  const custom = normalizeCustomToolInvocations(config);
@@ -148,22 +190,7 @@ function agentClient(args = []) {
148
190
  ...(parsed.taskRef ? { taskRef: parsed.taskRef } : {}),
149
191
  ...(parsed.version ? { version: parsed.version } : {})
150
192
  });
151
- if (parsed.format === 'json') {
152
- process.stdout.write(`${JSON.stringify({
153
- status: 'rendered',
154
- changed: false,
155
- commands,
156
- diagnostics: custom.diagnostics,
157
- error: null
158
- })}\n`);
159
- return;
160
- }
161
- for (const command of commands) {
162
- process.stdout.write(` - ${command.displayName}: ${command.command}\n`);
163
- }
164
- for (const diagnostic of custom.diagnostics) {
165
- process.stderr.write(`${diagnostic.code} at ${diagnostic.path}\n`);
166
- }
193
+ writeNextStepOutput(parsed.format, commands, custom.diagnostics);
167
194
  }
168
195
  catch (error) {
169
196
  if (error instanceof AgentClientConfigError) {
@@ -173,5 +200,38 @@ function agentClient(args = []) {
173
200
  failure('AGENT_CLIENT_RENDER_INVALID', error instanceof Error ? error.message : String(error));
174
201
  }
175
202
  }
203
+ function readAgentClientConfig() {
204
+ try {
205
+ const raw = JSON.parse(fs.readFileSync(path.join(process.cwd(), '.agents', '.airc.json'), 'utf8'));
206
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
207
+ throw new Error('configuration root must be an object');
208
+ return raw;
209
+ }
210
+ catch (error) {
211
+ failure('AGENT_CLIENT_CONFIG_INVALID', String(error));
212
+ return null;
213
+ }
214
+ }
215
+ function writeNextStepOutput(format, commands, diagnostics) {
216
+ if (format === 'json') {
217
+ process.stdout.write(`${JSON.stringify({ status: 'rendered', changed: false, commands, diagnostics, error: null })}\n`);
218
+ return;
219
+ }
220
+ for (const command of commands)
221
+ process.stdout.write(` - ${command.displayName}: ${command.command}\n`);
222
+ for (const diagnostic of diagnostics)
223
+ process.stderr.write(`${diagnostic.code} at ${diagnostic.path}\n`);
224
+ }
225
+ function agentClient(args = []) {
226
+ if (!ensureInternalHandlerRoute('agent-client', args))
227
+ return;
228
+ if (internalHandlerRoute('agent-client', 'launch-carrier', args[0] ?? '')) {
229
+ return launchCarrier(args);
230
+ }
231
+ if (internalHandlerRoute('agent-client', 'model-selection', args[0] ?? '')) {
232
+ return modelSelection(args);
233
+ }
234
+ renderNextSteps(args);
235
+ }
176
236
  export { agentClient };
177
237
  //# sourceMappingURL=agent-client.js.map
@@ -33,8 +33,8 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
33
33
  'task-create': ['input'],
34
34
  'task-qualification': ['proposal', 'confirm', 'supersede', 'revoke'],
35
35
  'sandbox-control': ['serve', 'execute', 'recover', 'client'],
36
- 'agent-client': ['next-steps', 'model-selection'],
37
- 'codex-lifecycle': ['preflight', 'capability-arm', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
36
+ 'agent-client': ['next-steps', 'model-selection', 'launch-carrier'],
37
+ 'codex-lifecycle': ['preflight', 'hook-event', 'resolve-start', 'resolve-stop', 'consume'],
38
38
  'codex-sandbox-controller': ['verify-context', 'run'],
39
39
  'git-workflow': ['inspect', 'preview-tree', 'snapshot', 'compare-trees', 'commit', 'push-rebased'],
40
40
  'task-delivery': ['deliver'],
@@ -196,7 +196,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
196
196
  if (transaction.phase === 'prepared') {
197
197
  const staged = await syncPullRequestSummary(taskRef, {
198
198
  cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult: primaryResult, strict: true,
199
- manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
199
+ manualValidation: { phase: 'pending', transactionId: transaction.transactionId }, lockAlreadyHeld: true
200
200
  });
201
201
  if (!['applied', 'no-op'].includes(staged.status))
202
202
  transactionFailure(staged.error ?? { code: 'MANUAL_VALIDATION_TRANSACTION_FAILED', message: 'pending summary staging failed' });
@@ -261,7 +261,7 @@ async function executeManualValidationTransactionLocked(taskRef, values, cwd, re
261
261
  try {
262
262
  await syncPullRequestSummary(taskRef, {
263
263
  cwd, client: options.client, agent, body: pendingBody, changeReportFile: path.resolve(cwd, values.changeReportFile), primaryResult: primaryResult, strict: true,
264
- manualValidation: { phase: 'pending' }, lockAlreadyHeld: true
264
+ manualValidation: { phase: 'pending', transactionId: transaction.transactionId }, lockAlreadyHeld: true
265
265
  });
266
266
  }
267
267
  catch {
@@ -10,7 +10,7 @@ const USAGE = `Usage: agent-infra-internal platform-pr inspect <task-ref> [--cwd
10
10
  agent-infra-internal platform-pr bind <task-ref> --pr <token> --agent <agent> [--dry-run] [--cwd <path>]
11
11
  agent-infra-internal platform-pr skip <task-ref> --agent <agent> [--dry-run] [--cwd <path>]
12
12
  agent-infra-internal platform-pr sync <task-ref> --agent <agent> [--metadata] [--closing-issue] --result <pr_created|pr_reused|no_op> [--dry-run] [--cwd <path>]
13
- agent-infra-internal platform-pr sync-in-labels --pr <N> [--dry-run] [--cwd <path>]
13
+ agent-infra-internal platform-pr sync-in-labels --pr <token> [--dry-run] [--cwd <path>]
14
14
  agent-infra-internal platform-pr summary-context <task-ref> [--cwd <path>]
15
15
  agent-infra-internal platform-pr change-report <task-ref> --agent <agent> --mechanical-file <path> --precheck-file <path> [--dry-run] [--cwd <path>]
16
16
  agent-infra-internal platform-pr summary-sync <task-ref> --agent <agent> --body-file <path|-> --change-report-file <path> --result <pr_created|pr_reused|no_op> [--manual-phase pending|final ...] [--strict] [--dry-run] [--cwd <path>]
@@ -91,13 +91,12 @@ async function platformPr(args = []) {
91
91
  fail(`sync-in-labels does not accept --${unexpected}`);
92
92
  return;
93
93
  }
94
- const pr = Number(values.pr);
95
- if (!Number.isInteger(pr) || pr <= 0) {
96
- fail('sync-in-labels requires a positive --pr');
94
+ if (typeof values.pr !== 'string' || !values.pr || values.pr.trim() !== values.pr) {
95
+ fail('sync-in-labels requires a non-empty --pr token');
97
96
  return;
98
97
  }
99
98
  const cwd = path.resolve(typeof values.cwd === 'string' ? values.cwd : process.cwd());
100
- finish(await syncPlatformPullRequestInLabels(pr, { cwd, dryRun: values.dryRun === true }));
99
+ finish(await syncPlatformPullRequestInLabels(values.pr, { cwd, dryRun: values.dryRun === true }));
101
100
  return;
102
101
  }
103
102
  const taskRef = args[1];
@@ -1,6 +1,6 @@
1
1
  import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.js';
2
2
  import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
3
- const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
3
+ const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--reason-code <reason>] [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
4
4
  function taskArtifact(args = []) {
5
5
  if (!ensureInternalHandlerRoute('task-artifact', args))
6
6
  return;
@@ -54,7 +54,21 @@ async function taskCreate(args) {
54
54
  throw new Error('TASK_CREATE_INPUT_INVALID: input must be a regular file');
55
55
  if (stat.size > SANDBOX_CONTROL_MAX_BYTES)
56
56
  throw new Error('TASK_CREATE_INPUT_TOO_LARGE: input exceeds the control limit');
57
- const candidate = validateTaskCreateCandidate(JSON.parse(fs.readFileSync(inputPath, 'utf8')));
57
+ const inputContent = fs.readFileSync(inputPath, 'utf8');
58
+ let candidate;
59
+ try {
60
+ candidate = validateTaskCreateCandidate(JSON.parse(inputContent));
61
+ }
62
+ catch (error) {
63
+ const message = error instanceof Error ? error.message : String(error);
64
+ const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_INPUT_INVALID';
65
+ output(failed(code, message, false, {
66
+ requestId: null,
67
+ accepted: false,
68
+ recovery: 'new-request-id'
69
+ }));
70
+ return;
71
+ }
58
72
  const environment = resolveSandboxControlTransport();
59
73
  if (environment.kind === 'fail-closed') {
60
74
  output(failed(environment.reasonCode ?? 'TASK_CONTROL_TRANSPORT_INVALID', 'sandbox client control configuration is invalid'));
@@ -65,9 +65,7 @@ async function taskFinalization(args = []) {
65
65
  const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
66
66
  if (transport.kind === 'sandbox-local') {
67
67
  try {
68
- const response = requestSandboxTaskFinalization({
69
- agent: boundOperation.request.agent
70
- });
68
+ const response = requestSandboxTaskFinalization({ agent: boundOperation.request.agent });
71
69
  process.stdout.write(response.stdout);
72
70
  process.stderr.write(response.stderr);
73
71
  process.exitCode = response.exitCode ?? 1;
@@ -3,7 +3,8 @@ import path from 'node:path';
3
3
  import { accessSandboxTaskView, parseSandboxTaskView, taskViewFromStatus } from '../sandbox/control/task-view.js';
4
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
5
5
  import { PUBLIC_CLI_COMMAND_ALIASES, PUBLIC_CLI_SELECTOR_ALIASES, internalRouteSelector } from './cli-route-inventory.js';
6
- import { readSandboxControlIdentitySentinel } from '../sandbox/control/identity-sentinel.js';
6
+ import { hasSandboxControlMarker, resolveSandboxControlTransport, sandboxTaskMarkerState } from '../sandbox/environment.js';
7
+ export { resolveSandboxControlTransport } from '../sandbox/environment.js';
7
8
  const descriptor = (dispatcher, command, selector, scope, effect, taskRefSource = scope === 'non-task' ? 'none' : 'argv') => ({ dispatcher, command, selector, scope, effect, taskRefSource, guardBeforeImport: true });
8
9
  function internalTaskRoutes() {
9
10
  return [
@@ -18,8 +19,8 @@ function internalTaskRoutes() {
18
19
  descriptor('internal', 'sandbox-control', 'client', 'conditional', 'progress', 'delegated'),
19
20
  descriptor('internal', 'agent-client', 'next-steps', 'non-task', 'diagnostic', 'none'),
20
21
  descriptor('internal', 'agent-client', 'model-selection', 'non-task', 'diagnostic', 'none'),
22
+ descriptor('internal', 'agent-client', 'launch-carrier', 'non-task', 'diagnostic', 'none'),
21
23
  descriptor('internal', 'codex-lifecycle', 'preflight', 'non-task', 'diagnostic', 'none'),
22
- descriptor('internal', 'codex-lifecycle', 'capability-arm', 'conditional', 'progress'),
23
24
  descriptor('internal', 'codex-lifecycle', 'hook-event', 'conditional', 'progress'),
24
25
  descriptor('internal', 'codex-lifecycle', 'resolve-start', 'conditional', 'progress'),
25
26
  descriptor('internal', 'codex-lifecycle', 'resolve-stop', 'conditional', 'progress'),
@@ -232,141 +233,6 @@ export function resolveDelegatedTaskOperation(args) {
232
233
  return null;
233
234
  return resolveTaskOperation('internal', rest[0], rest.slice(1));
234
235
  }
235
- const TASK_MARKER_KEYS = [
236
- 'AGENT_INFRA_TASK_ID',
237
- 'AGENT_INFRA_CONTROL_TOKEN',
238
- 'AGENT_INFRA_CONTROL_GENERATION',
239
- 'AGENT_INFRA_CONTROL_DIR',
240
- 'AGENT_INFRA_CONTROL_STATUS_DIR',
241
- 'AGENT_INFRA_RUNTIME_DIR'
242
- ];
243
- const TASK_CONTROL_MARKER_KEYS = [
244
- 'AGENT_INFRA_CONTROL_TOKEN',
245
- 'AGENT_INFRA_CONTROL_GENERATION',
246
- 'AGENT_INFRA_CONTROL_DIR',
247
- 'AGENT_INFRA_CONTROL_STATUS_DIR'
248
- ];
249
- const TASK_CONTROL_CONFIG_KEYS = [
250
- 'AGENT_INFRA_CONTROL_TOKEN',
251
- 'AGENT_INFRA_CONTROL_GENERATION',
252
- 'AGENT_INFRA_CONTROL_ROOT_ID',
253
- 'AGENT_INFRA_CONTROL_DIR',
254
- 'AGENT_INFRA_CONTROL_STATUS_DIR'
255
- ];
256
- export const SANDBOX_CONTROL_STATUS_MOUNT = '/run/agent-infra/control-status';
257
- function nativeDirectoryProbe(candidate) {
258
- try {
259
- // The fixed mount is a trust anchor. Do not use a mutable node:fs export:
260
- // a preload must not turn a mounted sandbox into the direct-host path by
261
- // replacing the ordinary filesystem probe.
262
- const binding = process.binding('fs');
263
- if (!Function.prototype.toString.call(binding.internalModuleStat).includes('[native code]'))
264
- return 'unknown';
265
- const result = binding.internalModuleStat(candidate);
266
- if (result === 1)
267
- return 'present';
268
- if (result === -2)
269
- return 'absent';
270
- return 'unknown';
271
- }
272
- catch {
273
- return 'unknown';
274
- }
275
- }
276
- function taskMarkerState(env) {
277
- const present = (key) => Boolean(env[key]);
278
- const taskId = present('AGENT_INFRA_TASK_ID');
279
- const controls = TASK_CONTROL_MARKER_KEYS.every(present);
280
- const runtime = present('AGENT_INFRA_RUNTIME_DIR');
281
- const any = TASK_MARKER_KEYS.some(present);
282
- if (!any)
283
- return 'none';
284
- if (taskId && controls && runtime)
285
- return 'task-bound';
286
- if (!taskId && controls && !runtime)
287
- return 'branch-only';
288
- return 'incomplete';
289
- }
290
- export function resolveSandboxControlTransport(env = process.env, options = {}) {
291
- const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
292
- || Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
293
- || Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
294
- const fixedStatusDir = options.statusMountPath
295
- ?? SANDBOX_CONTROL_STATUS_MOUNT;
296
- const fixedStatusProbe = path.isAbsolute(fixedStatusDir)
297
- ? nativeDirectoryProbe(fixedStatusDir) : 'absent';
298
- if (fixedStatusProbe === 'unknown') {
299
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
300
- }
301
- const fixedStatusMounted = fixedStatusProbe === 'present';
302
- // A fixed status mount is installed only in a task-bound sandbox. Its
303
- // presence is not mutable by the sandbox process, so marker removal must
304
- // never make that process look like a direct host invocation.
305
- if (!hasAnyMarker) {
306
- return fixedStatusMounted
307
- ? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' }
308
- : { kind: 'direct-host', reasonCode: null };
309
- }
310
- const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
311
- if (!hasCompleteConfig) {
312
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
313
- }
314
- const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
315
- const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
316
- ? nativeDirectoryProbe(configuredStatusDir) : 'absent';
317
- if (configuredStatusProbe === 'unknown') {
318
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
319
- }
320
- const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir)
321
- ? configuredStatusProbe === 'present' ? configuredStatusDir : null
322
- : fixedStatusMounted ? fixedStatusDir : null;
323
- const statusMounted = statusDir !== null;
324
- if (env.AGENT_INFRA_EXECUTOR_MANIFEST || env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING) {
325
- return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
326
- }
327
- const taskBound = Boolean(env.AGENT_INFRA_TASK_ID);
328
- const runtime = Boolean(env.AGENT_INFRA_RUNTIME_DIR);
329
- if (statusMounted) {
330
- let sentinel;
331
- try {
332
- sentinel = readSandboxControlIdentitySentinel(statusDir);
333
- }
334
- catch (error) {
335
- const message = error instanceof Error ? error.message : '';
336
- return { kind: 'fail-closed', reasonCode: message.endsWith('MISSING')
337
- ? 'SANDBOX_CONTROL_IDENTITY_MISSING'
338
- : 'SANDBOX_CONTROL_IDENTITY_MALFORMED' };
339
- }
340
- if (sentinel.generation !== env.AGENT_INFRA_CONTROL_GENERATION) {
341
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH' };
342
- }
343
- if (env.AGENT_INFRA_CONTROL_ROOT_ID && sentinel.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
344
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH' };
345
- }
346
- if (sentinel.mode === 'task-bound'
347
- && (sentinel.taskId !== env.AGENT_INFRA_TASK_ID || !runtime)) {
348
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
349
- }
350
- if (sentinel.mode === 'branch-only' && (taskBound || runtime)) {
351
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
352
- }
353
- if (!hasCompleteConfig) {
354
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
355
- }
356
- return { kind: options.localWorkflow ? 'sandbox-local' : 'broker-client', reasonCode: null };
357
- }
358
- if (!hasAnyMarker)
359
- return { kind: 'direct-host', reasonCode: null };
360
- if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
361
- return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
362
- }
363
- return options.localWorkflow
364
- ? { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_MISSING' }
365
- : { kind: 'broker-client', reasonCode: null };
366
- }
367
- export function hasTaskBoundMarker(env = process.env) {
368
- return taskMarkerState(env) === 'task-bound';
369
- }
370
236
  function explicitTaskRefForOperation(dispatcher, command, args, selected) {
371
237
  if (selected.taskRefSource === 'none' || selected.taskRefSource === 'environment')
372
238
  return null;
@@ -446,7 +312,7 @@ function isSandboxExecutorRoute(dispatcher, command, args, env) {
446
312
  && first(args) === 'execute'
447
313
  && Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST)
448
314
  && Boolean(env.AGENT_INFRA_RUNTIME_DIR)
449
- && !TASK_CONTROL_MARKER_KEYS.some((key) => Boolean(env[key]));
315
+ && !hasSandboxControlMarker(env);
450
316
  }
451
317
  function viewFromEnvironment(env) {
452
318
  const statusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
@@ -492,7 +358,7 @@ export function guardTaskOperation(dispatcher, command, args = [], options = {})
492
358
  taskView: null
493
359
  };
494
360
  }
495
- const markerState = taskMarkerState(env);
361
+ const markerState = sandboxTaskMarkerState(env);
496
362
  if (markerState === 'incomplete' && !isSandboxExecutorRoute(dispatcher, command, args, env)) {
497
363
  throw new TaskViewOperationError('SANDBOX_TASK_VIEW_MARKER_INVALID', 'sandbox task markers are incomplete; refusing to infer host-direct authority');
498
364
  }
@@ -152,7 +152,7 @@ function inplaceValidation(config, target, options) {
152
152
  if (!row)
153
153
  throw new Error('SANDBOX_VALIDATION_CONTAINER_NOT_FOUND');
154
154
  const control = sandboxControlPaths({
155
- base: config.controlBase, project: config.project,
155
+ base: config.controlBase, repoRoot: config.repoRoot, project: config.project,
156
156
  container: row.name, identity: target.workspace
157
157
  });
158
158
  const manifest = readSandboxControlManifest(control.manifestPath);
@@ -472,7 +472,8 @@ async function syncPlatformIssueImpl(taskRef, options) {
472
472
  let providerMetadata = null;
473
473
  let repositoryLabels = [];
474
474
  let milestones = [];
475
- if (base.context.capabilities.triage && (options.status !== undefined || options.inLabels !== undefined || options.milestone !== undefined || options.issueType || options.fields)) {
475
+ if (base.context.capabilities.triage && (options.status !== undefined || options.inLabels !== undefined || options.milestone !== undefined
476
+ || (base.context.capabilities.push && (options.issueType || options.fields)))) {
476
477
  const metadata = base.provider.issues?.describeRepository
477
478
  ? await base.provider.issues.describeRepository({ context: providerOperationContext(base.loadedContext) })
478
479
  : unsupportedProviderOperation(base.provider, 'issues.describeRepository');
@@ -551,18 +552,29 @@ async function syncPlatformIssueImpl(taskRef, options) {
551
552
  }
552
553
  }
553
554
  if (options.issueType || options.fields) {
554
- plan.operations.push(...(providerMetadata
555
- ? planProviderMetadata(inspected.value, providerMetadata, base.frontmatter, options, base.context.capabilities)
556
- : [{ name: 'issue-type', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }]));
555
+ plan.operations.push(...(!base.context.capabilities.push
556
+ ? [
557
+ ...(options.issueType ? [{ name: 'issue-type', status: 'skipped', reasonCode: 'PUSH_REQUIRED' }] : []),
558
+ ...(options.fields ? [{ name: 'fields', status: 'skipped', reasonCode: 'PUSH_REQUIRED' }] : [])
559
+ ]
560
+ : providerMetadata
561
+ ? planProviderMetadata(inspected.value, providerMetadata, base.frontmatter, options, base.context.capabilities)
562
+ : [{ name: 'issue-type', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }]));
557
563
  }
558
564
  const failure = plan.operations.find((operation) => operation.status === 'failed');
559
565
  if (failure)
560
566
  return result('failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: { code: failure.reasonCode || 'ISSUE_SYNC_FAILED', message: `Operation ${failure.name} failed`, retryable: false } });
561
567
  const planned = plan.operations.filter((operation) => operation.status === 'planned');
562
568
  if (options.dryRun)
563
- return result(planned.length ? 'planned' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
569
+ return result(planned.length ? 'planned' : 'no-op', base.resolved.taskId, base.issueNumber, {
570
+ platform: base.context.platform, capabilities: base.context.capabilities,
571
+ issue: snapshot, operations: plan.operations, error: null
572
+ });
564
573
  if (planned.length === 0)
565
- return result('no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
574
+ return result('no-op', base.resolved.taskId, base.issueNumber, {
575
+ platform: base.context.platform, capabilities: base.context.capabilities,
576
+ issue: snapshot, operations: plan.operations, error: null
577
+ });
566
578
  const payload = applyRestOperations(snapshot, planned);
567
579
  const updated = base.provider.issues?.update
568
580
  ? await base.provider.issues.update({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity, currentLabels: snapshot.labels, patch: payload, mutation: { idempotencyKey: `issue:update:${base.resolved.taskId}` } })