@moretea-labs/forge 1.8.0 → 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 +73 -7
- package/CHANGELOG.md +34 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- 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-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +531 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +229 -2
- package/adapters/chatgpt/work-conversation-binding-store.ts +99 -54
- package/adapters/computer/chatgpt-extension-target.ts +708 -0
- package/adapters/computer/chrome-extension/background.js +541 -0
- package/adapters/computer/chrome-extension/content.js +402 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +32 -0
- package/adapters/computer/desktop-operator-discovery.ts +11 -31
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +178 -24
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -7
- package/adapters/mcp/instructions.ts +6 -5
- package/adapters/mcp/multi-repository.ts +10 -47
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +490 -113
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +83 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +14 -54
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +132 -47
- package/adapters/mcp/runtime-gateway/process-tools.ts +173 -23
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +19 -507
- package/adapters/mcp/runtime-gateway/routing-policy.ts +506 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +15 -66
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +123 -178
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +11 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +37 -1
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +416 -270
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +140 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +262 -3123
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +229 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +320 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +180 -0
- package/adapters/mcp/server.ts +91 -26
- package/adapters/mcp/setup.ts +5 -13
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +703 -3092
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +5 -10
- package/adapters/mcp/tool-mapping/repository-tools.ts +170 -213
- package/adapters/mcp/toolset-names.ts +11 -37
- package/adapters/mcp/toolset.ts +19 -92
- 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 +377 -0
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +200 -683
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- 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/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +26 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/global-working-rules.md +19 -0
- 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 +205 -51
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +234 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +74 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +342 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- 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 +6 -5
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +3 -3
- 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 +16 -4
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- 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 -52
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +15 -7
- 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 +25 -13
- 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 +25 -25
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +348 -97
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +87 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +166 -143
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +18 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +34 -65
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +847 -729
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +153 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/assemble-template.ts +11 -72
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +94 -201
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +921 -239
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- 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 +6 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +1 -3
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +12 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +95 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/sync-generated-authority.mjs +0 -1
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +12 -35
- package/src/cli/agent-jobs/integration.ts +20 -51
- package/src/cli/agent-jobs/job-manager.ts +7 -3
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- 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 +137 -135
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +5 -44
- package/src/cli/controller/completion-target.ts +3 -72
- 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 +83 -102
- 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 +107 -84
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +25 -52
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +42 -10
- package/src/cli/editing/edit-session.ts +18 -12
- 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 +19 -14
- package/src/cli/local-bridge/console-view-models.ts +4 -13
- package/src/cli/local-bridge/facade-api.ts +64 -193
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +37 -18
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +79 -12
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +101 -5
- package/src/cli/repository/inspector.ts +185 -343
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +16 -0
- package/src/runtime/context/semantic-navigation.ts +7 -35
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +215 -12
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +616 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +197 -110
- package/src/runtime/control-plane/execution/work-handle-store.ts +72 -44
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +60 -34
- package/src/runtime/control-plane/execution/work-preparation-service.ts +231 -267
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +201 -199
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +33 -85
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +6 -2
- package/src/runtime/control-plane/facade/capability-registry.ts +364 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +9 -9
- package/src/runtime/control-plane/generated-cache-retention.ts +206 -59
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +256 -117
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -12
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +29 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +295 -107
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- 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/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +82 -30
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +55 -18
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- 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 +3 -8
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +97 -29
- package/src/runtime/plugins/app-store-connect-adapter.ts +290 -18
- package/src/runtime/plugins/browser-adapter.ts +1124 -333
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +431 -137
- package/src/runtime/plugins/browser-manifest-surface.ts +47 -12
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +18 -0
- package/src/runtime/plugins/browser-runtime.ts +40 -36
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +228 -0
- package/src/runtime/plugins/computer-registration.ts +298 -20
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +189 -3
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +24 -12
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +46 -41
- 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/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -100
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +682 -435
- package/src/runtime/plugins/types.ts +24 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +4 -4
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +30 -40
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/release-coordinator.ts +312 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +254 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +227 -29
- package/src/runtime/root/package-runtime-release.ts +117 -30
- package/src/runtime/root/package-runtime-service.ts +24 -24
- package/src/runtime/root/release-execution-canary.ts +57 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -40
- package/src/runtime/root/release-materialize.ts +372 -106
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +160 -29
- package/src/runtime/root/scheduled-controller-composition.ts +29 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +126 -44
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -9
- package/src/runtime/root/workflow-supervisor-composition.ts +681 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +564 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +67 -22
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- 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 +5 -2
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/shared/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3792 -417
- package/src/runtime/standalone-recovery/entry.ts +507 -45
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +12 -2
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -3
- package/src/runtime/workflow/schedules/engine.ts +166 -178
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +95 -11
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +218 -0
- package/supervisor/control-plane.ts +963 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +147 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +1151 -0
- package/supervisor/native-messaging/host.ts +58 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +250 -0
- package/supervisor/server.ts +412 -0
- package/supervisor/store.ts +1870 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +261 -0
- package/SKILL.md +0 -427
- package/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- 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/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/check-tarball-install-smoke.sh +0 -150
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/stage-runtime-release.ts +0 -44
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- 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/control-plane/execution/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync } from 'fs';
|
|
2
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { spawnSync } from 'child_process';
|
|
5
5
|
import { performance } from 'perf_hooks';
|
|
@@ -7,6 +7,7 @@ import { repositoryControllerRoot } from '../../cli/repositories/controller-home
|
|
|
7
7
|
import { runBoundedProcess } from '../execution/thin-harness/async-process';
|
|
8
8
|
import { readJsonFile, writeJsonAtomic } from '../shared/json-files';
|
|
9
9
|
import { AssistantPluginError, toAssistantPluginError } from './errors';
|
|
10
|
+
import { ensureIosDeviceOwnership, iosCoreDeviceOwnershipId, releaseIosDeviceOwnership } from './ios-device-ownership';
|
|
10
11
|
import {
|
|
11
12
|
interactionAutomationEngine,
|
|
12
13
|
interactionMayOwnTarget,
|
|
@@ -25,7 +26,7 @@ import type {
|
|
|
25
26
|
AssistantPluginAuthorizationTarget,
|
|
26
27
|
AssistantPluginCapability,
|
|
27
28
|
} from './types';
|
|
28
|
-
import { executeRemoteXpcHidInput,
|
|
29
|
+
import { executeRemoteXpcHidInput, remoteXpcHidStatus, stopRemoteXpcHidForDevice } from './ios/remote-xpc-hid';
|
|
29
30
|
|
|
30
31
|
const PROVIDER = 'ios-device' as const;
|
|
31
32
|
const SESSION_EXPIRY_MS = 2 * 60 * 60_000;
|
|
@@ -37,6 +38,9 @@ const MAX_JSON_BYTES = 64 * 1024;
|
|
|
37
38
|
const MAX_EVENTS = 200;
|
|
38
39
|
const MAX_BATCH_STEPS = 20;
|
|
39
40
|
const MAX_BATCH_WAIT_MS = 5_000;
|
|
41
|
+
const MAX_EXPORTED_DOCUMENT_BYTES = 20 * 1024 * 1024;
|
|
42
|
+
const MAX_DOCUMENT_INDEX_BYTES = 512 * 1024;
|
|
43
|
+
const MAX_SCREENSHOT_BYTES = 25 * 1024 * 1024;
|
|
40
44
|
|
|
41
45
|
interface CommandResult {
|
|
42
46
|
ok: boolean;
|
|
@@ -259,7 +263,11 @@ function statePath(input: AssistantPluginActionExecutionInput, interactionId: st
|
|
|
259
263
|
|
|
260
264
|
function artifactDir(input: AssistantPluginActionExecutionInput, interactionId: string): string {
|
|
261
265
|
const path = join(controllerRoot(input), 'artifacts', 'ios', 'physical-device', sanitize(interactionId));
|
|
262
|
-
mkdirSync(path, { recursive: true });
|
|
266
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
267
|
+
if (!lstatSync(path).isDirectory()) {
|
|
268
|
+
throw new AssistantPluginError('IOS_DEVICE_ARTIFACT_DIR_INVALID', 'The private iPhone artifact directory is not a regular directory.', { retryable: false });
|
|
269
|
+
}
|
|
270
|
+
chmodSync(path, 0o700);
|
|
263
271
|
return path;
|
|
264
272
|
}
|
|
265
273
|
|
|
@@ -322,6 +330,19 @@ function requireFiniteNumber(value: unknown, name: string): number {
|
|
|
322
330
|
throw new AssistantPluginError('PLUGIN_ACTION_ARGUMENT_INVALID', `${name} must be a finite number.`, { retryable: false });
|
|
323
331
|
}
|
|
324
332
|
|
|
333
|
+
function requireAppDocumentPath(value: unknown): string {
|
|
334
|
+
const path = requireString(value, 'path');
|
|
335
|
+
const parts = path.split('/');
|
|
336
|
+
if (parts.length !== 2 || parts[0] !== 'Documents'
|
|
337
|
+
|| !/^[^\\/\u0000-\u001f\u007f]+\.(?:pdf|png|jpe?g|heic)$/i.test(parts[1] ?? '')
|
|
338
|
+
|| parts[1] === '.' || parts[1] === '..' || path.length > 512) {
|
|
339
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_PATH_INVALID',
|
|
340
|
+
'Provide one exact PDF/image filename immediately within the selected app Documents directory.',
|
|
341
|
+
{ retryable: false });
|
|
342
|
+
}
|
|
343
|
+
return path;
|
|
344
|
+
}
|
|
345
|
+
|
|
325
346
|
function requireBundleId(value: unknown): string {
|
|
326
347
|
const bundleId = requireString(value, 'bundle_id');
|
|
327
348
|
if (!/^[A-Za-z0-9][A-Za-z0-9.-]+$/.test(bundleId) || !bundleId.includes('.') || /^[a-z][a-z0-9+.-]*:\/\//i.test(bundleId)) {
|
|
@@ -478,7 +499,12 @@ async function physicalDeviceLockState(input: AssistantPluginActionExecutionInpu
|
|
|
478
499
|
|
|
479
500
|
async function requirePhysicalDeviceUnlocked(input: AssistantPluginActionExecutionInput, device: PhysicalDevice): Promise<void> {
|
|
480
501
|
const lockState = await physicalDeviceLockState(input, device);
|
|
481
|
-
if (lockState.passcodeRequired
|
|
502
|
+
if (lockState.passcodeRequired === false) return;
|
|
503
|
+
if (lockState.passcodeRequired !== true) {
|
|
504
|
+
throw new AssistantPluginError('IOS_DEVICE_LOCK_STATE_UNVERIFIED',
|
|
505
|
+
'CoreDevice did not report a verified iPhone lock state; app activation was not attempted.',
|
|
506
|
+
{ retryable: false });
|
|
507
|
+
}
|
|
482
508
|
throw new AssistantPluginError(
|
|
483
509
|
'IOS_DEVICE_LOCKED',
|
|
484
510
|
`The selected iPhone ${device.name} is locked. Unlock it before launching an app through CoreDevice.`,
|
|
@@ -520,18 +546,49 @@ async function physicalDisplayGeometry(input: AssistantPluginActionExecutionInpu
|
|
|
520
546
|
};
|
|
521
547
|
}
|
|
522
548
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
549
|
+
const PNG_CRC_TABLE = Uint32Array.from({ length: 256 }, (_, byte) => {
|
|
550
|
+
let crc = byte;
|
|
551
|
+
for (let bit = 0; bit < 8; bit += 1) {
|
|
552
|
+
crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
|
|
553
|
+
}
|
|
554
|
+
return crc >>> 0;
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
function pngDisplayGeometry(image: Buffer, pointScale?: number): DisplayGeometry | undefined {
|
|
558
|
+
// No screenshot observation is issued until all PNG chunks have valid bounds
|
|
559
|
+
// and CRCs; a plausible header/trailer alone is not a usable image.
|
|
560
|
+
if (image.length < 57 || image.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a') return undefined;
|
|
561
|
+
let offset = 8;
|
|
562
|
+
let width = 0;
|
|
563
|
+
let height = 0;
|
|
564
|
+
let sawImageData = false;
|
|
565
|
+
while (offset + 12 <= image.length) {
|
|
566
|
+
const length = image.readUInt32BE(offset);
|
|
567
|
+
if (length > image.length - offset - 12) return undefined;
|
|
568
|
+
const kind = image.toString('ascii', offset + 4, offset + 8);
|
|
569
|
+
const end = offset + 8 + length;
|
|
570
|
+
if (!/^[A-Za-z]{4}$/.test(kind)) return undefined;
|
|
571
|
+
let crc = 0xffffffff;
|
|
572
|
+
for (let pos = offset + 4; pos < end; pos += 1) {
|
|
573
|
+
crc = PNG_CRC_TABLE[(crc ^ image[pos]!) & 0xff]! ^ (crc >>> 8);
|
|
574
|
+
}
|
|
575
|
+
if (((crc ^ 0xffffffff) >>> 0) !== image.readUInt32BE(end)) return undefined;
|
|
576
|
+
if (kind === 'IHDR') {
|
|
577
|
+
if (offset !== 8 || length !== 13) return undefined;
|
|
578
|
+
width = image.readUInt32BE(offset + 8);
|
|
579
|
+
height = image.readUInt32BE(offset + 12);
|
|
580
|
+
if (width <= 1 || height <= 1 || width > 10_000 || height > 10_000
|
|
581
|
+
|| image[offset + 18] !== 0 || image[offset + 19] !== 0 || (image[offset + 20] ?? 2) > 1) return undefined;
|
|
582
|
+
} else if (kind === 'IDAT') {
|
|
583
|
+
if (!width || length === 0) return undefined;
|
|
584
|
+
sawImageData = true;
|
|
585
|
+
} else if (kind === 'IEND') {
|
|
586
|
+
return sawImageData && length === 0 && end + 4 === image.length
|
|
587
|
+
? { width, height, pointScale } : undefined;
|
|
588
|
+
}
|
|
589
|
+
offset = end + 4;
|
|
534
590
|
}
|
|
591
|
+
return undefined;
|
|
535
592
|
}
|
|
536
593
|
|
|
537
594
|
async function sessionDisplayGeometry(
|
|
@@ -632,12 +689,42 @@ function expirePhysicalDeviceSessions(input: AssistantPluginActionExecutionInput
|
|
|
632
689
|
error: { code: 'IOS_DEVICE_SESSION_EXPIRED', message: 'The physical iOS interaction expired.' },
|
|
633
690
|
});
|
|
634
691
|
appendEvent(input, record.interactionId, 'session_expired');
|
|
635
|
-
if (state?.device.identifier)
|
|
692
|
+
if (state?.device.identifier) {
|
|
693
|
+
stopRemoteXpcHidForDevice(state.device.identifier);
|
|
694
|
+
releaseIosDeviceOwnership(input.controllerHome, record.owner.repoId, record.interactionId, iosCoreDeviceOwnershipId(state.device));
|
|
695
|
+
}
|
|
636
696
|
expired += 1;
|
|
637
697
|
}
|
|
638
698
|
return expired;
|
|
639
699
|
}
|
|
640
700
|
|
|
701
|
+
function deviceOwnershipError(
|
|
702
|
+
selected: PhysicalDevice,
|
|
703
|
+
result: ReturnType<typeof ensureIosDeviceOwnership>,
|
|
704
|
+
): AssistantPluginError {
|
|
705
|
+
return new AssistantPluginError('PLUGIN_RESOURCE_BUSY', 'The selected iPhone is already owned by another active Forge interaction.', {
|
|
706
|
+
retryable: true,
|
|
707
|
+
details: {
|
|
708
|
+
device: { identifier: selected.identifier, name: selected.name },
|
|
709
|
+
blocker: result.blocker,
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function reusablePhysicalSession(
|
|
715
|
+
input: AssistantPluginActionExecutionInput,
|
|
716
|
+
selected: PhysicalDevice,
|
|
717
|
+
bundleId: string,
|
|
718
|
+
): InteractionSessionRecord | undefined {
|
|
719
|
+
const aliases = [selected.identifier, selected.udid].filter((value): value is string => Boolean(value));
|
|
720
|
+
return listInteractionSessions(input.repoRoot, PROVIDER).find((entry) => {
|
|
721
|
+
if (!isInteractionSessionActive(entry.status) || interactionAutomationEngine(entry) !== 'coredevice') return false;
|
|
722
|
+
if (!interactionMayOwnTarget(entry, aliases)) return false;
|
|
723
|
+
const state = readState(input, entry.interactionId);
|
|
724
|
+
return state?.device.identifier === selected.identifier && state.bundleId === bundleId;
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
641
728
|
function requireRecord(input: AssistantPluginActionExecutionInput, allowTerminal = false): { record: InteractionSessionRecord; state: PhysicalSessionState } {
|
|
642
729
|
const interactionId = requireString(input.args.interaction_id, 'interaction_id');
|
|
643
730
|
const record = readInteractionSession(input.repoRoot, PROVIDER, interactionId);
|
|
@@ -668,8 +755,17 @@ function requireRecord(input: AssistantPluginActionExecutionInput, allowTerminal
|
|
|
668
755
|
});
|
|
669
756
|
appendEvent(input, interactionId, 'session_expired');
|
|
670
757
|
stopRemoteXpcHidForDevice(state.device.identifier);
|
|
758
|
+
releaseIosDeviceOwnership(input.controllerHome, record.owner.repoId, interactionId, iosCoreDeviceOwnershipId(state.device));
|
|
671
759
|
throw new AssistantPluginError('IOS_DEVICE_SESSION_EXPIRED', 'The physical iOS interaction expired; open a new session.', { retryable: false });
|
|
672
760
|
}
|
|
761
|
+
if (isInteractionSessionActive(record.status)) {
|
|
762
|
+
const ownership = ensureIosDeviceOwnership(input.controllerHome, input.repoId, interactionId, iosCoreDeviceOwnershipId(state.device));
|
|
763
|
+
if (!ownership.acquired) throw deviceOwnershipError(state.device, ownership);
|
|
764
|
+
const active = patchInteractionSession(input.repoRoot, PROVIDER, interactionId, {
|
|
765
|
+
expiresAt: new Date(hooks.now().getTime() + SESSION_EXPIRY_MS).toISOString(),
|
|
766
|
+
});
|
|
767
|
+
if (active) return { record: active, state };
|
|
768
|
+
}
|
|
673
769
|
return { record, state };
|
|
674
770
|
}
|
|
675
771
|
|
|
@@ -795,23 +891,20 @@ function requireObservedPhysicalTargetForeground(state: PhysicalSessionState): P
|
|
|
795
891
|
function resolvePhysicalBatchForegroundObservation(
|
|
796
892
|
state: PhysicalSessionState,
|
|
797
893
|
observationId: string | undefined,
|
|
798
|
-
): { foregroundObservation: PhysicalForegroundObservation; source: '
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
},
|
|
808
|
-
source: 'atomic_screenshot',
|
|
809
|
-
screenshotAgeMs: ageMs,
|
|
810
|
-
};
|
|
894
|
+
): { foregroundObservation: PhysicalForegroundObservation; source: 'explicit_confirmation'; screenshotAgeMs?: number } {
|
|
895
|
+
// Observation freshness proves only that the screen was captured. It never proves
|
|
896
|
+
// the target app was foreground. Require a separate, model-reviewed confirmation.
|
|
897
|
+
const observed = observationId ? recentScreenshotObservation(state, observationId) : undefined;
|
|
898
|
+
const confirmed = requireObservedPhysicalTargetForeground(state);
|
|
899
|
+
if (observationId && confirmed.observationId !== observationId) {
|
|
900
|
+
throw new AssistantPluginError('IOS_DEVICE_FOREGROUND_OBSERVATION_REQUIRED',
|
|
901
|
+
'The batch observation must match the separately confirmed target-app foreground.',
|
|
902
|
+
{ retryable: true, details: { hidMutationDispatched: false } });
|
|
811
903
|
}
|
|
812
904
|
return {
|
|
813
|
-
foregroundObservation:
|
|
905
|
+
foregroundObservation: confirmed,
|
|
814
906
|
source: 'explicit_confirmation',
|
|
907
|
+
...(observed ? { screenshotAgeMs: observed.ageMs } : {}),
|
|
815
908
|
};
|
|
816
909
|
}
|
|
817
910
|
|
|
@@ -940,6 +1033,96 @@ async function waitPhysicalInputBatch(durationMs: number, signal?: AbortSignal):
|
|
|
940
1033
|
});
|
|
941
1034
|
}
|
|
942
1035
|
|
|
1036
|
+
/** CoreDevice screenshot is the one observation authority for the device. */
|
|
1037
|
+
async function capturePhysicalScreen(
|
|
1038
|
+
input: AssistantPluginActionExecutionInput,
|
|
1039
|
+
state: PhysicalSessionState,
|
|
1040
|
+
label: string,
|
|
1041
|
+
): Promise<Record<string, unknown>> {
|
|
1042
|
+
const path = join(artifactDir(input, state.interactionId), `${sanitize(label)}-${hooks.now().getTime()}-${randomUUID()}.png`);
|
|
1043
|
+
// A new capture supersedes the old screen. A failed observation cannot leave
|
|
1044
|
+
// an earlier foreground confirmation available to authorize subsequent HID.
|
|
1045
|
+
state.lastScreenshot = undefined;
|
|
1046
|
+
state.foregroundObservation = undefined;
|
|
1047
|
+
writeState(input, state);
|
|
1048
|
+
try {
|
|
1049
|
+
const screenshotResult = await runCoreJson(input, [
|
|
1050
|
+
'device', 'capture', 'screenshot', '--device', state.device.identifier, '--destination', path,
|
|
1051
|
+
], 'IOS_DEVICE_SCREENSHOT_FAILED', 60_000);
|
|
1052
|
+
if (!existsSync(path)) {
|
|
1053
|
+
throw new AssistantPluginError('IOS_DEVICE_SCREENSHOT_MISSING', 'CoreDevice succeeded without creating the requested screenshot.', { retryable: false });
|
|
1054
|
+
}
|
|
1055
|
+
const file = lstatSync(path);
|
|
1056
|
+
// The enclosing directory is 0700 during CoreDevice's write; the finished
|
|
1057
|
+
// screenshot itself must also be private before it is read or returned.
|
|
1058
|
+
if (file.isFile()) chmodSync(path, 0o600);
|
|
1059
|
+
const image = file.isFile() && file.size <= MAX_SCREENSHOT_BYTES && file.size > 0 ? readFileSync(path) : undefined;
|
|
1060
|
+
const screenshotDisplay = image ? pngDisplayGeometry(image, state.display?.pointScale) : undefined;
|
|
1061
|
+
if (!screenshotDisplay || !image) {
|
|
1062
|
+
throw new AssistantPluginError('IOS_DEVICE_SCREENSHOT_INVALID', 'CoreDevice did not provide a bounded PNG screenshot with valid image dimensions and terminal marker.', { retryable: false });
|
|
1063
|
+
}
|
|
1064
|
+
const screenshotSha256 = createHash('sha256').update(image).digest('hex');
|
|
1065
|
+
const observedAt = timestamp();
|
|
1066
|
+
const observationId = randomUUID();
|
|
1067
|
+
state.display = { ...screenshotDisplay, observedAt };
|
|
1068
|
+
state.lastScreenshot = { observationId, observedAt, label };
|
|
1069
|
+
state.foregroundObservation = undefined;
|
|
1070
|
+
state.events.push({ at: observedAt, type: 'screenshot', details: { label, observationId } });
|
|
1071
|
+
state.events = state.events.slice(-MAX_EVENTS);
|
|
1072
|
+
writeState(input, state);
|
|
1073
|
+
return {
|
|
1074
|
+
screenshotResult: bounded(screenshotResult),
|
|
1075
|
+
observation: { observationId, observedAt, label, ttlMs: INPUT_FOREGROUND_OBSERVATION_TTL_MS,
|
|
1076
|
+
screenshotSha256, screenshotBytes: image.length, width: screenshotDisplay.width, height: screenshotDisplay.height },
|
|
1077
|
+
foregroundConfirmation: {
|
|
1078
|
+
actionId: 'physical_device_confirm_foreground',
|
|
1079
|
+
arguments: { interaction_id: state.interactionId, observation_id: observationId, bundle_id: state.bundleId },
|
|
1080
|
+
requires: ['independent visual confirmation of target app'],
|
|
1081
|
+
ttlMs: INPUT_FOREGROUND_OBSERVATION_TTL_MS,
|
|
1082
|
+
},
|
|
1083
|
+
artifactCandidates: [{ kind: 'ios_physical_device_screenshot', mediaType: 'image/png', path,
|
|
1084
|
+
sha256: screenshotSha256, bytes: image.length }],
|
|
1085
|
+
};
|
|
1086
|
+
} catch (error) {
|
|
1087
|
+
// A partially written or invalid image is not an observation artifact.
|
|
1088
|
+
if (existsSync(path)) {
|
|
1089
|
+
try { unlinkSync(path); } catch { /* Preserve the original capture failure. */ }
|
|
1090
|
+
}
|
|
1091
|
+
throw error;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** After a mutation, screenshot failures are observations, never a reason to retry input. */
|
|
1096
|
+
async function optionalPostActionCapture(
|
|
1097
|
+
input: AssistantPluginActionExecutionInput,
|
|
1098
|
+
state: PhysicalSessionState,
|
|
1099
|
+
label: string,
|
|
1100
|
+
timingStages: TimingStages,
|
|
1101
|
+
): Promise<Record<string, unknown>> {
|
|
1102
|
+
const startedAt = performance.now();
|
|
1103
|
+
try {
|
|
1104
|
+
const capture = await capturePhysicalScreen(input, state, label);
|
|
1105
|
+
recordTiming(timingStages, 'postActionScreenshot', startedAt, false);
|
|
1106
|
+
const { screenshotResult: _ignored, ...observation } = capture;
|
|
1107
|
+
return { ...observation, postActionCapture: { status: 'captured' } };
|
|
1108
|
+
} catch (error) {
|
|
1109
|
+
recordTiming(timingStages, 'postActionScreenshot', startedAt, false);
|
|
1110
|
+
const normalized = toAssistantPluginError(error, {
|
|
1111
|
+
code: 'IOS_DEVICE_SCREENSHOT_FAILED',
|
|
1112
|
+
message: 'The physical iPhone input completed, but its follow-up screenshot could not be captured.',
|
|
1113
|
+
retryable: false,
|
|
1114
|
+
});
|
|
1115
|
+
// A secondary diagnostic write must not turn a completed HID effect into a replayable error.
|
|
1116
|
+
try { appendEvent(input, state.interactionId, 'post_action_screenshot_failed', { code: normalized.code }); } catch { /* observation failure remains explicit */ }
|
|
1117
|
+
return {
|
|
1118
|
+
postActionCapture: {
|
|
1119
|
+
status: 'failed', code: normalized.code,
|
|
1120
|
+
mutationAlreadyApplied: true, retryOnly: 'physical_device_screenshot',
|
|
1121
|
+
},
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
943
1126
|
export function isIosPhysicalDeviceAction(actionId: string): boolean {
|
|
944
1127
|
return actionId.startsWith('physical_device_');
|
|
945
1128
|
}
|
|
@@ -1044,10 +1227,10 @@ export function iosPhysicalDeviceActions(): AssistantPluginActionDescriptor[] {
|
|
|
1044
1227
|
},
|
|
1045
1228
|
{
|
|
1046
1229
|
actionId: 'physical_device_open', title: 'Open physical iOS app session',
|
|
1047
|
-
description: '
|
|
1230
|
+
description: 'Open or reactivate the target app in the same exact CoreDevice iPhone session; activation metadata never proves foreground. Set observe_after_open=true to return a new screenshot without authorizing input. prewarm_input=true defers isolated RemoteXPC worker startup to the first input action instead of wasting a cross-action prewarm.',
|
|
1048
1231
|
readOnly: false, risk: 'workspace_write', confirmation: 'authorization', defaultTimeoutMs: 2 * 60_000, cancellable: true, idempotent: false,
|
|
1049
1232
|
scopes: ['ios.device'], resourceClaims: mutationClaims,
|
|
1050
|
-
argumentsSchema: { type: 'object', properties: { device: { type: 'string' }, bundle_id: { type: 'string' }, relaunch: { type: 'boolean' }, prewarm_input: { type: 'boolean' } }, required: ['device', 'bundle_id'], additionalProperties: false },
|
|
1233
|
+
argumentsSchema: { type: 'object', properties: { device: { type: 'string' }, bundle_id: { type: 'string' }, relaunch: { type: 'boolean' }, prewarm_input: { type: 'boolean' }, observe_after_open: { type: 'boolean' } }, required: ['device', 'bundle_id'], additionalProperties: false },
|
|
1051
1234
|
},
|
|
1052
1235
|
{
|
|
1053
1236
|
actionId: 'physical_device_screenshot', title: 'Capture physical iOS screenshot',
|
|
@@ -1069,18 +1252,29 @@ export function iosPhysicalDeviceActions(): AssistantPluginActionDescriptor[] {
|
|
|
1069
1252
|
},
|
|
1070
1253
|
{
|
|
1071
1254
|
actionId: 'physical_device_observed_tap', title: 'Tap a freshly observed physical iPhone screen',
|
|
1072
|
-
description: 'One-shot
|
|
1255
|
+
description: 'One-shot screenshot-bound tap for an independently inspected system-screen navigation, without reactivation. Observation freshness is not app identity: never dispatch from an unseen screenshot or use this system-navigation path for ordinary in-app taps; verify exact target foreground first and use physical_device_confirm_foreground plus physical_device_batch/tap. capture_after=true returns the next screenshot; never retry an uncertain tap.',
|
|
1073
1256
|
readOnly: false, risk: 'workspace_write', confirmation: 'authorization', defaultTimeoutMs: 30_000, cancellable: true, idempotent: false,
|
|
1074
1257
|
scopes: ['ios.device'], resourceClaims: mutationClaims,
|
|
1075
1258
|
argumentsSchema: {
|
|
1076
1259
|
type: 'object',
|
|
1077
|
-
properties: { ...interactionProperty, observation_id: { type: 'string' }, x: { type: 'number', minimum: 0 }, y: { type: 'number', minimum: 0 } },
|
|
1260
|
+
properties: { ...interactionProperty, observation_id: { type: 'string' }, capture_after: { type: 'boolean' }, x: { type: 'number', minimum: 0 }, y: { type: 'number', minimum: 0 } },
|
|
1078
1261
|
required: ['interaction_id', 'observation_id', 'x', 'y'], additionalProperties: false,
|
|
1079
1262
|
},
|
|
1080
1263
|
},
|
|
1264
|
+
{
|
|
1265
|
+
actionId: 'physical_device_observed_swipe', title: 'Swipe a freshly observed physical iPhone screen',
|
|
1266
|
+
description: 'One-shot screenshot-bound swipe for system navigation, consuming the observed screen without app reactivation. capture_after=true returns the next CoreDevice screenshot and new observation in the same call; app form gestures still require foreground confirmation.',
|
|
1267
|
+
readOnly: false, risk: 'workspace_write', confirmation: 'authorization', defaultTimeoutMs: 30_000, cancellable: true, idempotent: false,
|
|
1268
|
+
scopes: ['ios.device'], resourceClaims: mutationClaims,
|
|
1269
|
+
argumentsSchema: {
|
|
1270
|
+
type: 'object',
|
|
1271
|
+
properties: { ...interactionProperty, observation_id: { type: 'string' }, capture_after: { type: 'boolean' }, x: { type: 'number', minimum: 0 }, y: { type: 'number', minimum: 0 }, to_x: { type: 'number', minimum: 0 }, to_y: { type: 'number', minimum: 0 }, duration_ms: { type: 'number', minimum: 80, maximum: 3000 } },
|
|
1272
|
+
required: ['interaction_id', 'observation_id', 'x', 'y', 'to_x', 'to_y'], additionalProperties: false,
|
|
1273
|
+
},
|
|
1274
|
+
},
|
|
1081
1275
|
{
|
|
1082
1276
|
actionId: 'physical_device_batch', title: 'Run fast physical iPhone input batch',
|
|
1083
|
-
description: '
|
|
1277
|
+
description: 'Run up to 20 short visually grounded taps, swipes, text edits or waits on the single CoreDevice/HID provider. First independently inspect the screenshot and call physical_device_confirm_foreground with the exact target bundle. observation_id is an optional match check, not an authorization shortcut. capture_after=true returns the NEXT PNG and observation for separate inspection and confirmation. Never retry partially applied or unknown input.',
|
|
1084
1278
|
readOnly: false, risk: 'workspace_write', confirmation: 'authorization', defaultTimeoutMs: 60_000, cancellable: true, idempotent: false,
|
|
1085
1279
|
scopes: ['ios.device'], resourceClaims: mutationClaims,
|
|
1086
1280
|
argumentsSchema: {
|
|
@@ -1088,6 +1282,7 @@ export function iosPhysicalDeviceActions(): AssistantPluginActionDescriptor[] {
|
|
|
1088
1282
|
properties: {
|
|
1089
1283
|
...interactionProperty,
|
|
1090
1284
|
observation_id: { type: 'string' },
|
|
1285
|
+
capture_after: { type: 'boolean' },
|
|
1091
1286
|
steps: {
|
|
1092
1287
|
type: 'array', minItems: 1, maxItems: MAX_BATCH_STEPS,
|
|
1093
1288
|
items: {
|
|
@@ -1146,6 +1341,20 @@ export function iosPhysicalDeviceActions(): AssistantPluginActionDescriptor[] {
|
|
|
1146
1341
|
required: ['interaction_id', 'text'], additionalProperties: false,
|
|
1147
1342
|
},
|
|
1148
1343
|
},
|
|
1344
|
+
{
|
|
1345
|
+
actionId: 'physical_device_documents_list', title: 'Discover selected-app iPhone documents',
|
|
1346
|
+
description: 'With explicit interactive confirmation, list bounded PDF/image filenames in the selected app Documents sandbox. No general Files/iCloud or other-app search.',
|
|
1347
|
+
readOnly: false, risk: 'workspace_write', confirmation: 'strong_confirmation', requiredConfirmationText: 'list-iphone-app-documents',
|
|
1348
|
+
defaultTimeoutMs: 60_000, cancellable: true, idempotent: true, scopes: ['ios.device'], resourceClaims: stateClaim,
|
|
1349
|
+
argumentsSchema: { type: 'object', properties: { ...interactionProperty, bundle_id: { type: 'string' } }, required: ['interaction_id', 'bundle_id'], additionalProperties: false },
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
actionId: 'physical_device_document_export', title: 'Export one selected-app iPhone document',
|
|
1353
|
+
description: 'With explicit interactive confirmation, copy one exact PDF/image from the selected app Documents sandbox into private Controller artifacts; no bulk export, deletion or upload.',
|
|
1354
|
+
readOnly: false, risk: 'workspace_write', confirmation: 'strong_confirmation', requiredConfirmationText: 'export-iphone-app-document',
|
|
1355
|
+
defaultTimeoutMs: 90_000, cancellable: true, idempotent: false, scopes: ['ios.device'], resourceClaims: mutationClaims,
|
|
1356
|
+
argumentsSchema: { type: 'object', properties: { ...interactionProperty, bundle_id: { type: 'string' }, path: { type: 'string', maxLength: 512 } }, required: ['interaction_id', 'bundle_id', 'path'], additionalProperties: false },
|
|
1357
|
+
},
|
|
1149
1358
|
{
|
|
1150
1359
|
actionId: 'physical_device_events', title: 'Read physical iOS events',
|
|
1151
1360
|
description: 'Read bounded, redacted provider events without input text or credentials.',
|
|
@@ -1161,9 +1370,9 @@ export function iosPhysicalDeviceActions(): AssistantPluginActionDescriptor[] {
|
|
|
1161
1370
|
argumentsSchema: { type: 'object', properties: { ...interactionProperty }, required: ['interaction_id'], additionalProperties: false },
|
|
1162
1371
|
},
|
|
1163
1372
|
];
|
|
1164
|
-
return actions.map((action) => action.readOnly
|
|
1373
|
+
return actions.map((action) => action.readOnly
|
|
1165
1374
|
? action
|
|
1166
|
-
: { ...action, executionMode: '
|
|
1375
|
+
: { ...action, executionMode: 'runtime' });
|
|
1167
1376
|
}
|
|
1168
1377
|
|
|
1169
1378
|
export async function executeIosPhysicalDeviceAction(input: AssistantPluginActionExecutionInput): Promise<Record<string, unknown>> {
|
|
@@ -1198,6 +1407,11 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1198
1407
|
const selectStartedAt = performance.now();
|
|
1199
1408
|
const selected = await selectDevice(input, input.args.device);
|
|
1200
1409
|
recordTiming(timingStages, 'deviceSelection', selectStartedAt, false);
|
|
1410
|
+
if (!selected.connected) {
|
|
1411
|
+
throw new AssistantPluginError('IOS_DEVICE_NOT_CONNECTED',
|
|
1412
|
+
'The selected paired iPhone has no usable CoreDevice connection; app activation was not attempted.',
|
|
1413
|
+
{ retryable: true });
|
|
1414
|
+
}
|
|
1201
1415
|
const bundleId = requireBundleId(input.args.bundle_id);
|
|
1202
1416
|
const appsStartedAt = performance.now();
|
|
1203
1417
|
const lockStartedAt = performance.now();
|
|
@@ -1213,10 +1427,60 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1213
1427
|
const sessionLookupStartedAt = performance.now();
|
|
1214
1428
|
const expiredSessionCount = expirePhysicalDeviceSessions(input);
|
|
1215
1429
|
pruneInteractionSessions(input.repoRoot, PROVIDER, 100);
|
|
1430
|
+
const reusable = reusablePhysicalSession(input, selected, bundleId);
|
|
1216
1431
|
const conflict = listInteractionSessions(input.repoRoot, PROVIDER).find((entry) =>
|
|
1217
1432
|
isInteractionSessionActive(entry.status) && interactionMayOwnTarget(entry, selectedAliases));
|
|
1218
1433
|
recordTiming(timingStages, 'sessionLookup', sessionLookupStartedAt, false);
|
|
1219
1434
|
if (expiredSessionCount > 0) timingStages.sessionReconcile = { ms: timingStages.sessionLookup.ms, cached: false };
|
|
1435
|
+
if (reusable) {
|
|
1436
|
+
const ownership = ensureIosDeviceOwnership(input.controllerHome, input.repoId, reusable.interactionId, iosCoreDeviceOwnershipId(selected));
|
|
1437
|
+
if (!ownership.acquired) throw deviceOwnershipError(selected, ownership);
|
|
1438
|
+
const existingState = readState(input, reusable.interactionId);
|
|
1439
|
+
if (!existingState) {
|
|
1440
|
+
patchInteractionSession(input.repoRoot, PROVIDER, reusable.interactionId, {
|
|
1441
|
+
status: 'failed',
|
|
1442
|
+
error: { code: 'IOS_DEVICE_SESSION_STATE_MISSING', message: 'The existing physical iPhone session has no recoverable state.' },
|
|
1443
|
+
});
|
|
1444
|
+
appendEvent(input, reusable.interactionId, 'session_state_missing');
|
|
1445
|
+
releaseIosDeviceOwnership(input.controllerHome, input.repoId, reusable.interactionId, iosCoreDeviceOwnershipId(selected));
|
|
1446
|
+
throw new AssistantPluginError('IOS_DEVICE_SESSION_STATE_MISSING', 'The existing physical iPhone session has no recoverable state.', { retryable: false });
|
|
1447
|
+
}
|
|
1448
|
+
// Owning a session never proves the target app remains in the foreground.
|
|
1449
|
+
// Invalidate the old observation BEFORE activating another foreground app.
|
|
1450
|
+
existingState.lastScreenshot = undefined;
|
|
1451
|
+
existingState.foregroundObservation = undefined;
|
|
1452
|
+
writeState(input, existingState);
|
|
1453
|
+
const activationArgs = ['device', 'process', 'launch', '--device', selected.identifier, '--activate'];
|
|
1454
|
+
if (input.args.relaunch === true) activationArgs.push('--terminate-existing');
|
|
1455
|
+
activationArgs.push(bundleId);
|
|
1456
|
+
const activationStartedAt = performance.now();
|
|
1457
|
+
const activation = await runCoreJson(input, activationArgs, 'IOS_DEVICE_LAUNCH_FAILED', 60_000)
|
|
1458
|
+
.finally(() => recordTiming(timingStages, 'activationRequest', activationStartedAt, false));
|
|
1459
|
+
appendEvent(input, reusable.interactionId, 'app_activated', { bundleId, relaunch: input.args.relaunch === true });
|
|
1460
|
+
const active = patchInteractionSession(input.repoRoot, PROVIDER, reusable.interactionId, {
|
|
1461
|
+
expiresAt: new Date(hooks.now().getTime() + SESSION_EXPIRY_MS).toISOString(),
|
|
1462
|
+
}) ?? reusable;
|
|
1463
|
+
const nextObservation = input.args.observe_after_open === true
|
|
1464
|
+
? await optionalPostActionCapture(input, existingState, 'resume', timingStages)
|
|
1465
|
+
: {};
|
|
1466
|
+
return finishTimedResult(actionStartedAt, timingStages, {
|
|
1467
|
+
...nextObservation,
|
|
1468
|
+
provider: 'coredevice',
|
|
1469
|
+
interaction: active,
|
|
1470
|
+
device: selected,
|
|
1471
|
+
app: apps[0],
|
|
1472
|
+
launch: bounded(activation),
|
|
1473
|
+
foregroundVerification: { authority: 'screenshot_observation', required: true },
|
|
1474
|
+
reusedSession: true,
|
|
1475
|
+
controlPlane: {
|
|
1476
|
+
lifecycle: 'coredevice',
|
|
1477
|
+
observation: 'coredevice',
|
|
1478
|
+
input: 'remote-xpc-hid',
|
|
1479
|
+
semanticFallback: 'agent-device',
|
|
1480
|
+
runnerOwned: false,
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1220
1484
|
if (conflict) {
|
|
1221
1485
|
throw new AssistantPluginError('PLUGIN_RESOURCE_BUSY', 'The selected iPhone already has an active forge interaction.', {
|
|
1222
1486
|
retryable: true,
|
|
@@ -1229,6 +1493,8 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1229
1493
|
});
|
|
1230
1494
|
}
|
|
1231
1495
|
const interactionId = `ios_device_${randomUUID()}`;
|
|
1496
|
+
const ownership = ensureIosDeviceOwnership(input.controllerHome, input.repoId, interactionId, iosCoreDeviceOwnershipId(selected));
|
|
1497
|
+
if (!ownership.acquired) throw deviceOwnershipError(selected, ownership);
|
|
1232
1498
|
const createdAt = timestamp();
|
|
1233
1499
|
const record: InteractionSessionRecord = {
|
|
1234
1500
|
schemaVersion: 1,
|
|
@@ -1263,13 +1529,10 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1263
1529
|
if (input.args.relaunch === true) args.push('--terminate-existing');
|
|
1264
1530
|
args.push(bundleId);
|
|
1265
1531
|
const prewarmStartedAt = performance.now();
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
deviceIdentifier: selected.identifier,
|
|
1271
|
-
udid: selected.udid,
|
|
1272
|
-
})
|
|
1532
|
+
// Separate plugin actions run in isolated processes. Starting a HID worker
|
|
1533
|
+
// here cannot accelerate the next input; defer startup to the input action.
|
|
1534
|
+
const inputPrewarm: Record<string, unknown> = input.args.prewarm_input === true
|
|
1535
|
+
? { backend: 'remote-xpc-hid', state: 'deferred_to_first_input', runnerOwned: false }
|
|
1273
1536
|
: { backend: 'remote-xpc-hid', state: 'not_requested', runnerOwned: false };
|
|
1274
1537
|
recordTiming(
|
|
1275
1538
|
timingStages,
|
|
@@ -1278,7 +1541,7 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1278
1541
|
inputPrewarm.state === 'ready' || inputPrewarm.state === 'test' || input.args.prewarm_input !== true,
|
|
1279
1542
|
);
|
|
1280
1543
|
const displayStartedAt = performance.now();
|
|
1281
|
-
const display = input.args.prewarm_input === true
|
|
1544
|
+
const display = input.args.prewarm_input === true && input.args.observe_after_open !== true
|
|
1282
1545
|
? await physicalDisplayGeometry(input, selected).then((result) => {
|
|
1283
1546
|
recordTiming(timingStages, 'displayInfo', displayStartedAt, false);
|
|
1284
1547
|
return result;
|
|
@@ -1287,7 +1550,7 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1287
1550
|
throw error;
|
|
1288
1551
|
})
|
|
1289
1552
|
: undefined;
|
|
1290
|
-
if (input.args.prewarm_input !== true) recordTiming(timingStages, 'displayInfo', displayStartedAt, true);
|
|
1553
|
+
if (input.args.prewarm_input !== true || input.args.observe_after_open === true) recordTiming(timingStages, 'displayInfo', displayStartedAt, true);
|
|
1291
1554
|
if (display) {
|
|
1292
1555
|
state.display = { ...display, observedAt: createdAt };
|
|
1293
1556
|
writeState(input, state);
|
|
@@ -1312,7 +1575,11 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1312
1575
|
// Semantic automation is an explicit opt-in action so repeated computer-use
|
|
1313
1576
|
// workflows cannot accidentally create fresh Runner lifecycles.
|
|
1314
1577
|
const active = patchInteractionSession(input.repoRoot, PROVIDER, interactionId, { status: 'waiting_for_user' }) ?? record;
|
|
1578
|
+
const nextObservation = input.args.observe_after_open === true
|
|
1579
|
+
? await optionalPostActionCapture(input, state, 'open', timingStages)
|
|
1580
|
+
: {};
|
|
1315
1581
|
return finishTimedResult(actionStartedAt, timingStages, {
|
|
1582
|
+
...nextObservation,
|
|
1316
1583
|
provider: 'coredevice',
|
|
1317
1584
|
interaction: active,
|
|
1318
1585
|
device: selected,
|
|
@@ -1344,6 +1611,7 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1344
1611
|
error: { code: normalized.code, message: normalized.message },
|
|
1345
1612
|
});
|
|
1346
1613
|
appendEvent(input, interactionId, 'open_failed', { code: normalized.code, message: normalized.message });
|
|
1614
|
+
releaseIosDeviceOwnership(input.controllerHome, input.repoId, interactionId, iosCoreDeviceOwnershipId(selected));
|
|
1347
1615
|
throw normalized;
|
|
1348
1616
|
}
|
|
1349
1617
|
}
|
|
@@ -1352,36 +1620,168 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1352
1620
|
const { record, state } = requireRecord(input, input.actionId === 'physical_device_close');
|
|
1353
1621
|
recordTiming(timingStages, 'sessionLookup', sessionLookupStartedAt, false);
|
|
1354
1622
|
switch (input.actionId) {
|
|
1355
|
-
case '
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
const result = await runCoreJson(input, [
|
|
1359
|
-
'device', 'capture', 'screenshot', '--device', state.device.identifier, '--destination', path,
|
|
1360
|
-
], 'IOS_DEVICE_SCREENSHOT_FAILED', 60_000);
|
|
1361
|
-
if (!existsSync(path)) {
|
|
1362
|
-
throw new AssistantPluginError('IOS_DEVICE_SCREENSHOT_MISSING', 'CoreDevice succeeded without creating the requested screenshot.', { retryable: false });
|
|
1623
|
+
case 'physical_device_documents_list': {
|
|
1624
|
+
if (requireBundleId(input.args.bundle_id) !== state.bundleId) {
|
|
1625
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_TARGET_MISMATCH', 'Document consent does not match the exact app session.', { retryable: false });
|
|
1363
1626
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1627
|
+
if (!state.device.connected) {
|
|
1628
|
+
throw new AssistantPluginError('IOS_DEVICE_NOT_CONNECTED', 'Exact iPhone is not connected.', { retryable: true });
|
|
1629
|
+
}
|
|
1630
|
+
let response: Record<string, unknown>;
|
|
1631
|
+
try {
|
|
1632
|
+
response = await runCoreJson(input, [
|
|
1633
|
+
'device', 'info', 'files', '--device', state.device.identifier,
|
|
1634
|
+
'--domain-type', 'appDataContainer', '--domain-identifier', state.bundleId,
|
|
1635
|
+
'--subdirectory', 'Documents', '--no-recurse',
|
|
1636
|
+
], 'IOS_DEVICE_DOCUMENT_LIST_FAILED', 60_000);
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
// CoreDevice's raw errors may contain private document names or paths.
|
|
1639
|
+
// Keep the stable failure code, not stderr, stdout or command arguments.
|
|
1640
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_LIST_UNVERIFIED',
|
|
1641
|
+
'The selected app document listing could not be verified.',
|
|
1642
|
+
{ retryable: false, details: {
|
|
1643
|
+
causeCode: error instanceof AssistantPluginError ? error.code : 'COREDEVICE_LIST_FAILURE',
|
|
1644
|
+
} });
|
|
1645
|
+
}
|
|
1646
|
+
const result = objectValue(response.result);
|
|
1647
|
+
const entries = Array.isArray(result.files) ? result.files
|
|
1648
|
+
: Array.isArray(result.entries) ? result.entries : undefined;
|
|
1649
|
+
if (!entries) {
|
|
1650
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_LIST_SCHEMA_UNKNOWN',
|
|
1651
|
+
'CoreDevice did not return a recognized file listing; no documents were verified.',
|
|
1652
|
+
{ retryable: false });
|
|
1653
|
+
}
|
|
1654
|
+
// A recognized top-level array with unrecognized row fields is not an
|
|
1655
|
+
// empty directory. Reject it rather than falsely reporting no documents.
|
|
1656
|
+
const listedNames = entries.map((entry) => typeof entry === 'string' ? entry
|
|
1657
|
+
: stringField(objectValue(entry), 'name') ?? stringField(objectValue(entry), 'fileName'));
|
|
1658
|
+
if (listedNames.some((name) => typeof name !== 'string' || !name.trim())) {
|
|
1659
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_LIST_SCHEMA_UNKNOWN',
|
|
1660
|
+
'CoreDevice returned file entries without recognizable names; document presence is unverified.',
|
|
1661
|
+
{ retryable: false });
|
|
1662
|
+
}
|
|
1663
|
+
const names = (listedNames as string[]).filter((name) =>
|
|
1664
|
+
!name.includes('/') && !name.includes('\\')
|
|
1665
|
+
&& /\.(?:pdf|png|jpe?g|heic)$/i.test(name)).slice(0, 100);
|
|
1666
|
+
return { provider: 'coredevice', deviceUnmodified: true,
|
|
1667
|
+
interactionId: record.interactionId, bundleId: state.bundleId,
|
|
1668
|
+
domain: 'appDataContainer/Documents', names, truncated: entries.length > 100 };
|
|
1669
|
+
}
|
|
1670
|
+
case 'physical_device_document_export': {
|
|
1671
|
+
if (requireBundleId(input.args.bundle_id) !== state.bundleId) {
|
|
1672
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_TARGET_MISMATCH', 'Document consent does not match the exact app session.', { retryable: false });
|
|
1673
|
+
}
|
|
1674
|
+
if (!state.device.connected) {
|
|
1675
|
+
throw new AssistantPluginError('IOS_DEVICE_NOT_CONNECTED', 'Exact iPhone is not connected.', { retryable: true });
|
|
1676
|
+
}
|
|
1677
|
+
const path = requireAppDocumentPath(input.args.path);
|
|
1678
|
+
const dir = join(artifactDir(input, record.interactionId), 'private-documents');
|
|
1679
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
1680
|
+
if (!lstatSync(dir).isDirectory()) {
|
|
1681
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_ARTIFACT_INVALID', 'The private document directory is not a regular directory.', { retryable: false });
|
|
1682
|
+
}
|
|
1683
|
+
chmodSync(dir, 0o700);
|
|
1684
|
+
const ext = path.slice(path.lastIndexOf('.')).toLowerCase();
|
|
1685
|
+
const destination = join(dir, randomUUID() + ext);
|
|
1686
|
+
try {
|
|
1687
|
+
// Validate the existing manifest before copying anything from the
|
|
1688
|
+
// phone. Corrupt, oversized or foreign records cannot be overwritten.
|
|
1689
|
+
const indexPath = join(dir, 'index.json');
|
|
1690
|
+
let entries: Array<Record<string, unknown>> = [];
|
|
1691
|
+
if (existsSync(indexPath)) {
|
|
1692
|
+
const indexFile = lstatSync(indexPath);
|
|
1693
|
+
if (!indexFile.isFile() || indexFile.size <= 0 || indexFile.size > MAX_DOCUMENT_INDEX_BYTES) {
|
|
1694
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_INDEX_INVALID',
|
|
1695
|
+
'The private document index is not a bounded regular file.', { retryable: false });
|
|
1696
|
+
}
|
|
1697
|
+
const prior = readJsonFile<unknown>(indexPath);
|
|
1698
|
+
const index = objectValue(prior);
|
|
1699
|
+
const stored = index.entries;
|
|
1700
|
+
const validEntries = Array.isArray(stored) && stored.length <= 500
|
|
1701
|
+
&& stored.every((item) => {
|
|
1702
|
+
const row = objectValue(item);
|
|
1703
|
+
return typeof row.exportedAt === 'string' && !Number.isNaN(Date.parse(row.exportedAt))
|
|
1704
|
+
&& typeof row.sourcePath === 'string' && /^Documents\/[^/\\]+\.(?:pdf|png|jpe?g|heic)$/i.test(row.sourcePath)
|
|
1705
|
+
&& typeof row.artifactName === 'string' && /^[a-f0-9-]{36}\.(?:pdf|png|jpe?g|heic)$/i.test(row.artifactName)
|
|
1706
|
+
&& typeof row.sha256 === 'string' && /^[a-f0-9]{64}$/.test(row.sha256)
|
|
1707
|
+
&& typeof row.bytes === 'number' && Number.isSafeInteger(row.bytes)
|
|
1708
|
+
&& row.bytes > 0 && row.bytes <= MAX_EXPORTED_DOCUMENT_BYTES;
|
|
1709
|
+
});
|
|
1710
|
+
if (index.schemaVersion !== 1 || index.bundleId !== state.bundleId
|
|
1711
|
+
|| index.interactionId !== record.interactionId || !validEntries) {
|
|
1712
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_INDEX_INVALID',
|
|
1713
|
+
'The private document index belongs to another session or is malformed.', { retryable: false });
|
|
1714
|
+
}
|
|
1715
|
+
entries = stored as Array<Record<string, unknown>>;
|
|
1716
|
+
}
|
|
1717
|
+
if (entries.length >= 500) {
|
|
1718
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_INDEX_FULL',
|
|
1719
|
+
'The private document index has reached its 500-item limit.', { retryable: false });
|
|
1720
|
+
}
|
|
1721
|
+
await runCoreJson(input, [
|
|
1722
|
+
'device', 'copy', 'from', '--device', state.device.identifier,
|
|
1723
|
+
'--domain-type', 'appDataContainer', '--domain-identifier', state.bundleId,
|
|
1724
|
+
'--source', path, '--destination', destination,
|
|
1725
|
+
], 'IOS_DEVICE_DOCUMENT_EXPORT_FAILED', 90_000);
|
|
1726
|
+
const file = lstatSync(destination);
|
|
1727
|
+
if (!file.isFile() || file.size <= 0 || file.size > MAX_EXPORTED_DOCUMENT_BYTES) {
|
|
1728
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_EXPORT_INVALID',
|
|
1729
|
+
'Expected exactly one nonempty regular document of at most 20 MiB.', { retryable: false });
|
|
1730
|
+
}
|
|
1731
|
+
chmodSync(destination, 0o600);
|
|
1732
|
+
const bytes = readFileSync(destination);
|
|
1733
|
+
// Validate more than magic bytes: a truncated CoreDevice copy must not
|
|
1734
|
+
// become a successful medical-document export or organized index entry.
|
|
1735
|
+
const isExpectedFormat = ext === '.pdf'
|
|
1736
|
+
? bytes.subarray(0, 5).toString('ascii') === '%PDF-'
|
|
1737
|
+
&& /%%EOF[\x00\x09\x0a\x0c\x0d\x20]*$/.test(bytes.subarray(-2048).toString('latin1'))
|
|
1738
|
+
: ext === '.png' ? pngDisplayGeometry(bytes) !== undefined
|
|
1739
|
+
: ext === '.jpg' || ext === '.jpeg'
|
|
1740
|
+
? bytes.length > 4 && bytes.subarray(0, 3).toString('hex') === 'ffd8ff'
|
|
1741
|
+
&& bytes.subarray(-2).toString('hex') === 'ffd9'
|
|
1742
|
+
: ext === '.heic' && bytes.length >= 16
|
|
1743
|
+
&& bytes.subarray(4, 8).toString('ascii') === 'ftyp'
|
|
1744
|
+
&& /^(?:heic|heix|hevc|hevx|mif1|msf1)$/.test(bytes.subarray(8, 12).toString('ascii'))
|
|
1745
|
+
&& bytes.readUInt32BE(0) >= 16 && bytes.readUInt32BE(0) <= bytes.length;
|
|
1746
|
+
if (!isExpectedFormat) {
|
|
1747
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_FORMAT_INVALID',
|
|
1748
|
+
'Exported document framing is invalid or incomplete for the selected file type.', { retryable: false });
|
|
1749
|
+
}
|
|
1750
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
1751
|
+
// One canonical export writer also maintains a bounded private index of
|
|
1752
|
+
// facts; medical meaning and cross-app classification stay with the model.
|
|
1753
|
+
const entry = { exportedAt: timestamp(), sourcePath: path,
|
|
1754
|
+
artifactName: destination.slice(dir.length + 1), sha256, bytes: file.size };
|
|
1755
|
+
// Atomic rename is the last fallible operation before acknowledging the
|
|
1756
|
+
// export; a failed index update cannot produce a successful receipt.
|
|
1757
|
+
writeJsonAtomic(indexPath, { schemaVersion: 1, bundleId: state.bundleId,
|
|
1758
|
+
interactionId: record.interactionId, entries: [...entries, entry] }, { mode: 0o600 });
|
|
1759
|
+
return { provider: 'coredevice', deviceUnmodified: true, externallyUploaded: false,
|
|
1760
|
+
interactionId: record.interactionId, bundleId: state.bundleId,
|
|
1761
|
+
document: { domain: 'appDataContainer', sourcePath: path, localArtifact: destination,
|
|
1762
|
+
sha256, bytes: file.size },
|
|
1763
|
+
organizedIndex: { localArtifact: indexPath, entryCount: entries.length + 1 } };
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
// Raw CoreDevice error details may contain sensitive document filenames.
|
|
1766
|
+
// Never expose those details or retry an unknown/partial copy.
|
|
1767
|
+
let cleanup = 'no_partial_artifact';
|
|
1768
|
+
if (existsSync(destination)) {
|
|
1769
|
+
try { unlinkSync(destination); cleanup = 'partial_artifact_deleted'; }
|
|
1770
|
+
catch { cleanup = 'partial_artifact_cleanup_failed'; }
|
|
1771
|
+
}
|
|
1772
|
+
throw new AssistantPluginError('IOS_DEVICE_DOCUMENT_EXPORT_UNVERIFIED',
|
|
1773
|
+
'The selected document export was not verified. Input is not replayable.',
|
|
1774
|
+
{ retryable: false, details: {
|
|
1775
|
+
causeCode: error instanceof AssistantPluginError ? error.code : 'FILE_VERIFICATION_FAILED',
|
|
1776
|
+
cleanup,
|
|
1777
|
+
} });
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
case 'physical_device_screenshot': {
|
|
1781
|
+
const captured = await capturePhysicalScreen(input, state, sanitize(optionalString(input.args.label) ?? 'screenshot'));
|
|
1782
|
+
const { screenshotResult, ...observation } = captured;
|
|
1374
1783
|
return finishTimedResult(actionStartedAt, timingStages, {
|
|
1375
|
-
provider: 'coredevice', interaction: record, result:
|
|
1376
|
-
observation: { observationId, observedAt, label, ttlMs: INPUT_FOREGROUND_OBSERVATION_TTL_MS },
|
|
1377
|
-
fastPath: {
|
|
1378
|
-
actionId: 'physical_device_batch',
|
|
1379
|
-
arguments: { interaction_id: record.interactionId, observation_id: observationId },
|
|
1380
|
-
requires: ['steps'],
|
|
1381
|
-
ttlMs: INPUT_FOREGROUND_OBSERVATION_TTL_MS,
|
|
1382
|
-
oneShot: true,
|
|
1383
|
-
},
|
|
1384
|
-
artifactCandidates: [{ kind: 'ios_physical_device_screenshot', mediaType: 'image/png', path }],
|
|
1784
|
+
provider: 'coredevice', interaction: record, result: screenshotResult, ...observation,
|
|
1385
1785
|
});
|
|
1386
1786
|
}
|
|
1387
1787
|
case 'physical_device_confirm_foreground': {
|
|
@@ -1410,7 +1810,8 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1410
1810
|
deviceUnmodified: true,
|
|
1411
1811
|
};
|
|
1412
1812
|
}
|
|
1413
|
-
case 'physical_device_observed_tap':
|
|
1813
|
+
case 'physical_device_observed_tap':
|
|
1814
|
+
case 'physical_device_observed_swipe': {
|
|
1414
1815
|
const lockStartedAt = performance.now();
|
|
1415
1816
|
const unlock = await requireSessionUnlocked(input, state);
|
|
1416
1817
|
recordTiming(timingStages, 'lockState', lockStartedAt, unlock.cached);
|
|
@@ -1425,17 +1826,29 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1425
1826
|
const display = geometry.display;
|
|
1426
1827
|
const x = requireFiniteNumber(input.args.x, 'x');
|
|
1427
1828
|
const y = requireFiniteNumber(input.args.y, 'y');
|
|
1829
|
+
const swipe = input.actionId === 'physical_device_observed_swipe';
|
|
1830
|
+
const x2 = swipe ? requireFiniteNumber(input.args.to_x, 'to_x') : undefined;
|
|
1831
|
+
const y2 = swipe ? requireFiniteNumber(input.args.to_y, 'to_y') : undefined;
|
|
1832
|
+
const durationMs = swipe && typeof input.args.duration_ms === 'number'
|
|
1833
|
+
? Math.trunc(input.args.duration_ms) : 250;
|
|
1428
1834
|
consumePhysicalScreenObservation(input, state);
|
|
1429
1835
|
const hidStartedAt = performance.now();
|
|
1430
1836
|
const result = await executeRemoteXpcHidInput({
|
|
1431
1837
|
controllerHome: input.controllerHome, deviceIdentifier: state.device.identifier, udid: state.device.udid,
|
|
1432
|
-
width: display.width, height: display.height, action: '
|
|
1838
|
+
width: display.width, height: display.height, action: swipe ? 'swipe' : 'tap',
|
|
1839
|
+
x, y, ...(swipe ? { x2, y2, durationMs } : {}),
|
|
1433
1840
|
});
|
|
1434
1841
|
timingStages.hidWorkerReady = { ms: result.timings.workerStartupMs + result.timings.workerReadyMs, cached: result.reusedWorker };
|
|
1435
1842
|
timingStages.hidWriteAck = { ms: result.timings.hidMs ?? result.timings.requestMs, cached: false };
|
|
1436
1843
|
timingStages.hidRequestTotal = { ms: elapsedMs(hidStartedAt), cached: result.reusedWorker };
|
|
1437
|
-
appendEvent(input, record.interactionId, '
|
|
1438
|
-
|
|
1844
|
+
appendEvent(input, record.interactionId, swipe ? 'observed_swipe' : 'observed_tap', {
|
|
1845
|
+
requestId: input.requestId, observationId: observation.observationId, observationAgeMs: ageMs,
|
|
1846
|
+
x, y, ...(swipe ? { x2, y2, durationMs } : {}), reusedWorker: result.reusedWorker,
|
|
1847
|
+
});
|
|
1848
|
+
const nextObservation = input.args.capture_after === true
|
|
1849
|
+
? await optionalPostActionCapture(input, state, swipe ? 'swipe' : 'tap', timingStages)
|
|
1850
|
+
: {};
|
|
1851
|
+
return finishTimedResult(actionStartedAt, timingStages, { ...nextObservation, provider: 'coredevice', inputBackend: 'remote-xpc-hid', interaction: record, display, result: bounded(result), observationConsumed: true });
|
|
1439
1852
|
}
|
|
1440
1853
|
case 'physical_device_batch': {
|
|
1441
1854
|
const lockStartedAt = performance.now();
|
|
@@ -1568,7 +1981,11 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1568
1981
|
kinds: steps.map((step) => step.kind),
|
|
1569
1982
|
});
|
|
1570
1983
|
recordTiming(timingStages, 'eventPersistence', eventStartedAt, false);
|
|
1984
|
+
const nextObservation = input.args.capture_after === true
|
|
1985
|
+
? await optionalPostActionCapture(input, state, 'batch', timingStages)
|
|
1986
|
+
: {};
|
|
1571
1987
|
return finishTimedResult(actionStartedAt, timingStages, {
|
|
1988
|
+
...nextObservation,
|
|
1572
1989
|
provider: 'coredevice',
|
|
1573
1990
|
inputBackend: 'remote-xpc-hid',
|
|
1574
1991
|
interaction: record,
|
|
@@ -1698,7 +2115,8 @@ export async function executeIosPhysicalDeviceAction(input: AssistantPluginActio
|
|
|
1698
2115
|
patchInteractionSession(input.repoRoot, PROVIDER, record.interactionId, { status: 'closing' });
|
|
1699
2116
|
appendEvent(input, record.interactionId, 'session_closed', { inputWorkerRelease });
|
|
1700
2117
|
const finalRecord = patchInteractionSession(input.repoRoot, PROVIDER, record.interactionId, { status: 'closed', error: undefined }) ?? record;
|
|
1701
|
-
|
|
2118
|
+
const deviceOwnershipReleased = releaseIosDeviceOwnership(input.controllerHome, input.repoId, record.interactionId, iosCoreDeviceOwnershipId(state.device));
|
|
2119
|
+
return { provider: 'coredevice', interaction: finalRecord, deviceUnmodified: true, inputWorkerRelease, deviceOwnershipReleased };
|
|
1702
2120
|
}
|
|
1703
2121
|
default:
|
|
1704
2122
|
throw new AssistantPluginError('PLUGIN_ACTION_NOT_SUPPORTED', `ios/${input.actionId} is not supported.`, { retryable: false });
|