@moretea-labs/forge 1.8.1 → 1.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/AGENTS.md +5 -2
  2. package/CHANGELOG.md +20 -0
  3. package/README.en.md +24 -3
  4. package/README.md +10 -6
  5. package/README.zh-CN.md +5 -3
  6. package/THIRD_PARTY_NOTICES.md +1 -2
  7. package/adapters/chatgpt/browser-delivery-runtime.ts +5 -3
  8. package/adapters/chatgpt/provider-delivery.ts +1 -1
  9. package/adapters/chatgpt/work-conversation-binding-store.ts +20 -1
  10. package/adapters/computer/chatgpt-extension-target.ts +295 -50
  11. package/adapters/computer/chrome-extension/background.js +227 -71
  12. package/adapters/computer/chrome-extension/content.js +181 -157
  13. package/adapters/computer/chrome-extension/manifest.json +3 -1
  14. package/adapters/computer/desktop-operator-provider.ts +18 -6
  15. package/adapters/computer/interaction-target-authority.ts +39 -9
  16. package/adapters/mcp/access-mode.ts +18 -39
  17. package/adapters/mcp/auth.ts +2 -6
  18. package/adapters/mcp/instructions.ts +2 -2
  19. package/adapters/mcp/multi-repository.ts +7 -28
  20. package/adapters/mcp/runtime-gateway/context-adapter.ts +79 -40
  21. package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +7 -15
  22. package/adapters/mcp/runtime-gateway/execution-tools.ts +7 -13
  23. package/adapters/mcp/runtime-gateway/plugin-adapter.ts +16 -3
  24. package/adapters/mcp/runtime-gateway/process-tools.ts +3 -21
  25. package/adapters/mcp/runtime-gateway/router.ts +3 -5
  26. package/adapters/mcp/runtime-gateway/routing-policy.ts +17 -10
  27. package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1 -1
  28. package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +14 -9
  29. package/adapters/mcp/runtime-gateway/runtime-tools.ts +1 -5
  30. package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +36 -1
  31. package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +58 -4
  32. package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +7 -3
  33. package/adapters/mcp/runtime-gateway/work-adapter.ts +16 -16
  34. package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +0 -17
  35. package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +69 -12
  36. package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +26 -2
  37. package/adapters/mcp/server.ts +24 -3
  38. package/adapters/mcp/setup.ts +4 -10
  39. package/adapters/mcp/tool-mapping/access-tools.ts +0 -6
  40. package/adapters/mcp/tool-mapping/legacy-tool-service.ts +672 -2935
  41. package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +1 -1
  42. package/adapters/mcp/tool-mapping/repository-tools.ts +52 -86
  43. package/adapters/mcp/toolset-names.ts +11 -42
  44. package/adapters/mcp/toolset.ts +16 -90
  45. package/{src/cli → adapters}/mcp/transports/AGENTS.md +5 -5
  46. package/{src/cli → adapters}/mcp/transports/CLAUDE.md +5 -5
  47. package/adapters/mcp/transports/http-observation.ts +18 -6
  48. package/adapters/mcp/transports/http-post-lifecycle.ts +19 -0
  49. package/adapters/mcp/transports/http.ts +54 -14
  50. package/adapters/mcp/types.ts +0 -6
  51. package/assets/hooks/lib/session-state.sh +0 -38
  52. package/assets/hooks/post-bash.sh +1 -1
  53. package/assets/hooks/post-tool-observer.sh +0 -5
  54. package/assets/hooks/prompt-guard.sh +0 -55
  55. package/assets/partials/04-project-structure.partial.md +1 -1
  56. package/assets/partials/07-footer.partial.md +1 -1
  57. package/assets/partials-agents/02-operating-mode.partial.md +1 -1
  58. package/assets/partials-agents/08-deep-docs.partial.md +1 -1
  59. package/assets/reference-configs/agentic-development-flow.md +1 -1
  60. package/assets/reference-configs/external-tooling.md +0 -76
  61. package/assets/reference-configs/release-deploy.md +1 -1
  62. package/assets/templates/helpers/architecture-queue.sh +1 -1
  63. package/assets/templates/helpers/check-agent-tooling.sh +1 -327
  64. package/assets/templates/helpers/check-task-workflow.sh +1 -1
  65. package/assets/templates/helpers/ensure-task-workflow.sh +0 -14
  66. package/assets/templates/helpers/migrate-project-template.sh +2 -2
  67. package/assets/workflow-contract.v1.json +0 -2
  68. package/docs/README.md +5 -2
  69. package/docs/architecture/CURRENT.md +114 -30
  70. package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +42 -0
  71. package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +15 -2
  72. package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +4 -2
  73. package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +109 -4
  74. package/docs/architecture/decisions/20261009-exact-code-context-discovery.md +19 -0
  75. package/docs/architecture/decisions/20261009-generated-xcode-cache-retention.md +37 -0
  76. package/docs/architecture/decisions/20261011-autonomous-continuation-convergence.md +597 -0
  77. package/docs/architecture/decisions/20261017-edit-session-finalization-reconciliation.md +17 -0
  78. package/docs/architecture/decisions/20261019-retire-host-skills.md +16 -0
  79. package/docs/architecture/diagrams/agentic-dev-plugin-architecture.html +1 -1
  80. package/docs/architecture/domains/public-surface.md +3 -5
  81. package/docs/architecture/domains/verification.md +5 -5
  82. package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +2 -2
  83. package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +4 -4
  84. package/docs/architecture/reviews/20261015-code-search-runtime-acceptance.md +75 -0
  85. package/docs/forge-plugin-management.md +14 -2
  86. package/docs/operations/controller-browser-plugin.md +23 -0
  87. package/docs/operations/features.md +6 -7
  88. package/docs/operations/features.zh-CN.md +1 -1
  89. package/docs/operations/github-repository.md +4 -4
  90. package/docs/operations/github-semantic-release-projection.md +20 -0
  91. package/docs/operations/homebrew.md +2 -2
  92. package/docs/operations/mcp-tool-exposure.md +13 -54
  93. package/docs/operations/runtime-performance-diagnostics.md +8 -0
  94. package/docs/operations/standalone-disaster-recovery.md +1 -1
  95. package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
  96. package/docs/tutorials/03-first-repository-task.md +2 -0
  97. package/docs/wiki/Computer-and-Plugins.md +78 -0
  98. package/docs/wiki/Computer-and-Plugins.zh-CN.md +88 -0
  99. package/docs/wiki/Home.md +16 -2
  100. package/docs/wiki/Implementation.md +24 -12
  101. package/docs/wiki/Installation.md +28 -32
  102. package/docs/wiki/Integrations.md +4 -0
  103. package/docs/wiki/Quick-Start.md +35 -22
  104. package/docs/wiki/Releases-and-Upgrades.md +15 -25
  105. package/docs/wiki/_Sidebar.md +5 -1
  106. package/package.json +1 -12
  107. package/packages/kernel/controller/infrastructure/controller-session-store.ts +63 -38
  108. package/packages/kernel/identity/infrastructure/activity-projection-store.ts +10 -0
  109. package/packages/kernel/scheduler/application/eligibility.ts +4 -1
  110. package/packages/kernel/scheduler/infrastructure/schedule-store.ts +13 -10
  111. package/packages/kernel/work/application/work-service.ts +1 -0
  112. package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
  113. package/packages/kernel/work/domain/state-machine.ts +9 -2
  114. package/packages/kernel/work/infrastructure/work-contract-store.ts +28 -1
  115. package/packages/plugin-runtime/browser/provider-registry.ts +12 -36
  116. package/packages/plugin-runtime/computer/conversation-target.ts +17 -7
  117. package/scripts/assemble-template.ts +11 -72
  118. package/scripts/check-mcp-compatibility.ts +55 -74
  119. package/scripts/check-package-identity.mjs +0 -5
  120. package/scripts/check-runtime-architecture.mjs +305 -28
  121. package/scripts/init-project.sh +0 -46
  122. package/scripts/install-ios-hid-toolchain.sh +60 -0
  123. package/scripts/install-standalone-recovery.ts +1 -1
  124. package/scripts/lib/project-init-lib.sh +3 -3
  125. package/scripts/migrate-windows-wsl-controller-home.ts +2 -0
  126. package/scripts/open-source-audit-allowlist.txt +0 -1
  127. package/scripts/run-governed-gate.ts +2 -0
  128. package/scripts/smoke-ios-physical-device.ts +38 -10
  129. package/scripts/smoke-mcp-http-runtime.ts +7 -8
  130. package/scripts/smoke-mcp-tool-surface.ts +27 -41
  131. package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +6 -3
  132. package/scripts/sync-generated-authority.mjs +0 -1
  133. package/src/cli/agent-jobs/executor-health.ts +1 -1
  134. package/src/cli/agent-jobs/integration.ts +2 -2
  135. package/src/cli/chatgpt-browser/file-policy.ts +2 -2
  136. package/src/cli/commands/doctor.ts +1 -143
  137. package/src/cli/commands/global-runtime.ts +3 -196
  138. package/src/cli/commands/init-hook.ts +6 -23
  139. package/src/cli/commands/init.ts +3 -252
  140. package/src/cli/commands/mcp.ts +0 -5
  141. package/src/cli/commands/prompt-guard-decision.ts +0 -2
  142. package/src/cli/commands/recovery.ts +2 -14
  143. package/src/cli/commands/repository.ts +17 -1
  144. package/src/cli/commands/setup-execution.ts +41 -0
  145. package/src/cli/controller/completion-orchestrator.ts +1 -1
  146. package/src/cli/controller/composite-operations.ts +1 -1
  147. package/src/cli/controller/context/instruction-context.ts +1 -1
  148. package/src/cli/controller/context/known-paths.ts +2 -2
  149. package/src/cli/controller/context/query-planning.ts +11 -4
  150. package/src/cli/controller/context/rule-resolution.ts +2 -2
  151. package/src/cli/controller/context/source-materializer.ts +2 -2
  152. package/src/cli/controller/context/structural-provider.ts +84 -0
  153. package/src/cli/controller/context/structural.ts +6 -6
  154. package/src/cli/controller/context/types.ts +8 -9
  155. package/src/cli/controller/context-pack.ts +97 -76
  156. package/src/cli/controller/operational-plan.ts +1 -1
  157. package/src/cli/controller/systemd-user.ts +4 -10
  158. package/src/cli/editing/edit-session.ts +17 -5
  159. package/src/cli/github/github.ts +4 -4
  160. package/src/cli/github/semantic-projection.ts +174 -0
  161. package/src/cli/hook/prompt-intents.ts +0 -9
  162. package/src/cli/index.ts +1 -43
  163. package/src/cli/local-bridge/connector-freshness.ts +1 -12
  164. package/src/cli/local-bridge/console-automations.ts +1 -1
  165. package/src/cli/local-bridge/console-view-models.ts +1 -1
  166. package/src/cli/local-bridge/facade-api.ts +0 -2
  167. package/src/cli/repositories/command-classifier.ts +0 -9
  168. package/src/cli/repositories/command-scope.ts +2 -2
  169. package/src/cli/repositories/locks.ts +20 -0
  170. package/src/cli/repositories/registry.ts +2 -1
  171. package/src/cli/repositories/safe-patch.ts +2 -12
  172. package/src/cli/repositories/structured-git.ts +39 -3
  173. package/src/cli/repository/inspector.ts +185 -343
  174. package/src/effects/process-runner.ts +17 -1
  175. package/src/runtime/context/generic-lsp-provider.ts +1 -1
  176. package/src/runtime/context/semantic-navigation-contract.ts +7 -0
  177. package/src/runtime/context/semantic-navigation.ts +1 -1
  178. package/src/runtime/control-plane/cleanup-artifact-retention.ts +4 -4
  179. package/src/runtime/control-plane/execution/work-delivery-service.ts +150 -31
  180. package/src/runtime/control-plane/execution/work-handle-authority.ts +12 -4
  181. package/src/runtime/control-plane/execution/work-handle-store.ts +53 -1
  182. package/src/runtime/control-plane/execution/work-operation-service.ts +20 -10
  183. package/src/runtime/control-plane/execution/work-preparation-service.ts +108 -4
  184. package/src/runtime/control-plane/execution/work-process-evidence.ts +2 -2
  185. package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +28 -13
  186. package/src/runtime/control-plane/execution/work-validation-reconciler.ts +5 -1
  187. package/src/runtime/control-plane/execution/work-verification-snapshot.ts +1 -1
  188. package/src/runtime/control-plane/facade/capability-registry.ts +2 -0
  189. package/src/runtime/control-plane/facade/work-continuation.ts +1 -1
  190. package/src/runtime/control-plane/generated-cache-retention.ts +114 -63
  191. package/src/runtime/control-plane/global-scheduler/maintenance.ts +20 -8
  192. package/src/runtime/control-plane/global-scheduler/reconciliation.ts +1 -11
  193. package/src/runtime/control-plane/governance/external-effects.ts +1 -1
  194. package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +1 -0
  195. package/src/runtime/control-plane/runtime-cleanup.ts +12 -93
  196. package/src/runtime/evidence/sensitive-output.ts +22 -2
  197. package/src/runtime/execution/jobs/child-reference.ts +11 -0
  198. package/src/runtime/execution/jobs/receipt-recovery.ts +1 -11
  199. package/src/runtime/execution/process-runtime/command-facade.ts +3 -3
  200. package/src/runtime/execution/process-runtime/runtime.ts +20 -16
  201. package/src/runtime/execution/thin-harness/execution-router.ts +16 -16
  202. package/src/runtime/execution/thin-harness/fast-executor.ts +2 -2
  203. package/src/runtime/execution/thin-harness/search-worker.ts +2 -2
  204. package/src/runtime/execution/workers/executor.ts +1 -2
  205. package/src/runtime/maintenance/cleanup.ts +41 -5
  206. package/src/runtime/plugins/action-application.ts +12 -1
  207. package/src/runtime/plugins/app-store-connect-adapter.ts +252 -4
  208. package/src/runtime/plugins/browser-adapter.ts +381 -313
  209. package/src/runtime/plugins/browser-macos-bridge.ts +114 -112
  210. package/src/runtime/plugins/browser-manifest-surface.ts +2 -1
  211. package/src/runtime/plugins/browser-registration.ts +2 -0
  212. package/src/runtime/plugins/browser-runtime.ts +26 -35
  213. package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +11 -2
  214. package/src/runtime/plugins/computer-registration.ts +76 -9
  215. package/src/runtime/plugins/github-adapter.ts +30 -3
  216. package/src/runtime/plugins/gmail-adapter.ts +166 -1
  217. package/src/runtime/plugins/google-shared.ts +10 -5
  218. package/src/runtime/plugins/ios/remote-xpc-hid.ts +70 -30
  219. package/src/runtime/plugins/ios-adapter.ts +45 -40
  220. package/src/runtime/plugins/ios-agent-device.ts +54 -4
  221. package/src/runtime/plugins/ios-device-ownership.ts +103 -0
  222. package/src/runtime/plugins/ios-physical-device.ts +497 -79
  223. package/src/runtime/plugins/local-system-adapter.ts +1 -99
  224. package/src/runtime/plugins/store.ts +80 -7
  225. package/src/runtime/plugins/types.ts +4 -0
  226. package/src/runtime/projections/git-status-sampler.ts +2 -19
  227. package/src/runtime/recovery/actions.ts +1 -1
  228. package/src/runtime/recovery/lifecycle-inventory.ts +1 -11
  229. package/src/runtime/recovery/probe.ts +2 -2
  230. package/src/runtime/release/release-coordinator.ts +20 -4
  231. package/src/runtime/root/controller-progression-composition.ts +67 -5
  232. package/src/runtime/root/package-connector-service.ts +123 -11
  233. package/src/runtime/root/package-runtime-release.ts +6 -18
  234. package/src/runtime/root/package-runtime-service.ts +7 -13
  235. package/src/runtime/root/release-execution-canary.ts +18 -5
  236. package/src/runtime/root/release-manifest.ts +0 -34
  237. package/src/runtime/root/release-materialize.ts +0 -78
  238. package/src/runtime/root/runtime.ts +1 -2
  239. package/src/runtime/root/scheduled-controller-composition.ts +2 -1
  240. package/src/runtime/root/service.ts +90 -11
  241. package/src/runtime/root/types.ts +0 -6
  242. package/src/runtime/root/workflow-supervisor-composition.ts +207 -19
  243. package/src/runtime/root/workflow-supervisor-runtime.ts +365 -10
  244. package/src/runtime/safe-tooling/google-credential-store.ts +1 -11
  245. package/src/runtime/safe-tooling/ios-development.ts +22 -8
  246. package/src/runtime/safe-tooling/job-summary.ts +1 -1
  247. package/src/runtime/safe-tooling/workspace-auth.ts +4 -1
  248. package/src/runtime/shared/atomic-text-file.ts +21 -0
  249. package/src/runtime/shared/json-files.ts +8 -3
  250. package/src/runtime/standalone-recovery/core.ts +189 -174
  251. package/src/runtime/standalone-recovery/entry.ts +62 -30
  252. package/src/runtime/standalone-recovery/operation-lock.ts +11 -1
  253. package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +1 -0
  254. package/src/runtime/workflow/schedules/engine.ts +36 -14
  255. package/src/runtime/workflow/schedules/pending-activation.ts +11 -4
  256. package/src/runtime/workflow/schedules/work-continuation.ts +51 -6
  257. package/supervisor/client.ts +31 -14
  258. package/supervisor/control-plane.ts +210 -115
  259. package/supervisor/forge-validators.ts +88 -21
  260. package/supervisor/native-browser-adapter.ts +393 -28
  261. package/supervisor/native-messaging/host.ts +21 -2
  262. package/supervisor/protocol.ts +74 -26
  263. package/supervisor/server.ts +16 -8
  264. package/supervisor/store.ts +476 -57
  265. package/supervisor/types.ts +19 -13
  266. package/SKILL.md +0 -427
  267. package/adapters/computer/chatgpt-target-router.ts +0 -103
  268. package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +0 -87
  269. package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +0 -1
  270. package/adapters/mcp/toolset-selection.ts +0 -41
  271. package/agents/openai.yaml +0 -4
  272. package/assets/skill-hooks.json +0 -41
  273. package/assets/skill-version.json +0 -189
  274. package/assets/skills/claude-review/SKILL.md +0 -231
  275. package/assets/skills/codex-review/SKILL.md +0 -103
  276. package/assets/templates/helpers/check-skill-version.ts +0 -193
  277. package/scripts/check-skill-version.ts +0 -5
  278. package/scripts/ensure-codegraph.sh +0 -16
  279. package/scripts/run-skill-hook.ts +0 -244
  280. package/scripts/sync-codex-installed-copies.sh +0 -182
  281. package/src/cli/commands/tools.ts +0 -167
  282. package/src/cli/mcp/access-mode.ts +0 -2
  283. package/src/cli/mcp/access-tools.ts +0 -2
  284. package/src/cli/mcp/audit.ts +0 -2
  285. package/src/cli/mcp/auth.ts +0 -2
  286. package/src/cli/mcp/instructions.ts +0 -2
  287. package/src/cli/mcp/legacy-context.ts +0 -2
  288. package/src/cli/mcp/legacy-tool-service.ts +0 -2
  289. package/src/cli/mcp/multi-repository.ts +0 -2
  290. package/src/cli/mcp/oauth.ts +0 -2
  291. package/src/cli/mcp/openai-secure-tunnel.ts +0 -2
  292. package/src/cli/mcp/paths.ts +0 -2
  293. package/src/cli/mcp/policy.ts +0 -2
  294. package/src/cli/mcp/post-finalize-work-attribution.ts +0 -2
  295. package/src/cli/mcp/proxy-env.ts +0 -2
  296. package/src/cli/mcp/readiness-projection.ts +0 -2
  297. package/src/cli/mcp/redaction.ts +0 -2
  298. package/src/cli/mcp/repo.ts +0 -2
  299. package/src/cli/mcp/repository-tools.ts +0 -2
  300. package/src/cli/mcp/server.ts +0 -2
  301. package/src/cli/mcp/setup.ts +0 -2
  302. package/src/cli/mcp/tool-capability-cache.ts +0 -2
  303. package/src/cli/mcp/tool-contract.ts +0 -2
  304. package/src/cli/mcp/tools.ts +0 -2
  305. package/src/cli/mcp/toolset-names.ts +0 -2
  306. package/src/cli/mcp/toolset-selection.ts +0 -2
  307. package/src/cli/mcp/toolset.ts +0 -2
  308. package/src/cli/mcp/transports/http.ts +0 -2
  309. package/src/cli/mcp/transports/session-registry.ts +0 -2
  310. package/src/cli/mcp/transports/stdio.ts +0 -2
  311. package/src/cli/mcp/types.ts +0 -2
  312. package/src/cli/mcp/umbrella-tools.ts +0 -2
  313. package/src/cli/tools/codegraph.ts +0 -695
  314. package/src/core/adoption/helper-wrapper-plan.ts +0 -159
  315. package/src/runtime/context/codegraph-cache-boundary.ts +0 -255
  316. package/src/runtime/context/codegraph-read-provider.ts +0 -611
  317. package/src/runtime/context/codegraph-sidecar.cjs +0 -248
  318. package/src/runtime/control-plane/codegraph-cache-retention.ts +0 -164
  319. package/src/runtime/gateway/mcp/execution-tools.ts +0 -2
  320. package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +0 -2
  321. package/src/runtime/gateway/mcp/persisted-check-process.ts +0 -2
  322. package/src/runtime/gateway/mcp/process-tools.ts +0 -2
  323. package/src/runtime/gateway/mcp/router.ts +0 -2
  324. package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +0 -2
  325. package/src/runtime/gateway/mcp/runtime-tools.ts +0 -2
  326. package/src/runtime/gateway/mcp/work-validation-reconciler.ts +0 -2
  327. package/src/runtime/plugins/browser-provider-registry.ts +0 -2
  328. package/src/runtime/plugins/computer-chatgpt-macos-target.ts +0 -891
