@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -1,7 +1,9 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
  import { accessSandboxTaskView, parseSandboxTaskView, taskViewFromStatus } from '../sandbox/control/task-view.js';
3
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
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';
5
7
  const descriptor = (dispatcher, command, selector, scope, effect, taskRefSource = scope === 'non-task' ? 'none' : 'argv') => ({ dispatcher, command, selector, scope, effect, taskRefSource, guardBeforeImport: true });
6
8
  function internalTaskRoutes() {
7
9
  return [
@@ -14,6 +16,10 @@ function internalTaskRoutes() {
14
16
  descriptor('internal', 'sandbox-control', 'execute', 'non-task', 'progress', 'none'),
15
17
  descriptor('internal', 'sandbox-control', 'recover', 'conditional', 'recovery', 'delegated'),
16
18
  descriptor('internal', 'sandbox-control', 'client', 'conditional', 'progress', 'delegated'),
19
+ descriptor('internal', 'host-control', 'serve', 'non-task', 'progress', 'none'),
20
+ descriptor('internal', 'host-control', 'status', 'non-task', 'diagnostic', 'none'),
21
+ descriptor('internal', 'host-control', 'install', 'non-task', 'progress', 'none'),
22
+ descriptor('internal', 'host-control', 'uninstall', 'non-task', 'progress', 'none'),
17
23
  descriptor('internal', 'agent-client', 'next-steps', 'non-task', 'diagnostic', 'none'),
18
24
  descriptor('internal', 'agent-client', 'model-selection', 'non-task', 'diagnostic', 'none'),
19
25
  descriptor('internal', 'codex-lifecycle', 'preflight', 'non-task', 'diagnostic', 'none'),
@@ -110,7 +116,8 @@ function internalTaskRoutes() {
110
116
  descriptor('internal', 'task-snapshot', 'snapshot', 'conditional', 'diagnostic'),
111
117
  descriptor('internal', 'task-verify', 'event', 'task-bound', 'terminal-verdict'),
112
118
  descriptor('internal', 'task-validate', 'snapshot', 'conditional', 'diagnostic'),
113
- descriptor('internal', 'task-validate', 'inplace', 'task-bound', 'progress')
119
+ descriptor('internal', 'task-validate', 'inplace', 'task-bound', 'progress'),
120
+ descriptor('internal', 'manual-validation', 'transaction', 'task-bound', 'remote-write')
114
121
  ];
115
122
  }
116
123
  function publicTaskRoutes() {
@@ -169,12 +176,12 @@ export const TASK_OPERATION_DESCRIPTORS = Object.freeze([
169
176
  ...PUBLIC_OPERATION_DESCRIPTORS
170
177
  ]);
171
178
  export const INTERNAL_DISPATCHER_ROUTES = Object.freeze([
172
- 'task-create', 'task-qualification', 'sandbox-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
179
+ 'task-create', 'task-qualification', 'sandbox-control', 'host-control', 'agent-client', 'codex-lifecycle', 'codex-sandbox-controller',
173
180
  'git-workflow', 'task-delivery', 'release-workflow', 'platform-release-notes', 'platform-security', 'platform-metadata', 'platform-context',
174
181
  'platform-comment', 'platform-issue', 'platform-pr', 'platform-pr-review', 'pr-review-grade',
175
182
  'platform-checks', 'task-context', 'task-ledger', 'task-warning', 'task-activity', 'task-artifact',
176
183
  'task-orchestration', 'task-review', 'task-event', 'task-invalidation', 'task-lifecycle',
177
- 'task-finalization', 'task-override', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate'
184
+ 'task-finalization', 'task-override', 'task-short-id', 'task-snapshot', 'task-verify', 'task-validate', 'manual-validation'
178
185
  ]);
179
186
  export const PUBLIC_DISPATCHER_ROUTES = Object.freeze([
180
187
  'agent-client', 'cp', 'data', 'decide', 'help', 'init', 'merge', 'run', 'sandbox', 'server', 'task', 'sync', 'update', 'version'
@@ -239,6 +246,33 @@ const TASK_CONTROL_MARKER_KEYS = [
239
246
  'AGENT_INFRA_CONTROL_DIR',
240
247
  'AGENT_INFRA_CONTROL_STATUS_DIR'
241
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
+ }
242
276
  function taskMarkerState(env) {
243
277
  const present = (key) => Boolean(env[key]);
244
278
  const taskId = present('AGENT_INFRA_TASK_ID');
@@ -253,6 +287,70 @@ function taskMarkerState(env) {
253
287
  return 'branch-only';
254
288
  return 'incomplete';
255
289
  }
290
+ export function resolveSandboxControlTransport(env = process.env, options = {}) {
291
+ const configuredStatusDir = env.AGENT_INFRA_CONTROL_STATUS_DIR;
292
+ const fixedStatusDir = options.statusMountPath
293
+ ?? SANDBOX_CONTROL_STATUS_MOUNT;
294
+ const hasAnyMarker = TASK_MARKER_KEYS.some((key) => Boolean(env[key]))
295
+ || Boolean(env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING)
296
+ || Boolean(env.AGENT_INFRA_EXECUTOR_MANIFEST);
297
+ const fixedStatusProbe = !env.AGENT_INFRA_TEST_HOST_CONTROL_ENDPOINT && path.isAbsolute(fixedStatusDir)
298
+ ? nativeDirectoryProbe(fixedStatusDir) : 'absent';
299
+ if (fixedStatusProbe === 'unknown') {
300
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
301
+ }
302
+ const fixedStatusMounted = fixedStatusProbe === 'present';
303
+ const configuredStatusProbe = configuredStatusDir && path.isAbsolute(configuredStatusDir)
304
+ ? nativeDirectoryProbe(configuredStatusDir) : 'absent';
305
+ if (configuredStatusProbe === 'unknown') {
306
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_UNAVAILABLE' };
307
+ }
308
+ const statusDir = configuredStatusDir && path.isAbsolute(configuredStatusDir) && configuredStatusProbe === 'present'
309
+ ? configuredStatusDir
310
+ : fixedStatusMounted ? fixedStatusDir : null;
311
+ const statusMounted = statusDir !== null;
312
+ const hasCompleteConfig = TASK_CONTROL_CONFIG_KEYS.every((key) => Boolean(env[key]));
313
+ if (env.AGENT_INFRA_EXECUTOR_MANIFEST || env.AGENT_INFRA_CONTROL_CONTROLLER_BINDING) {
314
+ return { kind: 'fail-closed', reasonCode: 'TASK_CONTROL_TRANSPORT_INVALID' };
315
+ }
316
+ const taskBound = Boolean(env.AGENT_INFRA_TASK_ID);
317
+ const runtime = Boolean(env.AGENT_INFRA_RUNTIME_DIR);
318
+ if (statusMounted) {
319
+ let sentinel;
320
+ try {
321
+ sentinel = readSandboxControlIdentitySentinel(statusDir);
322
+ }
323
+ catch (error) {
324
+ const message = error instanceof Error ? error.message : '';
325
+ return { kind: 'fail-closed', reasonCode: message.endsWith('MISSING')
326
+ ? 'SANDBOX_CONTROL_IDENTITY_MISSING'
327
+ : 'SANDBOX_CONTROL_IDENTITY_MALFORMED' };
328
+ }
329
+ if (sentinel.generation !== env.AGENT_INFRA_CONTROL_GENERATION) {
330
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_GENERATION_MISMATCH' };
331
+ }
332
+ if (env.AGENT_INFRA_CONTROL_ROOT_ID && sentinel.controlRootId !== env.AGENT_INFRA_CONTROL_ROOT_ID) {
333
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_ROOT_ID_MISMATCH' };
334
+ }
335
+ if (sentinel.mode === 'task-bound'
336
+ && (sentinel.taskId !== env.AGENT_INFRA_TASK_ID || !runtime)) {
337
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
338
+ }
339
+ if (sentinel.mode === 'branch-only' && (taskBound || runtime)) {
340
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_IDENTITY_TOPOLOGY_MISMATCH' };
341
+ }
342
+ if (!hasCompleteConfig) {
343
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
344
+ }
345
+ return { kind: 'broker-client', reasonCode: null };
346
+ }
347
+ if (!hasAnyMarker)
348
+ return { kind: 'direct-host', reasonCode: null };
349
+ if (!hasCompleteConfig || (taskBound && !runtime) || (!taskBound && runtime)) {
350
+ return { kind: 'fail-closed', reasonCode: 'SANDBOX_CONTROL_CONFIGURATION_INCOMPLETE' };
351
+ }
352
+ return { kind: 'broker-client', reasonCode: null };
353
+ }
256
354
  export function hasTaskBoundMarker(env = process.env) {
257
355
  return taskMarkerState(env) === 'task-bound';
258
356
  }
@@ -1,8 +1,9 @@
1
+ import { parseReviewCommand } from '../task/review-command.js';
1
2
  import { finalizeReviewSummary } from '../task/review-finalization.js';
2
3
  import { consumeHumanOverride, failureId, overrideDryRunConflict } from '../task/human-override.js';
3
4
  import { resolveTaskRef } from '../task/resolve-ref.js';
4
5
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
5
- import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
6
+ import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
6
7
  const USAGE = 'Usage: agent-infra-internal task-review <task-ref> finalize-summary --stage <analysis|plan|code> --artifact <review-*.md> [--orchestrated] [--dry-run] [--override-ticket <ticket> --override-target <target> --override-scope <scope>]\n';
7
8
  function failUsage(message) {
8
9
  process.stdout.write(`${JSON.stringify({
@@ -21,61 +22,15 @@ async function taskReview(args = []) {
21
22
  process.stdout.write(USAGE);
22
23
  return;
23
24
  }
24
- if (args.length < 2) {
25
- failUsage('task ref and intent are required');
26
- return;
27
- }
28
- if (!internalHandlerRoute('task-review', 'finalize-summary', args[1] ?? '')) {
29
- failUsage(`unknown intent '${args[1]}'`);
30
- return;
31
- }
32
- let stage = '';
33
- let artifact = '';
34
- let dryRun = false;
35
- let orchestrated = false;
36
- let overrideTicket = '';
37
- let overrideTarget = '';
38
- let overrideScope = '';
39
- const seen = new Set();
40
- for (let index = 2; index < args.length; index += 1) {
41
- const flag = args[index];
42
- if (!['--stage', '--artifact', '--orchestrated', '--dry-run', '--override-ticket', '--override-target', '--override-scope'].includes(flag)) {
43
- failUsage(`unknown option '${flag}'`);
44
- return;
45
- }
46
- if (seen.has(flag)) {
47
- failUsage(`duplicate option '${flag}'`);
48
- return;
49
- }
50
- seen.add(flag);
51
- if (flag === '--dry-run') {
52
- dryRun = true;
53
- continue;
54
- }
55
- if (flag === '--orchestrated') {
56
- orchestrated = true;
57
- continue;
58
- }
59
- const value = args[++index];
60
- if (!value || value.startsWith('--')) {
61
- failUsage(`option '${flag}' requires a value`);
62
- return;
63
- }
64
- if (flag === '--stage')
65
- stage = value;
66
- else if (flag === '--artifact')
67
- artifact = value;
68
- else if (flag === '--override-ticket')
69
- overrideTicket = value;
70
- else if (flag === '--override-target')
71
- overrideTarget = value;
72
- else if (flag === '--override-scope')
73
- overrideScope = value;
25
+ let request;
26
+ try {
27
+ request = parseReviewCommand(args);
74
28
  }
75
- if (!stage || !artifact) {
76
- failUsage("options '--stage' and '--artifact' are required");
29
+ catch (error) {
30
+ failUsage(error instanceof Error ? error.message : String(error));
77
31
  return;
78
32
  }
33
+ const { stage, artifact, dryRun, orchestrated, overrideTicket, overrideTarget, overrideScope } = request;
79
34
  const dryRunConflict = overrideDryRunConflict({ dryRun, overrideTicket, overrideTarget, overrideScope });
80
35
  if (dryRunConflict) {
81
36
  failUsage(dryRunConflict.message);
@@ -16,13 +16,15 @@ import { readSandboxControlManifest } from '../sandbox/control/lifecycle.js';
16
16
  import { SANDBOX_CONTROL_FUTURE_SKEW_MS, SANDBOX_CONTROL_STATUS_STALE_MS } from '../sandbox/control/protocol.js';
17
17
  import { getProcessStartTime } from '../server/process-state.js';
18
18
  import { assertGitWorktreeBinding } from '../git/worktree-identity.js';
19
+ import { createManualValidationEvidence, manualValidationEvidenceSummary } from '../task/manual-validation-evidence.js';
20
+ import { writeJsonAtomic } from '../task/manual-validation-shared.js';
19
21
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
20
- const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] -- <command> [args...]`;
22
+ const USAGE = `Usage: agent-infra-internal task-validate <branch | TASK-id | N> [--scope snapshot|inplace] [--timeout <ms>] [--format text|json] [--evidence-file <path>] -- <command> [args...]`;
21
23
  const MAX_TIMEOUT_MS = 60 * 60 * 1000;
22
24
  const RECOVERY_GRACE_MS = 30_000;
23
25
  export function parseValidateArgs(args) {
24
26
  if (args.includes('--help') || args.includes('-h')) {
25
- return { target: '', scope: 'snapshot', timeoutMs: 300_000, format: 'text', command: [], help: true };
27
+ return { target: '', scope: 'snapshot', timeoutMs: 300_000, format: 'text', evidenceFile: null, command: [], help: true };
26
28
  }
27
29
  const separator = args.indexOf('--');
28
30
  if (separator < 0)
@@ -32,6 +34,7 @@ export function parseValidateArgs(args) {
32
34
  let scope = 'snapshot';
33
35
  let timeoutMs = 300_000;
34
36
  let format = 'text';
37
+ let evidenceFile = null;
35
38
  const positionals = [];
36
39
  for (let index = 0; index < optionArgs.length; index += 1) {
37
40
  const arg = optionArgs[index];
@@ -53,6 +56,12 @@ export function parseValidateArgs(args) {
53
56
  throw new Error('validate --format must be text or json');
54
57
  format = value;
55
58
  }
59
+ else if (arg === '--evidence-file') {
60
+ const value = optionArgs[++index];
61
+ if (!value || value.startsWith('-'))
62
+ throw new Error('validate --evidence-file requires a path');
63
+ evidenceFile = value;
64
+ }
56
65
  else if (arg.startsWith('-')) {
57
66
  throw new Error(`Unknown option: ${arg}`);
58
67
  }
@@ -62,7 +71,7 @@ export function parseValidateArgs(args) {
62
71
  }
63
72
  if (positionals.length !== 1 || command.length === 0)
64
73
  throw new Error(USAGE);
65
- return { target: positionals[0], scope, timeoutMs, format, command, help: false };
74
+ return { target: positionals[0], scope, timeoutMs, format, evidenceFile, command, help: false };
66
75
  }
67
76
  function runValidationCommand(command, cwd, scope, timeoutMs, json) {
68
77
  const [file, ...args] = command;
@@ -298,24 +307,35 @@ function taskValidate(args) {
298
307
  : internalHandlerRoute('task-validate', 'inplace', options.scope)
299
308
  ? inplaceValidation(config, target, options)
300
309
  : (() => { throw new Error('TASK_VALIDATE_SCOPE_UNREGISTERED'); })();
301
- const evidence = {
302
- version: 1,
310
+ const cleanup = result.cleanup();
311
+ const evidence = createManualValidationEvidence({
312
+ mode: target.workspace.mode,
303
313
  taskId: target.workspace.mode === 'task-bound' ? target.workspace.taskId : null,
304
314
  branch: target.branch,
305
- scope: options.scope,
306
315
  commit,
316
+ recoverable: target.workspace.mode === 'task-bound',
317
+ scope: options.scope,
307
318
  command: path.basename(options.command[0]),
308
319
  startedAt,
309
320
  completedAt: new Date().toISOString(),
310
321
  exitCode: result.exitCode,
311
322
  signal: result.signal,
312
- cleanup: result.cleanup()
313
- };
323
+ cleanup
324
+ });
325
+ if (options.evidenceFile) {
326
+ try {
327
+ writeJsonAtomic(path.resolve(options.evidenceFile), evidence);
328
+ }
329
+ catch (error) {
330
+ throw new Error(`MANUAL_VALIDATION_EVIDENCE_WRITE_FAILED: ${error instanceof Error ? error.message : String(error)}`);
331
+ }
332
+ }
333
+ const evidenceSummary = manualValidationEvidenceSummary(evidence);
314
334
  if (options.format === 'json') {
315
- process.stdout.write(`${JSON.stringify({ status: 'applied', changed: false, evidence, error: null })}\n`);
335
+ process.stdout.write(`${JSON.stringify({ status: 'applied', changed: false, evidence: evidenceSummary, evidenceFile: options.evidenceFile, error: null })}\n`);
316
336
  }
317
337
  else {
318
- process.stdout.write(`Validation ${result.exitCode === 0 ? 'passed' : 'failed'} (${options.scope}, ${evidence.command}, cleanup=${evidence.cleanup}).\n`);
338
+ process.stdout.write(`Validation ${result.exitCode === 0 ? 'passed' : 'failed'} (${options.scope}, ${evidenceSummary.command}, cleanup=${evidenceSummary.cleanup}).\n`);
319
339
  }
320
340
  process.exitCode = result.exitCode;
321
341
  }
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ // Inspect descendants without following symlinks. The caller supplies the trusted root.
4
+ export function inspectOwnedPath(rootInput, targetInput, expect) {
5
+ const root = path.resolve(rootInput);
6
+ const target = path.resolve(targetInput);
7
+ const relative = path.relative(root, target);
8
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative))
9
+ return null;
10
+ try {
11
+ const rootStat = fs.lstatSync(root);
12
+ if (rootStat.isSymbolicLink() || !rootStat.isDirectory())
13
+ return null;
14
+ let current = root;
15
+ for (const segment of relative.split(path.sep)) {
16
+ current = path.join(current, segment);
17
+ const stat = fs.lstatSync(current);
18
+ if (stat.isSymbolicLink())
19
+ return null;
20
+ if (process.platform !== 'win32' && typeof process.getuid === 'function' && stat.uid !== process.getuid())
21
+ return null;
22
+ if (current === target)
23
+ return (expect === 'file' ? stat.isFile() : stat.isDirectory()) ? stat : null;
24
+ if (!stat.isDirectory())
25
+ return null;
26
+ }
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ return null;
32
+ }
33
+ //# sourceMappingURL=owned-path.js.map
@@ -0,0 +1,11 @@
1
+ export function checkStatusBucket(status) {
2
+ const value = status.toLowerCase();
3
+ if (['pass', 'success', 'successful', 'neutral'].includes(value))
4
+ return 'pass';
5
+ if (['fail', 'failure', 'failed', 'error', 'timed_out', 'action_required'].includes(value))
6
+ return 'fail';
7
+ if (['cancel', 'cancelled', 'canceled', 'skipped', 'stale'].includes(value))
8
+ return 'cancel';
9
+ return 'pending';
10
+ }
11
+ //# sourceMappingURL=check-status.js.map
@@ -168,6 +168,9 @@ function sanitizeMarkdownDocument(value, options = {}) {
168
168
  return tail;
169
169
  return ok(result + tail.value);
170
170
  }
171
+ function canonicalizeCommentBody(value) {
172
+ return sanitizeMarkdownDocument(value, { reservedMarkers: [CONTROL_MARKER_PATTERN] });
173
+ }
171
174
  function renderSafeCodeFence(value, language = '', reservedMarkers = [CONTROL_MARKER_PATTERN]) {
172
175
  const content = markerSafe(normalized(value), reservedMarkers);
173
176
  const longestRun = Math.max(0, ...(content.match(/`+/g) || []).map((run) => run.length));
@@ -222,5 +225,5 @@ function splitDocumentPlaceholder(value, placeholder) {
222
225
  }
223
226
  return ok({ prefix: source.slice(0, position), suffix: source.slice(position + placeholder.length) });
224
227
  }
225
- export { CONTROL_MARKER_PATTERN, escapeHtmlAttribute, escapeHtmlText, escapeMarkdownLiteral, fenceRanges, renderSafeCodeFence, sanitizeMarkdownDocument, splitDocumentPlaceholder };
228
+ export { CONTROL_MARKER_PATTERN, escapeHtmlAttribute, escapeHtmlText, escapeMarkdownLiteral, fenceRanges, renderSafeCodeFence, canonicalizeCommentBody, sanitizeMarkdownDocument, splitDocumentPlaceholder };
226
229
  //# sourceMappingURL=comment-safety.js.map
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { COMPLETION_BACKFILL_FAMILIES, inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
4
- import { parseArtifactName } from '../task/artifact-lifecycle.js';
4
+ import { parseArtifactName } from '../task/artifact-name.js';
5
5
  import { parseTaskFrontmatter } from '../task/frontmatter.js';
6
6
  import { resolveTaskRef } from '../task/resolve-ref.js';
7
7
  import { getOpenWorkflowWarnings } from '../task/workflow-warnings.js';
@@ -0,0 +1,241 @@
1
+ import { checkStatusBucket } from './check-status.js';
2
+ const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){id issueType{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}} issueFieldValues(first:50){nodes{__typename ... on IssueFieldSingleSelectValue{name optionId field{... on IssueFieldSingleSelect{id name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{id name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{id name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{id name}}}}}}}}`;
3
+ function normalizeIssue(remote, repository, fallbackNumber) {
4
+ const number = Number.isInteger(remote.number) && Number(remote.number) > 0
5
+ ? Number(remote.number)
6
+ : fallbackNumber;
7
+ if (!number || !Number.isSafeInteger(number) || remote.pull_request)
8
+ return null;
9
+ return {
10
+ repository,
11
+ number,
12
+ databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
13
+ nodeId: remote.node_id || `issue-${number}`,
14
+ url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
15
+ state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
16
+ title: remote.title || '',
17
+ body: remote.body || '',
18
+ labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
19
+ assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
20
+ milestone: remote.milestone?.title || null,
21
+ issueType: remote.type?.name || null,
22
+ fields: {}
23
+ };
24
+ }
25
+ function fieldKind(value) {
26
+ if (value.__typename === 'IssueFieldSingleSelect' || value.__typename === 'IssueFieldSingleSelectValue')
27
+ return 'single-select';
28
+ if (value.__typename === 'IssueFieldDate' || value.__typename === 'IssueFieldDateValue')
29
+ return 'date';
30
+ if (value.__typename === 'IssueFieldText' || value.__typename === 'IssueFieldTextValue')
31
+ return 'text';
32
+ if (value.__typename === 'IssueFieldNumber' || value.__typename === 'IssueFieldNumberValue')
33
+ return 'number';
34
+ return null;
35
+ }
36
+ function normalizeFieldSchemas(value) {
37
+ if (!Array.isArray(value))
38
+ return [];
39
+ return value.flatMap((raw) => {
40
+ const item = raw;
41
+ const kind = fieldKind(item);
42
+ return item.id && item.name && kind ? [{
43
+ id: item.id,
44
+ name: item.name,
45
+ kind,
46
+ options: (item.options || []).flatMap((option) => option.id && option.name ? [{ id: option.id, name: option.name }] : [])
47
+ }] : [];
48
+ });
49
+ }
50
+ function normalizeCurrentFields(value) {
51
+ const issue = value?.data?.repository?.issue;
52
+ const type = issue?.issueType?.name ? {
53
+ id: issue.issueType.id || issue.issueType.name,
54
+ name: issue.issueType.name,
55
+ fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
56
+ } : null;
57
+ const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
58
+ const item = raw;
59
+ const kind = fieldKind(item);
60
+ const value = kind === 'single-select' ? item.name : item.value;
61
+ return item.field?.name && kind && value !== undefined
62
+ ? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
63
+ : [];
64
+ });
65
+ return { issueId: issue?.id || null, type, values };
66
+ }
67
+ function graphState(client, repository, issue, cwd) {
68
+ const [owner, name] = repository.split('/');
69
+ if (!owner || !name)
70
+ return null;
71
+ const current = client.json([
72
+ 'api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${issue}`
73
+ ], { cwd });
74
+ if (!current.ok)
75
+ return null;
76
+ return normalizeCurrentFields(current.value);
77
+ }
78
+ function fetchIssue(client, repository, issue, cwd) {
79
+ return client.json(['api', `repos/${repository}/issues/${issue}`], { cwd });
80
+ }
81
+ function inspectGitHubIssue(client, repository, issue, cwd) {
82
+ const fetched = fetchIssue(client, repository, issue, cwd);
83
+ if (!fetched.ok)
84
+ return fetched;
85
+ const snapshot = normalizeIssue(fetched.value, repository, issue);
86
+ return snapshot
87
+ ? { ok: true, value: snapshot }
88
+ : { ok: false, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
89
+ }
90
+ const CLOSING_PULL_REQUESTS_QUERY = `query($owner:String!,$name:String!,$issue:Int!,$cursor:String){repository(owner:$owner,name:$name){issue(number:$issue){closedByPullRequestsReferences(first:100,after:$cursor){nodes{number id url state title body isDraft headRefName headRefOid headRepository{nameWithOwner} baseRefName baseRefOid baseRepository{nameWithOwner} mergedAt mergeCommit{oid} labels(first:100){nodes{name}} assignees(first:100){nodes{login}} milestone{title}} pageInfo{hasNextPage endCursor}}}}}`;
91
+ function normalizePullRequest(remote, repository) {
92
+ const number = Number(remote.number);
93
+ const headRepository = remote.head?.repo?.full_name;
94
+ const baseRepository = remote.base?.repo?.full_name;
95
+ if (!Number.isInteger(number) || number <= 0 || !remote.node_id || !remote.html_url ||
96
+ !remote.head?.ref || !remote.head.sha || !headRepository || !remote.base?.ref || !baseRepository)
97
+ return null;
98
+ const mergeabilityDetail = remote.mergeable_state?.trim().toLowerCase() || null;
99
+ const mergeability = remote.mergeable === false
100
+ ? { state: 'conflicting', detail: mergeabilityDetail }
101
+ : remote.mergeable === true && mergeabilityDetail !== 'dirty'
102
+ ? { state: 'mergeable', detail: mergeabilityDetail }
103
+ : { state: 'unknown', detail: mergeabilityDetail };
104
+ return {
105
+ repository,
106
+ number,
107
+ nodeId: remote.node_id,
108
+ url: remote.html_url,
109
+ state: remote.state === 'closed' ? 'closed' : 'open',
110
+ title: remote.title || '',
111
+ body: remote.body || '',
112
+ draft: Boolean(remote.draft),
113
+ head: { repository: headRepository, ref: remote.head.ref, sha: remote.head.sha },
114
+ base: { repository: baseRepository, ref: remote.base.ref, sha: remote.base.sha || '' },
115
+ mergedAt: remote.merged_at || null,
116
+ mergeCommitSha: remote.merge_commit_sha || null,
117
+ labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
118
+ assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
119
+ milestone: remote.milestone?.title || null,
120
+ mergeability
121
+ };
122
+ }
123
+ function normalizeClosingPullRequest(node, repository) {
124
+ return normalizePullRequest({
125
+ number: node.number,
126
+ node_id: node.id,
127
+ html_url: node.url,
128
+ state: node.state === 'OPEN' ? 'open' : 'closed',
129
+ title: node.title,
130
+ body: node.body,
131
+ draft: node.isDraft,
132
+ head: { ref: node.headRefName, sha: node.headRefOid, repo: node.headRepository ? { full_name: node.headRepository.nameWithOwner } : null },
133
+ base: { ref: node.baseRefName, sha: node.baseRefOid, repo: node.baseRepository ? { full_name: node.baseRepository.nameWithOwner } : null },
134
+ merged_at: node.mergedAt,
135
+ merge_commit_sha: node.mergeCommit?.oid,
136
+ labels: node.labels?.nodes,
137
+ assignees: node.assignees?.nodes,
138
+ milestone: node.milestone
139
+ }, repository);
140
+ }
141
+ function inspectGitHubIssueClosingChangeRequests(client, repository, issueNumber, cwd) {
142
+ const [owner, name] = repository.split('/');
143
+ if (!owner || !name || !Number.isInteger(issueNumber) || issueNumber <= 0)
144
+ return {
145
+ ok: false,
146
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Repository or Issue identity is invalid', retryable: false }
147
+ };
148
+ const candidates = [];
149
+ let cursor = null;
150
+ for (;;) {
151
+ const args = [
152
+ 'api', 'graphql', '-f', `query=${CLOSING_PULL_REQUESTS_QUERY}`,
153
+ '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `issue=${issueNumber}`
154
+ ];
155
+ if (cursor)
156
+ args.push('-F', `cursor=${cursor}`);
157
+ const response = client.json(args, { cwd });
158
+ if (!response.ok)
159
+ return response;
160
+ const connection = response.value?.data?.repository?.issue?.closedByPullRequestsReferences;
161
+ if (!connection || !Array.isArray(connection.nodes) || !connection.pageInfo)
162
+ return {
163
+ ok: false,
164
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request response is incomplete', retryable: false }
165
+ };
166
+ for (const node of connection.nodes) {
167
+ const normalized = normalizeClosingPullRequest(node, repository);
168
+ if (!normalized)
169
+ return {
170
+ ok: false,
171
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request identity is incomplete', retryable: false }
172
+ };
173
+ candidates.push(normalized);
174
+ }
175
+ if (!connection.pageInfo.hasNextPage)
176
+ return { ok: true, value: candidates };
177
+ if (!connection.pageInfo.endCursor || connection.pageInfo.endCursor === cursor)
178
+ return {
179
+ ok: false,
180
+ error: { code: 'PR_IDENTITY_INVALID', message: 'Closing pull request pagination cursor is invalid', retryable: false }
181
+ };
182
+ cursor = connection.pageInfo.endCursor;
183
+ }
184
+ }
185
+ function inspectGitHubPullRequest(client, repository, number, cwd) {
186
+ const fetched = client.json(['api', `repos/${repository}/pulls/${number}`], { cwd });
187
+ if (!fetched.ok)
188
+ return fetched;
189
+ const pullRequest = normalizePullRequest(fetched.value, repository);
190
+ return pullRequest
191
+ ? { ok: true, value: pullRequest }
192
+ : { ok: false, error: { code: 'PR_IDENTITY_INVALID', message: 'Remote resource is not a valid pull request', retryable: false } };
193
+ }
194
+ function normalizeGitHubChecks(value) {
195
+ if (!Array.isArray(value))
196
+ return [];
197
+ return value.flatMap((raw) => {
198
+ const item = raw;
199
+ const name = String(item.name || item.context || '');
200
+ return name ? [{
201
+ name,
202
+ status: checkStatusBucket(String(item.bucket || item.status || item.conclusion || item.state || '')),
203
+ conclusion: item.conclusion ? String(item.conclusion) : item.state ? String(item.state) : null,
204
+ detailsUrl: item.link ? String(item.link) : item.detailsUrl ? String(item.detailsUrl) : null
205
+ }] : [];
206
+ });
207
+ }
208
+ function inspectGitHubRequiredChecks(client, repository, number, cwd) {
209
+ const inspected = client.json([
210
+ 'pr', 'checks', String(number), '--repo', repository,
211
+ '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
212
+ ], { cwd });
213
+ return inspected.ok
214
+ ? { ok: true, value: normalizeGitHubChecks(inspected.value) }
215
+ : { ok: false, error: inspected.error };
216
+ }
217
+ function parseRunJobIdentity(detailsUrl) {
218
+ try {
219
+ const url = new URL(detailsUrl);
220
+ if (url.hostname !== 'github.com')
221
+ return null;
222
+ const match = url.pathname.match(/\/actions\/runs\/(\d+)(?:\/job\/(\d+))?/);
223
+ if (!match)
224
+ return null;
225
+ return { runId: Number(match[1]), jobId: match[2] ? Number(match[2]) : null };
226
+ }
227
+ catch {
228
+ return null;
229
+ }
230
+ }
231
+ function fetchCheckLogText(client, args, cwd) {
232
+ if (!client.text)
233
+ return { ok: false, error: { code: 'PLATFORM_CLIENT_TEXT_UNAVAILABLE', message: 'Platform client does not support text responses', retryable: false } };
234
+ const fetched = client.text(args, { cwd });
235
+ if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
236
+ return fetched;
237
+ }
238
+ return client.text([...args, '--allow-escape-sequences'], { cwd });
239
+ }
240
+ export { ISSUE_FIELDS_QUERY, graphState, inspectGitHubIssue, normalizePullRequest, inspectGitHubIssueClosingChangeRequests, inspectGitHubPullRequest, inspectGitHubRequiredChecks, parseRunJobIdentity, fetchCheckLogText };
241
+ //# sourceMappingURL=github-data.js.map