@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.
- package/AGENTS.md +5 -2
- package/CHANGELOG.md +20 -0
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
- package/adapters/chatgpt/provider-delivery.ts +1 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
- package/adapters/computer/chatgpt-extension-target.ts +295 -50
- package/adapters/computer/chrome-extension/background.js +227 -71
- package/adapters/computer/chrome-extension/content.js +181 -157
- package/adapters/computer/chrome-extension/manifest.json +3 -1
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +39 -9
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -6
- package/adapters/mcp/instructions.ts +2 -2
- package/adapters/mcp/multi-repository.ts +7 -28
- package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
- package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
- package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
- package/adapters/mcp/runtime-gateway/router.ts +3 -5
- package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
- package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
- package/adapters/mcp/server.ts +24 -3
- package/adapters/mcp/setup.ts +4 -10
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
- package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
- package/adapters/mcp/toolset-names.ts +11 -42
- package/adapters/mcp/toolset.ts +16 -90
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +18 -6
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +54 -14
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +1 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +114 -30
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +5 -5
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +14 -2
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -54
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +24 -12
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +1 -12
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
- package/packages/kernel/work/application/work-service.ts +1 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +9 -2
- package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
- package/scripts/assemble-template.ts +11 -72
- package/scripts/check-mcp-compatibility.ts +55 -74
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +305 -28
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +3 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +0 -1
- package/scripts/run-governed-gate.ts +2 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +7 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
- package/scripts/sync-generated-authority.mjs +0 -1
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/agent-jobs/integration.ts +2 -2
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +2 -14
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/controller/completion-orchestrator.ts +1 -1
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +2 -2
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +97 -76
- package/src/cli/controller/operational-plan.ts +1 -1
- package/src/cli/controller/systemd-user.ts +4 -10
- package/src/cli/editing/edit-session.ts +17 -5
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +1 -1
- package/src/cli/local-bridge/console-view-models.ts +1 -1
- package/src/cli/local-bridge/facade-api.ts +0 -2
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-scope.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +2 -1
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +39 -3
- package/src/cli/repository/inspector.ts +185 -343
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +7 -0
- package/src/runtime/context/semantic-navigation.ts +1 -1
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
- package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
- package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
- package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
- package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
- package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
- package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
- package/src/runtime/execution/process-runtime/runtime.ts +20 -16
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +1 -2
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/plugins/action-application.ts +12 -1
- package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
- package/src/runtime/plugins/browser-adapter.ts +381 -313
- package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
- package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
- package/src/runtime/plugins/browser-registration.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +26 -35
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
- package/src/runtime/plugins/computer-registration.ts +76 -9
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +166 -1
- package/src/runtime/plugins/google-shared.ts +10 -5
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +45 -40
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/local-system-adapter.ts +1 -99
- package/src/runtime/plugins/store.ts +80 -7
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/recovery/actions.ts +1 -1
- package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/release/release-coordinator.ts +20 -4
- package/src/runtime/root/controller-progression-composition.ts +67 -5
- package/src/runtime/root/package-connector-service.ts +123 -11
- package/src/runtime/root/package-runtime-release.ts +6 -18
- package/src/runtime/root/package-runtime-service.ts +7 -13
- package/src/runtime/root/release-execution-canary.ts +18 -5
- package/src/runtime/root/release-manifest.ts +0 -34
- package/src/runtime/root/release-materialize.ts +0 -78
- package/src/runtime/root/runtime.ts +1 -2
- package/src/runtime/root/scheduled-controller-composition.ts +2 -1
- package/src/runtime/root/service.ts +90 -11
- package/src/runtime/root/types.ts +0 -6
- package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
- package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
- package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/standalone-recovery/core.ts +189 -174
- package/src/runtime/standalone-recovery/entry.ts +62 -30
- package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
- package/src/runtime/workflow/schedules/engine.ts +36 -14
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
- package/supervisor/client.ts +31 -14
- package/supervisor/control-plane.ts +210 -115
- package/supervisor/forge-validators.ts +88 -21
- package/supervisor/native-browser-adapter.ts +393 -28
- package/supervisor/native-messaging/host.ts +21 -2
- package/supervisor/protocol.ts +74 -26
- package/supervisor/server.ts +16 -8
- package/supervisor/store.ts +476 -57
- package/supervisor/types.ts +19 -13
- package/SKILL.md +0 -427
- package/adapters/computer/chatgpt-target-router.ts +0 -103
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
- 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:
|
|
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) } });
|