@moretea-labs/forge 1.8.1 → 1.8.2

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 (328) hide show
  1. package/AGENTS.md +5 -2
  2. package/CHANGELOG.md +20 -0
  3. package/README.en.md +24 -3
  4. package/README.md +10 -6
  5. package/README.zh-CN.md +5 -3
  6. package/THIRD_PARTY_NOTICES.md +1 -2
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
  8. package/adapters/chatgpt/provider-delivery.ts +1 -1
  9. package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
  10. package/adapters/computer/chatgpt-extension-target.ts +295 -50
  11. package/adapters/computer/chrome-extension/background.js +227 -71
  12. package/adapters/computer/chrome-extension/content.js +181 -157
  13. package/adapters/computer/chrome-extension/manifest.json +3 -1
  14. package/adapters/computer/desktop-operator-provider.ts +18 -6
  15. package/adapters/computer/interaction-target-authority.ts +39 -9
  16. package/adapters/mcp/access-mode.ts +18 -39
  17. package/adapters/mcp/auth.ts +2 -6
  18. package/adapters/mcp/instructions.ts +2 -2
  19. package/adapters/mcp/multi-repository.ts +7 -28
  20. package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
  21. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
  22. package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
  23. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
  24. package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
  25. package/adapters/mcp/runtime-gateway/router.ts +3 -5
  26. package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
  27. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
  28. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
  29. package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
  30. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
  31. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
  32. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
  33. package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
  34. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
  35. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
  36. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
  37. package/adapters/mcp/server.ts +24 -3
  38. package/adapters/mcp/setup.ts +4 -10
  39. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  40. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
  41. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
  42. package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
  43. package/adapters/mcp/toolset-names.ts +11 -42
  44. package/adapters/mcp/toolset.ts +16 -90
  45. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  46. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  47. package/adapters/mcp/transports/http-observation.ts +18 -6
  48. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  49. package/adapters/mcp/transports/http.ts +54 -14
  50. package/adapters/mcp/types.ts +0 -6
  51. package/assets/hooks/lib/session-state.sh +0 -38
  52. package/assets/hooks/post-bash.sh +1 -1
  53. package/assets/hooks/post-tool-observer.sh +0 -5
  54. package/assets/hooks/prompt-guard.sh +0 -55
  55. package/assets/partials/04-project-structure.partial.md +1 -1
  56. package/assets/partials/07-footer.partial.md +1 -1
  57. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  58. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  59. package/assets/reference-configs/agentic-development-flow.md +1 -1
  60. package/assets/reference-configs/external-tooling.md +0 -76
  61. package/assets/reference-configs/release-deploy.md +1 -1
  62. package/assets/templates/helpers/architecture-queue.sh +1 -1
  63. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  64. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  65. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  66. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  67. package/assets/workflow-contract.v1.json +0 -2
  68. package/docs/README.md +5 -2
  69. package/docs/architecture/CURRENT.md +114 -30
  70. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  71. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
  72. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
  73. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
  74. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  75. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  76. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  77. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  78. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  79. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  80. package/docs/architecture/domains/public-surface.md +3 -5
  81. package/docs/architecture/domains/verification.md +5 -5
  82. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
  83. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  84. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  85. package/docs/forge-plugin-management.md +14 -2
  86. package/docs/operations/controller-browser-plugin.md +23 -0
  87. package/docs/operations/features.md +6 -7
  88. package/docs/operations/features.zh-CN.md +1 -1
  89. package/docs/operations/github-repository.md +4 -4
  90. package/docs/operations/github-semantic-release-projection.md +20 -0
  91. package/docs/operations/homebrew.md +2 -2
  92. package/docs/operations/mcp-tool-exposure.md +13 -54
  93. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  94. package/docs/operations/standalone-disaster-recovery.md +1 -1
  95. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  96. package/docs/tutorials/03-first-repository-task.md +2 -0
  97. package/docs/wiki/Computer-and-Plugins.md +78 -0
  98. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  99. package/docs/wiki/Home.md +16 -2
  100. package/docs/wiki/Implementation.md +24 -12
  101. package/docs/wiki/Installation.md +28 -32
  102. package/docs/wiki/Integrations.md +4 -0
  103. package/docs/wiki/Quick-Start.md +35 -22
  104. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  105. package/docs/wiki/_Sidebar.md +5 -1
  106. package/package.json +1 -12
  107. package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
  108. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
  109. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  110. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
  111. package/packages/kernel/work/application/work-service.ts +1 -0
  112. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  113. package/packages/kernel/work/domain/state-machine.ts +9 -2
  114. package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
  115. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  116. package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
  117. package/scripts/assemble-template.ts +11 -72
  118. package/scripts/check-mcp-compatibility.ts +55 -74
  119. package/scripts/check-package-identity.mjs +0 -5
  120. package/scripts/check-runtime-architecture.mjs +305 -28
  121. package/scripts/init-project.sh +0 -46
  122. package/scripts/install-ios-hid-toolchain.sh +60 -0
  123. package/scripts/install-standalone-recovery.ts +1 -1
  124. package/scripts/lib/project-init-lib.sh +3 -3
  125. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  126. package/scripts/open-source-audit-allowlist.txt +0 -1
  127. package/scripts/run-governed-gate.ts +2 -0
  128. package/scripts/smoke-ios-physical-device.ts +38 -10
  129. package/scripts/smoke-mcp-http-runtime.ts +7 -8
  130. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  131. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
  132. package/scripts/sync-generated-authority.mjs +0 -1
  133. package/src/cli/agent-jobs/executor-health.ts +1 -1
  134. package/src/cli/agent-jobs/integration.ts +2 -2
  135. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  136. package/src/cli/commands/doctor.ts +1 -143
  137. package/src/cli/commands/global-runtime.ts +3 -196
  138. package/src/cli/commands/init-hook.ts +6 -23
  139. package/src/cli/commands/init.ts +3 -252
  140. package/src/cli/commands/mcp.ts +0 -5
  141. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  142. package/src/cli/commands/recovery.ts +2 -14
  143. package/src/cli/commands/repository.ts +17 -1
  144. package/src/cli/commands/setup-execution.ts +41 -0
  145. package/src/cli/controller/completion-orchestrator.ts +1 -1
  146. package/src/cli/controller/composite-operations.ts +1 -1
  147. package/src/cli/controller/context/instruction-context.ts +1 -1
  148. package/src/cli/controller/context/known-paths.ts +2 -2
  149. package/src/cli/controller/context/query-planning.ts +11 -4
  150. package/src/cli/controller/context/rule-resolution.ts +2 -2
  151. package/src/cli/controller/context/source-materializer.ts +2 -2
  152. package/src/cli/controller/context/structural-provider.ts +84 -0
  153. package/src/cli/controller/context/structural.ts +6 -6
  154. package/src/cli/controller/context/types.ts +8 -9
  155. package/src/cli/controller/context-pack.ts +97 -76
  156. package/src/cli/controller/operational-plan.ts +1 -1
  157. package/src/cli/controller/systemd-user.ts +4 -10
  158. package/src/cli/editing/edit-session.ts +17 -5
  159. package/src/cli/github/github.ts +4 -4
  160. package/src/cli/github/semantic-projection.ts +174 -0
  161. package/src/cli/hook/prompt-intents.ts +0 -9
  162. package/src/cli/index.ts +1 -43
  163. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  164. package/src/cli/local-bridge/console-automations.ts +1 -1
  165. package/src/cli/local-bridge/console-view-models.ts +1 -1
  166. package/src/cli/local-bridge/facade-api.ts +0 -2
  167. package/src/cli/repositories/command-classifier.ts +0 -9
  168. package/src/cli/repositories/command-scope.ts +2 -2
  169. package/src/cli/repositories/locks.ts +20 -0
  170. package/src/cli/repositories/registry.ts +2 -1
  171. package/src/cli/repositories/safe-patch.ts +2 -12
  172. package/src/cli/repositories/structured-git.ts +39 -3
  173. package/src/cli/repository/inspector.ts +185 -343
  174. package/src/effects/process-runner.ts +17 -1
  175. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  176. package/src/runtime/context/semantic-navigation-contract.ts +7 -0
  177. package/src/runtime/context/semantic-navigation.ts +1 -1
  178. package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
  179. package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
  180. package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
  181. package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
  182. package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
  183. package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
  184. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  185. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
  186. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
  187. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
  188. package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
  189. package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
  190. package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
  191. package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
  192. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
  193. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  194. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
  195. package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
  196. package/src/runtime/evidence/sensitive-output.ts +22 -2
  197. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  198. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  199. package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
  200. package/src/runtime/execution/process-runtime/runtime.ts +20 -16
  201. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  202. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  203. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  204. package/src/runtime/execution/workers/executor.ts +1 -2
  205. package/src/runtime/maintenance/cleanup.ts +41 -5
  206. package/src/runtime/plugins/action-application.ts +12 -1
  207. package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
  208. package/src/runtime/plugins/browser-adapter.ts +381 -313
  209. package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
  210. package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
  211. package/src/runtime/plugins/browser-registration.ts +2 -0
  212. package/src/runtime/plugins/browser-runtime.ts +26 -35
  213. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
  214. package/src/runtime/plugins/computer-registration.ts +76 -9
  215. package/src/runtime/plugins/github-adapter.ts +30 -3
  216. package/src/runtime/plugins/gmail-adapter.ts +166 -1
  217. package/src/runtime/plugins/google-shared.ts +10 -5
  218. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  219. package/src/runtime/plugins/ios-adapter.ts +45 -40
  220. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  221. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  222. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  223. package/src/runtime/plugins/local-system-adapter.ts +1 -99
  224. package/src/runtime/plugins/store.ts +80 -7
  225. package/src/runtime/plugins/types.ts +4 -0
  226. package/src/runtime/projections/git-status-sampler.ts +2 -19
  227. package/src/runtime/recovery/actions.ts +1 -1
  228. package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
  229. package/src/runtime/recovery/probe.ts +2 -2
  230. package/src/runtime/release/release-coordinator.ts +20 -4
  231. package/src/runtime/root/controller-progression-composition.ts +67 -5
  232. package/src/runtime/root/package-connector-service.ts +123 -11
  233. package/src/runtime/root/package-runtime-release.ts +6 -18
  234. package/src/runtime/root/package-runtime-service.ts +7 -13
  235. package/src/runtime/root/release-execution-canary.ts +18 -5
  236. package/src/runtime/root/release-manifest.ts +0 -34
  237. package/src/runtime/root/release-materialize.ts +0 -78
  238. package/src/runtime/root/runtime.ts +1 -2
  239. package/src/runtime/root/scheduled-controller-composition.ts +2 -1
  240. package/src/runtime/root/service.ts +90 -11
  241. package/src/runtime/root/types.ts +0 -6
  242. package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
  243. package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
  244. package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
  245. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  246. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  247. package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
  248. package/src/runtime/shared/atomic-text-file.ts +21 -0
  249. package/src/runtime/shared/json-files.ts +8 -3
  250. package/src/runtime/standalone-recovery/core.ts +189 -174
  251. package/src/runtime/standalone-recovery/entry.ts +62 -30
  252. package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
  253. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
  254. package/src/runtime/workflow/schedules/engine.ts +36 -14
  255. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  256. package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
  257. package/supervisor/client.ts +31 -14
  258. package/supervisor/control-plane.ts +210 -115
  259. package/supervisor/forge-validators.ts +88 -21
  260. package/supervisor/native-browser-adapter.ts +393 -28
  261. package/supervisor/native-messaging/host.ts +21 -2
  262. package/supervisor/protocol.ts +74 -26
  263. package/supervisor/server.ts +16 -8
  264. package/supervisor/store.ts +476 -57
  265. package/supervisor/types.ts +19 -13
  266. package/SKILL.md +0 -427
  267. package/adapters/computer/chatgpt-target-router.ts +0 -103
  268. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
  269. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  270. package/adapters/mcp/toolset-selection.ts +0 -41
  271. package/agents/openai.yaml +0 -4
  272. package/assets/skill-hooks.json +0 -41
  273. package/assets/skill-version.json +0 -189
  274. package/assets/skills/claude-review/SKILL.md +0 -231
  275. package/assets/skills/codex-review/SKILL.md +0 -103
  276. package/assets/templates/helpers/check-skill-version.ts +0 -193
  277. package/scripts/check-skill-version.ts +0 -5
  278. package/scripts/ensure-codegraph.sh +0 -16
  279. package/scripts/run-skill-hook.ts +0 -244
  280. package/scripts/sync-codex-installed-copies.sh +0 -182
  281. package/src/cli/commands/tools.ts +0 -167
  282. package/src/cli/mcp/access-mode.ts +0 -2
  283. package/src/cli/mcp/access-tools.ts +0 -2
  284. package/src/cli/mcp/audit.ts +0 -2
  285. package/src/cli/mcp/auth.ts +0 -2
  286. package/src/cli/mcp/instructions.ts +0 -2
  287. package/src/cli/mcp/legacy-context.ts +0 -2
  288. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  289. package/src/cli/mcp/multi-repository.ts +0 -2
  290. package/src/cli/mcp/oauth.ts +0 -2
  291. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  292. package/src/cli/mcp/paths.ts +0 -2
  293. package/src/cli/mcp/policy.ts +0 -2
  294. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  295. package/src/cli/mcp/proxy-env.ts +0 -2
  296. package/src/cli/mcp/readiness-projection.ts +0 -2
  297. package/src/cli/mcp/redaction.ts +0 -2
  298. package/src/cli/mcp/repo.ts +0 -2
  299. package/src/cli/mcp/repository-tools.ts +0 -2
  300. package/src/cli/mcp/server.ts +0 -2
  301. package/src/cli/mcp/setup.ts +0 -2
  302. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  303. package/src/cli/mcp/tool-contract.ts +0 -2
  304. package/src/cli/mcp/tools.ts +0 -2
  305. package/src/cli/mcp/toolset-names.ts +0 -2
  306. package/src/cli/mcp/toolset-selection.ts +0 -2
  307. package/src/cli/mcp/toolset.ts +0 -2
  308. package/src/cli/mcp/transports/http.ts +0 -2
  309. package/src/cli/mcp/transports/session-registry.ts +0 -2
  310. package/src/cli/mcp/transports/stdio.ts +0 -2
  311. package/src/cli/mcp/types.ts +0 -2
  312. package/src/cli/mcp/umbrella-tools.ts +0 -2
  313. package/src/cli/tools/codegraph.ts +0 -695
  314. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  315. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  316. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  317. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  318. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  319. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  320. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  321. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  322. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  323. package/src/runtime/gateway/mcp/router.ts +0 -2
  324. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  325. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  326. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  327. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
  328. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