@@ -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 stored refresh credential is available; the next provider request will refresh and verify an access token.'],
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
- : [...auth.warnings, ...(!auth.authenticated ? auth.errors : [])],
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
- throw new AssistantPluginError('PLUGIN_AUTH_FAILED', 'Google refresh token exchange failed.', {
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 TOOLCHAIN_VERSION = '10.2.1';
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
- export function remoteXpcHidStatus(controllerHome: string, env: NodeJS.ProcessEnv = process.env): Record<string, unknown> {
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: Boolean(python),
156
+ available: probe?.available === true,
122
157
  python,
123
- toolchainVersion: TOOLCHAIN_VERSION,
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-${TOOLCHAIN_VERSION}-py39`, 'bin', 'python'),
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 python = resolvePython(input.controllerHome, process.env);
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 ${TOOLCHAIN_VERSION} toolchain is unavailable.`, {
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-${TOOLCHAIN_VERSION}-py39`, 'bin', 'python') },
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
- const readiness = current
1093
- ? current.ready.then(() => undefined).catch(() => undefined)
1094
- : warmup;
1095
- if (readiness) {
1096
- let timer: NodeJS.Timeout | undefined;
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
- `RemoteXPC HID is still warming after the ${MUTATION_READY_BUDGET_MS}ms mutation readiness budget. No input was sent; retry the same action after prewarm completes.`,
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
  );