@moretea-labs/forge 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -2
- package/CHANGELOG.md +20 -0
- package/README.en.md +24 -3
- package/README.md +10 -6
- package/README.zh-CN.md +5 -3
- package/THIRD_PARTY_NOTICES.md +1 -2
- package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
- package/adapters/chatgpt/provider-delivery.ts +1 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
- package/adapters/computer/chatgpt-extension-target.ts +295 -50
- package/adapters/computer/chrome-extension/background.js +227 -71
- package/adapters/computer/chrome-extension/content.js +181 -157
- package/adapters/computer/chrome-extension/manifest.json +3 -1
- package/adapters/computer/desktop-operator-provider.ts +18 -6
- package/adapters/computer/interaction-target-authority.ts +39 -9
- package/adapters/mcp/access-mode.ts +18 -39
- package/adapters/mcp/auth.ts +2 -6
- package/adapters/mcp/instructions.ts +2 -2
- package/adapters/mcp/multi-repository.ts +7 -28
- package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
- package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
- package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
- package/adapters/mcp/runtime-gateway/router.ts +3 -5
- package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
- package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
- package/adapters/mcp/server.ts +24 -3
- package/adapters/mcp/setup.ts +4 -10
- package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
- package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
- package/adapters/mcp/toolset-names.ts +11 -42
- package/adapters/mcp/toolset.ts +16 -90
- package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
- package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
- package/adapters/mcp/transports/http-observation.ts +18 -6
- package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
- package/adapters/mcp/transports/http.ts +54 -14
- package/adapters/mcp/types.ts +0 -6
- package/assets/hooks/lib/session-state.sh +0 -38
- package/assets/hooks/post-bash.sh +1 -1
- package/assets/hooks/post-tool-observer.sh +0 -5
- package/assets/hooks/prompt-guard.sh +0 -55
- package/assets/partials/04-project-structure.partial.md +1 -1
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -1
- package/assets/partials-agents/08-deep-docs.partial.md +1 -1
- package/assets/reference-configs/agentic-development-flow.md +1 -1
- package/assets/reference-configs/external-tooling.md +0 -76
- package/assets/reference-configs/release-deploy.md +1 -1
- package/assets/templates/helpers/architecture-queue.sh +1 -1
- package/assets/templates/helpers/check-agent-tooling.sh +1 -327
- package/assets/templates/helpers/check-task-workflow.sh +1 -1
- package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
- package/assets/templates/helpers/migrate-project-template.sh +2 -2
- package/assets/workflow-contract.v1.json +0 -2
- package/docs/README.md +5 -2
- package/docs/architecture/CURRENT.md +114 -30
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
- package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
- package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
- package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
- package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
- package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
- package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
- package/docs/architecture/domains/public-surface.md +3 -5
- package/docs/architecture/domains/verification.md +5 -5
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
- package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
- package/docs/forge-plugin-management.md +14 -2
- package/docs/operations/controller-browser-plugin.md +23 -0
- package/docs/operations/features.md +6 -7
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/github-repository.md +4 -4
- package/docs/operations/github-semantic-release-projection.md +20 -0
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +13 -54
- package/docs/operations/runtime-performance-diagnostics.md +8 -0
- package/docs/operations/standalone-disaster-recovery.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/03-first-repository-task.md +2 -0
- package/docs/wiki/Computer-and-Plugins.md +78 -0
- package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
- package/docs/wiki/Home.md +16 -2
- package/docs/wiki/Implementation.md +24 -12
- package/docs/wiki/Installation.md +28 -32
- package/docs/wiki/Integrations.md +4 -0
- package/docs/wiki/Quick-Start.md +35 -22
- package/docs/wiki/Releases-and-Upgrades.md +15 -25
- package/docs/wiki/_Sidebar.md +5 -1
- package/package.json +1 -12
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
- package/packages/kernel/scheduler/application/eligibility.ts +4 -1
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
- package/packages/kernel/work/application/work-service.ts +1 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +9 -2
- package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
- package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
- package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
- package/scripts/assemble-template.ts +11 -72
- package/scripts/check-mcp-compatibility.ts +55 -74
- package/scripts/check-package-identity.mjs +0 -5
- package/scripts/check-runtime-architecture.mjs +305 -28
- package/scripts/init-project.sh +0 -46
- package/scripts/install-ios-hid-toolchain.sh +60 -0
- package/scripts/install-standalone-recovery.ts +1 -1
- package/scripts/lib/project-init-lib.sh +3 -3
- package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
- package/scripts/open-source-audit-allowlist.txt +0 -1
- package/scripts/run-governed-gate.ts +2 -0
- package/scripts/smoke-ios-physical-device.ts +38 -10
- package/scripts/smoke-mcp-http-runtime.ts +7 -8
- package/scripts/smoke-mcp-tool-surface.ts +27 -41
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
- package/scripts/sync-generated-authority.mjs +0 -1
- package/src/cli/agent-jobs/executor-health.ts +1 -1
- package/src/cli/agent-jobs/integration.ts +2 -2
- package/src/cli/chatgpt-browser/file-policy.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -143
- package/src/cli/commands/global-runtime.ts +3 -196
- package/src/cli/commands/init-hook.ts +6 -23
- package/src/cli/commands/init.ts +3 -252
- package/src/cli/commands/mcp.ts +0 -5
- package/src/cli/commands/prompt-guard-decision.ts +0 -2
- package/src/cli/commands/recovery.ts +2 -14
- package/src/cli/commands/repository.ts +17 -1
- package/src/cli/commands/setup-execution.ts +41 -0
- package/src/cli/controller/completion-orchestrator.ts +1 -1
- package/src/cli/controller/composite-operations.ts +1 -1
- package/src/cli/controller/context/instruction-context.ts +1 -1
- package/src/cli/controller/context/known-paths.ts +2 -2
- package/src/cli/controller/context/query-planning.ts +11 -4
- package/src/cli/controller/context/rule-resolution.ts +2 -2
- package/src/cli/controller/context/source-materializer.ts +2 -2
- package/src/cli/controller/context/structural-provider.ts +84 -0
- package/src/cli/controller/context/structural.ts +6 -6
- package/src/cli/controller/context/types.ts +8 -9
- package/src/cli/controller/context-pack.ts +97 -76
- package/src/cli/controller/operational-plan.ts +1 -1
- package/src/cli/controller/systemd-user.ts +4 -10
- package/src/cli/editing/edit-session.ts +17 -5
- package/src/cli/github/github.ts +4 -4
- package/src/cli/github/semantic-projection.ts +174 -0
- package/src/cli/hook/prompt-intents.ts +0 -9
- package/src/cli/index.ts +1 -43
- package/src/cli/local-bridge/connector-freshness.ts +1 -12
- package/src/cli/local-bridge/console-automations.ts +1 -1
- package/src/cli/local-bridge/console-view-models.ts +1 -1
- package/src/cli/local-bridge/facade-api.ts +0 -2
- package/src/cli/repositories/command-classifier.ts +0 -9
- package/src/cli/repositories/command-scope.ts +2 -2
- package/src/cli/repositories/locks.ts +20 -0
- package/src/cli/repositories/registry.ts +2 -1
- package/src/cli/repositories/safe-patch.ts +2 -12
- package/src/cli/repositories/structured-git.ts +39 -3
- package/src/cli/repository/inspector.ts +185 -343
- package/src/effects/process-runner.ts +17 -1
- package/src/runtime/context/generic-lsp-provider.ts +1 -1
- package/src/runtime/context/semantic-navigation-contract.ts +7 -0
- package/src/runtime/context/semantic-navigation.ts +1 -1
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
- package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
- package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
- package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
- package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
- package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
- package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
- package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
- package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
- package/src/runtime/control-plane/governance/external-effects.ts +1 -1
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
- package/src/runtime/evidence/sensitive-output.ts +22 -2
- package/src/runtime/execution/jobs/child-reference.ts +11 -0
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
- package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
- package/src/runtime/execution/process-runtime/runtime.ts +20 -16
- package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
- package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
- package/src/runtime/execution/workers/executor.ts +1 -2
- package/src/runtime/maintenance/cleanup.ts +41 -5
- package/src/runtime/plugins/action-application.ts +12 -1
- package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
- package/src/runtime/plugins/browser-adapter.ts +381 -313
- package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
- package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
- package/src/runtime/plugins/browser-registration.ts +2 -0
- package/src/runtime/plugins/browser-runtime.ts +26 -35
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
- package/src/runtime/plugins/computer-registration.ts +76 -9
- package/src/runtime/plugins/github-adapter.ts +30 -3
- package/src/runtime/plugins/gmail-adapter.ts +166 -1
- package/src/runtime/plugins/google-shared.ts +10 -5
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
- package/src/runtime/plugins/ios-adapter.ts +45 -40
- package/src/runtime/plugins/ios-agent-device.ts +54 -4
- package/src/runtime/plugins/ios-device-ownership.ts +103 -0
- package/src/runtime/plugins/ios-physical-device.ts +497 -79
- package/src/runtime/plugins/local-system-adapter.ts +1 -99
- package/src/runtime/plugins/store.ts +80 -7
- package/src/runtime/plugins/types.ts +4 -0
- package/src/runtime/projections/git-status-sampler.ts +2 -19
- package/src/runtime/recovery/actions.ts +1 -1
- package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
- package/src/runtime/recovery/probe.ts +2 -2
- package/src/runtime/release/release-coordinator.ts +20 -4
- package/src/runtime/root/controller-progression-composition.ts +67 -5
- package/src/runtime/root/package-connector-service.ts +123 -11
- package/src/runtime/root/package-runtime-release.ts +6 -18
- package/src/runtime/root/package-runtime-service.ts +7 -13
- package/src/runtime/root/release-execution-canary.ts +18 -5
- package/src/runtime/root/release-manifest.ts +0 -34
- package/src/runtime/root/release-materialize.ts +0 -78
- package/src/runtime/root/runtime.ts +1 -2
- package/src/runtime/root/scheduled-controller-composition.ts +2 -1
- package/src/runtime/root/service.ts +90 -11
- package/src/runtime/root/types.ts +0 -6
- package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
- package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
- package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
- package/src/runtime/safe-tooling/ios-development.ts +22 -8
- package/src/runtime/safe-tooling/job-summary.ts +1 -1
- package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
- package/src/runtime/shared/atomic-text-file.ts +21 -0
- package/src/runtime/shared/json-files.ts +8 -3
- package/src/runtime/standalone-recovery/core.ts +189 -174
- package/src/runtime/standalone-recovery/entry.ts +62 -30
- package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
- package/src/runtime/workflow/schedules/engine.ts +36 -14
- package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
- package/supervisor/client.ts +31 -14
- package/supervisor/control-plane.ts +210 -115
- package/supervisor/forge-validators.ts +88 -21
- package/supervisor/native-browser-adapter.ts +393 -28
- package/supervisor/native-messaging/host.ts +21 -2
- package/supervisor/protocol.ts +74 -26
- package/supervisor/server.ts +16 -8
- package/supervisor/store.ts +476 -57
- package/supervisor/types.ts +19 -13
- package/SKILL.md +0 -427
- package/adapters/computer/chatgpt-target-router.ts +0 -103
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
- package/adapters/mcp/toolset-selection.ts +0 -41
- package/agents/openai.yaml +0 -4
- package/assets/skill-hooks.json +0 -41
- package/assets/skill-version.json +0 -189
- package/assets/skills/claude-review/SKILL.md +0 -231
- package/assets/skills/codex-review/SKILL.md +0 -103
- package/assets/templates/helpers/check-skill-version.ts +0 -193
- package/scripts/check-skill-version.ts +0 -5
- package/scripts/ensure-codegraph.sh +0 -16
- package/scripts/run-skill-hook.ts +0 -244
- package/scripts/sync-codex-installed-copies.sh +0 -182
- package/src/cli/commands/tools.ts +0 -167
- package/src/cli/mcp/access-mode.ts +0 -2
- package/src/cli/mcp/access-tools.ts +0 -2
- package/src/cli/mcp/audit.ts +0 -2
- package/src/cli/mcp/auth.ts +0 -2
- package/src/cli/mcp/instructions.ts +0 -2
- package/src/cli/mcp/legacy-context.ts +0 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -2
- package/src/cli/mcp/multi-repository.ts +0 -2
- package/src/cli/mcp/oauth.ts +0 -2
- package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
- package/src/cli/mcp/paths.ts +0 -2
- package/src/cli/mcp/policy.ts +0 -2
- package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
- package/src/cli/mcp/proxy-env.ts +0 -2
- package/src/cli/mcp/readiness-projection.ts +0 -2
- package/src/cli/mcp/redaction.ts +0 -2
- package/src/cli/mcp/repo.ts +0 -2
- package/src/cli/mcp/repository-tools.ts +0 -2
- package/src/cli/mcp/server.ts +0 -2
- package/src/cli/mcp/setup.ts +0 -2
- package/src/cli/mcp/tool-capability-cache.ts +0 -2
- package/src/cli/mcp/tool-contract.ts +0 -2
- package/src/cli/mcp/tools.ts +0 -2
- package/src/cli/mcp/toolset-names.ts +0 -2
- package/src/cli/mcp/toolset-selection.ts +0 -2
- package/src/cli/mcp/toolset.ts +0 -2
- package/src/cli/mcp/transports/http.ts +0 -2
- package/src/cli/mcp/transports/session-registry.ts +0 -2
- package/src/cli/mcp/transports/stdio.ts +0 -2
- package/src/cli/mcp/types.ts +0 -2
- package/src/cli/mcp/umbrella-tools.ts +0 -2
- package/src/cli/tools/codegraph.ts +0 -695
- package/src/core/adoption/helper-wrapper-plan.ts +0 -159
- package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
- package/src/runtime/context/codegraph-read-provider.ts +0 -611
- package/src/runtime/context/codegraph-sidecar.cjs +0 -248
- package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
- package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
- package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
- package/src/runtime/gateway/mcp/process-tools.ts +0 -2
- package/src/runtime/gateway/mcp/router.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
- package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
- package/src/runtime/plugins/browser-provider-registry.ts +0 -2
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
listGitHubWorkflowRuns,
|
|
12
12
|
listGitHubWorkflows,
|
|
13
13
|
} from '../../cli/github/github';
|
|
14
|
+
import { syncGitHubSemanticRelease } from '../../cli/github/semantic-projection';
|
|
14
15
|
import type {
|
|
15
16
|
AssistantPluginActionDescriptor,
|
|
16
17
|
AssistantPluginActionExecutionInput,
|
|
@@ -72,7 +73,7 @@ function githubCapabilities(): AssistantPluginCapability[] {
|
|
|
72
73
|
title: 'Issue Sync',
|
|
73
74
|
description: 'Mirror controller issues and task metadata into GitHub issues.',
|
|
74
75
|
scopes: ['github:issues:write', 'controller:issues:write'],
|
|
75
|
-
actions: ['configure', 'publish_issue', 'refresh_issue', 'close_issue'],
|
|
76
|
+
actions: ['configure', 'publish_issue', 'refresh_issue', 'close_issue', 'sync_semantic_release'],
|
|
76
77
|
},
|
|
77
78
|
{
|
|
78
79
|
capabilityId: 'project-sync',
|
|
@@ -91,7 +92,7 @@ function githubCapabilities(): AssistantPluginCapability[] {
|
|
|
91
92
|
];
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
function githubActions(): AssistantPluginActionDescriptor[] {
|
|
95
|
+
function githubActions(projectConfigured: boolean): AssistantPluginActionDescriptor[] {
|
|
95
96
|
return [
|
|
96
97
|
{
|
|
97
98
|
actionId: 'configure',
|
|
@@ -145,6 +146,22 @@ function githubActions(): AssistantPluginActionDescriptor[] {
|
|
|
145
146
|
additionalProperties: false,
|
|
146
147
|
},
|
|
147
148
|
},
|
|
149
|
+
{
|
|
150
|
+
actionId: 'sync_semantic_release',
|
|
151
|
+
title: 'Sync semantic release to GitHub',
|
|
152
|
+
description: 'Idempotently project one root semantic release Work and explicitly linked child Works from Forge into GitHub Issues without changing Forge semantics.',
|
|
153
|
+
readOnly: false,
|
|
154
|
+
risk: 'remote_write',
|
|
155
|
+
confirmation: 'authorization',
|
|
156
|
+
defaultTimeoutMs: 120_000,
|
|
157
|
+
cancellable: true,
|
|
158
|
+
idempotent: true,
|
|
159
|
+
scopes: projectConfigured ? ['github:issues:write', 'github:projects:write'] : ['github:issues:write'],
|
|
160
|
+
resourceClaims: [{ resource: 'remote', mode: 'exclusive' }],
|
|
161
|
+
argumentsSchema: { type: 'object', required: ['release_work_id'], properties: {
|
|
162
|
+
release_work_id: { type: 'string', minLength: 1, maxLength: 200 },
|
|
163
|
+
}, additionalProperties: false },
|
|
164
|
+
},
|
|
148
165
|
{
|
|
149
166
|
actionId: 'refresh_issue',
|
|
150
167
|
title: 'Refresh linked GitHub issue',
|
|
@@ -321,7 +338,7 @@ export function buildGitHubPluginManifest(previousRevision = 0, previousUpdatedA
|
|
|
321
338
|
},
|
|
322
339
|
permissions: githubPermissions(status.ready, projectConfigured),
|
|
323
340
|
capabilities: githubCapabilities(),
|
|
324
|
-
actions: githubActions(),
|
|
341
|
+
actions: githubActions(projectConfigured),
|
|
325
342
|
updatedAt: previousUpdatedAt ?? now(),
|
|
326
343
|
};
|
|
327
344
|
}
|
|
@@ -343,6 +360,16 @@ export async function executeGitHubPluginAction(input: AssistantPluginActionExec
|
|
|
343
360
|
}
|
|
344
361
|
case 'publish_issue':
|
|
345
362
|
return { issue: publishIssueWithGitHubPlugin(input.repoRoot, String(input.args.issue_id ?? '').trim()) };
|
|
363
|
+
case 'sync_semantic_release': {
|
|
364
|
+
const config = loadGitHubPluginConfig(input.repoRoot);
|
|
365
|
+
if (!config.enabled || !getGitHubPluginStatus(input.repoRoot).ready) throw new Error('GITHUB_SEMANTIC_PLUGIN_NOT_READY');
|
|
366
|
+
return { ...syncGitHubSemanticRelease(input.repoRoot, {
|
|
367
|
+
releaseWorkId: String(input.args.release_work_id ?? '').trim(),
|
|
368
|
+
repository: config.repository,
|
|
369
|
+
projectOwner: config.projectOwner,
|
|
370
|
+
projectNumber: config.projectNumber,
|
|
371
|
+
}) };
|
|
372
|
+
}
|
|
346
373
|
case 'refresh_issue':
|
|
347
374
|
return refreshIssueWithGitHubPlugin(input.repoRoot, String(input.args.issue_id ?? '').trim()) as Record<string, unknown>;
|
|
348
375
|
case 'list_workflows': {
|
|
@@ -20,7 +20,18 @@ import {
|
|
|
20
20
|
stableMockId,
|
|
21
21
|
} from './google-shared';
|
|
22
22
|
|
|
23
|
-
const GMAIL_PLUGIN_ID = 'gmail';
|
|
23
|
+
export const GMAIL_PLUGIN_ID = 'gmail';
|
|
24
|
+
export const GMAIL_AUTOMATION_CAPABILITY_ID = 'scheduled-gmail-automation';
|
|
25
|
+
export const GMAIL_OWNER_NOTIFICATION_CAPABILITY_ID = 'owner-notification';
|
|
26
|
+
const GMAIL_AUTOMATION_GRANT_MINUTES = 90 * 24 * 60;
|
|
27
|
+
|
|
28
|
+
function gmailAutomationMailbox(config: GmailPluginConfig): string {
|
|
29
|
+
const mailbox = stringValue(config.accountEmail)?.toLowerCase();
|
|
30
|
+
if (!mailbox) {
|
|
31
|
+
throw new AssistantPluginError('GMAIL_AUTOMATION_MAILBOX_NOT_CONFIGURED', 'Set the Gmail plugin accountEmail to the connected mailbox before using scheduled Gmail automation.', { retryable: false });
|
|
32
|
+
}
|
|
33
|
+
return mailbox;
|
|
34
|
+
}
|
|
24
35
|
|
|
25
36
|
interface GmailProvider {
|
|
26
37
|
listMessages(args: Record<string, unknown>, config: GmailPluginConfig): Promise<Record<string, unknown>>;
|
|
@@ -97,6 +108,20 @@ function gmailCapabilities(): AssistantPluginCapability[] {
|
|
|
97
108
|
scopes: ['gmail.modify'],
|
|
98
109
|
actions: ['modify_message_labels', 'archive_message', 'mark_message_read', 'mark_message_unread', 'trash_message'],
|
|
99
110
|
},
|
|
111
|
+
{
|
|
112
|
+
capabilityId: GMAIL_AUTOMATION_CAPABILITY_ID,
|
|
113
|
+
title: 'Scheduled Gmail Automation',
|
|
114
|
+
description: 'After an explicit reusable grant, archive selected messages in the profile-verified mailbox. This does not authorize sending mail, replies or trash.',
|
|
115
|
+
scopes: ['gmail.modify'],
|
|
116
|
+
actions: ['authorize_scheduled_gmail_automation', 'archive_scheduled_message'],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
capabilityId: GMAIL_OWNER_NOTIFICATION_CAPABILITY_ID,
|
|
120
|
+
title: 'Owner Authorization Email',
|
|
121
|
+
description: 'Send Forge authorization/approval notices exclusively to the configured, Google-profile-verified owner mailbox. Does not authorize archive, replies, trash or arbitrary recipients.',
|
|
122
|
+
scopes: ['gmail.send'],
|
|
123
|
+
actions: ['authorize_owner_notifications', 'send_owner_notification'],
|
|
124
|
+
},
|
|
100
125
|
];
|
|
101
126
|
}
|
|
102
127
|
|
|
@@ -295,6 +320,79 @@ function gmailActions(): AssistantPluginActionDescriptor[] {
|
|
|
295
320
|
additionalProperties: false,
|
|
296
321
|
},
|
|
297
322
|
},
|
|
323
|
+
{
|
|
324
|
+
actionId: 'authorize_scheduled_gmail_automation',
|
|
325
|
+
title: 'Authorize scheduled Gmail automation',
|
|
326
|
+
description: 'Explicitly grant Forge up to 90 days to archive selected messages in the configured, profile-verified Gmail mailbox. It does not authorize sending messages or trash.',
|
|
327
|
+
readOnly: false,
|
|
328
|
+
risk: 'remote_write',
|
|
329
|
+
confirmation: 'authorization',
|
|
330
|
+
requiredConfirmationText: 'authorize-scheduled-gmail-automation',
|
|
331
|
+
defaultTimeoutMs: 30_000,
|
|
332
|
+
cancellable: false,
|
|
333
|
+
idempotent: true,
|
|
334
|
+
scopes: ['gmail.modify'],
|
|
335
|
+
resourceClaims: [{ resource: 'repo-state', mode: 'write' }],
|
|
336
|
+
argumentsSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
actionId: 'archive_scheduled_message',
|
|
340
|
+
title: 'Archive selected Gmail message for scheduled automation',
|
|
341
|
+
description: 'Archive one explicitly selected message in the fixed owner mailbox. Requires the dedicated scheduled Gmail automation grant.',
|
|
342
|
+
readOnly: false,
|
|
343
|
+
risk: 'remote_write',
|
|
344
|
+
confirmation: 'authorization',
|
|
345
|
+
requiresExplicitCapabilityGrant: true,
|
|
346
|
+
defaultTimeoutMs: 30_000,
|
|
347
|
+
cancellable: false,
|
|
348
|
+
idempotent: true,
|
|
349
|
+
scopes: ['gmail.modify'],
|
|
350
|
+
resourceClaims: [{ resource: 'remote', mode: 'exclusive' }],
|
|
351
|
+
argumentsSchema: {
|
|
352
|
+
type: 'object',
|
|
353
|
+
properties: { message_id: { type: 'string', minLength: 1, maxLength: 512 } },
|
|
354
|
+
required: ['message_id'],
|
|
355
|
+
additionalProperties: false,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
actionId: 'authorize_owner_notifications',
|
|
360
|
+
title: 'Authorize owner notification emails',
|
|
361
|
+
description: 'Explicitly allow Forge to send future authorization requests to this verified Gmail account, with links to affected ChatGPT conversations. Does not grant archiving, replies or arbitrary recipients.',
|
|
362
|
+
readOnly: false,
|
|
363
|
+
risk: 'remote_write',
|
|
364
|
+
confirmation: 'authorization',
|
|
365
|
+
requiredConfirmationText: 'authorize-owner-notifications',
|
|
366
|
+
defaultTimeoutMs: 30_000,
|
|
367
|
+
cancellable: false,
|
|
368
|
+
idempotent: true,
|
|
369
|
+
scopes: ['gmail.send'],
|
|
370
|
+
resourceClaims: [{ resource: 'repo-state', mode: 'write' }],
|
|
371
|
+
argumentsSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
actionId: 'send_owner_notification',
|
|
375
|
+
title: 'Send Forge notification to owner mailbox',
|
|
376
|
+
description: 'Send a plain-text Forge/Gmail automation notification only to the configured, profile-verified Gmail mailbox. No recipient, CC, BCC, or attachments can be supplied. Requires the dedicated scheduled Gmail automation grant.',
|
|
377
|
+
readOnly: false,
|
|
378
|
+
risk: 'remote_write',
|
|
379
|
+
confirmation: 'authorization',
|
|
380
|
+
requiresExplicitCapabilityGrant: true,
|
|
381
|
+
defaultTimeoutMs: 45_000,
|
|
382
|
+
cancellable: false,
|
|
383
|
+
idempotent: false,
|
|
384
|
+
scopes: ['gmail.send'],
|
|
385
|
+
resourceClaims: [{ resource: 'remote', mode: 'exclusive' }],
|
|
386
|
+
argumentsSchema: {
|
|
387
|
+
type: 'object',
|
|
388
|
+
properties: {
|
|
389
|
+
subject: { type: 'string', minLength: 1, maxLength: 180 },
|
|
390
|
+
body_text: { type: 'string', minLength: 1, maxLength: 12000 },
|
|
391
|
+
},
|
|
392
|
+
required: ['subject', 'body_text'],
|
|
393
|
+
additionalProperties: false,
|
|
394
|
+
},
|
|
395
|
+
},
|
|
298
396
|
{
|
|
299
397
|
actionId: 'modify_message_labels',
|
|
300
398
|
title: 'Modify Gmail message labels',
|
|
@@ -752,6 +850,40 @@ export async function executeGmailPluginAction(input: AssistantPluginActionExecu
|
|
|
752
850
|
return gmailProvider(current, input.repoRoot).createDraft(input.args, current);
|
|
753
851
|
case 'send_message':
|
|
754
852
|
return gmailProvider(current, input.repoRoot).sendMessage(input.args, current);
|
|
853
|
+
case 'authorize_owner_notifications':
|
|
854
|
+
return {
|
|
855
|
+
authorizedMailbox: gmailAutomationMailbox(current),
|
|
856
|
+
capabilityId: GMAIL_OWNER_NOTIFICATION_CAPABILITY_ID,
|
|
857
|
+
scopes: ['gmail.send'],
|
|
858
|
+
grantLifetimeDays: 90,
|
|
859
|
+
sent: false,
|
|
860
|
+
};
|
|
861
|
+
case 'authorize_scheduled_gmail_automation':
|
|
862
|
+
return {
|
|
863
|
+
authorizedMailbox: gmailAutomationMailbox(current),
|
|
864
|
+
capabilityId: GMAIL_AUTOMATION_CAPABILITY_ID,
|
|
865
|
+
scopes: ['gmail.modify'],
|
|
866
|
+
grantLifetimeDays: 90,
|
|
867
|
+
sent: false,
|
|
868
|
+
note: 'The plugin action store records the reusable grant after this explicit authorization action succeeds.',
|
|
869
|
+
};
|
|
870
|
+
case 'archive_scheduled_message':
|
|
871
|
+
return gmailProvider(current, input.repoRoot).archiveMessage(input.args, current);
|
|
872
|
+
case 'send_owner_notification': {
|
|
873
|
+
const subject = stringValue(input.args.subject);
|
|
874
|
+
const bodyText = stringValue(input.args.body_text);
|
|
875
|
+
if (!subject || !/^\[(Forge|Gmail)\]/.test(subject)) {
|
|
876
|
+
throw new AssistantPluginError('GMAIL_OWNER_NOTIFICATION_SUBJECT_INVALID', 'Owner notifications must start with [Forge] or [Gmail].', { retryable: false });
|
|
877
|
+
}
|
|
878
|
+
if (!bodyText) {
|
|
879
|
+
throw new AssistantPluginError('GMAIL_OWNER_NOTIFICATION_BODY_REQUIRED', 'Owner notifications require plain-text body_text.', { retryable: false });
|
|
880
|
+
}
|
|
881
|
+
return gmailProvider(current, input.repoRoot).sendMessage({
|
|
882
|
+
to: [gmailAutomationMailbox(current)],
|
|
883
|
+
subject,
|
|
884
|
+
body_text: bodyText,
|
|
885
|
+
}, current);
|
|
886
|
+
}
|
|
755
887
|
case 'modify_message_labels':
|
|
756
888
|
return gmailProvider(current, input.repoRoot).modifyMessageLabels(input.args, current);
|
|
757
889
|
case 'archive_message':
|
|
@@ -769,8 +901,41 @@ export async function executeGmailPluginAction(input: AssistantPluginActionExecu
|
|
|
769
901
|
}
|
|
770
902
|
}
|
|
771
903
|
|
|
904
|
+
async function resolveGmailAutomationAuthorizationContext(input: AssistantPluginActionExecutionInput) {
|
|
905
|
+
if (![
|
|
906
|
+
'authorize_scheduled_gmail_automation',
|
|
907
|
+
'archive_scheduled_message',
|
|
908
|
+
'authorize_owner_notifications',
|
|
909
|
+
'send_owner_notification',
|
|
910
|
+
].includes(input.actionId)) return undefined;
|
|
911
|
+
|
|
912
|
+
const current = loadGmailPluginConfig(input.repoRoot, input);
|
|
913
|
+
if (!current.enabled) {
|
|
914
|
+
throw new AssistantPluginError('GMAIL_AUTOMATION_DISABLED', 'Enable the Gmail plugin before authorizing scheduled Gmail automation.', { retryable: false });
|
|
915
|
+
}
|
|
916
|
+
const configuredEmail = gmailAutomationMailbox(current);
|
|
917
|
+
const profile = await gmailProvider(current, input.repoRoot).getProfile({}, current);
|
|
918
|
+
const actualEmail = stringValue(profile.emailAddress)?.toLowerCase();
|
|
919
|
+
if (actualEmail !== configuredEmail) {
|
|
920
|
+
throw new AssistantPluginError(
|
|
921
|
+
'GMAIL_AUTOMATION_MAILBOX_MISMATCH',
|
|
922
|
+
'The connected Gmail profile does not match the configured Gmail accountEmail.',
|
|
923
|
+
{ retryable: false },
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return {
|
|
927
|
+
target: {
|
|
928
|
+
kind: 'gmail-mailbox' as const,
|
|
929
|
+
id: configuredEmail,
|
|
930
|
+
identityFingerprint: `gmail-profile:${configuredEmail}`,
|
|
931
|
+
},
|
|
932
|
+
expiresInMinutes: GMAIL_AUTOMATION_GRANT_MINUTES,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
|
|
772
936
|
export const gmailPluginAdapter = {
|
|
773
937
|
pluginId: GMAIL_PLUGIN_ID,
|
|
774
938
|
buildManifest: buildGmailPluginManifest,
|
|
775
939
|
executeAction: executeGmailPluginAction,
|
|
940
|
+
resolveAuthorizationContext: resolveGmailAutomationAuthorizationContext,
|
|
776
941
|
};
|
|
@@ -331,7 +331,7 @@ export function resolveGoogleAuth(
|
|
|
331
331
|
credentialSource: refresh?.name,
|
|
332
332
|
accessToken: REFRESH_REQUIRED_ACCESS_TOKEN,
|
|
333
333
|
errors: [],
|
|
334
|
-
warnings: ['A
|
|
334
|
+
warnings: ['A refresh token is stored, but Google has not verified it. A matching OAuth client secret may also be required by the provider.'],
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
return {
|
|
@@ -351,7 +351,7 @@ export type GoogleReadinessMode =
|
|
|
351
351
|
export function resolveGoogleReadinessMode(config: GooglePluginConfig, auth: GoogleAuthState): GoogleReadinessMode {
|
|
352
352
|
if (!config.enabled) return 'disabled';
|
|
353
353
|
if (config.provider === 'mock' && auth.ready) return 'mock_provider_ready';
|
|
354
|
-
if (config.provider === 'google-workspace' && auth.ready) return 'live_provider_ready';
|
|
354
|
+
if (config.provider === 'google-workspace' && auth.ready && auth.probed) return 'live_provider_ready';
|
|
355
355
|
if (config.provider === 'google-workspace' && auth.authenticated) return 'live_token_unverified';
|
|
356
356
|
return 'missing_token';
|
|
357
357
|
}
|
|
@@ -379,10 +379,10 @@ export function pluginStateFromGoogleAuth(config: GooglePluginConfig, auth: Goog
|
|
|
379
379
|
checkedAt: new Date().toISOString(),
|
|
380
380
|
ready: config.enabled && ready,
|
|
381
381
|
probed: config.enabled ? auth.probed : false,
|
|
382
|
-
errors: [],
|
|
382
|
+
errors: !config.enabled || auth.authenticated ? [] : auth.errors,
|
|
383
383
|
warnings: !config.enabled
|
|
384
384
|
? ['Plugin is disabled. Enable it before using Google provider actions.']
|
|
385
|
-
:
|
|
385
|
+
: auth.warnings,
|
|
386
386
|
details: {
|
|
387
387
|
provider: config.provider,
|
|
388
388
|
accountEmail: config.accountEmail,
|
|
@@ -457,7 +457,12 @@ async function refreshGoogleAccessToken(service: GoogleService, timeoutMs: numbe
|
|
|
457
457
|
try { parsed = raw ? JSON.parse(raw) as Record<string, unknown> : {}; } catch { parsed = { raw }; }
|
|
458
458
|
const accessToken = typeof parsed.access_token === 'string' ? parsed.access_token.trim() : '';
|
|
459
459
|
if (!response.ok || !accessToken) {
|
|
460
|
-
|
|
460
|
+
const missingSecret = parsed.error === 'invalid_request'
|
|
461
|
+
&& typeof parsed.error_description === 'string'
|
|
462
|
+
&& /client_secret is missing/i.test(parsed.error_description);
|
|
463
|
+
throw new AssistantPluginError('PLUGIN_AUTH_FAILED', missingSecret
|
|
464
|
+
? 'Google OAuth client secret is missing for the configured client. Restore its matching secret in macOS Keychain, or reauthorize with a new OAuth client.'
|
|
465
|
+
: 'Google refresh token exchange failed.', {
|
|
461
466
|
retryable: false,
|
|
462
467
|
details: { service, status: response.status, providerError: parsed },
|
|
463
468
|
});
|
|
@@ -6,15 +6,16 @@ import { createInterface } from 'readline';
|
|
|
6
6
|
import { performance } from 'perf_hooks';
|
|
7
7
|
import { AssistantPluginError } from '../errors';
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
export const IOS_HID_TOOLCHAIN_VERSION = '11.24.0';
|
|
10
10
|
const WORKER_IDLE_MS = 15 * 60_000;
|
|
11
11
|
const WORKER_STARTUP_MS = 12_000;
|
|
12
|
-
const MUTATION_READY_BUDGET_MS = 2_500;
|
|
13
12
|
const REQUEST_TIMEOUT_MS = 12_000;
|
|
14
13
|
const RSD_ENDPOINT_CACHE_TTL_MS = 30_000;
|
|
15
14
|
const RSD_ENDPOINT_PERSISTED_TTL_MS = 10 * 60_000;
|
|
16
15
|
const MAX_TEXT_LENGTH = 2048;
|
|
17
16
|
const MAX_STDERR = 8 * 1024;
|
|
17
|
+
const TOOLCHAIN_PROBE_TTL_MS = 60_000;
|
|
18
|
+
const TOOLCHAIN_PROBE_TIMEOUT_MS = 5_000;
|
|
18
19
|
|
|
19
20
|
export interface RemoteXpcHidInput {
|
|
20
21
|
controllerHome: string;
|
|
@@ -99,6 +100,7 @@ const warmups = new Map<string, Promise<void>>();
|
|
|
99
100
|
const workerGenerations = new Map<string, number>();
|
|
100
101
|
const warmupFailures = new Map<string, WarmupFailure>();
|
|
101
102
|
const endpointCache = new Map<string, EndpointCacheEntry>();
|
|
103
|
+
const toolchainProbeCache = new Map<string, { checkedAt: number; installedVersion?: string; available: boolean; reason?: string }>();
|
|
102
104
|
|
|
103
105
|
export function setRemoteXpcHidExecutorForTest(executor: TestExecutor | undefined): void {
|
|
104
106
|
testExecutor = executor;
|
|
@@ -112,26 +114,71 @@ export function resetRemoteXpcHidForTest(): void {
|
|
|
112
114
|
workerGenerations.clear();
|
|
113
115
|
warmupFailures.clear();
|
|
114
116
|
endpointCache.clear();
|
|
117
|
+
toolchainProbeCache.clear();
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
|
|
120
|
+
const TOOLCHAIN_PROBE_SCRIPT = [
|
|
121
|
+
'from importlib.metadata import version',
|
|
122
|
+
'from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService',
|
|
123
|
+
'from pymobiledevice3.remote.core_device.pasteboard_service import PasteboardService',
|
|
124
|
+
'from pymobiledevice3.remote.core_device.hid_service import UniversalHIDServiceService, touch_session',
|
|
125
|
+
"print(version('pymobiledevice3'))",
|
|
126
|
+
].join('; ');
|
|
127
|
+
|
|
128
|
+
function probeHidToolchain(python: string): { installedVersion?: string; available: boolean; reason?: string } {
|
|
129
|
+
const cached = toolchainProbeCache.get(python);
|
|
130
|
+
if (cached && Date.now() - cached.checkedAt <= TOOLCHAIN_PROBE_TTL_MS) return cached;
|
|
131
|
+
const result = spawnSync(python, ['-c', TOOLCHAIN_PROBE_SCRIPT], {
|
|
132
|
+
encoding: 'utf8', timeout: TOOLCHAIN_PROBE_TIMEOUT_MS, maxBuffer: 8 * 1024,
|
|
133
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
134
|
+
});
|
|
135
|
+
const installedVersion = typeof result.stdout === 'string' ? result.stdout.trim() : undefined;
|
|
136
|
+
const available = result.status === 0 && installedVersion === IOS_HID_TOOLCHAIN_VERSION && !result.error;
|
|
137
|
+
const reason = available ? undefined : result.error?.message ?? (result.status === 0
|
|
138
|
+
? `Expected pymobiledevice3 ${IOS_HID_TOOLCHAIN_VERSION}, found ${installedVersion || 'unknown'}.`
|
|
139
|
+
: `pymobiledevice3 import/version probe failed (exit ${result.status ?? 'unknown'}).`);
|
|
140
|
+
const probe = { checkedAt: Date.now(), installedVersion, available, reason };
|
|
141
|
+
// A failed probe is not durable: an explicitly authorized installer may
|
|
142
|
+
// finish immediately afterward without requiring a Runtime restart.
|
|
143
|
+
if (available) toolchainProbeCache.set(python, probe);
|
|
144
|
+
else toolchainProbeCache.delete(python);
|
|
145
|
+
return probe;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hidToolchainStatus(controllerHome: string, env: NodeJS.ProcessEnv, allowProbe: boolean): Record<string, unknown> {
|
|
118
149
|
const python = resolvePython(controllerHome, env);
|
|
150
|
+
const cached = python ? toolchainProbeCache.get(python) : undefined;
|
|
151
|
+
const validCached = cached?.available === true && Date.now() - cached.checkedAt <= TOOLCHAIN_PROBE_TTL_MS
|
|
152
|
+
? cached : undefined;
|
|
153
|
+
const probe = python ? (allowProbe ? probeHidToolchain(python) : validCached) : undefined;
|
|
119
154
|
return {
|
|
120
155
|
backend: 'remote-xpc-hid',
|
|
121
|
-
available:
|
|
156
|
+
available: probe?.available === true,
|
|
122
157
|
python,
|
|
123
|
-
toolchainVersion:
|
|
158
|
+
toolchainVersion: IOS_HID_TOOLCHAIN_VERSION,
|
|
159
|
+
...(probe?.installedVersion ? { installedVersion: probe.installedVersion } : {}),
|
|
160
|
+
...(probe?.reason ? { reason: probe.reason } : {}),
|
|
124
161
|
transport: 'macos-trusted-coredevice-rsd',
|
|
125
162
|
runnerOwned: false,
|
|
126
163
|
};
|
|
127
164
|
}
|
|
128
165
|
|
|
166
|
+
// Manifest reads are passive: only an explicit HID action may start Python.
|
|
167
|
+
// This is a view of the same short-lived successful probe cache, not a second
|
|
168
|
+
// readiness authority or a durable grant.
|
|
169
|
+
export function remoteXpcHidCachedStatus(controllerHome: string, env: NodeJS.ProcessEnv = process.env): Record<string, unknown> {
|
|
170
|
+
return hidToolchainStatus(controllerHome, env, false);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function remoteXpcHidStatus(controllerHome: string, env: NodeJS.ProcessEnv = process.env): Record<string, unknown> {
|
|
174
|
+
return hidToolchainStatus(controllerHome, env, true);
|
|
175
|
+
}
|
|
176
|
+
|
|
129
177
|
function resolvePython(controllerHome: string, env: NodeJS.ProcessEnv): string | undefined {
|
|
130
178
|
const explicit = env.FORGE_IOS_HID_PYTHON?.trim();
|
|
131
179
|
const candidates = [
|
|
132
180
|
explicit,
|
|
133
|
-
join(controllerHome, 'toolchains', `pymobiledevice3-${
|
|
134
|
-
join(controllerHome, 'toolchains', `pymobiledevice3-${TOOLCHAIN_VERSION}`, 'bin', 'python'),
|
|
181
|
+
join(controllerHome, 'toolchains', `pymobiledevice3-${IOS_HID_TOOLCHAIN_VERSION}`, 'bin', 'python'),
|
|
135
182
|
].filter((value): value is string => Boolean(value));
|
|
136
183
|
return candidates.find((candidate) => existsSync(candidate));
|
|
137
184
|
}
|
|
@@ -143,6 +190,7 @@ import asyncio
|
|
|
143
190
|
import json
|
|
144
191
|
import sys
|
|
145
192
|
import time
|
|
193
|
+
from importlib.metadata import version as package_version
|
|
146
194
|
|
|
147
195
|
from pymobiledevice3.remote.remote_service_discovery import RemoteServiceDiscoveryService
|
|
148
196
|
from pymobiledevice3.remote.core_device.pasteboard_service import PasteboardService
|
|
@@ -222,7 +270,11 @@ async def main():
|
|
|
222
270
|
parser = argparse.ArgumentParser()
|
|
223
271
|
parser.add_argument('--host', required=True)
|
|
224
272
|
parser.add_argument('--port', required=True, type=int)
|
|
273
|
+
parser.add_argument('--expected-version', required=True)
|
|
225
274
|
args = parser.parse_args()
|
|
275
|
+
actual_version = package_version('pymobiledevice3')
|
|
276
|
+
if actual_version != args.expected_version:
|
|
277
|
+
raise RuntimeError(f'RemoteXPC HID pymobiledevice3 version mismatch: {actual_version} != {args.expected_version}')
|
|
226
278
|
async with RemoteServiceDiscoveryService((args.host, args.port)) as rsd:
|
|
227
279
|
async with touch_session(rsd) as hid:
|
|
228
280
|
connected = await hid.list_connected_services()
|
|
@@ -743,11 +795,12 @@ export function stopRemoteXpcHidForDevice(deviceIdentifier: string): Record<stri
|
|
|
743
795
|
}
|
|
744
796
|
|
|
745
797
|
function startWorker(input: Pick<RemoteXpcHidInput, 'controllerHome' | 'deviceIdentifier'>, endpoint: RsdEndpoint): WorkerRecord {
|
|
746
|
-
const
|
|
798
|
+
const status = remoteXpcHidStatus(input.controllerHome);
|
|
799
|
+
const python = status.available === true && typeof status.python === 'string' ? status.python : undefined;
|
|
747
800
|
if (!python) {
|
|
748
|
-
throw new AssistantPluginError('IOS_HID_TOOLCHAIN_MISSING', `The Controller-owned pymobiledevice3 ${
|
|
801
|
+
throw new AssistantPluginError('IOS_HID_TOOLCHAIN_MISSING', `The Controller-owned pymobiledevice3 ${IOS_HID_TOOLCHAIN_VERSION} toolchain is unavailable or incompatible.`, {
|
|
749
802
|
retryable: false,
|
|
750
|
-
details: { expected: join(input.controllerHome, 'toolchains', `pymobiledevice3-${
|
|
803
|
+
details: { expected: join(input.controllerHome, 'toolchains', `pymobiledevice3-${IOS_HID_TOOLCHAIN_VERSION}`, 'bin', 'python'), reason: status.reason },
|
|
751
804
|
});
|
|
752
805
|
}
|
|
753
806
|
const script = materializeWorker(input.controllerHome);
|
|
@@ -756,6 +809,7 @@ function startWorker(input: Pick<RemoteXpcHidInput, 'controllerHome' | 'deviceId
|
|
|
756
809
|
'-u', script,
|
|
757
810
|
'--host', endpoint.host,
|
|
758
811
|
'--port', String(endpoint.port),
|
|
812
|
+
'--expected-version', IOS_HID_TOOLCHAIN_VERSION,
|
|
759
813
|
], {
|
|
760
814
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
761
815
|
env: {
|
|
@@ -1089,23 +1143,11 @@ async function waitForMutationWorker(
|
|
|
1089
1143
|
}
|
|
1090
1144
|
const current = workers.get(input.deviceIdentifier);
|
|
1091
1145
|
const warmup = warmups.get(input.deviceIdentifier);
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
try {
|
|
1098
|
-
await Promise.race([
|
|
1099
|
-
readiness,
|
|
1100
|
-
new Promise<void>((resolve) => {
|
|
1101
|
-
timer = setTimeout(resolve, MUTATION_READY_BUDGET_MS);
|
|
1102
|
-
timer.unref?.();
|
|
1103
|
-
}),
|
|
1104
|
-
]);
|
|
1105
|
-
} finally {
|
|
1106
|
-
if (timer) clearTimeout(timer);
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1146
|
+
// Plugin actions use separate managed processes. A detached prewarm cannot
|
|
1147
|
+
// survive its owning action, so the first real mutation must finish its own
|
|
1148
|
+
// endpoint discovery and worker startup rather than time out and retry cold.
|
|
1149
|
+
const readiness = warmup ?? current?.ready.then(() => undefined).catch(() => undefined);
|
|
1150
|
+
if (readiness) await readiness;
|
|
1109
1151
|
const waitMs = Math.round((performance.now() - startedAt) * 100) / 100;
|
|
1110
1152
|
const worker = workers.get(input.deviceIdentifier);
|
|
1111
1153
|
if (!worker || worker.readyState !== 'ready') {
|
|
@@ -1115,15 +1157,13 @@ async function waitForMutationWorker(
|
|
|
1115
1157
|
}
|
|
1116
1158
|
throw new AssistantPluginError(
|
|
1117
1159
|
'IOS_HID_INPUT_NOT_READY',
|
|
1118
|
-
|
|
1160
|
+
'RemoteXPC HID worker did not reach ready state in this action. No input was sent.',
|
|
1119
1161
|
{
|
|
1120
1162
|
retryable: true,
|
|
1121
1163
|
details: {
|
|
1122
1164
|
deviceIdentifier: input.deviceIdentifier,
|
|
1123
1165
|
mutationDispatched: false,
|
|
1124
1166
|
workerReadyWaitMs: waitMs,
|
|
1125
|
-
readinessBudgetMs: MUTATION_READY_BUDGET_MS,
|
|
1126
|
-
prewarmContinues: warmups.has(input.deviceIdentifier) || worker?.readyState === 'starting',
|
|
1127
1167
|
},
|
|
1128
1168
|
},
|
|
1129
1169
|
);
|