@@ -1,6 +1,6 @@
1
- import { createPrivateKey, sign } from 'crypto';
1
+ import { createHash, createPrivateKey, sign } from 'crypto';
2
2
  import { accessSync, constants as fsConstants, existsSync, mkdirSync, promises as fsPromises, readFileSync, statSync, writeFileSync } from 'fs';
3
- import { dirname, join } from 'path';
3
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'path';
4
4
  import { CONTROLLER_SCOPE_REPO_ID, controllerSystemRoot } from '../../cli/repositories/controller-home';
5
5
  import type {
6
6
  AssistantPluginActionDescriptor,
@@ -459,6 +459,7 @@ function permissions(ready: boolean): AssistantPluginPermissionScope[] {
459
459
  permission('appstoreconnect.apps.read', 'read', 'Read App Store Connect apps, versions, localizations, builds, and TestFlight groups.', ready),
460
460
  permission('appstoreconnect.developer_resources.read', 'read', 'Read Apple Developer bundle IDs, capabilities, certificates, devices, and provisioning profiles.', ready),
461
461
  permission('appstoreconnect.metadata.write', 'write', 'Patch App Store Connect metadata after dry-run review and authorization.', ready),
462
+ permission('appstoreconnect.screenshots.write', 'write', 'Upload and order App Store screenshots in verified app/version/locale/device sets.', ready),
462
463
  permission('appstoreconnect.testflight.write', 'write', 'Assign builds to TestFlight groups and prepare beta review submissions.', ready),
463
464
  permission('appstoreconnect.release.write', 'write', 'Create App Store versions and gated review submissions.', ready),
464
465
  permission('appstoreconnect.xcodecloud.read', 'read', 'Read Xcode Cloud products and workflow configuration.', ready),
@@ -486,6 +487,12 @@ function capabilities(): AssistantPluginCapability[] {
486
487
  scopes: ['appstoreconnect.developer_resources.read'],
487
488
  actions: ['list_bundle_ids', 'list_bundle_id_capabilities', 'list_certificates', 'list_devices', 'list_profiles'],
488
489
  },
490
+ {
491
+ capabilityId: 'app-store-screenshots', title: 'App Store Screenshots',
492
+ description: 'Read screenshot sets and safely upload or order localized screenshots through official Apple reservations and upload operations.',
493
+ scopes: ['appstoreconnect.apps.read', 'appstoreconnect.screenshots.write'],
494
+ actions: ['list_app_screenshot_sets', 'list_app_screenshots', 'upload_app_screenshot', 'reorder_app_screenshots'],
495
+ },
489
496
  {
490
497
  capabilityId: 'app-store-metadata',
491
498
  title: 'App Metadata Update',
@@ -526,7 +533,7 @@ function actions(): AssistantPluginActionDescriptor[] {
526
533
  return [
527
534
  {
528
535
  actionId: 'configure', title: 'Configure App Store Connect plugin', description: 'Enable official App Store Connect API access and save non-secret defaults.', readOnly: false, risk: 'workspace_write', confirmation: 'authorization', defaultTimeoutMs: 30_000, cancellable: true, idempotent: true,
529
- scopes: ['appstoreconnect.apps.read', 'appstoreconnect.developer_resources.read', 'appstoreconnect.metadata.write', 'appstoreconnect.testflight.write', 'appstoreconnect.release.write', 'appstoreconnect.xcodecloud.read', 'appstoreconnect.xcodecloud.write'], resourceClaims: [{ resource: 'repo-state', mode: 'write' }],
536
+ scopes: ['appstoreconnect.apps.read', 'appstoreconnect.developer_resources.read', 'appstoreconnect.metadata.write', 'appstoreconnect.screenshots.write', 'appstoreconnect.testflight.write', 'appstoreconnect.release.write', 'appstoreconnect.xcodecloud.read', 'appstoreconnect.xcodecloud.write'], resourceClaims: [{ resource: 'repo-state', mode: 'write' }],
530
537
  argumentsSchema: { type: 'object', properties: { enabled: { type: 'boolean' }, provider: { type: 'string', enum: ['mock', 'app-store-connect-api'] }, issuer_id: { type: 'string' }, key_id: { type: 'string' }, private_key_path: { type: 'string' }, clear_private_key_path: { type: 'boolean' }, clear_api_identity: { type: 'boolean' }, team_id: { type: 'string' }, clear_team_id: { type: 'boolean' }, default_app_id: { type: 'string' }, clear_default_app_id: { type: 'boolean' }, default_locale: { type: 'string' }, default_timeout_ms: { type: 'number' } }, additionalProperties: false },
531
538
  },
532
539
  { actionId: 'auth_status', title: 'Check App Store Connect auth', description: 'Report API readiness without returning secrets.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 10_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: [], argumentsSchema: { type: 'object', properties: {}, additionalProperties: false } },
@@ -538,6 +545,10 @@ function actions(): AssistantPluginActionDescriptor[] {
538
545
  { actionId: 'list_profiles', title: 'List provisioning profiles', description: 'Read Apple Developer provisioning profiles, optionally filtered by profile name or type.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 45_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.developer_resources.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { name: { type: 'string' }, profile_type: { type: 'string' }, limit: { type: 'number' } }, additionalProperties: false } },
539
546
  { actionId: 'list_app_store_versions', title: 'List App Store versions', description: 'List App Store versions for one app.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 45_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, limit: { type: 'number' } }, additionalProperties: false } },
540
547
  { actionId: 'list_app_store_version_localizations', title: 'List App Store version localizations', description: 'List localizations for one App Store version.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 45_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { version_id: { type: 'string' }, limit: { type: 'number' } }, required: ['version_id'], additionalProperties: false } },
548
+ { actionId: 'list_app_screenshot_sets', title: 'List localized screenshot sets', description: 'Read device screenshot sets for a verified exact App/version/locale.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 60_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, version_id: { type: 'string' }, localization_id: { type: 'string' }, locale: { type: 'string' } }, required: ['app_id', 'version_id', 'localization_id', 'locale'], additionalProperties: false } },
549
+ { actionId: 'list_app_screenshots', title: 'List localized screenshots', description: 'Read screenshot upload state and checksum for an exact device screenshot set.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 60_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, version_id: { type: 'string' }, localization_id: { type: 'string' }, locale: { type: 'string' }, display_type: { type: 'string' } }, required: ['app_id', 'version_id', 'localization_id', 'locale', 'display_type'], additionalProperties: false } },
550
+ { actionId: 'upload_app_screenshot', title: 'Upload localized App screenshot', description: 'Verify exact App/version/locale/device, reserve Apple upload, PUT signed byte ranges, commit MD5, return processing state. Reconcile by checksum before retry.', readOnly: false, risk: 'remote_write', confirmation: 'authorization', defaultTimeoutMs: 180_000, cancellable: true, idempotent: false, scopes: ['appstoreconnect.screenshots.write'], resourceClaims: writeRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, version_id: { type: 'string' }, localization_id: { type: 'string' }, locale: { type: 'string' }, display_type: { type: 'string' }, file_path: { type: 'string' }, expected_sha256: { type: 'string' }, dry_run: { type: 'boolean' } }, required: ['app_id', 'version_id', 'localization_id', 'locale', 'display_type', 'file_path'], additionalProperties: false } },
551
+ { actionId: 'reorder_app_screenshots', title: 'Reorder localized screenshots', description: 'Replace exact screenshot ID order after reconciling same-set membership and processing state.', readOnly: false, risk: 'remote_write', confirmation: 'authorization', defaultTimeoutMs: 90_000, cancellable: true, idempotent: false, scopes: ['appstoreconnect.screenshots.write'], resourceClaims: writeRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, version_id: { type: 'string' }, localization_id: { type: 'string' }, locale: { type: 'string' }, display_type: { type: 'string' }, screenshot_ids: { type: 'array', items: { type: 'string' }, minItems: 1, maxItems: 10 }, dry_run: { type: 'boolean' } }, required: ['app_id', 'version_id', 'localization_id', 'locale', 'display_type', 'screenshot_ids'], additionalProperties: false } },
541
552
  { actionId: 'get_app_info', title: 'Get app info', description: 'Get App Info records and localizations for one app.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 45_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' } }, additionalProperties: false } },
542
553
  { actionId: 'list_app_infos', title: 'List app infos', description: 'List App Info records for one app.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 45_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, limit: { type: 'number' } }, additionalProperties: false } },
543
554
  { actionId: 'list_builds', title: 'List builds', description: 'List recent builds for one app with processing state fields.', readOnly: true, risk: 'readonly', confirmation: 'none', defaultTimeoutMs: 45_000, cancellable: true, idempotent: true, scopes: ['appstoreconnect.apps.read'], resourceClaims: readRemote, argumentsSchema: { type: 'object', properties: { app_id: { type: 'string' }, limit: { type: 'number' } }, additionalProperties: false } },
@@ -588,6 +599,205 @@ function actions(): AssistantPluginActionDescriptor[] {
588
599
  ];
589
600
  }
590
601
 
602
+
603
+ type AscScreenshotResource = {
604
+ id: string;
605
+ attributes?: {
606
+ locale?: string; platform?: string; appStoreState?: string; screenshotDisplayType?: string; fileName?: string; fileSize?: number;
607
+ sourceFileChecksum?: string | null; assetDeliveryState?: { state?: string; errors?: unknown[] };
608
+ uploadOperations?: Array<{ method: string; url: string; offset: number; length: number;
609
+ requestHeaders?: Array<{ name: string; value: string }> }>;
610
+ };
611
+ relationships?: Record<string, { data?: { id: string } | null }>;
612
+ };
613
+ type AscScreenshotResponse = { data: AscScreenshotResource };
614
+ type AscScreenshotList = { data: AscScreenshotResource[] };
615
+ function safeScreenshot(resource: AscScreenshotResource): Record<string, unknown> {
616
+ return { id: resource.id, fileName: resource.attributes?.fileName, fileSize: resource.attributes?.fileSize,
617
+ sourceFileChecksum: resource.attributes?.sourceFileChecksum ?? null,
618
+ state: resource.attributes?.assetDeliveryState?.state,
619
+ processingErrorCount: resource.attributes?.assetDeliveryState?.errors?.length ?? 0 };
620
+ }
621
+ async function verifyScreenshotOwner(config: AppStoreConnectPluginConfig, args: Record<string, unknown>): Promise<string> {
622
+ const app = requiredArg(args, 'app_id'), versionId = requiredArg(args, 'version_id');
623
+ const locId = requiredArg(args, 'localization_id'), locale = requiredArg(args, 'locale');
624
+ // Use the exact App's version and that version's localization collections:
625
+ // JSON:API relationships may contain only links, not linkage data.
626
+ const versions = await apiRequest<AscScreenshotList>(config, {
627
+ path: '/v1/apps/' + encodeURIComponent(app) + '/appStoreVersions', query: { limit: 200 },
628
+ });
629
+ const exactVersion = versions.data?.find((v) => v.id === versionId);
630
+ if (!exactVersion || exactVersion.attributes?.platform !== 'IOS' ||
631
+ exactVersion.attributes?.appStoreState !== 'PREPARE_FOR_SUBMISSION')
632
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Screenshot target must be an editable iOS version of the specified App.', { retryable: false });
633
+ const localizations = await apiRequest<AscScreenshotList>(config, {
634
+ path: '/v1/appStoreVersions/' + encodeURIComponent(versionId) + '/appStoreVersionLocalizations',
635
+ query: { 'filter[locale]': locale, limit: 200 },
636
+ });
637
+ if (!localizations.data?.some((loc) => loc.id === locId && loc.attributes?.locale === locale))
638
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Screenshot version/locale ownership mismatch.', { retryable: false });
639
+ return locId;
640
+ }
641
+ function screenshotType(args: Record<string, unknown>): string {
642
+ const display = requiredArg(args, 'display_type');
643
+ if (!/^APP_(IPHONE|IPAD|WATCH|APPLE_TV|DESKTOP|APPLE_VISION_PRO)[A-Z0-9_]*$/.test(display))
644
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Unsupported screenshot display type.', { retryable: false });
645
+ return display;
646
+ }
647
+ async function listScreenshotSets(config: AppStoreConnectPluginConfig, localizationId: string): Promise<AscScreenshotResource[]> {
648
+ const response = await apiRequest<AscScreenshotList>(config, {
649
+ path: '/v1/appStoreVersionLocalizations/' + encodeURIComponent(localizationId) + '/appScreenshotSets', query: { limit: 100 },
650
+ });
651
+ if (!Array.isArray(response.data)) throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Malformed screenshot sets.', { retryable: false });
652
+ return response.data;
653
+ }
654
+ async function findScreenshotSet(config: AppStoreConnectPluginConfig, locId: string, display: string, create: boolean): Promise<string | undefined> {
655
+ const matches = (await listScreenshotSets(config, locId)).filter((s) => s.attributes?.screenshotDisplayType === display);
656
+ if (matches.length > 1) throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Duplicate device screenshot sets.', { retryable: false });
657
+ if (matches.length) return matches[0].id;
658
+ if (!create) return undefined;
659
+ const created = await apiRequest<AscScreenshotResponse>(config, { path: '/v1/appScreenshotSets', method: 'POST',
660
+ body: { data: { type: 'appScreenshotSets', attributes: { screenshotDisplayType: display },
661
+ relationships: { appStoreVersionLocalization: { data: { type: 'appStoreVersionLocalizations', id: locId } } } } } });
662
+ if (!created.data?.id) throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple did not return a screenshot set ID.', { retryable: false });
663
+ return created.data.id;
664
+ }
665
+ async function listScreenshots(config: AppStoreConnectPluginConfig, setId: string): Promise<AscScreenshotResource[]> {
666
+ const response = await apiRequest<AscScreenshotList>(config, {
667
+ path: '/v1/appScreenshotSets/' + encodeURIComponent(setId) + '/appScreenshots', query: { limit: 100 },
668
+ });
669
+ if (!Array.isArray(response.data)) throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Malformed screenshot inventory.', { retryable: false });
670
+ return response.data;
671
+ }
672
+ async function screenshotFile(repoRoot: string, inputPath: string) {
673
+ const root = await fsPromises.realpath(repoRoot);
674
+ const path = await fsPromises.realpath(resolve(root, inputPath));
675
+ const rel = relative(root, path);
676
+ if (!rel || rel === '..' || rel.startsWith('../') || isAbsolute(rel))
677
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Screenshot file must be inside the authorized checkout.', { retryable: false });
678
+ const stat = await fsPromises.stat(path);
679
+ if (!stat.isFile() || stat.size === 0 || stat.size > 25 * 1024 * 1024)
680
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Screenshot must be a nonempty image under 25 MiB.', { retryable: false });
681
+ const bytes = await fsPromises.readFile(path), name = basename(path), ext = extname(name).toLowerCase();
682
+ const jpeg = (ext === '.jpg' || ext === '.jpeg') && bytes[0] === 0xff && bytes[1] === 0xd8;
683
+ const png = ext === '.png' && bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]));
684
+ if (!jpeg && !png) throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Screenshot image extension/signature mismatch.', { retryable: false });
685
+ return { bytes, name, relativePath: rel, sha256: createHash('sha256').update(bytes).digest('hex'), md5: createHash('md5').update(bytes).digest('hex') };
686
+ }
687
+ async function uploadScreenshotParts(parts: NonNullable<NonNullable<AscScreenshotResource['attributes']>['uploadOperations']>, bytes: Buffer): Promise<void> {
688
+ const ordered = [...parts].sort((a, b) => a.offset - b.offset);
689
+ let cursor = 0;
690
+ for (const part of ordered) {
691
+ if (!Number.isSafeInteger(part.offset) || !Number.isSafeInteger(part.length) || part.length <= 0 ||
692
+ part.offset !== cursor || part.length + cursor > bytes.length || part.method !== 'PUT')
693
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Invalid or non-contiguous Apple upload range.', { retryable: false });
694
+ const url = new URL(part.url);
695
+ if (url.protocol !== 'https:' || !(url.hostname === 'apple.com' || url.hostname.endsWith('.apple.com')) ||
696
+ url.username || url.password)
697
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Unexpected Apple upload host.', { retryable: false });
698
+ for (const h of part.requestHeaders ?? [])
699
+ if (!/^(content-type|content-md5|x-amz-[a-z0-9-]+|x-apple-[a-z0-9-]+)$/i.test(h.name))
700
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Unexpected Apple upload header.', { retryable: false });
701
+ cursor += part.length;
702
+ }
703
+ if (!ordered.length || cursor !== bytes.length)
704
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple upload operations do not cover the file.', { retryable: false });
705
+ for (const part of ordered) {
706
+ const controller = new AbortController(), timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
707
+ try {
708
+ const response = await fetch(part.url, { method: 'PUT',
709
+ headers: Object.fromEntries((part.requestHeaders ?? []).map((h) => [h.name, h.value])),
710
+ body: new Uint8Array(bytes.subarray(part.offset, part.offset + part.length)), redirect: 'manual', signal: controller.signal });
711
+ if (!response.ok) throw new Error('UPLOAD_HTTP_ERROR');
712
+ } catch {
713
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Screenshot part outcome is unknown. Reconcile reservation before retry.', {
714
+ retryable: false, details: { offset: part.offset, length: part.length } });
715
+ } finally { clearTimeout(timer); }
716
+ }
717
+ }
718
+ async function waitForScreenshotProcessing(config: AppStoreConnectPluginConfig, screenshotId: string, md5: string):
719
+ Promise<{ resource: AscScreenshotResource; complete: boolean }> {
720
+ // Apple commits asynchronously: UPLOAD_COMPLETE means accepted, not processed.
721
+ // Bounded polling never repeats a reservation or an uncertain upload/commit.
722
+ const deadline = Date.now() + 45_000;
723
+ for (;;) {
724
+ const response = await apiRequest<AscScreenshotResponse>(config, {
725
+ path: '/v1/appScreenshots/' + encodeURIComponent(screenshotId),
726
+ timeoutMs: 15_000,
727
+ });
728
+ const resource = response.data;
729
+ if (!resource || resource.id !== screenshotId)
730
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple screenshot identity readback failed; inspect before retry.', {
731
+ retryable: false, details: { screenshotId } });
732
+ const checksum = resource.attributes?.sourceFileChecksum?.toLowerCase();
733
+ if (checksum && checksum !== md5)
734
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple screenshot checksum mismatch; inspect before retry.', {
735
+ retryable: false, details: { screenshotId } });
736
+ const state = resource.attributes?.assetDeliveryState?.state;
737
+ if (state === 'FAILED')
738
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple screenshot processing failed; inspect before retry.', {
739
+ retryable: false, details: { screenshotId, processingErrorCount: resource.attributes?.assetDeliveryState?.errors?.length ?? 0 } });
740
+ if (state === 'COMPLETE') {
741
+ if (checksum !== md5)
742
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple screenshot completed without a confirmed checksum; inspect before retry.', {
743
+ retryable: false, details: { screenshotId } });
744
+ return { resource, complete: true };
745
+ }
746
+ if (Date.now() >= deadline) {
747
+ if (state !== 'UPLOAD_COMPLETE')
748
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple screenshot commit state is unresolved; inspect before retry.', {
749
+ retryable: false, details: { screenshotId, state: state ?? null } });
750
+ return { resource, complete: false };
751
+ }
752
+ await new Promise<void>((resolve) => setTimeout(resolve, Math.min(2_500, deadline - Date.now())));
753
+ }
754
+ }
755
+ async function uploadOfficialScreenshot(config: AppStoreConnectPluginConfig, args: Record<string, unknown>, repoRoot: string): Promise<Record<string, unknown>> {
756
+ const file = await screenshotFile(repoRoot, requiredArg(args, 'file_path'));
757
+ if (args.expected_sha256 && (stringValue(args.expected_sha256)?.toLowerCase() !== file.sha256))
758
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Screenshot SHA-256 differs from expected.', { retryable: false });
759
+ const display = screenshotType(args), loc = await verifyScreenshotOwner(config, args);
760
+ const existingSet = await findScreenshotSet(config, loc, display, false);
761
+ const existing = existingSet ? await listScreenshots(config, existingSet) : [];
762
+ const sameName = existing.filter((r) => r.attributes?.fileName === file.name);
763
+ if (sameName.length > 1)
764
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Multiple screenshots share the source filename; inspect before retry.', {
765
+ retryable: false, details: { screenshotIds: sameName.map((r) => r.id) } });
766
+ const complete = sameName.find((r) => r.attributes?.sourceFileChecksum?.toLowerCase() === file.md5 &&
767
+ r.attributes?.assetDeliveryState?.state === 'COMPLETE');
768
+ if (complete) return { applied: false, alreadyPresent: true, processingComplete: true, setId: existingSet, screenshot: safeScreenshot(complete) };
769
+ const committed = sameName.find((r) => r.attributes?.sourceFileChecksum?.toLowerCase() === file.md5 &&
770
+ r.attributes?.assetDeliveryState?.state === 'UPLOAD_COMPLETE');
771
+ if (committed) {
772
+ const processed = await waitForScreenshotProcessing(config, committed.id, file.md5);
773
+ return { applied: false, alreadyPresent: true, processingComplete: processed.complete, setId: existingSet,
774
+ screenshot: safeScreenshot(processed.resource) };
775
+ }
776
+ if (sameName.length) throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Same filename has different, failed or uncommitted contents; inspect before retry.', {
777
+ retryable: false, details: { screenshotIds: sameName.map((r) => r.id), states: sameName.map((r) => r.attributes?.assetDeliveryState?.state) } });
778
+ if (existing.length >= 10) throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Device set has ten screenshots already.', { retryable: false });
779
+ if (args.dry_run === true) return { dryRun: true, appId: args.app_id, versionId: args.version_id, locale: args.locale,
780
+ displayType: display, setId: existingSet ?? null, wouldCreateSet: !existingSet,
781
+ file: { relativePath: file.relativePath, name: file.name, size: file.bytes.length, sha256: file.sha256 } };
782
+ const setId = existingSet ?? await findScreenshotSet(config, loc, display, true);
783
+ if (!setId) throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Could not resolve screenshot set.', { retryable: false });
784
+ const reservation = await apiRequest<AscScreenshotResponse>(config, { path: '/v1/appScreenshots', method: 'POST',
785
+ body: { data: { type: 'appScreenshots', attributes: { fileName: file.name, fileSize: file.bytes.length },
786
+ relationships: { appScreenshotSet: { data: { type: 'appScreenshotSets', id: setId } } } } } });
787
+ const r = reservation.data;
788
+ if (!r?.id || !r.attributes?.uploadOperations)
789
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple reservation is incomplete; reconcile before retry.', { retryable: false });
790
+ await uploadScreenshotParts(r.attributes.uploadOperations, file.bytes);
791
+ await apiRequest<AscScreenshotResponse>(config, { path: '/v1/appScreenshots/' + encodeURIComponent(r.id),
792
+ method: 'PATCH', body: { data: { type: 'appScreenshots', id: r.id,
793
+ attributes: { uploaded: true, sourceFileChecksum: file.md5 } } } });
794
+ const processed = await waitForScreenshotProcessing(config, r.id, file.md5);
795
+ return { applied: true, setId, sha256: file.sha256, processingComplete: processed.complete,
796
+ screenshot: safeScreenshot(processed.resource),
797
+ ...(processed.complete ? {} : { note: 'Apple accepted the upload but processing remains pending. Read the exact screenshot set before further action.' }) };
798
+ }
799
+
800
+
591
801
  function appId(args: Record<string, unknown>, config: AppStoreConnectPluginConfig): string {
592
802
  const value = stringValue(args.app_id) ?? config.defaultAppId;
593
803
  if (!value) throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'app_id is required when no default_app_id is configured.', { retryable: false });
