@moretea-labs/forge 1.7.2 → 1.8.1
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 +121 -62
- package/CHANGELOG.md +40 -0
- package/CLAUDE.md +57 -62
- package/CONTRIBUTING.md +3 -2
- package/README.md +2 -2
- package/SKILL.md +0 -1
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/adapters/browser/index.ts +2 -0
- package/adapters/chatgpt/browser-delivery-host.ts +77 -0
- package/adapters/chatgpt/browser-delivery-runtime.ts +1256 -0
- package/adapters/chatgpt/controller-binding-store.ts +134 -0
- package/adapters/chatgpt/controller-round-host.ts +68 -0
- package/adapters/chatgpt/controller-round-settlement-store.ts +99 -0
- package/adapters/chatgpt/provider-delivery.ts +340 -0
- package/adapters/chatgpt/work-conversation-binding-store.ts +218 -0
- package/adapters/chatgpt/wsl-bridge-delivery-host.ts +63 -0
- package/adapters/computer/chatgpt-extension-target.ts +463 -0
- package/adapters/computer/chatgpt-target-router.ts +103 -0
- package/adapters/computer/chrome-extension/background.js +385 -0
- package/adapters/computer/chrome-extension/content.js +378 -0
- package/adapters/computer/chrome-extension/core.js +24 -0
- package/adapters/computer/chrome-extension/manifest.json +30 -0
- package/adapters/computer/desktop-operator-contract.ts +7 -0
- package/adapters/computer/desktop-operator-discovery.ts +181 -0
- package/adapters/computer/desktop-operator-negotiation.ts +174 -0
- package/adapters/computer/desktop-operator-provider.ts +312 -0
- package/adapters/computer/index.ts +1 -0
- package/adapters/computer/interaction-target-authority.ts +1068 -0
- package/adapters/controller-process/binding-store.ts +34 -0
- package/adapters/controller-process/controller-host.ts +26 -0
- package/adapters/mcp/access-mode.ts +132 -0
- package/adapters/mcp/audit.ts +32 -0
- package/adapters/mcp/auth.ts +406 -0
- package/adapters/mcp/instructions.ts +37 -0
- package/adapters/mcp/legacy-context.ts +18 -0
- package/adapters/mcp/multi-repository.ts +421 -0
- package/adapters/mcp/oauth.ts +464 -0
- package/adapters/mcp/paths.ts +143 -0
- package/adapters/mcp/policy.ts +232 -0
- package/adapters/mcp/proxy-env.ts +62 -0
- package/adapters/mcp/readiness-projection.ts +24 -0
- package/adapters/mcp/redaction.ts +21 -0
- package/adapters/mcp/repo.ts +24 -0
- package/adapters/mcp/runtime-gateway/artifact-adapter.ts +52 -0
- package/adapters/mcp/runtime-gateway/automation-receipt-adapter.ts +87 -0
- package/adapters/mcp/runtime-gateway/bounded-result-runtime.ts +62 -0
- package/adapters/mcp/runtime-gateway/cognition-settlement.ts +201 -0
- package/adapters/mcp/runtime-gateway/context-adapter.ts +1167 -0
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +206 -0
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +130 -0
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-control-adapter.ts +28 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +173 -0
- package/adapters/mcp/runtime-gateway/filesystem-adapter.ts +38 -0
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +412 -0
- package/adapters/mcp/runtime-gateway/legacy-ios-tool-adapter.ts +105 -0
- package/adapters/mcp/runtime-gateway/maintenance-adapter.ts +44 -0
- package/adapters/mcp/runtime-gateway/model-adapter.ts +63 -0
- package/adapters/mcp/runtime-gateway/persisted-check-process.ts +3 -0
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +380 -0
- package/adapters/mcp/runtime-gateway/process-tools.ts +457 -0
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +88 -0
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +285 -0
- package/adapters/mcp/runtime-gateway/recovery-client-adapter.ts +70 -0
- package/adapters/mcp/runtime-gateway/recovery-tool-contract.ts +99 -0
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +79 -0
- package/adapters/mcp/runtime-gateway/result-adapter.ts +114 -0
- package/adapters/mcp/runtime-gateway/router.ts +242 -0
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +1200 -0
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +574 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-shared.ts +27 -0
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +78 -0
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +187 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +45 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +962 -0
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +325 -0
- package/adapters/mcp/runtime-gateway/work-controller-operations.ts +257 -0
- package/adapters/mcp/runtime-gateway/work-detail-projection.ts +60 -0
- package/adapters/mcp/runtime-gateway/work-input-wire-migration.ts +246 -0
- package/adapters/mcp/runtime-gateway/work-learning-operations.ts +145 -0
- package/adapters/mcp/runtime-gateway/work-list-projection.ts +38 -0
- package/adapters/mcp/runtime-gateway/work-plan-operations.ts +382 -0
- package/adapters/mcp/runtime-gateway/work-repair-adapter.ts +308 -0
- package/adapters/mcp/runtime-gateway/work-requirement-operations.ts +148 -0
- package/adapters/mcp/runtime-gateway/work-semantic-operations.ts +263 -0
- package/adapters/mcp/runtime-gateway/work-validation-reconciler.ts +1 -0
- package/adapters/mcp/runtime-gateway/work-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +1092 -0
- package/adapters/mcp/setup.ts +789 -0
- package/adapters/mcp/tool-capability-cache.ts +87 -0
- package/adapters/mcp/tool-mapping/access-tools.ts +387 -0
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +5139 -0
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +145 -0
- package/adapters/mcp/tool-mapping/repository-tools.ts +1682 -0
- package/adapters/mcp/tool-mapping/tools.ts +9 -0
- package/adapters/mcp/tool-mapping/umbrella-tools.ts +117 -0
- package/adapters/mcp/toolset-names.ts +79 -0
- package/adapters/mcp/toolset-selection.ts +41 -0
- package/adapters/mcp/toolset.ts +337 -0
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +933 -0
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +519 -0
- package/adapters/mcp/transports/stdio.ts +8 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +223 -0
- package/adapters/mcp/types.ts +41 -0
- package/assets/AGENTS.md +2 -16
- package/assets/CLAUDE.md +2 -16
- package/assets/hooks/AGENTS.md +3 -14
- package/assets/hooks/CLAUDE.md +3 -14
- package/assets/hooks/hook-input.sh +9 -2
- package/assets/hooks/lib/workflow-state.sh +28 -10
- package/assets/hooks/prompt-guard.sh +63 -37
- package/assets/hooks/security-sentinel.sh +2 -1
- package/assets/hooks/session-start-context.sh +7 -5
- package/assets/hooks/stop-orchestrator.sh +13 -3
- package/assets/initializer-question-pack.v4.json +11 -7
- package/assets/partials/05-workflow.partial.md +18 -73
- package/assets/partials/07-footer.partial.md +1 -1
- package/assets/partials-agents/02-operating-mode.partial.md +1 -2
- package/assets/partials-agents/04-task-protocol.partial.md +14 -59
- package/assets/partials-agents/06-quality-safety.partial.md +11 -16
- package/assets/partials-agents/08-deep-docs.partial.md +1 -2
- package/assets/plan-map.json +11 -11
- package/assets/plugin-registry.v1.json +11 -3
- package/assets/plugin-trust-policy.v1.json +6 -0
- package/assets/recovery/ForgeRecovery.ps1 +100 -0
- package/assets/recovery/forge-wsl-rescue +271 -0
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/development-protocol.md +11 -15
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/global-working-rules.md +28 -1
- package/assets/reference-configs/harness-overview.md +22 -112
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +2 -2
- package/assets/skill-version.json +2 -2
- package/assets/templates/helpers/architecture-event.ts +3 -34
- package/assets/templates/helpers/architecture-queue.sh +61 -10
- package/assets/templates/helpers/archive-workflow.sh +90 -180
- package/assets/templates/helpers/capability-config.ts +8 -11
- package/assets/templates/helpers/capability-resolver.ts +37 -19
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-architecture-sync.sh +1 -9
- package/assets/templates/helpers/check-task-sync.sh +82 -11
- package/assets/templates/helpers/check-task-workflow.sh +1 -3
- package/assets/templates/helpers/context-contract-sync.sh +1 -12
- package/assets/templates/helpers/contract-worktree.sh +26 -24
- package/assets/templates/helpers/ensure-task-workflow.sh +6 -8
- package/assets/templates/helpers/inspect-project-state.ts +1 -1
- package/assets/templates/helpers/migrate-project-template.sh +1 -1
- package/assets/templates/helpers/migrate-workflow-docs.ts +53 -239
- package/assets/templates/helpers/new-plan.sh +2 -2
- package/assets/templates/helpers/plan-to-todo.sh +3 -31
- package/assets/templates/helpers/sprint-backlog.sh +1 -1
- package/assets/templates/helpers/switch-plan.sh +1 -1
- package/assets/templates/helpers/workflow-contract.ts +1 -0
- package/assets/workflow-contract.v1.json +63 -171
- package/assets/workflows/xiaohongshu/xiaohongshu.collect-metrics.v1.draft.json +118 -0
- package/assets/workflows/xiaohongshu/xiaohongshu.publish-image-note.v1.draft.json +316 -0
- package/assets/workflows/xiaohongshu/xiaohongshu.publish-long-text.v1.draft.json +324 -0
- package/docs/architecture/CURRENT.md +233 -28
- package/docs/architecture/EVOLUTION.md +13 -0
- package/docs/architecture/decisions/20260801-controller-home-sqlite-state.md +2 -1
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +14 -5
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +6 -3
- package/docs/architecture/decisions/20260831-independent-windows-wsl-host-rescue.md +59 -0
- package/docs/architecture/decisions/20260904-ios-device-migration-baseline.md +120 -0
- package/docs/architecture/decisions/20260904-repository-runtime-data-boundary.md +42 -0
- package/docs/architecture/decisions/20260907-assistant-learning-loop.md +37 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +311 -0
- package/docs/architecture/decisions/20260907-maintenance-diagnosis-bounded-read.md +39 -0
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +221 -0
- package/docs/architecture/decisions/20260919-autonomous-work-liveness.md +62 -0
- package/docs/architecture/decisions/20260924-thin-semantic-working-context.md +72 -0
- package/docs/architecture/decisions/20261001-supervisor-turn-authority.md +237 -0
- package/docs/architecture/decisions/20261005-forge-v3-desktop-client-boundary.md +77 -0
- package/docs/architecture/domains/verification.md +8 -0
- package/docs/architecture/domains/workflow-engine.md +7 -1
- package/docs/architecture/index.md +3 -3
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +19 -8
- package/docs/architecture/requests/2026-09-04-computer-plugin-workflow-taxonomy.md +124 -0
- package/docs/forge-chatgpt-mcp-setup.md +5 -3
- package/docs/forge-plugin-management.md +20 -4
- package/docs/forge-runtime-self-healing-loop.md +2 -0
- package/docs/operations/benchmark-cross-version-freeze.md +38 -0
- package/docs/operations/controller-browser-plugin.md +3 -1
- package/docs/operations/controller-performance-and-502.md +17 -0
- package/docs/operations/controller-reliability-runbook.md +2 -2
- package/docs/operations/features.md +1 -1
- package/docs/operations/features.zh-CN.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +2 -0
- package/docs/operations/platform-support.md +16 -8
- package/docs/operations/platform-support.zh-CN.md +2 -2
- package/docs/operations/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +38 -5
- package/docs/operations/uu-remote-rescue.md +41 -0
- package/docs/operations/windows-wsl-host-rescue.md +133 -0
- package/docs/public-usage-guide.md +1 -1
- package/docs/public-usage-guide.zh-CN.md +1 -1
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/tutorials/02-connect-chatgpt.md +4 -4
- package/docs/tutorials/02-connect-chatgpt.zh-CN.md +4 -4
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +46 -26
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +3 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +41 -0
- package/packages/kernel/controller/application/controller-service.ts +30 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +529 -0
- package/packages/kernel/controller/domain/controller-round.ts +110 -0
- package/packages/kernel/controller/domain/execution-quality.ts +135 -0
- package/packages/kernel/controller/domain/types.ts +56 -0
- package/packages/kernel/controller/index.ts +1 -0
- package/packages/kernel/controller/infrastructure/controller-binding-store.ts +84 -0
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +1970 -0
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +804 -0
- package/packages/kernel/controller/ports/controller-host.ts +18 -0
- package/packages/kernel/goal/api/index.ts +1 -0
- package/packages/kernel/goal/domain/types.ts +40 -0
- package/packages/kernel/goal/index.ts +1 -0
- package/packages/kernel/identity/api/index.ts +15 -0
- package/packages/kernel/identity/application/identity-service.ts +67 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/scope.ts +63 -0
- package/packages/kernel/identity/domain/semantic-sync.ts +81 -0
- package/packages/kernel/identity/domain/types.ts +44 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/domain/workspace.ts +56 -0
- package/packages/kernel/identity/index.ts +1 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +77 -0
- package/packages/kernel/identity/infrastructure/capability-broker.ts +81 -0
- package/packages/kernel/identity/infrastructure/grant-store.ts +284 -0
- package/packages/kernel/identity/infrastructure/identity-store.ts +75 -0
- package/packages/kernel/identity/infrastructure/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/memory/api/index.ts +5 -0
- package/packages/kernel/memory/application/experience-service.ts +118 -0
- package/packages/kernel/memory/application/outcome-service.ts +77 -0
- package/packages/kernel/memory/domain/experience.ts +103 -0
- package/packages/kernel/memory/domain/operational-prior.ts +92 -0
- package/packages/kernel/memory/index.ts +1 -0
- package/packages/kernel/scheduler/api/index.ts +4 -0
- package/packages/kernel/scheduler/application/eligibility.ts +252 -0
- package/packages/kernel/scheduler/application/schedule-service.ts +27 -0
- package/packages/kernel/scheduler/application/settlement.ts +182 -0
- package/packages/kernel/scheduler/domain/schedule.ts +236 -0
- package/packages/kernel/scheduler/index.ts +1 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +805 -0
- package/packages/kernel/work/api/index.ts +16 -0
- package/packages/kernel/work/application/work-service.ts +123 -0
- package/packages/kernel/work/domain/check-receipt.ts +43 -0
- package/packages/kernel/work/domain/context-closure-receipt.ts +32 -0
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-contracts.ts +157 -0
- package/packages/kernel/work/domain/engineering-design.ts +286 -0
- package/packages/kernel/work/domain/engineering-profile.ts +238 -0
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +247 -0
- package/packages/kernel/work/domain/execution-snapshot.ts +2 -0
- package/packages/kernel/work/domain/implementation-review.ts +193 -0
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +224 -0
- package/packages/kernel/work/domain/repository-completion-receipt.ts +73 -0
- package/{src/runtime/control-plane/facade/work-state-machine.ts → packages/kernel/work/domain/state-machine.ts} +37 -66
- package/packages/kernel/work/domain/types.ts +534 -0
- package/packages/kernel/work/index.ts +1 -0
- package/packages/kernel/work/infrastructure/work-contract-store.ts +1967 -0
- package/packages/kernel/work/ports/work-contract-store.ts +21 -0
- package/packages/plugin-runtime/browser/index.ts +3 -0
- package/packages/plugin-runtime/browser/provider-registry.ts +110 -0
- package/packages/plugin-runtime/browser/runtime-contract.ts +150 -0
- package/packages/plugin-runtime/browser/session-authority.ts +62 -0
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +8 -0
- package/packages/plugin-runtime/computer/provider-error.ts +26 -0
- package/packages/plugin-runtime/computer/provider-registration.ts +52 -0
- package/packages/plugin-runtime/computer/provider-registry.ts +63 -0
- package/packages/plugin-runtime/computer/provider.ts +19 -0
- package/packages/plugin-runtime/computer/target-authority.ts +215 -0
- package/packages/plugin-runtime/computer/target-persistence.ts +54 -0
- package/packages/plugin-runtime/external/index.ts +1 -0
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +581 -0
- package/packages/plugin-runtime/index.ts +3 -0
- package/packages/protocols/browser/index.ts +1 -0
- package/packages/protocols/browser/session.ts +98 -0
- package/packages/protocols/computer/contract.ts +225 -0
- package/packages/protocols/computer/index.ts +1 -0
- package/packages/protocols/failure/contract.ts +158 -0
- package/packages/protocols/failure/index.ts +1 -0
- package/packages/protocols/handoff/index.ts +1 -0
- package/packages/protocols/handoff/status.ts +22 -0
- package/packages/protocols/mcp/execution-context.ts +11 -0
- package/packages/protocols/mcp/index.ts +2 -0
- package/packages/protocols/mcp/tool-contract.ts +42 -0
- package/packages/workflow-runtime/api/index.ts +2 -0
- package/packages/workflow-runtime/application/interpreter.ts +342 -0
- package/packages/workflow-runtime/domain/workflow-asset.ts +223 -0
- package/packages/workflow-runtime/index.ts +1 -0
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/assemble-template.ts +1 -1
- package/scripts/bootstrap-runtime-maintenance-recovery.sh +4 -1
- package/scripts/canary-chatgpt-provider-delivery.ts +97 -0
- package/scripts/canary-controller-semantic-wait.ts +183 -0
- package/scripts/canary-global-hook.sh +2 -2
- package/scripts/check-mcp-compatibility.ts +85 -7
- package/scripts/check-platform-support.mjs +28 -2
- package/scripts/check-public-docs.sh +1 -1
- package/scripts/check-repository-hygiene.mjs +92 -0
- package/scripts/check-runtime-architecture.mjs +1643 -127
- package/scripts/check-source-duplication.mjs +9 -1
- package/scripts/controller-ngrok-rotation.sh +5 -4
- package/scripts/create-project-dirs.sh +10 -74
- package/scripts/forge-local-recovery-helper.mjs +282 -0
- package/scripts/forge-uu-remote-rescue-helper.d.mts +31 -0
- package/scripts/forge-uu-remote-rescue-helper.mjs +409 -0
- package/scripts/forge-windows-host-recovery-helper.mjs +136 -0
- package/scripts/hook-shim.sh +2 -2
- package/scripts/init-project.sh +12 -70
- package/scripts/install-desktop-operator-registration.ts +4 -2
- package/scripts/install-independent-windows-wsl-recovery.ts +125 -0
- package/scripts/install-standalone-recovery.ts +122 -10
- package/scripts/install-uu-remote-rescue-registration.ts +74 -0
- package/scripts/install-windows-host-recovery-registration.ts +22 -0
- package/scripts/lib/controller-home-env.sh +6 -3
- package/scripts/lib/project-init-lib.sh +42 -582
- package/scripts/migrate-chatgpt-automation-schedules.ts +2 -2
- package/scripts/migrate-windows-wsl-controller-home.ts +306 -0
- package/scripts/open-source-audit-allowlist.txt +11 -1
- package/scripts/portability-bindings.mjs +70 -0
- package/scripts/run-bounded-command.mjs +60 -0
- package/scripts/run-bun-test-file.ts +22 -6
- package/scripts/run-governed-gate.ts +4 -2
- package/scripts/run-mobile-monitor-bridge.ts +0 -1
- package/scripts/smoke-control-plane-failure-domain.ts +94 -0
- package/scripts/smoke-mcp-http-runtime.ts +6 -1
- package/scripts/smoke-runtime-recovery.ts +13 -11
- package/scripts/smoke-schedule-engine.ts +24 -16
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +92 -0
- package/scripts/smoke-workflow-supervisor-process-restart.ts +276 -0
- package/scripts/smoke-workflow-supervisor-restart-recovery.ts +150 -0
- package/scripts/smoke-workflow-supervisor.ts +103 -0
- package/scripts/test-governance.ts +51 -2
- package/scripts/verify-forge-runtime.sh +2 -1
- package/src/cli/agent-jobs/executor-health.ts +11 -34
- package/src/cli/agent-jobs/integration.ts +18 -49
- package/src/cli/agent-jobs/job-manager.ts +17 -4
- package/src/cli/chatgpt-browser/bind-server.ts +2 -1
- package/src/cli/chatgpt-browser/binding.ts +20 -10
- package/src/cli/chatgpt-browser/bridge-extension.ts +75 -0
- package/src/cli/chatgpt-browser/bridge-provider.ts +376 -11
- package/src/cli/chatgpt-browser/engine.ts +10 -11
- package/src/cli/chatgpt-browser/native-provider.ts +24 -83
- package/src/cli/chatgpt-browser/oracle-provider.ts +5 -3
- package/src/cli/chatgpt-browser/types.ts +1 -0
- package/src/cli/commands/bootstrap-control.ts +334 -0
- package/src/cli/commands/brain-assistant.ts +52 -0
- package/src/cli/commands/brain.ts +191 -40
- package/src/cli/commands/chatgpt.ts +32 -67
- package/src/cli/commands/computer.ts +437 -0
- package/src/cli/commands/doctor.ts +18 -1
- package/src/cli/commands/init-hook.ts +71 -61
- package/src/cli/commands/init.ts +3 -1
- package/src/cli/commands/mcp.ts +16 -10
- package/src/cli/commands/plugin.ts +38 -12
- package/src/cli/commands/recovery.ts +507 -80
- package/src/cli/commands/runtime.ts +12 -7
- package/src/cli/commands/setup-profile.ts +97 -39
- package/src/cli/commands/status.ts +2 -1
- package/src/cli/controller/check-runner.ts +500 -100
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +4 -43
- package/src/cli/controller/completion-target.ts +3 -72
- package/src/cli/controller/context/source-materializer.ts +81 -100
- package/src/cli/controller/context/types.ts +7 -1
- package/src/cli/controller/context-pack.ts +28 -6
- package/src/cli/controller/execution-completion.ts +4 -7
- package/src/cli/controller/execution-policy.ts +36 -153
- package/src/cli/controller/governance.ts +2 -3
- package/src/cli/controller/issue-store.ts +4 -99
- package/src/cli/controller/launch-agents.ts +59 -18
- package/src/cli/controller/operational-plan.ts +24 -51
- package/src/cli/controller/runtime-config.ts +5 -1
- package/src/cli/controller/stuck-state-migration.ts +2 -3
- package/src/cli/controller/systemd-user.ts +148 -0
- package/src/cli/controller/types.ts +10 -70
- package/src/cli/controller/worklog.ts +67 -13
- package/src/cli/editing/edit-operation-contract.ts +92 -0
- package/src/cli/editing/edit-session.ts +290 -20
- package/src/cli/github/plugin.ts +17 -12
- package/src/cli/hook/route-registry.ts +3 -5
- package/src/cli/hook/runtime.ts +49 -3
- package/src/cli/index.ts +3 -0
- package/src/cli/local-bridge/connector-freshness.ts +2 -2
- package/src/cli/local-bridge/console-automations.ts +20 -15
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +105 -205
- package/src/cli/local-bridge/job-store.ts +1 -1
- package/src/cli/local-bridge/server.ts +48 -174
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/access-mode.ts +2 -132
- package/src/cli/mcp/access-tools.ts +2 -387
- package/src/cli/mcp/audit.ts +2 -32
- package/src/cli/mcp/auth.ts +2 -404
- package/src/cli/mcp/instructions.ts +2 -34
- package/src/cli/mcp/legacy-context.ts +2 -16
- package/src/cli/mcp/legacy-tool-service.ts +2 -5308
- package/src/cli/mcp/multi-repository.ts +2 -436
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/mcp/oauth.ts +2 -375
- package/src/cli/mcp/openai-secure-tunnel.ts +2 -0
- package/src/cli/mcp/paths.ts +2 -140
- package/src/cli/mcp/policy.ts +2 -232
- package/src/cli/mcp/post-finalize-work-attribution.ts +2 -150
- package/src/cli/mcp/proxy-env.ts +2 -62
- package/src/cli/mcp/readiness-projection.ts +2 -24
- package/src/cli/mcp/redaction.ts +2 -21
- package/src/cli/mcp/repo.ts +2 -24
- package/src/cli/mcp/repository-tools.ts +2 -1613
- package/src/cli/mcp/server.ts +2 -951
- package/src/cli/mcp/setup.ts +2 -737
- package/src/cli/mcp/tool-capability-cache.ts +2 -87
- package/src/cli/mcp/tool-contract.ts +2 -14
- package/src/cli/mcp/tools.ts +2 -9
- package/src/cli/mcp/toolset-names.ts +2 -70
- package/src/cli/mcp/toolset-selection.ts +2 -41
- package/src/cli/mcp/toolset.ts +2 -336
- package/src/cli/mcp/transports/AGENTS.md +1 -10
- package/src/cli/mcp/transports/CLAUDE.md +1 -10
- package/src/cli/mcp/transports/http.ts +2 -1414
- package/src/cli/mcp/transports/session-registry.ts +2 -490
- package/src/cli/mcp/transports/stdio.ts +2 -8
- package/src/cli/mcp/types.ts +2 -41
- package/src/cli/mcp/umbrella-tools.ts +2 -117
- package/src/cli/repositories/command-executor.ts +91 -39
- package/src/cli/repositories/command-scope.ts +139 -4
- package/src/cli/repositories/controller-home.ts +167 -1
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/identity.ts +13 -0
- package/src/cli/repositories/registry.ts +145 -60
- package/src/cli/repositories/repository-snapshot.ts +53 -7
- package/src/cli/repositories/runtime-storage.ts +27 -2
- package/src/cli/repositories/safe-patch.ts +34 -38
- package/src/cli/repositories/selected-path-actions.ts +22 -2
- package/src/cli/repositories/structured-git.ts +167 -27
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/cli/runtime/helper-runner.ts +7 -0
- package/src/cli/runtime-invocation.ts +8 -1
- package/src/cli/tools/codegraph.ts +44 -25
- package/src/core/adoption/manifest-templates.ts +1 -1
- package/src/core/adoption/plan.ts +3 -26
- package/src/core/adoption/workflow-contract-plan.ts +17 -9
- package/src/effects/process-runner.ts +8 -14
- package/src/runtime/context/assistant-context.ts +167 -0
- package/src/runtime/context/assistant-work-context.ts +209 -0
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/codegraph-cache-boundary.ts +255 -0
- package/src/runtime/context/codegraph-read-provider.ts +55 -22
- package/src/runtime/context/codegraph-sidecar.cjs +8 -1
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +305 -0
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +203 -0
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/project-engineering-contract.ts +60 -0
- package/src/runtime/context/semantic-navigation-contract.ts +9 -0
- package/src/runtime/context/semantic-navigation.ts +6 -34
- package/src/runtime/context/typescript-navigation-process.ts +145 -0
- package/src/runtime/context/typescript-navigation.ts +89 -3
- package/src/runtime/control-plane/bootstrap/capability-provider.ts +74 -0
- package/src/runtime/control-plane/bootstrap/control-api.ts +57 -0
- package/src/runtime/control-plane/bootstrap/index.ts +5 -0
- package/src/runtime/control-plane/bootstrap/state-machine.ts +141 -0
- package/src/runtime/control-plane/bootstrap/store.ts +21 -0
- package/src/runtime/control-plane/bootstrap/types.ts +87 -0
- package/src/runtime/control-plane/cleanup-artifact-retention.ts +531 -0
- package/src/runtime/control-plane/codegraph-cache-retention.ts +164 -0
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +282 -0
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +380 -0
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +113 -16
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/implementation-review-content.ts +95 -0
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +439 -102
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +34 -11
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +87 -0
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +31 -44
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +46 -53
- package/src/runtime/control-plane/execution/work-execution-support.ts +85 -164
- package/src/runtime/control-plane/execution/work-handle-authority.ts +349 -73
- package/src/runtime/control-plane/execution/work-handle-store.ts +55 -2
- package/src/runtime/control-plane/execution/work-head-settlement.ts +108 -0
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +173 -28
- package/src/runtime/control-plane/execution/work-operation-service.ts +92 -40
- package/src/runtime/control-plane/execution/work-preparation-service.ts +145 -269
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +2 -3
- package/src/runtime/control-plane/execution/work-revision-diff.ts +39 -0
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +543 -225
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +59 -45
- package/src/runtime/control-plane/execution/work-verification-context.ts +1 -1
- package/src/runtime/control-plane/execution/work-verification-service.ts +662 -52
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +205 -16
- package/src/runtime/control-plane/facade/capability-registry.ts +380 -44
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +2 -488
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +6 -22
- package/src/runtime/control-plane/facade/goal-workloop.ts +565 -1202
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +145 -0
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +266 -52
- package/src/runtime/control-plane/facade/index.ts +4 -3
- package/src/runtime/control-plane/facade/operation-digest.ts +34 -73
- package/src/runtime/control-plane/facade/plan-contract-store.ts +671 -245
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +31 -23
- package/src/runtime/control-plane/facade/requirement-authority.ts +121 -0
- package/src/runtime/control-plane/facade/requirement-board.ts +9 -4
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +85 -0
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/semantic-admission.ts +8 -16
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -15
- package/src/runtime/control-plane/facade/types.ts +218 -620
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/facade/work-contract-store.ts +2 -1043
- package/src/runtime/control-plane/generated-cache-retention.ts +336 -19
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +13 -2
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +274 -84
- package/src/runtime/control-plane/global-scheduler/periodic-cleanup-entry.ts +55 -0
- package/src/runtime/control-plane/global-scheduler/periodic-cleanup-process.ts +98 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -2
- package/src/runtime/control-plane/global-scheduler/scheduler-worker-entry.ts +10 -0
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +227 -109
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +18 -6
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +160 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +2 -177
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +139 -670
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +25 -6
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -194
- package/src/runtime/control-plane/lifecycle-retention-metrics.ts +60 -0
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +302 -0
- package/src/runtime/control-plane/persistence/operational-prior-store.ts +348 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +230 -80
- package/src/runtime/control-plane/persistence/sqlite-store.ts +654 -78
- package/src/runtime/control-plane/persistence/workflow-content-store.ts +98 -0
- package/src/runtime/control-plane/persistence/workflow-registry-store.ts +186 -0
- package/src/runtime/control-plane/persistence/workflow-run-store.ts +123 -0
- package/src/runtime/control-plane/release-retention.ts +154 -26
- package/src/runtime/control-plane/repository-namespace-retention.ts +208 -0
- package/src/runtime/control-plane/runtime-cleanup.ts +844 -38
- package/src/runtime/control-plane/runtime-generation.ts +6 -0
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +479 -0
- package/src/runtime/control-plane/workspace/workspace-store.ts +249 -0
- package/src/runtime/diagnostics/incident-repair.ts +573 -0
- package/src/runtime/diagnostics/mcp-timing.ts +239 -6
- package/src/runtime/diagnostics/performance.ts +6 -14
- package/src/runtime/diagnostics/read-only-tool.ts +1 -1
- package/src/runtime/evidence/artifact-store.ts +216 -35
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/operational-shadow.ts +38 -0
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/process-check-receipt.ts +5 -43
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/evidence/sensitive-output.ts +4 -4
- package/src/runtime/execution/jobs/receipt-recovery.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +124 -6
- package/src/runtime/execution/managed-workspace.ts +112 -5
- package/src/runtime/execution/process-runtime/canary.ts +19 -5
- package/src/runtime/execution/process-runtime/check-classification.ts +48 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +24 -10
- package/src/runtime/execution/process-runtime/check-receipt.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +132 -0
- package/src/runtime/execution/process-runtime/check-result.ts +177 -2
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +58 -9
- package/src/runtime/execution/process-runtime/check-storage.ts +187 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +117 -114
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +4 -0
- package/src/runtime/execution/process-runtime/interactive-admission.ts +2 -1
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +283 -73
- package/src/runtime/execution/process-runtime/persisted-check.ts +130 -23
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +125 -64
- package/src/runtime/execution/process-runtime/runtime.ts +167 -17
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +27 -0
- package/src/runtime/execution/workers/executor.ts +18 -19
- package/src/runtime/execution/workers/worker-entry.ts +1 -1
- package/src/runtime/gateway/mcp/execution-tools.ts +2 -207
- package/src/runtime/gateway/mcp/legacy-ios-tool-adapter.ts +2 -61
- package/src/runtime/gateway/mcp/persisted-check-process.ts +2 -3
- package/src/runtime/gateway/mcp/process-tools.ts +2 -289
- package/src/runtime/gateway/mcp/router.ts +2 -1125
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +2 -565
- package/src/runtime/gateway/mcp/runtime-tools.ts +2 -6655
- package/src/runtime/gateway/mcp/work-validation-reconciler.ts +2 -1
- package/src/runtime/maintenance/cleanup.ts +38 -24
- package/src/runtime/platform/browser-product-discovery.ts +174 -0
- package/src/runtime/platform/computer-platform.ts +14 -0
- package/src/runtime/platform/executable-discovery.ts +54 -0
- package/src/runtime/platform/fetch-http-server.ts +90 -0
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +146 -0
- package/src/runtime/platform/windows-host-discovery.ts +118 -0
- package/src/runtime/platform/windows-recovery-deployment.ts +67 -0
- package/src/runtime/plugins/action-application.ts +192 -0
- package/src/runtime/plugins/app-store-connect-adapter.ts +47 -35
- package/src/runtime/plugins/browser-adapter.ts +898 -135
- package/src/runtime/plugins/browser-automation-service.ts +33 -34
- package/src/runtime/plugins/browser-macos-bridge.ts +386 -46
- package/src/runtime/plugins/browser-manifest-surface.ts +75 -17
- package/src/runtime/plugins/browser-native-session-service.ts +64 -14
- package/src/runtime/plugins/browser-provider-registry.ts +2 -109
- package/src/runtime/plugins/browser-registration.ts +32 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +2 -154
- package/src/runtime/plugins/browser-runtime.ts +55 -10
- package/src/runtime/plugins/browser-session-authority.ts +101 -291
- package/src/runtime/plugins/browser-session-legacy-migration.ts +203 -0
- package/src/runtime/plugins/browser-session-store.ts +243 -80
- package/src/runtime/plugins/browser-session-types.ts +2 -98
- package/src/runtime/plugins/capability-authorization-grants.ts +272 -59
- package/src/runtime/plugins/catalog-trust.ts +82 -0
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +1050 -0
- package/src/runtime/plugins/config-store.ts +119 -0
- package/src/runtime/plugins/desktop-operator-external-policy.ts +964 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +123 -12
- package/src/runtime/plugins/errors.ts +8 -1
- package/src/runtime/plugins/external-adapter.ts +82 -323
- package/src/runtime/plugins/external-provider-policy.ts +48 -0
- package/src/runtime/plugins/external-registration.ts +30 -2
- package/src/runtime/plugins/external-unix-socket-probe.cjs +9 -3
- package/src/runtime/plugins/external-unix-socket.ts +64 -193
- package/src/runtime/plugins/first-party-external-registration.ts +163 -0
- package/src/runtime/plugins/first-party-registry.ts +3 -1
- package/src/runtime/plugins/github-adapter.ts +1 -2
- package/src/runtime/plugins/gmail-adapter.ts +29 -8
- package/src/runtime/plugins/google-calendar-adapter.ts +8 -8
- package/src/runtime/plugins/google-shared.ts +45 -53
- package/src/runtime/plugins/google-tasks-adapter.ts +8 -8
- package/src/runtime/plugins/ios-adapter.ts +2 -3
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +192 -2
- package/src/runtime/plugins/macos-capability-broker.ts +15 -105
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/resend-adapter.ts +15 -24
- package/src/runtime/plugins/store.ts +1003 -387
- package/src/runtime/plugins/types.ts +28 -1
- package/src/runtime/plugins/uu-remote-rescue-registration.ts +155 -0
- package/src/runtime/plugins/windows-host-recovery-install.ts +68 -0
- package/src/runtime/plugins/windows-host-recovery-registration.ts +85 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +102 -430
- package/src/runtime/projections/controller-context.ts +12 -6
- package/src/runtime/projections/git-status-sampler.ts +73 -22
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +93 -43
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +229 -0
- package/src/runtime/recovery/index.ts +3 -0
- package/src/runtime/recovery/lifecycle-inventory.ts +338 -0
- package/src/runtime/recovery/local-jobs-repair.ts +14 -14
- package/src/runtime/recovery/maintenance-executor.ts +701 -168
- package/src/runtime/recovery/quarantine-retention.ts +256 -0
- package/src/runtime/recovery/store.ts +29 -11
- package/src/runtime/release/identity.ts +6 -0
- package/src/runtime/release/release-coordinator.ts +296 -0
- package/src/runtime/release/release-session.ts +716 -0
- package/src/runtime/release/release-state-migration.ts +25 -0
- package/src/runtime/resources/leases/store.ts +56 -25
- package/src/runtime/root/active-execution-power-assertion.ts +139 -0
- package/src/runtime/root/assistant-learning-loop.ts +45 -0
- package/src/runtime/root/browser-session-composition.ts +59 -0
- package/src/runtime/root/computer-composition.ts +376 -0
- package/src/runtime/root/computer-target-composition.ts +16 -0
- package/src/runtime/root/computer-target-persistence.ts +39 -0
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +126 -0
- package/src/runtime/root/controller-services.ts +2 -0
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +8 -6
- package/src/runtime/root/gateway-adapter.ts +3 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +5 -3
- package/src/runtime/root/mcp-transport.ts +116 -23
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +212 -74
- package/src/runtime/root/package-runtime-release.ts +199 -33
- package/src/runtime/root/package-runtime-service.ts +77 -108
- package/src/runtime/root/release-execution-canary.ts +103 -0
- package/src/runtime/root/release-loader.ts +150 -0
- package/src/runtime/root/release-manifest.ts +185 -7
- package/src/runtime/root/release-materialize.ts +474 -31
- package/src/runtime/root/release-store.ts +425 -37
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +379 -32
- package/src/runtime/root/scheduled-controller-composition.ts +76 -0
- package/src/runtime/root/scheduler.ts +12 -4
- package/src/runtime/root/service-runner.ts +3 -3
- package/src/runtime/root/service.ts +73 -45
- package/src/runtime/root/status.ts +49 -0
- package/src/runtime/root/types.ts +55 -3
- package/src/runtime/root/workflow-supervisor-composition.ts +493 -0
- package/src/runtime/root/workflow-supervisor-runtime.ts +209 -0
- package/src/runtime/root/write-fence.ts +50 -2
- package/src/runtime/safe-tooling/google-credential-store.ts +76 -21
- package/src/runtime/safe-tooling/google-oauth-broker.ts +25 -7
- package/src/runtime/safe-tooling/workspace-auth.ts +1 -1
- package/src/runtime/shared/bounded-child-supervisor.ts +9 -1
- package/src/runtime/shared/local-bridge-surface.ts +1 -1
- package/src/runtime/shared/process-environment.ts +36 -10
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/shared/storage-capacity.ts +68 -0
- package/src/runtime/standalone-recovery/controller-home-migration.ts +1108 -0
- package/src/runtime/standalone-recovery/core.ts +4609 -495
- package/src/runtime/standalone-recovery/entry.ts +642 -81
- package/src/runtime/standalone-recovery/independent-host-rescue.ts +172 -0
- package/src/runtime/standalone-recovery/installer.ts +399 -177
- package/src/runtime/standalone-recovery/mcp-server.ts +79 -0
- package/src/runtime/standalone-recovery/mutation-identity-contract.ts +21 -0
- package/src/runtime/standalone-recovery/operation-lock.ts +160 -0
- package/src/runtime/standalone-recovery/performance.ts +173 -0
- package/src/runtime/standalone-recovery/release.ts +58 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/watchdog/workflow-watchdog.ts +1 -1
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +11 -4
- package/src/runtime/workflow/schedules/engine.ts +298 -484
- package/src/runtime/workflow/schedules/pending-activation.ts +1 -1
- package/src/runtime/workflow/schedules/settlement.ts +2 -191
- package/src/runtime/workflow/schedules/store.ts +2 -607
- package/src/runtime/workflow/schedules/types.ts +2 -127
- package/src/runtime/workflow/schedules/work-continuation.ts +220 -16
- package/src/runtime/workflows/first-party/xiaohongshu.ts +122 -0
- package/src/runtime/workflows/runtime.ts +482 -0
- package/src/testing/test-governance.ts +95 -30
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +201 -0
- package/supervisor/control-plane.ts +868 -0
- package/supervisor/entry.ts +15 -0
- package/supervisor/forge-validators.ts +80 -0
- package/supervisor/index.ts +5 -0
- package/supervisor/native-browser-adapter.ts +786 -0
- package/supervisor/native-messaging/host.ts +39 -0
- package/supervisor/native-messaging/manifest.ts +26 -0
- package/supervisor/native-messaging/protocol.ts +27 -0
- package/supervisor/paths.ts +18 -0
- package/supervisor/protocol.ts +202 -0
- package/supervisor/server.ts +404 -0
- package/supervisor/store.ts +1451 -0
- package/supervisor/tsconfig.json +8 -0
- package/supervisor/types.ts +255 -0
- package/assets/templates/helpers/archive-architecture-request.sh +0 -244
- package/docs/architecture/history/README.md +0 -7
- package/docs/architecture/history/chatgpt-handoff-facade.md +0 -397
- package/docs/architecture/history/global-hook-runtime.md +0 -166
- package/docs/architecture/history/ios-semantic-automation-provider-v2.md +0 -170
- package/docs/architecture/requests/archive/2026/20260528-154740-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-154816-assets-hooks-assets-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161851-ai-harness-workflow-contract-json-ai-harness-workflow-contract-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161853-scripts-check-task-workflow-sh-scripts-check-task-workflow-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-161911-assets-workflow-contract-v1-json-assets-workflow-contract-v1-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222416-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222444-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222457-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222648-ai-hooks-ai-hooks-pre-edit-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222705-ai-hooks-ai-hooks-worktree-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222726-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-222727-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-224736-ai-hooks-ai-hooks-hook-input-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233259-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233335-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233456-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233457-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233637-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260528-233638-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-000322-root-package-json.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-004238-ai-hooks-ai-hooks-lib-workflow-state-sh.md +0 -59
- package/docs/architecture/requests/archive/2026/20260529-020654-ai-harness-policy-json-ai-harness-policy-json.md +0 -63
- package/docs/architecture/requests/archive/2026/20260529-094236-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094323-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094331-ai-hooks-ai-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094339-assets-hooks-assets-hooks-prompt-guard-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094430-assets-hooks-assets-hooks-session-start-context-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260529-094446-ai-hooks-ai-hooks-session-start-context-sh.md +0 -60
- package/docs/architecture/requests/archive/2026/20260613-002040-runtime-harness-hook-adapters.md +0 -68
- package/docs/architecture/requests/archive/2026/root.md +0 -68
- package/docs/architecture/requests/archive/2026/runtime-harness-hook-adapters.md +0 -76
- package/docs/architecture/requests/archive/2026/verification-evals-checks.md +0 -68
- package/docs/architecture/requests/archive/2026/workflow-engine-contract-assets.md +0 -69
- package/scripts/archive-architecture-request.sh +0 -4
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -139
- package/scripts/benchmark-ios-agent-device.ts +0 -514
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -170
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -67
- package/scripts/package-source-archive.sh +0 -147
- package/scripts/public-release-files.txt +0 -39
- package/scripts/public-release.ts +0 -174
- package/scripts/publish-release-tarball.sh +0 -27
- package/scripts/route-nl-vs-ts-eval.ts +0 -679
- package/scripts/run-skill-evals.ts +0 -1022
- package/scripts/stage-runtime-release.ts +0 -39
- package/src/cli/controller/progress.ts +0 -656
- package/src/cli/controller/work-mode.ts +0 -181
- package/src/cli/local-bridge/console-assets.ts +0 -17
- package/src/cli/local-bridge/dashboard.ts +0 -17
- package/src/cli/local-bridge/frontend/src/App.tsx +0 -10
- package/src/cli/local-bridge/frontend/src/components/Button.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/DetailPane.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/components/SearchField.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/SectionHeader.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/Segmented.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/components/StatusText.tsx +0 -3
- package/src/cli/local-bridge/frontend/src/design/icons.tsx +0 -11
- package/src/cli/local-bridge/frontend/src/design/tokens.css +0 -8
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +0 -153
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +0 -37
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +0 -183
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/system/SystemView.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +0 -30
- package/src/cli/local-bridge/frontend/src/lib/format.ts +0 -4
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +0 -7
- package/src/cli/local-bridge/frontend/src/main.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/AppShell.tsx +0 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +0 -2
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +0 -4
- package/src/cli/local-bridge/frontend/src/styles.css +0 -28
- package/src/cli/local-bridge/frontend/src/types.ts +0 -15
- package/src/cli/local-bridge/frontend/src/vite-env.d.ts +0 -1
- package/src/cli/local-bridge/frontend/vite.config.ts +0 -27
- package/src/cli/local-bridge/ui-dist/app.css +0 -1
- package/src/cli/local-bridge/ui-dist/app.js +0 -11228
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -385
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -1913
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -449
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -1118
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -93
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -382
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -0,0 +1,1256 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
import type { ExecutionJobOrigin } from '../../src/runtime/execution/jobs/types';
|
|
4
|
+
import { browserActions } from '../../src/runtime/plugins/browser-manifest-surface';
|
|
5
|
+
import { executeControllerScopedPluginAction, getControllerPluginManifest, submitControllerPluginAction } from '../../src/runtime/plugins/store';
|
|
6
|
+
import {
|
|
7
|
+
CHATGPT_AUTOMATION_RATE_LIMITED,
|
|
8
|
+
CHATGPT_AUTOMATION_RATE_LIMITED_AFTER_SUBMIT,
|
|
9
|
+
CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN,
|
|
10
|
+
ChatgptProviderDeliveryError,
|
|
11
|
+
chatgptProviderPageFailure,
|
|
12
|
+
DEFAULT_CHATGPT_AUTOMATION_MODEL,
|
|
13
|
+
type ChatgptAutomationReasoning,
|
|
14
|
+
type ChatgptAutomationTabCleanupStatus,
|
|
15
|
+
type ChatgptProviderPageFailureCode,
|
|
16
|
+
} from './provider-delivery';
|
|
17
|
+
|
|
18
|
+
// The installed/connected primary Forge plugin is `forge`, not the separate
|
|
19
|
+
// Plugin Creator package `forge-current-1-8-1` or the Forge Recovery app.
|
|
20
|
+
// Live ChatGPT selection yields an exact app:// ProseMirror Forge chip.
|
|
21
|
+
const CHATGPT_FORGE_PLUGIN_SLUG = 'forge';
|
|
22
|
+
const CHATGPT_FORGE_PLUGIN_LABEL = 'forge';
|
|
23
|
+
const CHATGPT_FORGE_PLUGIN_PATH = 'app://asdk_app_6ac09022b26081918f20ff868c773824';
|
|
24
|
+
const CHATGPT_PLUGIN_PICKER_OPTIONS = '[data-mention-section-id="plugins"] [data-mention-section-items] > button';
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
type ChatgptBrowserActionOrigin = Pick<ExecutionJobOrigin, 'surface' | 'actor'>;
|
|
28
|
+
interface ChatgptBrowserActionContext {
|
|
29
|
+
origin: ChatgptBrowserActionOrigin;
|
|
30
|
+
authorizationGrantRefs: Set<string>;
|
|
31
|
+
}
|
|
32
|
+
const chatgptBrowserActionOrigin = new AsyncLocalStorage<ChatgptBrowserActionContext>();
|
|
33
|
+
|
|
34
|
+
export function withChatgptBrowserActionOrigin<T>(
|
|
35
|
+
origin: ChatgptBrowserActionOrigin,
|
|
36
|
+
operation: () => Promise<T>,
|
|
37
|
+
authorizationGrantRefs: Set<string> = new Set(),
|
|
38
|
+
): Promise<T> {
|
|
39
|
+
return chatgptBrowserActionOrigin.run({ origin, authorizationGrantRefs }, operation);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Strip the legacy text trigger; only a real ChatGPT app chip selects Forge. */
|
|
43
|
+
export function chatgptSupervisorPromptBody(prompt: string): string {
|
|
44
|
+
const value = prompt.trim().replace(/^@forge(?:[ \t]*\r?\n|[ \t]+|$)/i, '').trim();
|
|
45
|
+
if (!value) throw new Error('CHATGPT_AUTOMATION_PROMPT_REQUIRED');
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ChatGPT's composer is a ProseMirror textbox; the historical #prompt-textarea id is no longer stable.
|
|
50
|
+
// Bind delivery to the semantic composer contract used by the live UI so readiness, fill, and Enter fallback share one selector authority.
|
|
51
|
+
const CHATGPT_PROMPT_SELECTOR = '[data-composer-markdown][role="textbox"][contenteditable="true"]';
|
|
52
|
+
const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"], button[aria-label="Send"], button[aria-label="发送"], button[data-testid*="send"]';
|
|
53
|
+
const CHATGPT_USER_MESSAGE_SELECTOR = '[data-message-author-role="user"], [data-chatgpt-search-unit-key$=":user"], [data-content-search-unit-key$=":user"]';
|
|
54
|
+
const CHATGPT_ASSISTANT_MESSAGE_SELECTOR = '[data-message-author-role="assistant"], [data-chatgpt-search-unit-key$=":assistant"], [data-content-search-unit-key$=":assistant"]';
|
|
55
|
+
const CHATGPT_STOP_GENERATING_SELECTOR = '[data-testid="stop-button"], button[aria-label*="Stop"]';
|
|
56
|
+
const CHATGPT_INTELLIGENCE_CONTROL_SELECTORS = [
|
|
57
|
+
'main button, main [role="button"]',
|
|
58
|
+
'button, [role="button"]',
|
|
59
|
+
] as const;
|
|
60
|
+
const CHATGPT_CAPABILITY_SLIDER_SELECTOR = '[role="slider"]';
|
|
61
|
+
const CHATGPT_CAPABILITY_MENUITEM_SELECTOR = '[role="menuitem"][aria-keyshortcuts~="ArrowLeft"][aria-keyshortcuts~="ArrowRight"]';
|
|
62
|
+
|
|
63
|
+
function requestId(workId: string, actionId: string): string {
|
|
64
|
+
return `chatgpt-work:${workId}:${actionId}:${randomUUID()}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const CHATGPT_BROWSER_AUTHORIZATION_ACTIONS = new Set(
|
|
68
|
+
browserActions()
|
|
69
|
+
.filter((action) => !action.readOnly && action.confirmation === 'authorization')
|
|
70
|
+
.map((action) => action.actionId),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
export function chatgptBrowserActionArgs(actionId: string, args: Record<string, unknown>): Record<string, unknown> {
|
|
74
|
+
// Browser transport policy is controller-scoped configuration. Do not copy
|
|
75
|
+
// it into every action envelope: older Browser action schemas legitimately
|
|
76
|
+
// reject these optional fields even though the persisted configuration still
|
|
77
|
+
// supports the same policy. Keeping the envelope action-specific also lets a
|
|
78
|
+
// resumed ChatGPT round cross a connector/schema rotation without failing
|
|
79
|
+
// before the provider can observe the conversation.
|
|
80
|
+
void actionId;
|
|
81
|
+
return args;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function chatgptBrowserActionResult(envelope: Record<string, unknown>, actionId: string): Record<string, unknown> {
|
|
85
|
+
const result = envelope.result;
|
|
86
|
+
if (!result || typeof result !== 'object' || Array.isArray(result)) {
|
|
87
|
+
throw new Error(`CHATGPT_BROWSER_ACTION_RESULT_INVALID:${actionId}`);
|
|
88
|
+
}
|
|
89
|
+
return result as Record<string, unknown>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function controllerBrowserAction(
|
|
93
|
+
controllerHome: string,
|
|
94
|
+
workId: string,
|
|
95
|
+
actionId: string,
|
|
96
|
+
args: Record<string, unknown>,
|
|
97
|
+
timeoutMs?: number,
|
|
98
|
+
): Promise<Record<string, unknown>> {
|
|
99
|
+
const context = chatgptBrowserActionOrigin.getStore();
|
|
100
|
+
const origin = context?.origin ?? { surface: 'schedule', actor: 'chatgpt-work-continuation' };
|
|
101
|
+
const actionRequest = {
|
|
102
|
+
pluginId: 'browser',
|
|
103
|
+
actionId,
|
|
104
|
+
requestId: requestId(workId, actionId),
|
|
105
|
+
args: chatgptBrowserActionArgs(actionId, args),
|
|
106
|
+
timeoutMs,
|
|
107
|
+
origin,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// Interactive ChatGPT delivery is the only place allowed to establish a
|
|
111
|
+
// reusable controller-scoped Browser grant. Scheduled delivery stays on the
|
|
112
|
+
// low-level executor and must present explicit refs already bound to this Work.
|
|
113
|
+
if (origin.surface === 'chatgpt-action' && CHATGPT_BROWSER_AUTHORIZATION_ACTIONS.has(actionId)) {
|
|
114
|
+
const submitted = await submitControllerPluginAction(controllerHome, actionRequest);
|
|
115
|
+
const grantId = submitted.authorization?.grantId?.trim();
|
|
116
|
+
if (grantId) context?.authorizationGrantRefs.add(grantId);
|
|
117
|
+
if (!submitted.result) throw new Error(`CHATGPT_BROWSER_ACTION_RESULT_INVALID:${actionId}`);
|
|
118
|
+
return chatgptBrowserActionResult(submitted.result, actionId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const envelope = await executeControllerScopedPluginAction({
|
|
122
|
+
controllerHome,
|
|
123
|
+
...actionRequest,
|
|
124
|
+
authorizationGrantRefs: [...(context?.authorizationGrantRefs ?? [])],
|
|
125
|
+
});
|
|
126
|
+
return chatgptBrowserActionResult(envelope, actionId);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function ensureControllerChatgptBrowser(controllerHome: string, workId: string): Promise<void> {
|
|
130
|
+
// Browser configure is not a read: it persists configuration and closes managed
|
|
131
|
+
// contexts. Scheduled continuations must not disturb an already-enabled provider
|
|
132
|
+
// merely to prove it is available. Read the existing controller-scoped manifest
|
|
133
|
+
// authority, and only enable it when that persisted authority is explicitly disabled;
|
|
134
|
+
// action-level transport overrides still fail closed later.
|
|
135
|
+
if (getControllerPluginManifest(controllerHome, 'browser').enabled) return;
|
|
136
|
+
await controllerBrowserAction(controllerHome, workId, 'configure', { enabled: true });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function stringField(value: unknown): string | undefined {
|
|
140
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface BrowserQueryMatch {
|
|
144
|
+
text?: string;
|
|
145
|
+
selectorHint?: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function queryMatches(result: Record<string, unknown> | undefined): BrowserQueryMatch[] {
|
|
149
|
+
if (!result || !Array.isArray(result.matches)) return [];
|
|
150
|
+
return result.matches.filter((value): value is BrowserQueryMatch => Boolean(value) && typeof value === 'object');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function matchText(value: BrowserQueryMatch): string {
|
|
154
|
+
return typeof value.text === 'string' ? value.text.trim() : '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function matchSelector(value: BrowserQueryMatch | undefined): string | undefined {
|
|
158
|
+
return typeof value?.selectorHint === 'string' && value.selectorHint.trim() ? value.selectorHint.trim() : undefined;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function normalizeChatgptOutboundText(value: string): string {
|
|
162
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES = ['收起', 'Collapse', 'Show less'] as const;
|
|
166
|
+
const MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS = 100_000;
|
|
167
|
+
const MIN_TRUNCATED_CHATGPT_OUTBOUND_PREFIX_CHARS = 256;
|
|
168
|
+
const CHATGPT_PROVIDER_ERROR_SELECTOR = '[role="alert"], [aria-live="assertive"], [data-testid*="error"], [data-testid*="toast"], [data-sonner-toast]';
|
|
169
|
+
const CHATGPT_DELIVERY_FAILURE_PROBE_INTERVAL_MS = 1_000;
|
|
170
|
+
|
|
171
|
+
interface BrowserFailedRequest {
|
|
172
|
+
url?: string;
|
|
173
|
+
method?: string;
|
|
174
|
+
status?: number;
|
|
175
|
+
failure?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
type ChatgptFailedRequestBaseline = Map<string, number>;
|
|
179
|
+
|
|
180
|
+
export function chatgptOutboundMessageMatchesPrompt(
|
|
181
|
+
messageText: string,
|
|
182
|
+
prompt: string,
|
|
183
|
+
options: { truncated?: boolean; boundForgePlugin?: boolean } = {},
|
|
184
|
+
): boolean {
|
|
185
|
+
const message = normalizeChatgptOutboundText(messageText);
|
|
186
|
+
const normalizedPrompt = normalizeChatgptOutboundText(prompt);
|
|
187
|
+
if (!message || !normalizedPrompt) return false;
|
|
188
|
+
const candidatePrompts = [normalizedPrompt,
|
|
189
|
+
...(options.boundForgePlugin ? [`${normalizedPrompt} ${CHATGPT_FORGE_PLUGIN_LABEL}`] : []),
|
|
190
|
+
];
|
|
191
|
+
if (candidatePrompts.some((candidate) => message === candidate)) return true;
|
|
192
|
+
if (candidatePrompts.some((candidate) => CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES.some((suffix) => message === `${candidate} ${suffix}`))) return true;
|
|
193
|
+
// Browser text extraction is deliberately bounded. A large ControllerRound
|
|
194
|
+
// prompt can exceed that bound, so requiring exact equality makes successful
|
|
195
|
+
// submissions mechanically unverifiable. Accept only an explicitly reported
|
|
196
|
+
// truncation of a substantial exact prefix; ordinary partial/mismatched text
|
|
197
|
+
// remains insufficient evidence.
|
|
198
|
+
return options.truncated === true
|
|
199
|
+
&& message.length >= MIN_TRUNCATED_CHATGPT_OUTBOUND_PREFIX_CHARS
|
|
200
|
+
&& candidatePrompts.some((candidate) => candidate.startsWith(message));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function chatgptAutomationDeliveryFailure(
|
|
204
|
+
bodyText: string | undefined,
|
|
205
|
+
): ChatgptProviderPageFailureCode | undefined {
|
|
206
|
+
return chatgptProviderPageFailure(bodyText);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function chatgptSubmissionSettlementWaitBudget(timeoutMs?: number): number {
|
|
210
|
+
return Math.min(Math.max(timeoutMs ?? 30_000, 3_000), 30_000);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function chatgptSubmissionObservationDelayMs(observationIndex: number): number {
|
|
214
|
+
const schedule = [500, 1_000, 2_000, 3_000, 5_000] as const;
|
|
215
|
+
return schedule[Math.min(Math.max(0, Math.trunc(observationIndex)), schedule.length - 1)];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function chatgptSubmissionAcceptanceObserved(input: {
|
|
219
|
+
outboundConfirmed: boolean;
|
|
220
|
+
hasConversationIdentity: boolean;
|
|
221
|
+
assistantResponseObserved: boolean;
|
|
222
|
+
generationInProgress: boolean;
|
|
223
|
+
}): boolean {
|
|
224
|
+
return input.outboundConfirmed
|
|
225
|
+
&& input.hasConversationIdentity
|
|
226
|
+
&& (input.assistantResponseObserved || input.generationInProgress);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function chatgptComposerRetainsPrompt(
|
|
230
|
+
composerText: string | undefined,
|
|
231
|
+
prompt: string,
|
|
232
|
+
): boolean {
|
|
233
|
+
if (composerText === undefined) return false;
|
|
234
|
+
const composer = normalizeChatgptOutboundText(composerText);
|
|
235
|
+
const expected = normalizeChatgptOutboundText(prompt);
|
|
236
|
+
return Boolean(composer && expected && composer === expected);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function currentChatgptComposerText(
|
|
240
|
+
controllerHome: string,
|
|
241
|
+
workId: string,
|
|
242
|
+
browserSessionId: string,
|
|
243
|
+
timeoutMs?: number,
|
|
244
|
+
): Promise<string | undefined> {
|
|
245
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'get_text', {
|
|
246
|
+
session_id: browserSessionId,
|
|
247
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
248
|
+
max_chars: MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS,
|
|
249
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
250
|
+
}, timeoutMs).catch(() => undefined);
|
|
251
|
+
return typeof result?.text === 'string' ? result.text : undefined;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function currentChatgptComposerHtml(
|
|
255
|
+
controllerHome: string,
|
|
256
|
+
workId: string,
|
|
257
|
+
browserSessionId: string,
|
|
258
|
+
timeoutMs?: number,
|
|
259
|
+
): Promise<string | undefined> {
|
|
260
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'get_html', {
|
|
261
|
+
session_id: browserSessionId,
|
|
262
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
263
|
+
max_chars: MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS,
|
|
264
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
265
|
+
}, timeoutMs).catch(() => undefined);
|
|
266
|
+
return typeof result?.text === 'string' && result.truncated !== true ? result.text : undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** A real ChatGPT plugin mention is a non-editable ProseMirror app chip, not @ text. */
|
|
270
|
+
export function chatgptForgePluginMentionBound(
|
|
271
|
+
composerHtml: string | undefined,
|
|
272
|
+
composerText: string | undefined,
|
|
273
|
+
prompt: string,
|
|
274
|
+
): boolean {
|
|
275
|
+
if (!composerHtml || !composerText) return false;
|
|
276
|
+
const chips = [...composerHtml.matchAll(/\b(?:plugin|app)-mention-name="([^"]+)"/g)].map((match) => match[1]);
|
|
277
|
+
if (chips.length !== 1 || chips[0] !== CHATGPT_FORGE_PLUGIN_SLUG
|
|
278
|
+
|| !composerHtml.includes(`app-mention-path="${CHATGPT_FORGE_PLUGIN_PATH}"`)) return false;
|
|
279
|
+
const normalized = normalizeChatgptOutboundText(composerText);
|
|
280
|
+
const payload = normalizeChatgptOutboundText(prompt);
|
|
281
|
+
return normalized === `${payload} ${CHATGPT_FORGE_PLUGIN_LABEL}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Return the single exact main-Forge suggestion after normalizing UI line breaks. */
|
|
285
|
+
export function chatgptForgePluginPickerIndex(labels: string[]): number | undefined {
|
|
286
|
+
const matches = labels.flatMap((label, index) => {
|
|
287
|
+
const value = normalizeChatgptOutboundText(label);
|
|
288
|
+
return value === CHATGPT_FORGE_PLUGIN_LABEL || value.startsWith(`${CHATGPT_FORGE_PLUGIN_LABEL} `)
|
|
289
|
+
? [index] : [];
|
|
290
|
+
});
|
|
291
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Browser owns DOM actions; Supervisor remains the only effect and delivery owner. */
|
|
295
|
+
async function bindChatgptForgePluginMention(
|
|
296
|
+
controllerHome: string,
|
|
297
|
+
workId: string,
|
|
298
|
+
browserSessionId: string,
|
|
299
|
+
prompt: string,
|
|
300
|
+
targetUrl: string,
|
|
301
|
+
timeoutMs?: number,
|
|
302
|
+
): Promise<void> {
|
|
303
|
+
const baseArgs = { session_id: browserSessionId, timeout_ms: timeoutMs ?? 60_000 };
|
|
304
|
+
// The payload and final standalone @ paragraph were inserted together.
|
|
305
|
+
// ProseMirror drops the leading whitespace of a later ` @` insertion.
|
|
306
|
+
await controllerBrowserAction(controllerHome, workId, 'type', {
|
|
307
|
+
...baseArgs, selector: CHATGPT_PROMPT_SELECTOR, text: CHATGPT_FORGE_PLUGIN_LABEL,
|
|
308
|
+
post_action_wait_ms: 120,
|
|
309
|
+
}, timeoutMs);
|
|
310
|
+
const entries = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
311
|
+
...baseArgs, selector: CHATGPT_PLUGIN_PICKER_OPTIONS, limit: 80,
|
|
312
|
+
}, timeoutMs);
|
|
313
|
+
const index = chatgptForgePluginPickerIndex(queryMatches(entries).map(matchText));
|
|
314
|
+
if (index === undefined) {
|
|
315
|
+
throw new ChatgptProviderDeliveryError('CHATGPT_AUTOMATION_PLUGIN_NOT_IN_PICKER',
|
|
316
|
+
`CHATGPT_AUTOMATION_PLUGIN_NOT_IN_PICKER:${CHATGPT_FORGE_PLUGIN_SLUG}:${targetUrl}`,
|
|
317
|
+
{ conversationUrl: targetUrl });
|
|
318
|
+
}
|
|
319
|
+
// The option order was observed from this exact open picker. Never select
|
|
320
|
+
// another Forge-prefixed app as a fallback; verify the resulting app chip.
|
|
321
|
+
await controllerBrowserAction(controllerHome, workId, 'click', {
|
|
322
|
+
...baseArgs, selector: `${CHATGPT_PLUGIN_PICKER_OPTIONS}:nth-of-type(${index + 1})`,
|
|
323
|
+
}, timeoutMs);
|
|
324
|
+
const [composerHtml, composerText] = await Promise.all([
|
|
325
|
+
currentChatgptComposerHtml(controllerHome, workId, browserSessionId, timeoutMs),
|
|
326
|
+
currentChatgptComposerText(controllerHome, workId, browserSessionId, timeoutMs),
|
|
327
|
+
]);
|
|
328
|
+
if (!chatgptForgePluginMentionBound(composerHtml, composerText, prompt)) {
|
|
329
|
+
throw new ChatgptProviderDeliveryError('CHATGPT_AUTOMATION_PLUGIN_MENTION_UNVERIFIED',
|
|
330
|
+
`CHATGPT_AUTOMATION_PLUGIN_MENTION_UNVERIFIED:${targetUrl}`, { conversationUrl: targetUrl });
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function latestChatgptUserMessage(
|
|
335
|
+
controllerHome: string,
|
|
336
|
+
workId: string,
|
|
337
|
+
browserSessionId: string,
|
|
338
|
+
timeoutMs?: number,
|
|
339
|
+
): Promise<{ selector?: string; preview: string; url?: string }> {
|
|
340
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
341
|
+
session_id: browserSessionId,
|
|
342
|
+
selector: CHATGPT_USER_MESSAGE_SELECTOR,
|
|
343
|
+
limit: 1,
|
|
344
|
+
from_end: true,
|
|
345
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
346
|
+
}, timeoutMs);
|
|
347
|
+
const latest = queryMatches(result).at(-1);
|
|
348
|
+
return { selector: matchSelector(latest), preview: latest ? matchText(latest) : '', url: resultUrl(result) };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function fullChatgptMessageText(
|
|
352
|
+
controllerHome: string,
|
|
353
|
+
workId: string,
|
|
354
|
+
browserSessionId: string,
|
|
355
|
+
message: { selector?: string; preview: string },
|
|
356
|
+
timeoutMs?: number,
|
|
357
|
+
): Promise<{ text: string; truncated: boolean }> {
|
|
358
|
+
if (!message.selector) return { text: message.preview, truncated: false };
|
|
359
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'get_text', {
|
|
360
|
+
session_id: browserSessionId,
|
|
361
|
+
selector: message.selector,
|
|
362
|
+
max_chars: MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS,
|
|
363
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
364
|
+
}, timeoutMs).catch(() => undefined);
|
|
365
|
+
return {
|
|
366
|
+
text: stringField(result?.text) ?? message.preview,
|
|
367
|
+
truncated: result?.truncated === true,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function latestChatgptAssistantMessage(
|
|
372
|
+
controllerHome: string,
|
|
373
|
+
workId: string,
|
|
374
|
+
browserSessionId: string,
|
|
375
|
+
timeoutMs?: number,
|
|
376
|
+
): Promise<{ selector?: string; preview: string }> {
|
|
377
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
378
|
+
session_id: browserSessionId,
|
|
379
|
+
selector: CHATGPT_ASSISTANT_MESSAGE_SELECTOR,
|
|
380
|
+
limit: 1,
|
|
381
|
+
from_end: true,
|
|
382
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
383
|
+
}, timeoutMs);
|
|
384
|
+
const latest = queryMatches(result).at(-1);
|
|
385
|
+
return { selector: matchSelector(latest), preview: latest ? matchText(latest) : '' };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function chatgptMessageObservationChanged(
|
|
389
|
+
before: { selector?: string; preview: string },
|
|
390
|
+
latest: { selector?: string; preview: string },
|
|
391
|
+
): boolean {
|
|
392
|
+
return Boolean(
|
|
393
|
+
(latest.selector && latest.selector !== before.selector)
|
|
394
|
+
|| (!before.preview && latest.preview)
|
|
395
|
+
|| latest.preview !== before.preview,
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function chatgptGenerationInProgress(
|
|
400
|
+
controllerHome: string,
|
|
401
|
+
workId: string,
|
|
402
|
+
browserSessionId: string,
|
|
403
|
+
timeoutMs?: number,
|
|
404
|
+
): Promise<boolean> {
|
|
405
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
406
|
+
session_id: browserSessionId,
|
|
407
|
+
selector: CHATGPT_STOP_GENERATING_SELECTOR,
|
|
408
|
+
limit: 1,
|
|
409
|
+
timeout_ms: Math.min(timeoutMs ?? 1_000, 1_000),
|
|
410
|
+
}, timeoutMs).catch(() => undefined);
|
|
411
|
+
return queryMatches(result).length > 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function failedRequestKey(request: BrowserFailedRequest): string {
|
|
415
|
+
return `${request.method?.toUpperCase() ?? ''}|${request.status ?? ''}|${request.url ?? ''}|${request.failure ?? ''}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Only provider requests causally inside the current send pipeline may classify that send as rate-limited. */
|
|
419
|
+
export function chatgptFailedRequestIsCausalRateLimit(request: Pick<BrowserFailedRequest, 'url' | 'method' | 'status'>): boolean {
|
|
420
|
+
if (request.status !== 429 || request.method?.trim().toUpperCase() !== 'POST') return false;
|
|
421
|
+
try {
|
|
422
|
+
const url = new URL(request.url ?? '');
|
|
423
|
+
if (url.protocol !== 'https:' || url.hostname !== 'chatgpt.com') return false;
|
|
424
|
+
const path = url.pathname.toLowerCase();
|
|
425
|
+
return /^\/backend-api\/(?:f\/)?conversation(?:\/|$)/.test(path)
|
|
426
|
+
|| path === '/backend-api/sentinel/chat-requirements';
|
|
427
|
+
} catch {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function chatgptFailedRequestBaseline(
|
|
433
|
+
controllerHome: string,
|
|
434
|
+
workId: string,
|
|
435
|
+
browserSessionId: string,
|
|
436
|
+
timeoutMs?: number,
|
|
437
|
+
): Promise<ChatgptFailedRequestBaseline | undefined> {
|
|
438
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'get_failed_requests', {
|
|
439
|
+
session_id: browserSessionId,
|
|
440
|
+
timeout_ms: Math.min(timeoutMs ?? 2_000, 2_000),
|
|
441
|
+
}, timeoutMs).catch(() => undefined);
|
|
442
|
+
if (!result || !Array.isArray(result.failedRequests)) return undefined;
|
|
443
|
+
const baseline = new Map<string, number>();
|
|
444
|
+
for (const request of result.failedRequests) {
|
|
445
|
+
if (!request || typeof request !== 'object') continue;
|
|
446
|
+
const key = failedRequestKey(request as BrowserFailedRequest);
|
|
447
|
+
baseline.set(key, (baseline.get(key) ?? 0) + 1);
|
|
448
|
+
}
|
|
449
|
+
return baseline;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function chatgptProviderFailureOnErrorUi(
|
|
453
|
+
controllerHome: string,
|
|
454
|
+
workId: string,
|
|
455
|
+
browserSessionId: string,
|
|
456
|
+
timeoutMs?: number,
|
|
457
|
+
): Promise<ChatgptProviderPageFailureCode | undefined> {
|
|
458
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
459
|
+
session_id: browserSessionId,
|
|
460
|
+
selector: CHATGPT_PROVIDER_ERROR_SELECTOR,
|
|
461
|
+
limit: 20,
|
|
462
|
+
timeout_ms: Math.min(timeoutMs ?? 2_000, 2_000),
|
|
463
|
+
}, timeoutMs).catch(() => undefined);
|
|
464
|
+
for (const match of queryMatches(result)) {
|
|
465
|
+
const failure = chatgptAutomationDeliveryFailure(matchText(match));
|
|
466
|
+
if (failure) return failure;
|
|
467
|
+
}
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function chatgptDeliveryFailureOnPage(
|
|
472
|
+
controllerHome: string,
|
|
473
|
+
workId: string,
|
|
474
|
+
browserSessionId: string,
|
|
475
|
+
failedRequestBaseline: ChatgptFailedRequestBaseline | undefined,
|
|
476
|
+
timeoutMs?: number,
|
|
477
|
+
): Promise<ChatgptProviderPageFailureCode | undefined> {
|
|
478
|
+
const uiFailure = await chatgptProviderFailureOnErrorUi(controllerHome, workId, browserSessionId, timeoutMs);
|
|
479
|
+
if (uiFailure) return uiFailure;
|
|
480
|
+
if (!failedRequestBaseline) return undefined;
|
|
481
|
+
|
|
482
|
+
const current = await controllerBrowserAction(controllerHome, workId, 'get_failed_requests', {
|
|
483
|
+
session_id: browserSessionId,
|
|
484
|
+
timeout_ms: Math.min(timeoutMs ?? 2_000, 2_000),
|
|
485
|
+
}, timeoutMs).catch(() => undefined);
|
|
486
|
+
if (!current || !Array.isArray(current.failedRequests)) return undefined;
|
|
487
|
+
const seen = new Map<string, number>();
|
|
488
|
+
for (const value of current.failedRequests) {
|
|
489
|
+
if (!value || typeof value !== 'object') continue;
|
|
490
|
+
const request = value as BrowserFailedRequest;
|
|
491
|
+
const key = failedRequestKey(request);
|
|
492
|
+
const occurrence = (seen.get(key) ?? 0) + 1;
|
|
493
|
+
seen.set(key, occurrence);
|
|
494
|
+
const baselineCount = failedRequestBaseline.get(key) ?? 0;
|
|
495
|
+
if (occurrence <= baselineCount) continue;
|
|
496
|
+
if (chatgptFailedRequestIsCausalRateLimit(request)) return CHATGPT_AUTOMATION_RATE_LIMITED;
|
|
497
|
+
}
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function chatgptSendControlUnavailable(error: unknown): boolean {
|
|
502
|
+
return error instanceof Error && error.message.includes('PLUGIN_BROWSER_SELECTOR_UNAVAILABLE');
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function normalizeExecutionControlLabel(label: string | undefined): string {
|
|
506
|
+
return (label ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function chatgptAutomationReasoningLevelFromLabel(label: string | undefined): ChatgptAutomationReasoning | undefined {
|
|
510
|
+
const normalized = normalizeExecutionControlLabel(label);
|
|
511
|
+
if (!normalized) return undefined;
|
|
512
|
+
|
|
513
|
+
const exact = new Map<string, ChatgptAutomationReasoning>([
|
|
514
|
+
['medium', 'medium'], ['中', 'medium'], ['中等', 'medium'],
|
|
515
|
+
['high', 'high'], ['高', 'high'],
|
|
516
|
+
['xhigh', 'xhigh'], ['extrahigh', 'xhigh'], ['超高', 'xhigh'], ['极高', 'xhigh'],
|
|
517
|
+
]);
|
|
518
|
+
const exactLevel = exact.get(normalized);
|
|
519
|
+
if (exactLevel) return exactLevel;
|
|
520
|
+
|
|
521
|
+
const hasReasoningContext = ['reasoning', 'thinking', '推理', '思考']
|
|
522
|
+
.some((token) => normalized.includes(token));
|
|
523
|
+
if (!hasReasoningContext) return undefined;
|
|
524
|
+
if (['xhigh', 'extrahigh', '超高', '极高'].some((token) => normalized.includes(token))) return 'xhigh';
|
|
525
|
+
if (['medium', '中等'].some((token) => normalized.includes(token))) return 'medium';
|
|
526
|
+
if (['high', '高'].some((token) => normalized.includes(token))) return 'high';
|
|
527
|
+
return undefined;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function modelLabelMatches(label: string | undefined, model: string): boolean {
|
|
531
|
+
if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL || !label) return false;
|
|
532
|
+
const normalized = normalizeExecutionControlLabel(label);
|
|
533
|
+
return normalized.includes('5.6sol') || normalized.includes('gpt5.6sol');
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export function chatgptAutomationModelFamilyMenuTrigger(
|
|
537
|
+
label: string | undefined,
|
|
538
|
+
ariaHasPopup: string | undefined,
|
|
539
|
+
): boolean {
|
|
540
|
+
if (ariaHasPopup !== 'menu') return false;
|
|
541
|
+
const normalized = normalizeExecutionControlLabel(label);
|
|
542
|
+
// ChatGPT's composer can rotate model names independently of Forge releases.
|
|
543
|
+
// Locate the combined intelligence control by a bounded model-family prefix
|
|
544
|
+
// plus its menu-trigger role; do not treat arbitrary GPT prose/buttons as authority.
|
|
545
|
+
return /^gpt\d/.test(normalized);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function reasoningLabelMatches(label: string | undefined, reasoning: ChatgptAutomationReasoning): boolean {
|
|
549
|
+
return chatgptAutomationReasoningLevelFromLabel(label) === reasoning;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function isReasoningControlLabel(label: string | undefined): boolean {
|
|
553
|
+
return reasoningLabelMatches(label, 'medium')
|
|
554
|
+
|| reasoningLabelMatches(label, 'high')
|
|
555
|
+
|| reasoningLabelMatches(label, 'xhigh');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function reasoningSliderValue(reasoning: ChatgptAutomationReasoning): number {
|
|
559
|
+
if (reasoning === 'medium') return 2;
|
|
560
|
+
if (reasoning === 'high') return 3;
|
|
561
|
+
return 4;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function findChatgptIntelligenceControl(
|
|
565
|
+
controllerHome: string,
|
|
566
|
+
workId: string,
|
|
567
|
+
browserSessionId: string,
|
|
568
|
+
timeoutMs?: number,
|
|
569
|
+
): Promise<BrowserQueryMatch | undefined> {
|
|
570
|
+
for (const selector of CHATGPT_INTELLIGENCE_CONTROL_SELECTORS) {
|
|
571
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
572
|
+
session_id: browserSessionId,
|
|
573
|
+
selector,
|
|
574
|
+
// Prefer the composer/main region so sidebar history cannot crowd the
|
|
575
|
+
// reasoning control out of a bounded query. Keep a larger global fallback
|
|
576
|
+
// for ChatGPT layouts that place the control outside <main>.
|
|
577
|
+
limit: chatgptAutomationControlQueryLimit(selector),
|
|
578
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
579
|
+
}, timeoutMs);
|
|
580
|
+
const candidates = queryMatches(result);
|
|
581
|
+
const exactMatch = candidates.find((candidate) => {
|
|
582
|
+
const label = matchText(candidate);
|
|
583
|
+
return modelLabelMatches(label, DEFAULT_CHATGPT_AUTOMATION_MODEL) || isReasoningControlLabel(label);
|
|
584
|
+
});
|
|
585
|
+
if (exactMatch) return exactMatch;
|
|
586
|
+
for (const candidate of candidates) {
|
|
587
|
+
const candidateSelector = matchSelector(candidate);
|
|
588
|
+
if (!candidateSelector || !/^gpt\d/.test(normalizeExecutionControlLabel(matchText(candidate)))) continue;
|
|
589
|
+
const popup = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
590
|
+
session_id: browserSessionId,
|
|
591
|
+
selector: candidateSelector,
|
|
592
|
+
attribute: 'aria-haspopup',
|
|
593
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
594
|
+
}, timeoutMs).catch(() => undefined);
|
|
595
|
+
if (chatgptAutomationModelFamilyMenuTrigger(matchText(candidate), stringField(popup?.value))) return candidate;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export function chatgptAutomationControlQueryLimit(selector: string): number {
|
|
602
|
+
// Long tool-heavy conversations can contribute well over 80 buttons before
|
|
603
|
+
// the composer controls. Keep the preferred <main> scan bounded but large
|
|
604
|
+
// enough that the current reasoning control is not crowded out by history.
|
|
605
|
+
return selector.startsWith('main ') ? 160 : 320;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export function chatgptAutomationControlWaitBudgets(timeoutMs?: number): { waitBudgetMs: number; probeTimeoutMs: number } {
|
|
609
|
+
const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 30_000, 1_000), 30_000);
|
|
610
|
+
// Native Chrome attachment has a few seconds of Apple Events/DOM latency on
|
|
611
|
+
// long conversations. Keep each probe bounded while allowing a real query to
|
|
612
|
+
// complete; the total readiness window remains capped at 30 seconds.
|
|
613
|
+
return { waitBudgetMs, probeTimeoutMs: Math.min(waitBudgetMs, 5_000) };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function waitForChatgptIntelligenceControl(
|
|
617
|
+
controllerHome: string,
|
|
618
|
+
workId: string,
|
|
619
|
+
browserSessionId: string,
|
|
620
|
+
timeoutMs?: number,
|
|
621
|
+
): Promise<BrowserQueryMatch | undefined> {
|
|
622
|
+
const { waitBudgetMs, probeTimeoutMs } = chatgptAutomationControlWaitBudgets(timeoutMs);
|
|
623
|
+
const deadline = Date.now() + waitBudgetMs;
|
|
624
|
+
do {
|
|
625
|
+
const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, probeTimeoutMs);
|
|
626
|
+
if (control) return control;
|
|
627
|
+
if (Date.now() >= deadline) break;
|
|
628
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 500));
|
|
629
|
+
} while (Date.now() < deadline);
|
|
630
|
+
return undefined;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export function chatgptAutomationPageFailure(
|
|
634
|
+
bodyText: string | undefined,
|
|
635
|
+
composerAvailable: boolean,
|
|
636
|
+
): 'CHATGPT_AUTOMATION_LOGIN_REQUIRED' | 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE' | undefined {
|
|
637
|
+
if (composerAvailable) return undefined;
|
|
638
|
+
const normalized = (bodyText ?? '').toLowerCase();
|
|
639
|
+
const loginMarkers = [
|
|
640
|
+
'log in', 'sign up', 'continue with google', 'continue with apple',
|
|
641
|
+
'登录', '登陆', '注册', '使用 google 继续', '使用 apple 继续',
|
|
642
|
+
];
|
|
643
|
+
return loginMarkers.some((marker) => normalized.includes(marker))
|
|
644
|
+
? 'CHATGPT_AUTOMATION_LOGIN_REQUIRED'
|
|
645
|
+
: 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE';
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function assertChatgptAutomationComposerReady(
|
|
649
|
+
controllerHome: string,
|
|
650
|
+
workId: string,
|
|
651
|
+
browserSessionId: string,
|
|
652
|
+
timeoutMs?: number,
|
|
653
|
+
): Promise<void> {
|
|
654
|
+
const composerResult = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
655
|
+
session_id: browserSessionId,
|
|
656
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
657
|
+
limit: 1,
|
|
658
|
+
timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
|
|
659
|
+
}, timeoutMs).catch(() => undefined);
|
|
660
|
+
const composerAvailable = queryMatches(composerResult).length > 0;
|
|
661
|
+
if (composerAvailable) return;
|
|
662
|
+
|
|
663
|
+
const bodyResult = await controllerBrowserAction(controllerHome, workId, 'get_text', {
|
|
664
|
+
session_id: browserSessionId,
|
|
665
|
+
selector: 'body',
|
|
666
|
+
max_chars: 4_000,
|
|
667
|
+
timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
|
|
668
|
+
}, timeoutMs).catch(() => undefined);
|
|
669
|
+
const failure = chatgptAutomationPageFailure(stringField(bodyResult?.text), composerAvailable);
|
|
670
|
+
throw new Error(failure ?? 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE');
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export async function ensureChatgptExecutionPreference(
|
|
674
|
+
controllerHome: string,
|
|
675
|
+
workId: string,
|
|
676
|
+
browserSessionId: string,
|
|
677
|
+
model: string,
|
|
678
|
+
reasoning: ChatgptAutomationReasoning,
|
|
679
|
+
timeoutMs?: number,
|
|
680
|
+
): Promise<boolean> {
|
|
681
|
+
if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL) throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${model}`);
|
|
682
|
+
let control = await waitForChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs);
|
|
683
|
+
if (!control) {
|
|
684
|
+
await assertChatgptAutomationComposerReady(controllerHome, workId, browserSessionId, timeoutMs);
|
|
685
|
+
throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
|
|
686
|
+
}
|
|
687
|
+
// Current ChatGPT UI exposes the reasoning level (for example `高`) on the
|
|
688
|
+
// composer control instead of the model label. The model remains fail-closed
|
|
689
|
+
// through normalizeModel above; only the user-adjustable reasoning control is
|
|
690
|
+
// required to be observable in the page before unattended dispatch proceeds.
|
|
691
|
+
if (reasoningLabelMatches(matchText(control), reasoning)) return true;
|
|
692
|
+
let controlSelector = matchSelector(control);
|
|
693
|
+
if (!controlSelector) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
|
|
694
|
+
|
|
695
|
+
const expanded = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
696
|
+
session_id: browserSessionId,
|
|
697
|
+
selector: controlSelector,
|
|
698
|
+
attribute: 'aria-expanded',
|
|
699
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
700
|
+
}, timeoutMs).catch(() => undefined);
|
|
701
|
+
if (stringField(expanded?.value) !== 'true') {
|
|
702
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
703
|
+
session_id: browserSessionId,
|
|
704
|
+
selector: controlSelector,
|
|
705
|
+
key: 'ArrowDown',
|
|
706
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
707
|
+
post_action_wait_ms: 150,
|
|
708
|
+
}, timeoutMs);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const currentValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
712
|
+
session_id: browserSessionId,
|
|
713
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
714
|
+
attribute: 'aria-valuenow',
|
|
715
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
716
|
+
}, timeoutMs).catch(() => undefined);
|
|
717
|
+
const currentValue = Number(stringField(currentValueResult?.value));
|
|
718
|
+
const targetValue = reasoningSliderValue(reasoning);
|
|
719
|
+
if (!Number.isInteger(currentValue) || currentValue < 0 || currentValue > 4) {
|
|
720
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_STATE_UNAVAILABLE:${reasoning}`);
|
|
721
|
+
}
|
|
722
|
+
const direction = targetValue > currentValue ? 'ArrowRight' : 'ArrowLeft';
|
|
723
|
+
for (let index = 0; index < Math.abs(targetValue - currentValue); index += 1) {
|
|
724
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
725
|
+
session_id: browserSessionId,
|
|
726
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
727
|
+
key: direction,
|
|
728
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
729
|
+
post_action_wait_ms: 120,
|
|
730
|
+
}, timeoutMs);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const verifiedValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
734
|
+
session_id: browserSessionId,
|
|
735
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
736
|
+
attribute: 'aria-valuenow',
|
|
737
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
738
|
+
}, timeoutMs).catch(() => undefined);
|
|
739
|
+
if (Number(stringField(verifiedValueResult?.value)) !== targetValue) {
|
|
740
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
|
|
741
|
+
}
|
|
742
|
+
control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs) ?? control;
|
|
743
|
+
if (isReasoningControlLabel(matchText(control)) && !reasoningLabelMatches(matchText(control), reasoning)) {
|
|
744
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
|
|
745
|
+
}
|
|
746
|
+
controlSelector = matchSelector(control) ?? controlSelector;
|
|
747
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
748
|
+
session_id: browserSessionId,
|
|
749
|
+
selector: controlSelector,
|
|
750
|
+
key: 'Escape',
|
|
751
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
752
|
+
post_action_wait_ms: 100,
|
|
753
|
+
}, timeoutMs).catch(() => undefined);
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function resultUrl(result: Record<string, unknown>): string | undefined {
|
|
758
|
+
return stringField(result.url)
|
|
759
|
+
?? stringField((result.session as Record<string, unknown> | undefined)?.url);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function resultSessionId(result: Record<string, unknown>): string | undefined {
|
|
763
|
+
return stringField(result.session_id)
|
|
764
|
+
?? stringField((result.session as Record<string, unknown> | undefined)?.sessionId);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
interface ChatgptBrowserSessionInventoryItem {
|
|
768
|
+
sessionId?: unknown;
|
|
769
|
+
url?: unknown;
|
|
770
|
+
liveness?: unknown;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function chatgptUrlMatchesContinuationTarget(observedUrl: string, targetUrl: string): boolean {
|
|
774
|
+
try {
|
|
775
|
+
const observed = new URL(observedUrl);
|
|
776
|
+
const target = new URL(targetUrl);
|
|
777
|
+
const allowedHosts = new Set(['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com']);
|
|
778
|
+
if (observed.protocol !== 'https:' || target.protocol !== 'https:' || !allowedHosts.has(observed.hostname) || !allowedHosts.has(target.hostname)) return false;
|
|
779
|
+
const targetConversation = /\/c\/([^/?#]+)/.exec(target.pathname)?.[1];
|
|
780
|
+
if (targetConversation) {
|
|
781
|
+
const observedConversation = /\/c\/([^/?#]+)/.exec(observed.pathname)?.[1];
|
|
782
|
+
return observedConversation === targetConversation;
|
|
783
|
+
}
|
|
784
|
+
return observed.pathname === target.pathname;
|
|
785
|
+
} catch {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** The saved Browser session id is not proof of the live native tab identity. */
|
|
791
|
+
export function chatgptBrowserObservationMatchesTarget(
|
|
792
|
+
observation: Record<string, unknown>,
|
|
793
|
+
browserSessionId: string,
|
|
794
|
+
targetUrl: string,
|
|
795
|
+
): boolean {
|
|
796
|
+
const observedSessionId = stringField(observation.sessionId) ?? resultSessionId(observation);
|
|
797
|
+
if (observedSessionId && observedSessionId !== browserSessionId) return false;
|
|
798
|
+
const connection = observation.browserConnection;
|
|
799
|
+
const liveTab = connection && typeof connection === 'object'
|
|
800
|
+
? (connection as Record<string, unknown>).tab : undefined;
|
|
801
|
+
const liveTabUrl = liveTab && typeof liveTab === 'object'
|
|
802
|
+
? stringField((liveTab as Record<string, unknown>).url) : undefined;
|
|
803
|
+
const urls = [resultUrl(observation), liveTabUrl].filter((url): url is string => Boolean(url));
|
|
804
|
+
return urls.length > 0 && urls.every((url) => chatgptUrlMatchesContinuationTarget(url, targetUrl));
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async function verifyChatgptExactSubmissionTab(
|
|
808
|
+
controllerHome: string,
|
|
809
|
+
workId: string,
|
|
810
|
+
browserSessionId: string,
|
|
811
|
+
targetUrl: string,
|
|
812
|
+
timeoutMs?: number,
|
|
813
|
+
): Promise<void> {
|
|
814
|
+
const observed = await controllerBrowserAction(controllerHome, workId, 'get_text', {
|
|
815
|
+
session_id: browserSessionId,
|
|
816
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
817
|
+
max_chars: 1,
|
|
818
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
819
|
+
}, timeoutMs);
|
|
820
|
+
if (!chatgptBrowserObservationMatchesTarget(observed, browserSessionId, targetUrl)) {
|
|
821
|
+
throw new ChatgptProviderDeliveryError('CHATGPT_AUTOMATION_TARGET_IDENTITY_CHANGED',
|
|
822
|
+
`CHATGPT_AUTOMATION_TARGET_IDENTITY_CHANGED:${targetUrl}`, { conversationUrl: targetUrl });
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function completeChatgptBrowserInventorySessions(
|
|
827
|
+
inventory: Record<string, unknown>,
|
|
828
|
+
): ChatgptBrowserSessionInventoryItem[] | undefined {
|
|
829
|
+
if (stringField(inventory.nextCursor)) return undefined;
|
|
830
|
+
return Array.isArray(inventory.sessions)
|
|
831
|
+
? inventory.sessions as ChatgptBrowserSessionInventoryItem[]
|
|
832
|
+
: [];
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export function reconciledNewChatgptOpenPageSessionId(
|
|
836
|
+
beforeInventory: Record<string, unknown>,
|
|
837
|
+
afterInventory: Record<string, unknown>,
|
|
838
|
+
targetUrl: string,
|
|
839
|
+
): string | undefined {
|
|
840
|
+
const beforeSessions = completeChatgptBrowserInventorySessions(beforeInventory);
|
|
841
|
+
const afterSessions = completeChatgptBrowserInventorySessions(afterInventory);
|
|
842
|
+
if (!beforeSessions || !afterSessions) return undefined;
|
|
843
|
+
const beforeIds = new Set(beforeSessions
|
|
844
|
+
.map((entry) => stringField(entry.sessionId))
|
|
845
|
+
.filter((value): value is string => Boolean(value)));
|
|
846
|
+
const matches = afterSessions.flatMap((entry) => {
|
|
847
|
+
const sessionId = stringField(entry.sessionId);
|
|
848
|
+
const url = stringField(entry.url);
|
|
849
|
+
return sessionId
|
|
850
|
+
&& !beforeIds.has(sessionId)
|
|
851
|
+
&& entry.liveness === 'live'
|
|
852
|
+
&& url
|
|
853
|
+
&& chatgptUrlMatchesContinuationTarget(url, targetUrl)
|
|
854
|
+
? [sessionId]
|
|
855
|
+
: [];
|
|
856
|
+
});
|
|
857
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function browserMutationOutcomeUnknown(error: unknown, actionId: string): boolean {
|
|
861
|
+
return error instanceof Error
|
|
862
|
+
&& error.message.includes('PLUGIN_BROWSER_MUTATION_OUTCOME_UNKNOWN')
|
|
863
|
+
&& error.message.includes(`Browser action ${actionId}`);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
export function chatgptAutomationNavigationRequiresReplacement(error: unknown): boolean {
|
|
867
|
+
if (!(error instanceof Error)) return false;
|
|
868
|
+
return [
|
|
869
|
+
'BROWSER_AUTOMATION_BACKGROUND_NAVIGATION_REQUIRES_REPLACEMENT',
|
|
870
|
+
'PLUGIN_BROWSER_SESSION_STATE_LOST',
|
|
871
|
+
'PLUGIN_SESSION_NOT_FOUND',
|
|
872
|
+
'PLUGIN_BROWSER_NATIVE_TAB_IDENTITY_UNPROVEN',
|
|
873
|
+
].some((marker) => error.message.includes(marker));
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
export async function closeChatgptAutomationTabAfterDispatch(
|
|
877
|
+
controllerHome: string,
|
|
878
|
+
workId: string,
|
|
879
|
+
browserSessionId: string,
|
|
880
|
+
timeoutMs?: number,
|
|
881
|
+
authorizationGrantRefs: readonly string[] = [],
|
|
882
|
+
): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
|
|
883
|
+
try {
|
|
884
|
+
const closePage = () => controllerBrowserAction(controllerHome, workId, 'close_page', {
|
|
885
|
+
session_id: browserSessionId,
|
|
886
|
+
}, Math.min(timeoutMs ?? 15_000, 15_000));
|
|
887
|
+
const closed = authorizationGrantRefs.length > 0
|
|
888
|
+
? await withChatgptBrowserActionOrigin(
|
|
889
|
+
{ surface: 'schedule', actor: 'chatgpt-work-continuation' },
|
|
890
|
+
closePage,
|
|
891
|
+
new Set(authorizationGrantRefs),
|
|
892
|
+
)
|
|
893
|
+
: await closePage();
|
|
894
|
+
if (closed.preservedUserOwnedTab === true) return { status: 'preserved_user_owned' };
|
|
895
|
+
if (closed.resourceClosed === true) return { status: 'closed' };
|
|
896
|
+
return { status: 'session_closed' };
|
|
897
|
+
} catch (error) {
|
|
898
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
899
|
+
return {
|
|
900
|
+
status: 'failed',
|
|
901
|
+
error: {
|
|
902
|
+
code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_AUTOMATION_TAB_CLEANUP_FAILED',
|
|
903
|
+
message,
|
|
904
|
+
},
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Settles the browser resource for one completed Work-bound ChatGPT round.
|
|
911
|
+
* Conversation identity is durable in the Work binding and intentionally
|
|
912
|
+
* survives this ephemeral tab/session cleanup. Browser ownership policy keeps
|
|
913
|
+
* user-owned native tabs intact.
|
|
914
|
+
*/
|
|
915
|
+
export async function settleWorkChatgptAutomationTab(input: {
|
|
916
|
+
controllerHome: string;
|
|
917
|
+
workId: string;
|
|
918
|
+
browserSessionId: string;
|
|
919
|
+
timeoutMs?: number;
|
|
920
|
+
authorizationGrantRefs?: readonly string[];
|
|
921
|
+
}): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
|
|
922
|
+
if (input.browserSessionId.startsWith('forge-chatgpt-bridge-')) return { status: 'session_closed' };
|
|
923
|
+
return closeChatgptAutomationTabAfterDispatch(
|
|
924
|
+
input.controllerHome,
|
|
925
|
+
input.workId,
|
|
926
|
+
input.browserSessionId,
|
|
927
|
+
input.timeoutMs,
|
|
928
|
+
input.authorizationGrantRefs,
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
export function isChatgptConversationUrl(value: string): boolean {
|
|
933
|
+
try {
|
|
934
|
+
const url = new URL(value);
|
|
935
|
+
return url.protocol === 'https:'
|
|
936
|
+
&& ['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(url.hostname)
|
|
937
|
+
&& /\/c\/[^/?#]+/.test(url.pathname);
|
|
938
|
+
} catch {
|
|
939
|
+
return false;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
export async function navigateWorkConversation(
|
|
944
|
+
controllerHome: string,
|
|
945
|
+
workId: string,
|
|
946
|
+
browserSessionId: string,
|
|
947
|
+
targetUrl: string,
|
|
948
|
+
timeoutMs?: number,
|
|
949
|
+
): Promise<{ submissionTargetUrl: string; recoveredFromStaleBinding: boolean; browserSessionId: string }> {
|
|
950
|
+
const navigate = async (sessionId: string, url: string) => controllerBrowserAction(controllerHome, workId, 'navigate', {
|
|
951
|
+
session_id: sessionId,
|
|
952
|
+
url,
|
|
953
|
+
wait_until: 'domcontentloaded',
|
|
954
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
955
|
+
retries: 1,
|
|
956
|
+
}, timeoutMs);
|
|
957
|
+
const openReplacement = async (sessionId: string, url: string): Promise<string> => {
|
|
958
|
+
// Fresh ControllerRound transport already owns an explicit Browser session
|
|
959
|
+
// identity. Use create_session with that exact id so native Browser never has
|
|
960
|
+
// to guess among multiple reusable Forge-owned tabs. The Browser adapter still
|
|
961
|
+
// owns tab identity/replacement and can recover a stale saved tab behind this id.
|
|
962
|
+
try {
|
|
963
|
+
const opened = await controllerBrowserAction(controllerHome, workId, 'create_session', {
|
|
964
|
+
session_id: sessionId,
|
|
965
|
+
url,
|
|
966
|
+
// Direct Browser delivery must never silently escalate to the macOS
|
|
967
|
+
// Apple Events compatibility provider. Work-bound unattended delivery is
|
|
968
|
+
// owned by Workflow Supervisor's extension transport; this legacy direct
|
|
969
|
+
// lane uses a Forge-managed browser resource unless the caller explicitly
|
|
970
|
+
// configured a different transport elsewhere.
|
|
971
|
+
browser_mode: 'managed_persistent',
|
|
972
|
+
native_attach_mode: 'disabled',
|
|
973
|
+
cdp_attach_fallback: 'fail_closed',
|
|
974
|
+
wait_until: 'domcontentloaded',
|
|
975
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
976
|
+
retries: 1,
|
|
977
|
+
}, timeoutMs);
|
|
978
|
+
const replacementSessionId = resultSessionId(opened);
|
|
979
|
+
if (replacementSessionId !== sessionId) throw new Error('CHATGPT_AUTOMATION_REPLACEMENT_SESSION_NOT_CONFIRMED');
|
|
980
|
+
return replacementSessionId;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (!browserMutationOutcomeUnknown(error, 'create_session')) throw error;
|
|
983
|
+
try {
|
|
984
|
+
const verified = await controllerBrowserAction(controllerHome, workId, 'verify_state', {
|
|
985
|
+
session_id: sessionId,
|
|
986
|
+
expected_url: url,
|
|
987
|
+
}, timeoutMs);
|
|
988
|
+
if (verified.matched === true && stringField(verified.sessionId) === sessionId) return sessionId;
|
|
989
|
+
} catch {
|
|
990
|
+
// Preserve the original create_session mutation-unknown evidence.
|
|
991
|
+
}
|
|
992
|
+
throw error;
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
try {
|
|
996
|
+
// Prove the exact saved Browser resource is still attachable before dispatching
|
|
997
|
+
// a navigation mutation. A stale native tab must be replaced before any write;
|
|
998
|
+
// discovering staleness only after mutation creates an avoidable outcome-unknown window.
|
|
999
|
+
try {
|
|
1000
|
+
await controllerBrowserAction(controllerHome, workId, 'verify_state', {
|
|
1001
|
+
session_id: browserSessionId,
|
|
1002
|
+
expected_url: targetUrl,
|
|
1003
|
+
}, timeoutMs);
|
|
1004
|
+
} catch (preflightError) {
|
|
1005
|
+
if (!chatgptAutomationNavigationRequiresReplacement(preflightError)) throw preflightError;
|
|
1006
|
+
const replacementSessionId = await openReplacement(browserSessionId, targetUrl);
|
|
1007
|
+
// create_session establishes only the session capability. Activating the
|
|
1008
|
+
// exact replacement page is an interaction-capability action, so an
|
|
1009
|
+
// interactive prepare also establishes the grant required by later
|
|
1010
|
+
// schedule-origin fill/click actions on this same origin.
|
|
1011
|
+
await controllerBrowserAction(controllerHome, workId, 'activate_page', {
|
|
1012
|
+
session_id: replacementSessionId,
|
|
1013
|
+
}, timeoutMs);
|
|
1014
|
+
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
|
|
1015
|
+
}
|
|
1016
|
+
await navigate(browserSessionId, targetUrl);
|
|
1017
|
+
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId };
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
if (browserMutationOutcomeUnknown(error, 'navigate')) {
|
|
1020
|
+
try {
|
|
1021
|
+
const verified = await controllerBrowserAction(controllerHome, workId, 'verify_state', {
|
|
1022
|
+
session_id: browserSessionId,
|
|
1023
|
+
expected_url: targetUrl,
|
|
1024
|
+
}, timeoutMs);
|
|
1025
|
+
if (verified.matched === true && stringField(verified.sessionId) === browserSessionId) {
|
|
1026
|
+
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId };
|
|
1027
|
+
}
|
|
1028
|
+
} catch {
|
|
1029
|
+
// Preserve the original mutation-unknown evidence. Verification failure is
|
|
1030
|
+
// not authority to replay navigate or to invent a replacement session.
|
|
1031
|
+
}
|
|
1032
|
+
throw error;
|
|
1033
|
+
}
|
|
1034
|
+
if (chatgptAutomationNavigationRequiresReplacement(error)) {
|
|
1035
|
+
const replacementSessionId = await openReplacement(browserSessionId, targetUrl);
|
|
1036
|
+
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId: replacementSessionId };
|
|
1037
|
+
}
|
|
1038
|
+
// A bound /c/<id> is transport identity, not a hint. Falling back to the
|
|
1039
|
+
// ChatGPT root silently creates a replacement conversation and can turn one
|
|
1040
|
+
// transient navigation failure into duplicate provider recovery. Preserve the
|
|
1041
|
+
// exact conversation and let the owning continuation reconcile or retry it.
|
|
1042
|
+
throw error;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export async function submitChatgptPrompt(
|
|
1047
|
+
controllerHome: string,
|
|
1048
|
+
workId: string,
|
|
1049
|
+
browserSessionId: string,
|
|
1050
|
+
prompt: string,
|
|
1051
|
+
targetUrl: string,
|
|
1052
|
+
timeoutMs?: number,
|
|
1053
|
+
): Promise<string> {
|
|
1054
|
+
// Verify the live native tab before reading/sending. The saved session may
|
|
1055
|
+
// have retargeted while its Browser handle remained stable.
|
|
1056
|
+
await verifyChatgptExactSubmissionTab(
|
|
1057
|
+
controllerHome, workId, browserSessionId, targetUrl, timeoutMs,
|
|
1058
|
+
);
|
|
1059
|
+
const renderedPrompt = chatgptSupervisorPromptBody(prompt);
|
|
1060
|
+
const [before, beforeAssistant, failedRequestBaseline, preexistingPageFailure] = await Promise.all([
|
|
1061
|
+
latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs)
|
|
1062
|
+
.catch((): { selector?: string; preview: string; url?: string } => ({ selector: undefined, preview: '', url: targetUrl })),
|
|
1063
|
+
latestChatgptAssistantMessage(controllerHome, workId, browserSessionId, timeoutMs)
|
|
1064
|
+
.catch((): { selector?: string; preview: string } => ({ selector: undefined, preview: '' })),
|
|
1065
|
+
chatgptFailedRequestBaseline(controllerHome, workId, browserSessionId, timeoutMs),
|
|
1066
|
+
chatgptProviderFailureOnErrorUi(controllerHome, workId, browserSessionId, timeoutMs),
|
|
1067
|
+
]);
|
|
1068
|
+
if (preexistingPageFailure === CHATGPT_AUTOMATION_RATE_LIMITED) {
|
|
1069
|
+
throw new ChatgptProviderDeliveryError(
|
|
1070
|
+
CHATGPT_AUTOMATION_RATE_LIMITED,
|
|
1071
|
+
`${CHATGPT_AUTOMATION_RATE_LIMITED}:${targetUrl}`,
|
|
1072
|
+
{ conversationUrl: targetUrl },
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
const mentionTriggerDraft = `${renderedPrompt}\n@`;
|
|
1076
|
+
await controllerBrowserAction(controllerHome, workId, 'fill', {
|
|
1077
|
+
session_id: browserSessionId,
|
|
1078
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
1079
|
+
text: mentionTriggerDraft,
|
|
1080
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
1081
|
+
post_action_wait_ms: 100,
|
|
1082
|
+
}, timeoutMs);
|
|
1083
|
+
|
|
1084
|
+
// Browser fill can report success even when the live ProseMirror composer did
|
|
1085
|
+
// not retain the intended payload. Never send or trigger Enter fallback on
|
|
1086
|
+
// an unverified draft; the owning effect must remain unreplayed.
|
|
1087
|
+
const verifiedComposerText = await currentChatgptComposerText(
|
|
1088
|
+
controllerHome, workId, browserSessionId, timeoutMs,
|
|
1089
|
+
);
|
|
1090
|
+
if (!chatgptComposerRetainsPrompt(verifiedComposerText, mentionTriggerDraft)) {
|
|
1091
|
+
throw new ChatgptProviderDeliveryError(
|
|
1092
|
+
'CHATGPT_AUTOMATION_COMPOSER_UNVERIFIED',
|
|
1093
|
+
`CHATGPT_AUTOMATION_COMPOSER_UNVERIFIED:${targetUrl}`,
|
|
1094
|
+
{ conversationUrl: targetUrl },
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// The immutable effect marker alone is not enough: choose the exact main
|
|
1099
|
+
// Forge app and verify its structured mention before sending the payload.
|
|
1100
|
+
await bindChatgptForgePluginMention(
|
|
1101
|
+
controllerHome, workId, browserSessionId, renderedPrompt, targetUrl, timeoutMs,
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
// A popup or plugin-selection action can change the native tab. Re-attest
|
|
1105
|
+
// immediately before Send, never infer identity from a successful click.
|
|
1106
|
+
await verifyChatgptExactSubmissionTab(
|
|
1107
|
+
controllerHome, workId, browserSessionId, targetUrl, timeoutMs,
|
|
1108
|
+
);
|
|
1109
|
+
|
|
1110
|
+
let observedUrl = targetUrl;
|
|
1111
|
+
let submitOutcomeUnknown = false;
|
|
1112
|
+
let observedNewOutbound = false;
|
|
1113
|
+
try {
|
|
1114
|
+
const sent = await controllerBrowserAction(controllerHome, workId, 'click', {
|
|
1115
|
+
session_id: browserSessionId,
|
|
1116
|
+
selector: CHATGPT_SEND_SELECTOR,
|
|
1117
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
1118
|
+
post_action_wait_ms: 250,
|
|
1119
|
+
}, timeoutMs);
|
|
1120
|
+
observedUrl = resultUrl(sent) ?? observedUrl;
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
if (browserMutationOutcomeUnknown(error, 'click')) {
|
|
1123
|
+
// Never blind-replay an outcome-unknown submit. First reconcile the exact
|
|
1124
|
+
// provider state. If the same payload is still wholly present in the
|
|
1125
|
+
// composer, no new user message exists, and no conversation identity was
|
|
1126
|
+
// created, the original click is mechanically proven not to have committed.
|
|
1127
|
+
// Only then may the same provider dispatch resume Send once without
|
|
1128
|
+
// refilling the composer or creating a new semantic effect generation.
|
|
1129
|
+
submitOutcomeUnknown = true;
|
|
1130
|
+
const latestAfterUnknown = await latestChatgptUserMessage(
|
|
1131
|
+
controllerHome,
|
|
1132
|
+
workId,
|
|
1133
|
+
browserSessionId,
|
|
1134
|
+
timeoutMs,
|
|
1135
|
+
).catch(() => undefined);
|
|
1136
|
+
if (latestAfterUnknown) {
|
|
1137
|
+
observedUrl = latestAfterUnknown.url ?? observedUrl;
|
|
1138
|
+
observedNewOutbound = chatgptMessageObservationChanged(before, latestAfterUnknown);
|
|
1139
|
+
}
|
|
1140
|
+
const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
|
|
1141
|
+
const [composerHtml, composerText] = await Promise.all([
|
|
1142
|
+
currentChatgptComposerHtml(controllerHome, workId, browserSessionId, timeoutMs),
|
|
1143
|
+
currentChatgptComposerText(controllerHome, workId, browserSessionId, timeoutMs),
|
|
1144
|
+
]);
|
|
1145
|
+
if (
|
|
1146
|
+
!observedNewOutbound
|
|
1147
|
+
&& !hasConversationIdentity
|
|
1148
|
+
&& chatgptForgePluginMentionBound(composerHtml, composerText, renderedPrompt)
|
|
1149
|
+
) {
|
|
1150
|
+
try {
|
|
1151
|
+
const resumed = await controllerBrowserAction(controllerHome, workId, 'click', {
|
|
1152
|
+
session_id: browserSessionId,
|
|
1153
|
+
selector: CHATGPT_SEND_SELECTOR,
|
|
1154
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
1155
|
+
post_action_wait_ms: 250,
|
|
1156
|
+
}, timeoutMs);
|
|
1157
|
+
observedUrl = resultUrl(resumed) ?? observedUrl;
|
|
1158
|
+
submitOutcomeUnknown = false;
|
|
1159
|
+
} catch (resumeError) {
|
|
1160
|
+
if (!browserMutationOutcomeUnknown(resumeError, 'click')) throw resumeError;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
} else if (chatgptSendControlUnavailable(error)) {
|
|
1164
|
+
const pressed = await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
1165
|
+
session_id: browserSessionId,
|
|
1166
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
1167
|
+
key: 'Enter',
|
|
1168
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
1169
|
+
post_action_wait_ms: 250,
|
|
1170
|
+
}, timeoutMs);
|
|
1171
|
+
observedUrl = resultUrl(pressed) ?? observedUrl;
|
|
1172
|
+
} else {
|
|
1173
|
+
throw error;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
const deadline = Date.now() + chatgptSubmissionSettlementWaitBudget(timeoutMs);
|
|
1178
|
+
let nextFailureProbeAt = 0;
|
|
1179
|
+
let observationIndex = 0;
|
|
1180
|
+
do {
|
|
1181
|
+
const latest = await latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs).catch(() => undefined);
|
|
1182
|
+
if (latest) {
|
|
1183
|
+
observedUrl = latest.url ?? observedUrl;
|
|
1184
|
+
const isNewOutbound = chatgptMessageObservationChanged(before, latest);
|
|
1185
|
+
if (isNewOutbound) {
|
|
1186
|
+
observedNewOutbound = true;
|
|
1187
|
+
const fullText = await fullChatgptMessageText(controllerHome, workId, browserSessionId, latest, timeoutMs);
|
|
1188
|
+
const outboundConfirmed = chatgptOutboundMessageMatchesPrompt(fullText.text, renderedPrompt, { truncated: fullText.truncated, boundForgePlugin: true });
|
|
1189
|
+
const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
|
|
1190
|
+
if (outboundConfirmed && hasConversationIdentity) {
|
|
1191
|
+
const [latestAssistant, generationInProgress] = await Promise.all([
|
|
1192
|
+
latestChatgptAssistantMessage(controllerHome, workId, browserSessionId, timeoutMs).catch(() => undefined),
|
|
1193
|
+
chatgptGenerationInProgress(controllerHome, workId, browserSessionId, timeoutMs),
|
|
1194
|
+
]);
|
|
1195
|
+
const assistantResponseObserved = Boolean(
|
|
1196
|
+
latestAssistant?.preview
|
|
1197
|
+
&& chatgptMessageObservationChanged(beforeAssistant, latestAssistant),
|
|
1198
|
+
);
|
|
1199
|
+
if (chatgptSubmissionAcceptanceObserved({
|
|
1200
|
+
outboundConfirmed,
|
|
1201
|
+
hasConversationIdentity,
|
|
1202
|
+
assistantResponseObserved,
|
|
1203
|
+
generationInProgress,
|
|
1204
|
+
})) {
|
|
1205
|
+
return observedUrl;
|
|
1206
|
+
}
|
|
1207
|
+
if (Date.now() >= nextFailureProbeAt) {
|
|
1208
|
+
const deliveryFailure = await chatgptDeliveryFailureOnPage(
|
|
1209
|
+
controllerHome,
|
|
1210
|
+
workId,
|
|
1211
|
+
browserSessionId,
|
|
1212
|
+
failedRequestBaseline,
|
|
1213
|
+
timeoutMs,
|
|
1214
|
+
);
|
|
1215
|
+
if (deliveryFailure) {
|
|
1216
|
+
const failureCode = deliveryFailure === CHATGPT_AUTOMATION_RATE_LIMITED
|
|
1217
|
+
? CHATGPT_AUTOMATION_RATE_LIMITED_AFTER_SUBMIT
|
|
1218
|
+
: deliveryFailure;
|
|
1219
|
+
throw new ChatgptProviderDeliveryError(
|
|
1220
|
+
failureCode,
|
|
1221
|
+
`${failureCode}:${observedUrl}`,
|
|
1222
|
+
{ conversationUrl: observedUrl },
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
nextFailureProbeAt = Date.now() + CHATGPT_DELIVERY_FAILURE_PROBE_INTERVAL_MS;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
const remainingMs = deadline - Date.now();
|
|
1231
|
+
if (remainingMs <= 0) break;
|
|
1232
|
+
const observationDelayMs = chatgptSubmissionObservationDelayMs(observationIndex);
|
|
1233
|
+
observationIndex += 1;
|
|
1234
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, Math.min(observationDelayMs, remainingMs)));
|
|
1235
|
+
} while (Date.now() < deadline);
|
|
1236
|
+
const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
|
|
1237
|
+
const finalComposerText = submitOutcomeUnknown && !observedNewOutbound && !hasConversationIdentity
|
|
1238
|
+
? await currentChatgptComposerText(controllerHome, workId, browserSessionId, timeoutMs)
|
|
1239
|
+
: undefined;
|
|
1240
|
+
const submitProvablyNotApplied = submitOutcomeUnknown
|
|
1241
|
+
&& !observedNewOutbound
|
|
1242
|
+
&& !hasConversationIdentity
|
|
1243
|
+
&& chatgptComposerRetainsPrompt(finalComposerText, renderedPrompt);
|
|
1244
|
+
// A fresh /c/<id> or any changed outbound user message is provider-side
|
|
1245
|
+
// evidence that the send may have committed even when DOM observation lagged.
|
|
1246
|
+
// Keep that ambiguity fenced. Conversely, an exact unchanged composer proves
|
|
1247
|
+
// the send did not commit and must not be mislabeled outcome_unknown.
|
|
1248
|
+
const failureCode = (submitOutcomeUnknown && !submitProvablyNotApplied) || hasConversationIdentity || observedNewOutbound
|
|
1249
|
+
? CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN
|
|
1250
|
+
: 'CHATGPT_AUTOMATION_SUBMISSION_NOT_CONFIRMED';
|
|
1251
|
+
throw new ChatgptProviderDeliveryError(
|
|
1252
|
+
failureCode,
|
|
1253
|
+
`${failureCode}:${observedUrl}`,
|
|
1254
|
+
{ conversationUrl: observedUrl },
|
|
1255
|
+
);
|
|
1256
|
+
}
|