@moretea-labs/forge 1.8.0 → 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 +70 -7
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +17 -2
- package/CONTRIBUTING.md +3 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/adapters/chatgpt/browser-delivery-host.ts +7 -1
- package/adapters/chatgpt/browser-delivery-runtime.ts +529 -56
- package/adapters/chatgpt/controller-binding-store.ts +101 -22
- package/adapters/chatgpt/controller-round-host.ts +15 -25
- package/adapters/chatgpt/controller-round-settlement-store.ts +58 -4
- package/adapters/chatgpt/provider-delivery.ts +228 -1
- package/adapters/chatgpt/work-conversation-binding-store.ts +79 -53
- 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-discovery.ts +11 -31
- package/adapters/computer/interaction-target-authority.ts +142 -18
- package/adapters/mcp/auth.ts +0 -1
- package/adapters/mcp/instructions.ts +5 -4
- package/adapters/mcp/multi-repository.ts +3 -19
- package/adapters/mcp/oauth.ts +73 -0
- package/adapters/mcp/paths.ts +4 -1
- 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 +411 -73
- package/adapters/mcp/runtime-gateway/context-pack-sidecar.ts +91 -0
- package/adapters/mcp/runtime-gateway/controller-authority-adapter.ts +20 -5
- package/adapters/mcp/runtime-gateway/core-capability-adapter.ts +258 -0
- package/adapters/mcp/runtime-gateway/engineering-preconditions.ts +10 -27
- package/adapters/mcp/runtime-gateway/engineering-tool-contract.ts +15 -0
- package/adapters/mcp/runtime-gateway/execution-tools.ts +8 -42
- package/adapters/mcp/runtime-gateway/gateway-execution-adapter.ts +2 -1
- package/adapters/mcp/runtime-gateway/plugin-adapter.ts +117 -45
- package/adapters/mcp/runtime-gateway/process-tools.ts +190 -22
- package/adapters/mcp/runtime-gateway/protected-computer-adapter.ts +16 -4
- package/adapters/mcp/runtime-gateway/recovery-adapter.ts +69 -52
- package/adapters/mcp/runtime-gateway/repository-compat-adapter.ts +4 -35
- package/adapters/mcp/runtime-gateway/result-adapter.ts +10 -4
- package/adapters/mcp/runtime-gateway/router.ts +18 -504
- package/adapters/mcp/runtime-gateway/routing-policy.ts +499 -0
- package/adapters/mcp/runtime-gateway/runtime-observation-adapter.ts +14 -65
- package/adapters/mcp/runtime-gateway/runtime-readiness-observation.ts +218 -0
- package/adapters/mcp/runtime-gateway/runtime-tool-definitions.ts +113 -173
- package/adapters/mcp/runtime-gateway/runtime-tools.ts +15 -5
- package/adapters/mcp/runtime-gateway/scheduler-adapter.ts +1 -0
- package/adapters/mcp/runtime-gateway/shared-adapter.ts +20 -0
- package/adapters/mcp/runtime-gateway/status-inbox-adapter.ts +358 -266
- package/adapters/mcp/runtime-gateway/typescript-navigation-sidecar.ts +136 -0
- package/adapters/mcp/runtime-gateway/work-adapter.ts +261 -3122
- 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-workflow-operations.ts +113 -0
- package/adapters/mcp/runtime-gateway/workflow-supervisor-adapter.ts +156 -0
- package/adapters/mcp/server.ts +67 -23
- package/adapters/mcp/setup.ts +1 -3
- package/adapters/mcp/tool-mapping/legacy-tool-service.ts +44 -170
- package/adapters/mcp/tool-mapping/post-finalize-work-attribution.ts +4 -9
- package/adapters/mcp/tool-mapping/repository-tools.ts +133 -142
- package/adapters/mcp/toolset-names.ts +5 -0
- package/adapters/mcp/toolset.ts +6 -5
- package/adapters/mcp/transports/http-observation.ts +365 -0
- package/adapters/mcp/transports/http.ts +154 -677
- package/adapters/mcp/transports/oauth-http.ts +322 -0
- package/adapters/mcp/transports/session-registry.ts +6 -0
- package/adapters/mcp/tunnels/openai-secure-tunnel.ts +89 -5
- package/assets/plugin-registry.v1.json +6 -4
- package/assets/reference-configs/agentic-development-flow.md +25 -0
- package/assets/reference-configs/global-working-rules.md +19 -0
- package/assets/skill-version.json +2 -2
- package/docs/architecture/CURRENT.md +111 -41
- 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/20260907-assistant-learning-loop.md +2 -0
- package/docs/architecture/decisions/20260907-controller-round-transition-policy.md +19 -4
- package/docs/architecture/decisions/20260916-workflow-supervisor-authority.md +249 -0
- package/docs/architecture/decisions/20260917-cognitive-memory-foundation.md +82 -0
- package/docs/architecture/decisions/20260918-stable-candidate-release-lifecycle.md +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 +1 -0
- package/docs/architecture/domains/workflow-engine.md +6 -0
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/forge-plugin-management.md +2 -2
- 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/releasing.md +2 -0
- package/docs/operations/standalone-disaster-recovery.md +14 -6
- package/docs/wiki/Implementation.md +1 -1
- package/package.json +25 -14
- package/packages/kernel/cognition/api/index.ts +6 -0
- package/packages/kernel/cognition/application/activation-service.ts +451 -0
- package/packages/kernel/cognition/application/consolidation-service.ts +185 -0
- package/packages/kernel/cognition/application/legacy-experience-adapter.ts +38 -0
- package/packages/kernel/cognition/application/memory-service.ts +64 -0
- package/packages/kernel/cognition/domain/learning.ts +106 -0
- package/packages/kernel/cognition/domain/memory.ts +254 -0
- package/packages/kernel/controller/api/index.ts +1 -0
- package/packages/kernel/controller/application/continuation-service.ts +281 -0
- package/packages/kernel/controller/application/controller-round-service.ts +12 -0
- package/packages/kernel/controller/domain/controller-round-transition-policy.ts +242 -21
- package/packages/kernel/controller/domain/controller-round.ts +4 -1
- package/packages/kernel/controller/domain/execution-quality.ts +3 -1
- package/packages/kernel/controller/infrastructure/controller-round-store.ts +689 -103
- package/packages/kernel/controller/infrastructure/controller-session-store.ts +297 -71
- package/packages/kernel/controller/ports/controller-host.ts +2 -0
- package/packages/kernel/identity/api/index.ts +10 -0
- package/packages/kernel/identity/domain/activity-projection.ts +43 -0
- package/packages/kernel/identity/domain/capability-broker.ts +60 -0
- package/packages/kernel/identity/domain/grant.ts +72 -0
- package/packages/kernel/identity/domain/owned-resource.ts +116 -0
- package/packages/kernel/identity/domain/user-request.ts +93 -0
- package/packages/kernel/identity/infrastructure/activity-projection-store.ts +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/owned-resource-store.ts +259 -0
- package/packages/kernel/identity/infrastructure/user-request-store.ts +182 -0
- package/packages/kernel/scheduler/api/index.ts +0 -2
- package/packages/kernel/scheduler/application/schedule-service.ts +3 -0
- package/packages/kernel/scheduler/application/settlement.ts +0 -15
- package/packages/kernel/scheduler/domain/schedule.ts +87 -0
- package/packages/kernel/scheduler/infrastructure/schedule-store.ts +153 -133
- package/packages/kernel/work/api/index.ts +3 -2
- package/packages/kernel/work/application/work-service.ts +17 -42
- package/packages/kernel/work/domain/current-task-lineage.ts +104 -0
- package/packages/kernel/work/domain/engineering-design.ts +10 -5
- package/packages/kernel/work/domain/engineering-profile.ts +8 -12
- package/packages/kernel/work/domain/evaluation-promotion-receipt.ts +186 -0
- package/packages/kernel/work/domain/execution-concurrency.ts +19 -26
- package/packages/kernel/work/domain/execution-snapshot.ts +0 -31
- package/packages/kernel/work/domain/implementation-review.ts +14 -331
- package/packages/kernel/work/domain/objective-graph.ts +119 -0
- package/packages/kernel/work/domain/project-engineering-contract.ts +23 -1
- package/packages/kernel/work/domain/repository-completion-receipt.ts +1 -1
- package/packages/kernel/work/domain/state-machine.ts +25 -63
- package/packages/kernel/work/domain/types.ts +57 -51
- package/packages/kernel/work/infrastructure/work-contract-store.ts +819 -728
- package/packages/kernel/work/ports/work-contract-store.ts +5 -2
- package/packages/plugin-runtime/browser/session-authority.ts +2 -1
- package/packages/plugin-runtime/computer/chatgpt-identity.ts +24 -0
- package/packages/plugin-runtime/computer/conversation-target.ts +143 -0
- package/packages/plugin-runtime/computer/index.ts +2 -0
- package/packages/plugin-runtime/computer/target-authority.ts +26 -1
- package/packages/plugin-runtime/external/unix-jsonl-transport.ts +142 -18
- package/packages/protocols/computer/contract.ts +27 -1
- package/scripts/activate-source-baseline.ts +7 -9
- package/scripts/canary-controller-semantic-wait.ts +12 -24
- package/scripts/check-mcp-compatibility.ts +48 -136
- package/scripts/check-runtime-architecture.mjs +622 -217
- package/scripts/forge-local-recovery-helper.mjs +93 -15
- package/scripts/lib/project-init-lib.sh +3 -0
- package/scripts/open-source-audit-allowlist.txt +1 -2
- package/scripts/run-bounded-command.mjs +5 -0
- package/scripts/run-governed-gate.ts +0 -1
- 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 +5 -0
- package/scripts/smoke-runtime-recovery.ts +12 -43
- package/scripts/smoke-schedule-engine.ts +23 -15
- package/scripts/smoke-unattended-transport-recovery.ts +223 -0
- package/scripts/smoke-user-request-continuation-restart.ts +232 -0
- package/scripts/smoke-workflow-supervisor-chrome-adapter.ts +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/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 +7 -3
- package/src/cli/commands/brain-assistant.ts +27 -13
- package/src/cli/commands/brain.ts +13 -15
- package/src/cli/commands/chatgpt.ts +1 -163
- package/src/cli/commands/recovery.ts +135 -121
- package/src/cli/commands/runtime.ts +3 -3
- package/src/cli/commands/setup-profile.ts +8 -0
- package/src/cli/controller/check-runner.ts +13 -2
- package/src/cli/controller/completion-backlog.ts +3 -3
- package/src/cli/controller/completion-orchestrator.ts +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-pack.ts +10 -8
- 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 +39 -1
- package/src/cli/editing/edit-session.ts +1 -7
- package/src/cli/local-bridge/console-automations.ts +18 -13
- package/src/cli/local-bridge/console-view-models.ts +3 -12
- package/src/cli/local-bridge/facade-api.ts +64 -191
- package/src/cli/local-bridge/server.ts +24 -147
- package/src/cli/local-bridge/types.ts +0 -1
- package/src/cli/mcp/oauth-client-retirement.ts +74 -0
- package/src/cli/repositories/command-executor.ts +29 -23
- package/src/cli/repositories/command-scope.ts +35 -16
- package/src/cli/repositories/controller-home.ts +48 -0
- package/src/cli/repositories/ephemeral-workspace.ts +20 -30
- package/src/cli/repositories/git-command-executor.ts +2 -2
- package/src/cli/repositories/registry.ts +77 -11
- package/src/cli/repositories/structured-git.ts +62 -2
- package/src/cli/repository/session-cache.ts +92 -6
- package/src/runtime/context/assistant-context.ts +20 -7
- package/src/runtime/context/assistant-work-context.ts +89 -27
- package/src/runtime/context/automatic-learning.ts +565 -0
- package/src/runtime/context/cognitive-attention.ts +173 -0
- package/src/runtime/context/cognitive-skill-canary.ts +158 -0
- package/src/runtime/context/context-closure.ts +46 -9
- package/src/runtime/context/context-pack-process.ts +103 -0
- package/src/runtime/context/context-plane.ts +1 -9
- package/src/runtime/context/direct-controller-learning.ts +298 -0
- package/src/runtime/context/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/cleanup-artifact-retention.ts +212 -9
- package/src/runtime/control-plane/concurrency/work-execution-concurrency.ts +9 -47
- package/src/runtime/control-plane/execution/controller-authority-recovery.ts +34 -4
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +29 -20
- package/src/runtime/control-plane/execution/execution-identity.ts +23 -3
- package/src/runtime/control-plane/execution/repository-work-attribution.ts +7 -30
- package/src/runtime/control-plane/execution/retained-work-resume.ts +206 -199
- package/src/runtime/control-plane/execution/session-store.ts +31 -1
- package/src/runtime/control-plane/execution/validation.ts +6 -8
- package/src/runtime/control-plane/execution/work-authority-reconciler.ts +13 -42
- package/src/runtime/control-plane/execution/work-check-evidence.ts +137 -0
- package/src/runtime/control-plane/execution/work-completion-authority.ts +30 -85
- package/src/runtime/control-plane/execution/work-delivery-service.ts +497 -0
- package/src/runtime/control-plane/execution/work-evidence-policy.ts +12 -50
- package/src/runtime/control-plane/execution/work-execution-support.ts +50 -149
- package/src/runtime/control-plane/execution/work-handle-authority.ts +188 -109
- package/src/runtime/control-plane/execution/work-handle-store.ts +19 -43
- package/src/runtime/control-plane/execution/work-head-settlement.ts +53 -20
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +52 -29
- package/src/runtime/control-plane/execution/work-operation-service.ts +43 -27
- package/src/runtime/control-plane/execution/work-preparation-service.ts +126 -266
- 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 +173 -186
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -84
- package/src/runtime/control-plane/execution/work-verification-service.ts +406 -50
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +5 -1
- package/src/runtime/control-plane/facade/capability-registry.ts +362 -37
- package/src/runtime/control-plane/facade/check-normalization.ts +3 -9
- package/src/runtime/control-plane/facade/codex-delegation.ts +10 -11
- package/src/runtime/control-plane/facade/controller-connection.ts +135 -0
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +1 -20
- package/src/runtime/control-plane/facade/goal-workloop.ts +281 -1753
- package/src/runtime/control-plane/facade/handoff-inbox-application.ts +12 -25
- package/src/runtime/control-plane/facade/handoff-inbox-store.ts +256 -52
- package/src/runtime/control-plane/facade/index.ts +0 -1
- package/src/runtime/control-plane/facade/operation-digest.ts +12 -15
- package/src/runtime/control-plane/facade/plan-contract-store.ts +451 -991
- package/src/runtime/control-plane/facade/policy-gate.ts +0 -23
- package/src/runtime/control-plane/facade/repository-work-admission.ts +12 -22
- package/src/runtime/control-plane/facade/requirement-authority.ts +102 -199
- package/src/runtime/control-plane/facade/requirement-board.ts +3 -3
- package/src/runtime/control-plane/facade/rh-work-operation-contract.ts +51 -14
- package/src/runtime/control-plane/facade/self-healing-loop.ts +18 -53
- package/src/runtime/control-plane/facade/suggested-actions.ts +15 -18
- package/src/runtime/control-plane/facade/types.ts +49 -108
- package/src/runtime/control-plane/facade/work-continuation.ts +8 -8
- package/src/runtime/control-plane/generated-cache-retention.ts +97 -1
- package/src/runtime/control-plane/global-scheduler/autonomous-continuation.ts +248 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +1 -1
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +239 -112
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +2 -1
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +66 -40
- package/src/runtime/control-plane/global-scheduler/wake-signal.ts +42 -2
- package/src/runtime/control-plane/governance/access-policy.ts +19 -2
- package/src/runtime/control-plane/governance/authorization.ts +20 -3
- package/src/runtime/control-plane/launcher/chatgpt-round-continuation.ts +63 -300
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +28 -300
- package/src/runtime/control-plane/launcher/local-conversation-controller.ts +238 -0
- package/src/runtime/control-plane/launcher/provider-mcp-bootstrap.ts +5 -2
- package/src/runtime/control-plane/launcher/thin-launcher.ts +96 -240
- package/src/runtime/control-plane/persistence/cognition-store.ts +820 -0
- package/src/runtime/control-plane/persistence/experience-store.ts +162 -29
- package/src/runtime/control-plane/persistence/requirement-store.ts +216 -74
- package/src/runtime/control-plane/persistence/sqlite-store.ts +113 -6
- package/src/runtime/control-plane/release-retention.ts +135 -47
- package/src/runtime/control-plane/repository-namespace-retention.ts +2 -2
- package/src/runtime/control-plane/runtime-cleanup.ts +290 -21
- package/src/runtime/control-plane/workspace/project-onboarding.ts +80 -0
- package/src/runtime/control-plane/workspace/semantic-sync-service.ts +19 -5
- package/src/runtime/control-plane/workspace/workspace-store.ts +169 -0
- package/src/runtime/diagnostics/incident-repair.ts +313 -146
- package/src/runtime/diagnostics/mcp-timing.ts +114 -0
- package/src/runtime/evidence/artifact-store.ts +158 -40
- package/src/runtime/evidence/evidence-store.ts +112 -8
- package/src/runtime/evidence/output-handle-index.ts +225 -0
- package/src/runtime/evidence/result-store.ts +158 -44
- package/src/runtime/execution/jobs/store.ts +3 -4
- package/src/runtime/execution/managed-workspace.ts +57 -0
- package/src/runtime/execution/process-runtime/check-facade.ts +1 -1
- package/src/runtime/execution/process-runtime/check-result-retention.ts +2 -2
- package/src/runtime/execution/process-runtime/check-result.ts +105 -1
- package/src/runtime/execution/process-runtime/check-runner-sidecar.ts +18 -8
- package/src/runtime/execution/process-runtime/command-facade.ts +79 -27
- package/src/runtime/execution/process-runtime/gc.ts +15 -2
- package/src/runtime/execution/process-runtime/handle-index.ts +176 -0
- package/src/runtime/execution/process-runtime/host-command-lane.ts +139 -0
- package/src/runtime/execution/process-runtime/index.ts +3 -0
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +251 -72
- package/src/runtime/execution/process-runtime/persisted-check.ts +3 -3
- package/src/runtime/execution/process-runtime/process-runner-entry.ts +47 -5
- package/src/runtime/execution/process-runtime/process-scope.ts +160 -0
- package/src/runtime/execution/process-runtime/resource-claims.ts +65 -61
- package/src/runtime/execution/process-runtime/runtime.ts +35 -2
- package/src/runtime/execution/process-runtime/store.ts +30 -2
- package/src/runtime/execution/process-runtime/types.ts +6 -1
- package/src/runtime/execution/workers/executor.ts +2 -6
- package/src/runtime/platform/service-inventory.ts +130 -0
- package/src/runtime/platform/service-manager.ts +8 -0
- package/src/runtime/plugins/action-application.ts +85 -28
- package/src/runtime/plugins/app-store-connect-adapter.ts +38 -14
- package/src/runtime/plugins/browser-adapter.ts +757 -34
- package/src/runtime/plugins/browser-automation-service.ts +23 -3
- package/src/runtime/plugins/browser-macos-bridge.ts +321 -29
- package/src/runtime/plugins/browser-manifest-surface.ts +45 -11
- package/src/runtime/plugins/browser-native-session-service.ts +63 -13
- package/src/runtime/plugins/browser-registration.ts +16 -0
- package/src/runtime/plugins/browser-runtime.ts +14 -1
- package/src/runtime/plugins/browser-session-authority.ts +2 -2
- package/src/runtime/plugins/browser-session-store.ts +46 -76
- package/src/runtime/plugins/capability-authorization-grants.ts +258 -62
- package/src/runtime/plugins/computer-chatgpt-browser-adapter-installer.ts +219 -0
- package/src/runtime/plugins/computer-chatgpt-macos-target.ts +891 -0
- package/src/runtime/plugins/computer-registration.ts +222 -11
- package/src/runtime/plugins/desktop-operator-external-policy.ts +36 -4
- package/src/runtime/plugins/desktop-operator-registration.ts +12 -4
- package/src/runtime/plugins/external-adapter.ts +42 -6
- package/src/runtime/plugins/external-registration.ts +21 -1
- package/src/runtime/plugins/gmail-adapter.ts +23 -2
- package/src/runtime/plugins/google-calendar-adapter.ts +2 -2
- package/src/runtime/plugins/google-shared.ts +14 -7
- package/src/runtime/plugins/google-tasks-adapter.ts +2 -2
- package/src/runtime/plugins/ios-adapter.ts +1 -1
- package/src/runtime/plugins/lightweight-action.ts +208 -2
- package/src/runtime/plugins/local-system-adapter.ts +12 -2
- package/src/runtime/plugins/managed-process-adapter.ts +12 -5
- package/src/runtime/plugins/plugin-action-sidecar.ts +17 -13
- package/src/runtime/plugins/plugin-management-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +602 -428
- package/src/runtime/plugins/types.ts +20 -0
- package/src/runtime/plugins/xiaohongshu-publish.ts +16 -0
- package/src/runtime/projections/invalidation.ts +7 -3
- package/src/runtime/projections/materialized-view.ts +80 -36
- package/src/runtime/recovery/actions.ts +3 -3
- package/src/runtime/recovery/capability-action-application.ts +46 -45
- package/src/runtime/recovery/lifecycle-inventory.ts +29 -29
- package/src/runtime/recovery/maintenance-executor.ts +374 -123
- package/src/runtime/recovery/store.ts +29 -11
- 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/computer-composition.ts +191 -6
- package/src/runtime/root/controller-progression-composition.ts +192 -0
- package/src/runtime/root/controller-round-composition.ts +20 -1
- package/src/runtime/root/deployment-topology.ts +106 -0
- package/src/runtime/root/entry.ts +4 -3
- package/src/runtime/root/known-good-recovery.ts +184 -0
- package/src/runtime/root/local-bridge.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +111 -18
- package/src/runtime/root/ownership.ts +261 -9
- package/src/runtime/root/package-connector-service.ts +105 -19
- package/src/runtime/root/package-runtime-release.ts +117 -18
- package/src/runtime/root/package-runtime-service.ts +17 -11
- package/src/runtime/root/release-execution-canary.ts +44 -12
- package/src/runtime/root/release-loader.ts +90 -8
- package/src/runtime/root/release-manifest.ts +80 -6
- package/src/runtime/root/release-materialize.ts +372 -28
- package/src/runtime/root/release-store.ts +395 -36
- package/src/runtime/root/runtime-lane.ts +191 -0
- package/src/runtime/root/runtime.ts +159 -27
- package/src/runtime/root/scheduled-controller-composition.ts +28 -3
- package/src/runtime/root/service-runner.ts +2 -2
- package/src/runtime/root/service.ts +36 -33
- package/src/runtime/root/status.ts +48 -0
- package/src/runtime/root/types.ts +32 -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/process-environment.ts +20 -9
- package/src/runtime/shared/response-budget.ts +1 -3
- package/src/runtime/standalone-recovery/controller-home-migration.ts +16 -24
- package/src/runtime/standalone-recovery/core.ts +3702 -342
- package/src/runtime/standalone-recovery/entry.ts +469 -39
- package/src/runtime/standalone-recovery/installer.ts +262 -148
- package/src/runtime/standalone-recovery/mcp-server.ts +29 -193
- package/src/runtime/standalone-recovery/operation-lock.ts +1 -1
- package/src/runtime/standalone-recovery/performance.ts +54 -16
- package/src/runtime/standalone-recovery/release.ts +53 -5
- package/src/runtime/standalone-recovery/service-labels.ts +5 -2
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +5 -2
- package/src/runtime/workflow/schedules/browser-probe.ts +17 -6
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +10 -3
- package/src/runtime/workflow/schedules/engine.ts +138 -172
- package/src/runtime/workflow/schedules/work-continuation.ts +44 -5
- package/src/runtime/workflows/runtime.ts +15 -19
- package/src/testing/test-governance.ts +5 -1
- package/supervisor/chatgpt-conversation.ts +23 -0
- package/supervisor/client.ts +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/adapters/chatgpt/controller-host.ts +0 -119
- package/adapters/mcp/controller-round-compatibility.ts +0 -184
- package/adapters/mcp/frozen-client-semantic-compatibility.ts +0 -292
- package/adapters/mcp/runtime-gateway/work-compat-adapter.ts +0 -502
- package/packages/kernel/progression/api/index.ts +0 -2
- package/packages/kernel/progression/application/projector.ts +0 -211
- package/packages/kernel/progression/domain/types.ts +0 -116
- package/packages/kernel/progression/index.ts +0 -1
- package/packages/kernel/scheduler/application/continuation-service.ts +0 -212
- package/packages/kernel/scheduler/domain/continuation.ts +0 -38
- package/packages/kernel/scheduler/infrastructure/continuation-dispatch-store.ts +0 -38
- package/packages/kernel/work/domain/admission-policy.ts +0 -34
- package/scripts/benchmark-background-check-overlap.ts +0 -228
- package/scripts/benchmark-computer-hotpath.ts +0 -255
- package/scripts/benchmark-controller-hotpath.ts +0 -212
- package/scripts/benchmark-controller-round2.ts +0 -191
- package/scripts/benchmark-ios-agent-device.ts +0 -511
- package/scripts/benchmark-mcp-host.sh +0 -117
- package/scripts/benchmark-mcp-transport.ts +0 -169
- package/scripts/benchmark-operational-memory-activation.ts +0 -235
- package/scripts/benchmark-operational-shadow.ts +0 -28
- package/scripts/benchmark-quality-harness.ts +0 -146
- package/scripts/benchmark-route-session-concurrency.ts +0 -915
- package/scripts/benchmark-thin-harness-gateway-ab.ts +0 -632
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +0 -498
- package/scripts/benchmark-thin-harness.ts +0 -251
- package/scripts/benchmark-typescript-navigation.ts +0 -186
- package/scripts/benchmark-work-efficiency.ts +0 -334
- package/scripts/build-controller-ui.ts +0 -28
- package/scripts/check-npm-release.sh +0 -32
- package/scripts/check-public-release-surface.sh +0 -19
- package/scripts/check-release-published.sh +0 -57
- package/scripts/check-release-readiness.sh +0 -37
- package/scripts/check-release-version.mjs +0 -74
- package/scripts/check-tarball-install-smoke.sh +0 -150
- 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/stage-runtime-release.ts +0 -44
- 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/content-equivalent-commit-authority.ts +0 -169
- package/src/runtime/control-plane/execution/direct-canonical-work-reconciliation.ts +0 -85
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +0 -655
- package/src/runtime/control-plane/execution/work-finalization-service.ts +0 -2850
- package/src/runtime/control-plane/execution/work-path-scope.ts +0 -37
- package/src/runtime/control-plane/execution/work-task-receipt.ts +0 -468
- package/src/runtime/control-plane/facade/controller-round-relay.ts +0 -3
- package/src/runtime/control-plane/facade/work-admission-policy.ts +0 -142
- package/src/runtime/control-plane/facade/work-implementation-review.ts +0 -2
- package/src/runtime/control-plane/facade/work-state-machine.ts +0 -2
- package/src/runtime/control-plane/facade/work-task-projection.ts +0 -66
- package/src/runtime/control-plane/routing/index.ts +0 -1
- package/src/runtime/control-plane/routing/route-policy.ts +0 -401
- package/src/runtime/control-plane/routing/workspace-admission.ts +0 -70
|
@@ -2,16 +2,27 @@ import { randomUUID } from 'crypto';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import type { ExecutionJobOrigin } from '../../src/runtime/execution/jobs/types';
|
|
4
4
|
import { browserActions } from '../../src/runtime/plugins/browser-manifest-surface';
|
|
5
|
-
import {
|
|
5
|
+
import { executeControllerScopedPluginAction, getControllerPluginManifest, submitControllerPluginAction } from '../../src/runtime/plugins/store';
|
|
6
6
|
import {
|
|
7
|
+
CHATGPT_AUTOMATION_RATE_LIMITED,
|
|
8
|
+
CHATGPT_AUTOMATION_RATE_LIMITED_AFTER_SUBMIT,
|
|
7
9
|
CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN,
|
|
8
10
|
ChatgptProviderDeliveryError,
|
|
11
|
+
chatgptProviderPageFailure,
|
|
9
12
|
DEFAULT_CHATGPT_AUTOMATION_MODEL,
|
|
10
13
|
type ChatgptAutomationReasoning,
|
|
11
14
|
type ChatgptAutomationTabCleanupStatus,
|
|
15
|
+
type ChatgptProviderPageFailureCode,
|
|
12
16
|
} from './provider-delivery';
|
|
13
17
|
|
|
14
|
-
|
|
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
|
+
|
|
15
26
|
|
|
16
27
|
type ChatgptBrowserActionOrigin = Pick<ExecutionJobOrigin, 'surface' | 'actor'>;
|
|
17
28
|
interface ChatgptBrowserActionContext {
|
|
@@ -28,16 +39,20 @@ export function withChatgptBrowserActionOrigin<T>(
|
|
|
28
39
|
return chatgptBrowserActionOrigin.run({ origin, authorizationGrantRefs }, operation);
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
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();
|
|
33
45
|
if (!value) throw new Error('CHATGPT_AUTOMATION_PROMPT_REQUIRED');
|
|
34
|
-
|
|
35
|
-
return `${DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION} ${value}`;
|
|
46
|
+
return value;
|
|
36
47
|
}
|
|
37
48
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
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"]';
|
|
41
56
|
const CHATGPT_INTELLIGENCE_CONTROL_SELECTORS = [
|
|
42
57
|
'main button, main [role="button"]',
|
|
43
58
|
'button, [role="button"]',
|
|
@@ -55,16 +70,15 @@ const CHATGPT_BROWSER_AUTHORIZATION_ACTIONS = new Set(
|
|
|
55
70
|
.map((action) => action.actionId),
|
|
56
71
|
);
|
|
57
72
|
|
|
58
|
-
const CHATGPT_BROWSER_TRANSPORT_OVERRIDES = {
|
|
59
|
-
browser_mode: 'attach_preferred',
|
|
60
|
-
cdp_attach_fallback: 'fail_closed',
|
|
61
|
-
native_attach_mode: 'auto',
|
|
62
|
-
native_browser_candidates: ['chrome'],
|
|
63
|
-
} as const;
|
|
64
|
-
|
|
65
73
|
export function chatgptBrowserActionArgs(actionId: string, args: Record<string, unknown>): Record<string, unknown> {
|
|
66
|
-
|
|
67
|
-
|
|
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;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
export function chatgptBrowserActionResult(envelope: Record<string, unknown>, actionId: string): Record<string, unknown> {
|
|
@@ -97,11 +111,7 @@ async function controllerBrowserAction(
|
|
|
97
111
|
// reusable controller-scoped Browser grant. Scheduled delivery stays on the
|
|
98
112
|
// low-level executor and must present explicit refs already bound to this Work.
|
|
99
113
|
if (origin.surface === 'chatgpt-action' && CHATGPT_BROWSER_AUTHORIZATION_ACTIONS.has(actionId)) {
|
|
100
|
-
const submitted = await
|
|
101
|
-
controllerHome,
|
|
102
|
-
controllerPluginRepository(controllerHome),
|
|
103
|
-
actionRequest,
|
|
104
|
-
);
|
|
114
|
+
const submitted = await submitControllerPluginAction(controllerHome, actionRequest);
|
|
105
115
|
const grantId = submitted.authorization?.grantId?.trim();
|
|
106
116
|
if (grantId) context?.authorizationGrantRefs.add(grantId);
|
|
107
117
|
if (!submitted.result) throw new Error(`CHATGPT_BROWSER_ACTION_RESULT_INVALID:${actionId}`);
|
|
@@ -155,17 +165,31 @@ function normalizeChatgptOutboundText(value: string): string {
|
|
|
155
165
|
const CHATGPT_OUTBOUND_MESSAGE_UI_SUFFIXES = ['收起', 'Collapse', 'Show less'] as const;
|
|
156
166
|
const MAX_CHATGPT_OUTBOUND_VERIFICATION_CHARS = 100_000;
|
|
157
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>;
|
|
158
179
|
|
|
159
180
|
export function chatgptOutboundMessageMatchesPrompt(
|
|
160
181
|
messageText: string,
|
|
161
182
|
prompt: string,
|
|
162
|
-
options: { truncated?: boolean } = {},
|
|
183
|
+
options: { truncated?: boolean; boundForgePlugin?: boolean } = {},
|
|
163
184
|
): boolean {
|
|
164
185
|
const message = normalizeChatgptOutboundText(messageText);
|
|
165
186
|
const normalizedPrompt = normalizeChatgptOutboundText(prompt);
|
|
166
187
|
if (!message || !normalizedPrompt) return false;
|
|
167
|
-
|
|
168
|
-
|
|
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;
|
|
169
193
|
// Browser text extraction is deliberately bounded. A large ControllerRound
|
|
170
194
|
// prompt can exceed that bound, so requiring exact equality makes successful
|
|
171
195
|
// submissions mechanically unverifiable. Accept only an explicitly reported
|
|
@@ -173,7 +197,138 @@ export function chatgptOutboundMessageMatchesPrompt(
|
|
|
173
197
|
// remains insufficient evidence.
|
|
174
198
|
return options.truncated === true
|
|
175
199
|
&& message.length >= MIN_TRUNCATED_CHATGPT_OUTBOUND_PREFIX_CHARS
|
|
176
|
-
&&
|
|
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
|
+
}
|
|
177
332
|
}
|
|
178
333
|
|
|
179
334
|
async function latestChatgptUserMessage(
|
|
@@ -213,8 +368,138 @@ async function fullChatgptMessageText(
|
|
|
213
368
|
};
|
|
214
369
|
}
|
|
215
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
|
+
|
|
216
501
|
function chatgptSendControlUnavailable(error: unknown): boolean {
|
|
217
|
-
return error instanceof Error &&
|
|
502
|
+
return error instanceof Error && error.message.includes('PLUGIN_BROWSER_SELECTOR_UNAVAILABLE');
|
|
218
503
|
}
|
|
219
504
|
|
|
220
505
|
function normalizeExecutionControlLabel(label: string | undefined): string {
|
|
@@ -438,7 +723,7 @@ export async function ensureChatgptExecutionPreference(
|
|
|
438
723
|
for (let index = 0; index < Math.abs(targetValue - currentValue); index += 1) {
|
|
439
724
|
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
440
725
|
session_id: browserSessionId,
|
|
441
|
-
selector:
|
|
726
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
442
727
|
key: direction,
|
|
443
728
|
timeout_ms: timeoutMs ?? 60_000,
|
|
444
729
|
post_action_wait_ms: 120,
|
|
@@ -502,6 +787,42 @@ function chatgptUrlMatchesContinuationTarget(observedUrl: string, targetUrl: str
|
|
|
502
787
|
}
|
|
503
788
|
}
|
|
504
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
|
+
|
|
505
826
|
function completeChatgptBrowserInventorySessions(
|
|
506
827
|
inventory: Record<string, unknown>,
|
|
507
828
|
): ChatgptBrowserSessionInventoryItem[] | undefined {
|
|
@@ -557,11 +878,19 @@ export async function closeChatgptAutomationTabAfterDispatch(
|
|
|
557
878
|
workId: string,
|
|
558
879
|
browserSessionId: string,
|
|
559
880
|
timeoutMs?: number,
|
|
881
|
+
authorizationGrantRefs: readonly string[] = [],
|
|
560
882
|
): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
|
|
561
883
|
try {
|
|
562
|
-
const
|
|
884
|
+
const closePage = () => controllerBrowserAction(controllerHome, workId, 'close_page', {
|
|
563
885
|
session_id: browserSessionId,
|
|
564
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();
|
|
565
894
|
if (closed.preservedUserOwnedTab === true) return { status: 'preserved_user_owned' };
|
|
566
895
|
if (closed.resourceClosed === true) return { status: 'closed' };
|
|
567
896
|
return { status: 'session_closed' };
|
|
@@ -588,6 +917,7 @@ export async function settleWorkChatgptAutomationTab(input: {
|
|
|
588
917
|
workId: string;
|
|
589
918
|
browserSessionId: string;
|
|
590
919
|
timeoutMs?: number;
|
|
920
|
+
authorizationGrantRefs?: readonly string[];
|
|
591
921
|
}): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
|
|
592
922
|
if (input.browserSessionId.startsWith('forge-chatgpt-bridge-')) return { status: 'session_closed' };
|
|
593
923
|
return closeChatgptAutomationTabAfterDispatch(
|
|
@@ -595,6 +925,7 @@ export async function settleWorkChatgptAutomationTab(input: {
|
|
|
595
925
|
input.workId,
|
|
596
926
|
input.browserSessionId,
|
|
597
927
|
input.timeoutMs,
|
|
928
|
+
input.authorizationGrantRefs,
|
|
598
929
|
);
|
|
599
930
|
}
|
|
600
931
|
|
|
@@ -632,6 +963,14 @@ export async function navigateWorkConversation(
|
|
|
632
963
|
const opened = await controllerBrowserAction(controllerHome, workId, 'create_session', {
|
|
633
964
|
session_id: sessionId,
|
|
634
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',
|
|
635
974
|
wait_until: 'domcontentloaded',
|
|
636
975
|
timeout_ms: timeoutMs ?? 60_000,
|
|
637
976
|
retries: 1,
|
|
@@ -665,6 +1004,13 @@ export async function navigateWorkConversation(
|
|
|
665
1004
|
} catch (preflightError) {
|
|
666
1005
|
if (!chatgptAutomationNavigationRequiresReplacement(preflightError)) throw preflightError;
|
|
667
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);
|
|
668
1014
|
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
|
|
669
1015
|
}
|
|
670
1016
|
await navigate(browserSessionId, targetUrl);
|
|
@@ -689,15 +1035,11 @@ export async function navigateWorkConversation(
|
|
|
689
1035
|
const replacementSessionId = await openReplacement(browserSessionId, targetUrl);
|
|
690
1036
|
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId: replacementSessionId };
|
|
691
1037
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (!chatgptAutomationNavigationRequiresReplacement(fallbackError)) throw fallbackError;
|
|
698
|
-
const replacementSessionId = await openReplacement(browserSessionId, 'https://chatgpt.com/');
|
|
699
|
-
return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
|
|
700
|
-
}
|
|
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;
|
|
701
1043
|
}
|
|
702
1044
|
}
|
|
703
1045
|
|
|
@@ -709,17 +1051,62 @@ export async function submitChatgptPrompt(
|
|
|
709
1051
|
targetUrl: string,
|
|
710
1052
|
timeoutMs?: number,
|
|
711
1053
|
): Promise<string> {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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@`;
|
|
715
1076
|
await controllerBrowserAction(controllerHome, workId, 'fill', {
|
|
716
1077
|
session_id: browserSessionId,
|
|
717
1078
|
selector: CHATGPT_PROMPT_SELECTOR,
|
|
718
|
-
text:
|
|
1079
|
+
text: mentionTriggerDraft,
|
|
719
1080
|
timeout_ms: timeoutMs ?? 60_000,
|
|
720
1081
|
post_action_wait_ms: 100,
|
|
721
1082
|
}, timeoutMs);
|
|
722
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
|
+
|
|
723
1110
|
let observedUrl = targetUrl;
|
|
724
1111
|
let submitOutcomeUnknown = false;
|
|
725
1112
|
let observedNewOutbound = false;
|
|
@@ -733,9 +1120,46 @@ export async function submitChatgptPrompt(
|
|
|
733
1120
|
observedUrl = resultUrl(sent) ?? observedUrl;
|
|
734
1121
|
} catch (error) {
|
|
735
1122
|
if (browserMutationOutcomeUnknown(error, 'click')) {
|
|
736
|
-
// Never replay an outcome-unknown submit
|
|
737
|
-
//
|
|
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.
|
|
738
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
|
+
}
|
|
739
1163
|
} else if (chatgptSendControlUnavailable(error)) {
|
|
740
1164
|
const pressed = await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
741
1165
|
session_id: browserSessionId,
|
|
@@ -750,28 +1174,78 @@ export async function submitChatgptPrompt(
|
|
|
750
1174
|
}
|
|
751
1175
|
}
|
|
752
1176
|
|
|
753
|
-
const deadline = Date.now() +
|
|
1177
|
+
const deadline = Date.now() + chatgptSubmissionSettlementWaitBudget(timeoutMs);
|
|
1178
|
+
let nextFailureProbeAt = 0;
|
|
1179
|
+
let observationIndex = 0;
|
|
754
1180
|
do {
|
|
755
1181
|
const latest = await latestChatgptUserMessage(controllerHome, workId, browserSessionId, timeoutMs).catch(() => undefined);
|
|
756
1182
|
if (latest) {
|
|
757
1183
|
observedUrl = latest.url ?? observedUrl;
|
|
758
|
-
const isNewOutbound =
|
|
759
|
-
(latest.selector && latest.selector !== before.selector)
|
|
760
|
-
|| (!before.preview && latest.preview)
|
|
761
|
-
|| latest.preview !== before.preview,
|
|
762
|
-
);
|
|
1184
|
+
const isNewOutbound = chatgptMessageObservationChanged(before, latest);
|
|
763
1185
|
if (isNewOutbound) {
|
|
764
1186
|
observedNewOutbound = true;
|
|
765
1187
|
const fullText = await fullChatgptMessageText(controllerHome, workId, browserSessionId, latest, timeoutMs);
|
|
766
|
-
|
|
767
|
-
|
|
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
|
+
}
|
|
768
1227
|
}
|
|
769
1228
|
}
|
|
770
1229
|
}
|
|
771
|
-
|
|
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)));
|
|
772
1235
|
} while (Date.now() < deadline);
|
|
773
1236
|
const hasConversationIdentity = /\/c\/[^/?#]+/.test(observedUrl);
|
|
774
|
-
const
|
|
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
|
|
775
1249
|
? CHATGPT_AUTOMATION_SUBMISSION_OUTCOME_UNKNOWN
|
|
776
1250
|
: 'CHATGPT_AUTOMATION_SUBMISSION_NOT_CONFIRMED';
|
|
777
1251
|
throw new ChatgptProviderDeliveryError(
|
|
@@ -780,4 +1254,3 @@ export async function submitChatgptPrompt(
|
|
|
780
1254
|
{ conversationUrl: observedUrl },
|
|
781
1255
|
);
|
|
782
1256
|
}
|
|
783
|
-
|