@@ -701,6 +911,10 @@ function mockResponse(actionId: string, args: Record<string, unknown>, config: A
701
911
  readinessMode: 'mock_provider_ready',
702
912
  };
703
913
  }
914
+ if (actionId === 'list_app_screenshot_sets') return { data: [{ id: stableMockId('screenshot_set', args), displayType: stringValue(args.display_type) ?? 'APP_IPHONE_61' }], provider: 'mock' };
915
+ if (actionId === 'list_app_screenshots') return { setId: stableMockId('screenshot_set', args), data: [], provider: 'mock' };
916
+ if (actionId === 'upload_app_screenshot' || actionId === 'reorder_app_screenshots')
917
+ throw new AssistantPluginError('PLUGIN_ACTION_NOT_SUPPORTED', 'Mock mode cannot perform screenshot uploads or reordering.', { retryable: false });
704
918
  if (actionId === 'list_apps') return { data: [{ type: 'apps', id, attributes: { name: 'Mock App', bundleId: stringValue(args.bundle_id) ?? 'com.example.app', sku: 'MOCK' } }], meta: { provider: 'mock' } };
705
919
  if (actionId === 'list_app_store_versions') return { data: [{ type: 'appStoreVersions', id: stableMockId('version', { id }), attributes: { versionString: '1.0.0', appStoreState: 'PREPARE_FOR_SUBMISSION', platform: 'IOS' } }] };
706
920
  if (actionId === 'list_app_store_version_localizations') {
@@ -911,12 +1125,46 @@ export async function executeAppStoreConnectPluginAction(input: AssistantPluginA
911
1125
  case 'list_profiles':
912
1126
  return apiRequest(config, { path: '/v1/profiles', query: { 'filter[name]': stringValue(input.args.name), 'filter[profileType]': stringValue(input.args.profile_type), limit: limit(input.args.limit) } });
913
1127
  case 'list_app_store_versions':
914
- return apiRequest(config, { path: '/v1/appStoreVersions', query: { 'filter[app]': appId(input.args, config), limit: limit(input.args.limit) } });
1128
+ return apiRequest(config, { path: `/v1/apps/${encodeURIComponent(appId(input.args, config))}/appStoreVersions`, query: { limit: limit(input.args.limit) } });
915
1129
  case 'list_app_store_version_localizations':
916
1130
  return apiRequest(config, {
917
1131
  path: `/v1/appStoreVersions/${encodeURIComponent(requiredArg(input.args, 'version_id'))}/appStoreVersionLocalizations`,
918
1132
  query: { limit: limit(input.args.limit) },
919
1133
  });
1134
+ case 'list_app_screenshot_sets': {
1135
+ const locId = await verifyScreenshotOwner(config, input.args);
1136
+ const sets = await listScreenshotSets(config, locId);
1137
+ return { data: sets.map((s) => ({ id: s.id, displayType: s.attributes?.screenshotDisplayType })) };
1138
+ }
1139
+ case 'list_app_screenshots': {
1140
+ const loc = await verifyScreenshotOwner(config, input.args);
1141
+ const display = screenshotType(input.args);
1142
+ const setId = await findScreenshotSet(config, loc, display, false);
1143
+ return { setId: setId ?? null, data: setId ? (await listScreenshots(config, setId)).map(safeScreenshot) : [] };
1144
+ }
1145
+ case 'upload_app_screenshot':
1146
+ return uploadOfficialScreenshot(config, input.args, input.repoRoot);
1147
+ case 'reorder_app_screenshots': {
1148
+ const loc = await verifyScreenshotOwner(config, input.args);
1149
+ const setId = await findScreenshotSet(config, loc, screenshotType(input.args), false);
1150
+ if (!setId) throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Cannot reorder a missing screenshot set.', { retryable: false });
1151
+ const original = await listScreenshots(config, setId);
1152
+ const next = input.args.screenshot_ids;
1153
+ if (!Array.isArray(next) || next.some((id) => typeof id !== 'string') ||
1154
+ next.length !== original.length || new Set(next).size !== original.length ||
1155
+ original.some((entry) => !next.includes(entry.id) || !entry.attributes?.sourceFileChecksum ||
1156
+ entry.attributes?.assetDeliveryState?.state !== 'COMPLETE'))
1157
+ throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', 'Reorder requires exactly all committed IDs in the same screenshot set.', { retryable: false });
1158
+ if (next.every((id, i) => id === original[i].id))
1159
+ return { applied: false, alreadyOrdered: true, setId, screenshotIds: next };
1160
+ if (input.args.dry_run === true) return { dryRun: true, setId, screenshotIds: next };
1161
+ await apiRequest(config, { path: '/v1/appScreenshotSets/' + encodeURIComponent(setId) + '/relationships/appScreenshots',
1162
+ method: 'PATCH', body: { data: next.map((id) => ({ type: 'appScreenshots', id })) } });
1163
+ const checked = await listScreenshots(config, setId);
1164
+ if (!next.every((id, i) => id === checked[i]?.id))
1165
+ throw new AssistantPluginError('PLUGIN_PROVIDER_ERROR', 'Apple screenshot order does not match the requested order after write.', { retryable: false });
1166
+ return { applied: true, setId, screenshotIds: next };
1167
+ }
920
1168
  case 'get_app_info':
921
1169
  case 'list_app_infos':
922
1170
  return apiRequest(config, { path: `/v1/apps/${encodeURIComponent(appId(input.args, config))}/appInfos`, query: { include: 'appInfoLocalizations', limit: limit(input.args.limit) } });