@moretea-labs/forge 1.5.1 → 1.7.0
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 +2 -0
- package/CHANGELOG.md +46 -0
- package/CLAUDE.md +2 -0
- package/DESIGN.md +7 -2
- package/README.md +4 -4
- package/README.zh-CN.md +2 -2
- package/SKILL.md +2 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/agents/openai.yaml +1 -1
- package/assets/hooks/hook-input.sh +11 -8
- package/assets/hooks/lib/session-state.sh +10 -7
- package/assets/hooks/lib/workflow-state.sh +13 -177
- package/assets/hooks/post-tool-observer.sh +8 -2
- package/assets/hooks/pre-edit-guard.sh +8 -39
- package/assets/hooks/prompt-guard.sh +7 -83
- package/assets/hooks/session-start-context.sh +56 -1
- package/assets/partials/05-workflow.partial.md +1 -1
- package/assets/partials-agents/03-orchestration.partial.md +1 -1
- package/assets/plugin-registry.v1.json +54 -3
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.md +1 -1
- package/assets/reference-configs/hook-operations.md +1 -1
- package/assets/reference-configs/sprint-contracts.md +3 -3
- package/assets/skill-version.json +2 -2
- package/assets/templates/contract.template.md +2 -7
- package/assets/templates/helpers/capture-plan.sh +2 -2
- package/assets/templates/helpers/check-task-workflow.sh +4 -4
- package/assets/templates/helpers/contract-worktree.sh +1 -21
- package/assets/templates/helpers/ensure-task-workflow.sh +13 -38
- package/assets/templates/helpers/migrate-project-template.sh +22 -45
- package/assets/templates/helpers/new-plan.sh +1 -1
- package/assets/templates/helpers/plan-to-todo.sh +12 -37
- package/assets/templates/helpers/ship-worktrees.sh +2 -29
- package/assets/templates/helpers/sprint-backlog.sh +6 -6
- package/assets/templates/helpers/verify-contract.sh +1 -111
- package/assets/templates/helpers/verify-sprint.sh +3 -180
- package/assets/templates/plan.template.md +1 -1
- package/assets/templates/review.template.md +9 -34
- package/assets/workflow-contract.v1.json +10 -19
- package/docs/README.md +10 -10
- package/docs/architecture/CURRENT.md +170 -0
- package/docs/architecture/EVOLUTION.md +42 -0
- package/docs/architecture/current/README.md +4 -156
- package/docs/architecture/decisions/20260803-release-identity-binding-and-exit-policy.md +2 -2
- package/docs/architecture/decisions/20260823-immediate-controller-relay.md +53 -0
- package/docs/architecture/decisions/20260826-browser-runtime-v2.md +73 -0
- package/docs/architecture/history/README.md +7 -0
- package/docs/architecture/{chatgpt-handoff-facade.md → history/chatgpt-handoff-facade.md} +4 -0
- package/docs/architecture/{global-hook-runtime.md → history/global-hook-runtime.md} +7 -3
- package/docs/architecture/{ios-semantic-automation-provider-v2.md → history/ios-semantic-automation-provider-v2.md} +6 -2
- package/docs/architecture/history.md +3 -34
- package/docs/architecture/index.md +15 -98
- package/docs/architecture/modules/controller-runtime/transport-lifecycle.md +1 -1
- package/docs/architecture/reviews/2026-08-22-semantic-harness-convergence-acceptance.md +39 -0
- package/docs/architecture/versions/1.6.md +38 -0
- package/docs/architecture/versions/1.7.md +41 -0
- package/docs/forge-chatgpt-browser-engine.md +2 -2
- package/docs/forge-chatgpt-mcp-setup.md +4 -28
- package/docs/forge-chatgpt-supervised-automation.md +3 -3
- package/docs/forge-github-issue-launcher.md +1 -1
- package/docs/forge-runtime-self-healing-loop.md +1 -1
- package/docs/operations/controller-browser-plugin.md +38 -22
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +3 -3
- package/docs/operations/github-repository.md +1 -1
- package/docs/operations/homebrew.md +2 -2
- package/docs/operations/mcp-tool-exposure.md +5 -3
- package/docs/tutorials/01-install-and-start.md +1 -1
- package/docs/tutorials/01-install-and-start.zh-CN.md +1 -1
- package/docs/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Installation.md +1 -1
- package/docs/wiki/Integrations.md +2 -2
- package/docs/wiki/Quick-Start.md +1 -1
- package/docs/wiki/Work-Lifecycle.md +1 -1
- package/package.json +29 -10
- package/scripts/activate-source-baseline.ts +17 -13
- package/scripts/benchmark-background-check-overlap.ts +228 -0
- package/scripts/benchmark-mcp-host.sh +117 -0
- package/scripts/benchmark-mcp-transport.ts +170 -0
- package/scripts/benchmark-quality-harness.ts +146 -0
- package/scripts/benchmark-route-session-concurrency.ts +207 -6
- package/scripts/benchmark-thin-harness-mcp-e2e.ts +7 -1
- package/scripts/benchmark-typescript-navigation.ts +186 -0
- package/scripts/benchmark-work-efficiency.ts +334 -0
- package/scripts/capture-plan.sh +3 -3
- package/scripts/check-agent-tooling.sh +8 -35
- package/scripts/check-architecture-sync.sh +17 -86
- package/scripts/check-mcp-compatibility.ts +14 -0
- package/scripts/check-public-docs.sh +14 -1
- package/scripts/check-runtime-architecture.mjs +107 -64
- package/scripts/check-stable-baseline.ts +92 -0
- package/scripts/check-task-workflow.sh +4 -4
- package/scripts/contract-worktree.sh +1 -21
- package/scripts/ensure-task-workflow.sh +22 -55
- package/scripts/host-temporary-cloud-mcp-e2e.sh +265 -0
- package/scripts/install-standalone-recovery.ts +24 -0
- package/scripts/lib/project-init-lib.sh +15 -44
- package/scripts/migrate-chatgpt-automation-schedules.ts +64 -0
- package/scripts/migrate-project-template.sh +23 -1179
- package/scripts/new-plan.sh +1 -1
- package/scripts/plan-to-todo.sh +11 -36
- package/scripts/run-governed-gate.ts +2 -1
- package/scripts/ship-worktrees.sh +2 -29
- package/scripts/smoke-ios-physical-device.ts +4 -2
- package/scripts/smoke-runtime-recovery.ts +1 -35
- package/scripts/smoke-schedule-engine.ts +1 -20
- package/scripts/sprint-backlog.sh +1 -1
- package/scripts/verify-contract.sh +1 -111
- package/scripts/verify-forge-runtime.sh +0 -1
- package/scripts/verify-sprint.sh +3 -180
- package/scripts/workflow-contract.ts +1 -5
- package/src/cli/commands/chatgpt.ts +31 -2
- package/src/cli/commands/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +37 -10
- package/src/cli/commands/recovery.ts +25 -3
- package/src/cli/commands/runtime.ts +55 -3
- package/src/cli/commands/security.ts +12 -1
- package/src/cli/commands/setup-profile.ts +31 -21
- package/src/cli/controller/check-runner.ts +106 -3
- package/src/cli/controller/composite-result.ts +1 -1
- package/src/cli/controller/context/focus.ts +51 -0
- package/src/cli/controller/context/instruction-context.ts +114 -0
- package/src/cli/controller/context/known-paths.ts +76 -0
- package/src/cli/controller/context/query-planning.ts +110 -0
- package/src/cli/controller/context/rule-resolution.ts +532 -0
- package/src/cli/controller/context/source-materializer.ts +278 -0
- package/src/cli/controller/context/structural.ts +16 -0
- package/src/cli/controller/context/types.ts +286 -0
- package/src/cli/controller/context-pack.ts +898 -430
- package/src/cli/controller/launch-agents.ts +77 -2
- package/src/cli/controller/operational-plan.ts +2 -2
- package/src/cli/controller/work-mode.ts +6 -4
- package/src/cli/editing/edit-session.ts +60 -7
- package/src/cli/github/github.ts +133 -0
- package/src/cli/hook/prompt-guard-decision.ts +17 -18
- package/src/cli/hook/prompt-intents.ts +1 -1
- package/src/cli/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +225 -67
- package/src/cli/local-bridge/console-view-models.ts +3 -35
- package/src/cli/local-bridge/facade-api.ts +2 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +153 -3
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +183 -3
- package/src/cli/local-bridge/frontend/src/features/repositories/RepositoriesView.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/features/work/WorkView.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/lib/api.ts +18 -11
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/CommandBar.tsx +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +4 -3
- package/src/cli/local-bridge/frontend/src/styles.css +27 -11
- package/src/cli/local-bridge/frontend/src/types.ts +8 -7
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +29 -532
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +5101 -4967
- package/src/cli/mcp/auth.ts +5 -5
- package/src/cli/mcp/instructions.ts +3 -2
- package/src/cli/mcp/legacy-tool-service.ts +0 -189
- package/src/cli/mcp/multi-repository.ts +16 -5
- package/src/cli/mcp/oauth.ts +175 -26
- package/src/cli/mcp/paths.ts +8 -2
- package/src/cli/mcp/repository-tools.ts +356 -101
- package/src/cli/mcp/server.ts +625 -54
- package/src/cli/mcp/tool-capability-cache.ts +1 -1
- package/src/cli/mcp/toolset-selection.ts +1 -1
- package/src/cli/mcp/toolset.ts +42 -5
- package/src/cli/mcp/transports/http.ts +125 -15
- package/src/cli/mcp/transports/session-registry.ts +67 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +209 -14
- package/src/cli/repositories/command-executor.ts +55 -521
- package/src/cli/repositories/command-process.ts +108 -0
- package/src/cli/repositories/command-scope.ts +99 -41
- package/src/cli/repositories/controller-home.ts +7 -8
- package/src/cli/repositories/git-command-executor.ts +2 -0
- package/src/cli/repositories/locks.ts +53 -16
- package/src/cli/repositories/repository-snapshot.ts +121 -0
- package/src/cli/repositories/safe-patch.ts +6 -4
- package/src/cli/repositories/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +110 -10
- package/src/cli/repository/inspector.ts +565 -62
- package/src/cli/repository/session-cache.ts +58 -17
- package/src/cli/v81-entry.ts +1 -4
- package/src/effects/fs-transaction.ts +85 -17
- package/src/runtime/context/codegraph-read-provider.ts +233 -2
- package/src/runtime/context/codegraph-sidecar.cjs +74 -40
- package/src/runtime/context/generic-lsp-provider.ts +355 -0
- package/src/runtime/context/lsp-client.ts +283 -0
- package/src/runtime/context/semantic-navigation.ts +275 -0
- package/src/runtime/context/swift-navigation.ts +565 -0
- package/src/runtime/context/typescript-navigation.ts +226 -0
- package/src/runtime/control-plane/dispatch-priority.ts +28 -0
- package/src/runtime/control-plane/execution/direct-edit-work-completion.ts +196 -2
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/execution-identity.ts +65 -14
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- package/src/runtime/control-plane/execution/validation.ts +15 -2
- package/src/runtime/control-plane/execution/verification-evidence.ts +58 -3
- package/src/runtime/control-plane/execution/work-handle-store.ts +27 -2
- package/src/runtime/control-plane/execution/work-lifecycle-audit.ts +258 -0
- package/src/runtime/control-plane/execution/work-remote-delivery.ts +215 -0
- package/src/runtime/control-plane/execution/work-task-receipt.ts +54 -11
- package/src/runtime/control-plane/execution/work-terminal-cleanup.ts +175 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +28 -21
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +87 -9
- package/src/runtime/control-plane/facade/capability-registry.ts +129 -25
- package/src/runtime/control-plane/facade/controller-round-relay.ts +991 -0
- package/src/runtime/control-plane/facade/controller-session-store.ts +144 -14
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +9 -2
- package/src/runtime/control-plane/facade/goal-workloop.ts +885 -105
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +211 -26
- package/src/runtime/control-plane/facade/requirement-board.ts +24 -8
- package/src/runtime/control-plane/facade/semantic-admission.ts +205 -0
- package/src/runtime/control-plane/facade/suggested-actions.ts +5 -1
- package/src/runtime/control-plane/facade/types.ts +55 -11
- package/src/runtime/control-plane/facade/work-contract-store.ts +204 -73
- package/src/runtime/control-plane/generated-cache-retention.ts +271 -0
- package/src/runtime/control-plane/global-scheduler/config.ts +44 -0
- package/src/runtime/control-plane/global-scheduler/dispatch-capacity.ts +80 -0
- package/src/runtime/control-plane/global-scheduler/durable-admission.ts +46 -0
- package/src/runtime/control-plane/global-scheduler/maintenance.ts +145 -0
- package/src/runtime/control-plane/global-scheduler/projection-refresh.ts +84 -0
- package/src/runtime/control-plane/global-scheduler/reconciliation.ts +3 -3
- package/src/runtime/control-plane/global-scheduler/scheduler.ts +273 -542
- package/src/runtime/control-plane/global-scheduler/source-scan.ts +59 -0
- package/src/runtime/control-plane/global-scheduler/state.ts +88 -0
- package/src/runtime/control-plane/global-scheduler/worker-attachment.ts +57 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-decision.ts +38 -0
- package/src/runtime/control-plane/global-scheduler/worker-exit-reconciler.ts +175 -0
- package/src/runtime/control-plane/global-scheduler/worker-launch.ts +111 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle-store.ts +64 -0
- package/src/runtime/control-plane/global-scheduler/worker-lifecycle.ts +150 -0
- package/src/runtime/control-plane/global-scheduler/worker-process.ts +119 -0
- package/src/runtime/control-plane/global-scheduler/worker-stderr.ts +60 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-binding-store.ts +50 -0
- package/src/runtime/control-plane/launcher/chatgpt-work-continuation.ts +642 -51
- package/src/runtime/control-plane/launcher/launch-reservation-store.ts +40 -0
- package/src/runtime/control-plane/launcher/thin-launcher.ts +194 -10
- package/src/runtime/control-plane/persistence/controller-home-migration.ts +487 -0
- package/src/runtime/control-plane/persistence/requirement-store.ts +15 -11
- package/src/runtime/control-plane/persistence/sqlite-store.ts +33 -2
- package/src/runtime/control-plane/release-retention.ts +62 -2
- package/src/runtime/control-plane/routing/route-policy.ts +7 -16
- package/src/runtime/control-plane/runtime-status-client.ts +0 -2
- package/src/runtime/diagnostics/mcp-timing.ts +19 -0
- package/src/runtime/diagnostics/mcp-transport-benchmark.ts +129 -0
- package/src/runtime/diagnostics/performance.ts +24 -1
- package/src/runtime/evidence/event-ledger.ts +1 -1
- package/src/runtime/execution/jobs/store.ts +58 -4
- package/src/runtime/execution/jobs/types.ts +1 -1
- package/src/runtime/execution/managed-workspace.ts +203 -1
- package/src/runtime/execution/process-runtime/check-facade.ts +52 -14
- package/src/runtime/execution/process-runtime/check-result.ts +63 -2
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +346 -40
- package/src/runtime/execution/process-runtime/gc.ts +98 -12
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +57 -6
- package/src/runtime/execution/process-runtime/resource-claims.ts +97 -8
- package/src/runtime/execution/process-runtime/runtime.ts +59 -4
- package/src/runtime/execution/process-runtime/store.ts +7 -0
- package/src/runtime/execution/process-runtime/types.ts +2 -0
- package/src/runtime/execution/thin-harness/async-process.ts +12 -2
- package/src/runtime/execution/thin-harness/search-worker.ts +54 -5
- package/src/runtime/execution/workers/executor.ts +0 -48
- package/src/runtime/gateway/mcp/execution-tools.ts +1219 -126
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +185 -83
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2555 -1616
- package/src/runtime/model-clients/deepseek-adapter.ts +0 -32
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2925 -318
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff-host.ts +6 -31
- package/src/runtime/plugins/browser-handoff.ts +92 -5
- package/src/runtime/plugins/browser-macos-bridge.ts +586 -45
- package/src/runtime/plugins/browser-node-bridge.ts +6 -34
- package/src/runtime/plugins/browser-provider-registry.ts +109 -0
- package/src/runtime/plugins/browser-runtime-contract.ts +152 -0
- package/src/runtime/plugins/browser-runtime.ts +183 -0
- package/src/runtime/plugins/browser-session-authority.ts +332 -0
- package/src/runtime/plugins/capability-authorization-grants.ts +354 -0
- package/src/runtime/plugins/desktop-operator-registration.ts +15 -12
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-registration.ts +84 -5
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +4 -2
- package/src/runtime/plugins/github-adapter.ts +125 -0
- package/src/runtime/plugins/gmail-adapter.ts +28 -2
- package/src/runtime/plugins/ios/agent-device-provider.ts +1 -1
- package/src/runtime/plugins/ios/agent-device-typed-provider.ts +52 -9
- package/src/runtime/plugins/ios/remote-xpc-hid.ts +1171 -0
- package/src/runtime/plugins/ios-adapter.ts +106 -16
- package/src/runtime/plugins/ios-agent-device.ts +234 -64
- package/src/runtime/plugins/ios-physical-device.ts +1143 -392
- package/src/runtime/plugins/lightweight-action.ts +153 -0
- package/src/runtime/plugins/local-system-adapter.ts +125 -8
- package/src/runtime/plugins/macos-capability-broker.ts +55 -9
- package/src/runtime/plugins/plugin-action-sidecar.ts +59 -0
- package/src/runtime/plugins/plugin-management-adapter.ts +181 -0
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +366 -24
- package/src/runtime/plugins/types.ts +39 -2
- package/src/runtime/plugins/xiaohongshu-publish.ts +515 -0
- package/src/runtime/projections/git-status-sampler.ts +16 -16
- package/src/runtime/projections/materialized-view.ts +28 -10
- package/src/runtime/recovery/actions.ts +0 -10
- package/src/runtime/recovery/classifier.ts +0 -9
- package/src/runtime/recovery/maintenance-executor.ts +415 -18
- package/src/runtime/recovery/probe.ts +0 -1
- package/src/runtime/recovery/types.ts +0 -1
- package/src/runtime/root/mcp-transport.ts +180 -10
- package/src/runtime/root/package-connector-service.ts +186 -8
- package/src/runtime/root/package-runtime-release.ts +143 -17
- package/src/runtime/root/package-runtime-service.ts +430 -22
- package/src/runtime/root/release-manifest.ts +7 -2
- package/src/runtime/root/release-materialize.ts +162 -19
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -27
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/index.ts +0 -1
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- package/src/runtime/safe-tooling/types.ts +0 -30
- package/src/runtime/shared/trusted-executable.ts +62 -0
- package/src/runtime/shared/trusted-node-executable.ts +48 -0
- package/src/runtime/standalone-recovery/core.ts +702 -94
- package/src/runtime/standalone-recovery/entry.ts +99 -23
- package/src/runtime/standalone-recovery/installer.ts +19 -3
- package/src/runtime/standalone-recovery/service-labels.ts +2 -0
- package/src/runtime/standalone-recovery/watchdog-heartbeat.ts +160 -0
- package/src/runtime/workflow/schedules/browser-probe.ts +258 -0
- package/src/runtime/workflow/schedules/chatgpt-automation-migration.ts +40 -0
- package/src/runtime/workflow/schedules/engine.ts +509 -80
- package/src/runtime/workflow/schedules/github-issue-watch.ts +158 -0
- package/src/runtime/workflow/schedules/settlement.ts +48 -4
- package/src/runtime/workflow/schedules/store.ts +131 -10
- package/src/runtime/workflow/schedules/types.ts +6 -3
- package/src/runtime/workflow/schedules/work-continuation.ts +120 -34
- package/src/testing/test-governance.ts +8 -40
- package/assets/reference-configs/heartbeat-triage.md +0 -57
- package/assets/templates/helpers/harness-trace-grade.sh +0 -150
- package/assets/templates/helpers/heartbeat-triage.sh +0 -361
- package/assets/templates/helpers/maintenance-triage.sh +0 -167
- package/bin/forge-desktop-helper.mjs +0 -204
- package/docs/architecture/current/approved-target-architecture.zh-CN.md +0 -1501
- package/docs/architecture/current/architecture-invariants.md +0 -331
- package/docs/architecture/current/automation-and-schedule-engine.md +0 -453
- package/docs/architecture/current/codegraph-context-provider.md +0 -226
- package/docs/architecture/current/control-plane-authority-inventory.md +0 -94
- package/docs/architecture/current/controller-repository-decoupling.md +0 -148
- package/docs/architecture/current/dispatch-and-agent-strategy.md +0 -354
- package/docs/architecture/current/entity-model.md +0 -592
- package/docs/architecture/current/external-plugin-governance.md +0 -396
- package/docs/architecture/current/failure-recovery.md +0 -447
- package/docs/architecture/current/governance.md +0 -213
- package/docs/architecture/current/human-interaction-plane.md +0 -87
- package/docs/architecture/current/implementation-status.md +0 -128
- package/docs/architecture/current/job-and-run-lifecycle.md +0 -413
- package/docs/architecture/current/legacy-governance.md +0 -58
- package/docs/architecture/current/local-workspace-target-grants.md +0 -70
- package/docs/architecture/current/migration-roadmap.md +0 -105
- package/docs/architecture/current/multi-repository-execution.md +0 -371
- package/docs/architecture/current/operations-runbook.md +0 -45
- package/docs/architecture/current/peer-supercontroller-runtime.md +0 -32
- package/docs/architecture/current/personal-assistant-plugin-baseline.md +0 -206
- package/docs/architecture/current/repository-access-modes.md +0 -102
- package/docs/architecture/current/route-policy.md +0 -15
- package/docs/architecture/current/route-session-concurrency.md +0 -85
- package/docs/architecture/current/runtime-architecture-simplification.md +0 -214
- package/docs/architecture/current/runtime-directory-map.md +0 -49
- package/docs/architecture/current/runtime-health-and-resource-lifecycle.md +0 -85
- package/docs/architecture/current/scheduler-and-resource-claims.md +0 -422
- package/docs/architecture/current/session-aware-execution-and-authorization.md +0 -77
- package/docs/architecture/current/system-overview.md +0 -144
- package/docs/architecture/current/target-requirements-traceability.md +0 -202
- package/docs/architecture/current/thin-harness-governance-policy.md +0 -32
- package/docs/architecture/current/thin-harness-v1.md +0 -311
- package/docs/architecture/current/verification-and-release-gates.md +0 -391
- package/docs/forge-autonomous-goal-loop.md +0 -9
- package/docs/forge-provider-configuration.md +0 -7
- package/docs/operations/chatgpt-personal-assistant-runtime.md +0 -83
- package/docs/operations/controller-desktop-plugin.md +0 -112
- package/docs/operations/gmail-assistant-routines.md +0 -186
- package/docs/operations/personal-assistant-operator-guide.md +0 -61
- package/docs/operations/provider-configuration.md +0 -31
- package/scripts/harness-trace-grade.sh +0 -150
- package/scripts/heartbeat-triage.sh +0 -361
- package/scripts/maintenance-triage.sh +0 -167
- package/src/cli/local-bridge/frontend/src/features/settings/SettingsView.tsx +0 -2
- package/src/cli/repositories/goal-registry.ts +0 -239
- package/src/runtime/assistant/action-proposals.ts +0 -279
- package/src/runtime/assistant/intent.ts +0 -395
- package/src/runtime/assistant/model-provider.ts +0 -460
- package/src/runtime/assistant/openapi.ts +0 -223
- package/src/runtime/assistant/policy.ts +0 -89
- package/src/runtime/assistant/readiness.ts +0 -258
- package/src/runtime/assistant/routine-runtime.ts +0 -477
- package/src/runtime/assistant/schedule-binding.ts +0 -171
- package/src/runtime/assistant/standing-grants.ts +0 -375
- package/src/runtime/assistant/store.ts +0 -243
- package/src/runtime/assistant/types.ts +0 -135
- package/src/runtime/control-plane/goal-loop/config-facade.ts +0 -700
- package/src/runtime/control-plane/goal-loop/config-store.ts +0 -570
- package/src/runtime/control-plane/goal-loop/config-types.ts +0 -213
- package/src/runtime/control-plane/goal-loop/executor-router.ts +0 -109
- package/src/runtime/control-plane/goal-loop/failure-taxonomy.ts +0 -149
- package/src/runtime/control-plane/goal-loop/goal-contract-store.ts +0 -304
- package/src/runtime/control-plane/goal-loop/goal-loop-engine.ts +0 -1001
- package/src/runtime/control-plane/goal-loop/handoff-packet.ts +0 -160
- package/src/runtime/control-plane/goal-loop/index.ts +0 -13
- package/src/runtime/control-plane/goal-loop/local-tools.ts +0 -290
- package/src/runtime/control-plane/goal-loop/policy-gates.ts +0 -203
- package/src/runtime/control-plane/goal-loop/provider-dispatch.ts +0 -93
- package/src/runtime/control-plane/goal-loop/provider-registry.ts +0 -545
- package/src/runtime/control-plane/goal-loop/types.ts +0 -378
- package/src/runtime/control-plane/startup-recovery.ts +0 -198
- package/src/runtime/gateway/mcp/resource-policy.ts +0 -186
- package/src/runtime/personal-assistant/gmail-triage-manager.ts +0 -231
- package/src/runtime/personal-assistant/reporting-runtime.ts +0 -344
- package/src/runtime/personal-assistant/triage-runtime.ts +0 -748
- package/src/runtime/plugins/desktop-adapter.ts +0 -374
- package/src/runtime/safe-tooling/browser-safe.ts +0 -133
- package/src/runtime/workflow/findings/store.ts +0 -204
- package/src/runtime/workflow/findings/types.ts +0 -58
- package/src/runtime/workflow/portfolio/engine.ts +0 -20
- package/src/runtime/workflow/portfolio/store.ts +0 -185
- package/src/runtime/workflow/portfolio/types.ts +0 -29
|
@@ -1,17 +1,32 @@
|
|
|
1
|
-
import { randomUUID } from 'crypto';
|
|
2
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
import { buildBrowserPluginManifest, executeBrowserPluginAction } from '../../plugins/browser-adapter';
|
|
3
3
|
import { controllerPluginRepository } from '../../plugins/store';
|
|
4
4
|
import { getWorkContract } from '../facade/work-contract-store';
|
|
5
5
|
import {
|
|
6
6
|
bindChatgptWorkConversation,
|
|
7
7
|
getChatgptWorkConversationBinding,
|
|
8
|
+
parseChatgptConversationIdentity,
|
|
9
|
+
rebindChatgptWorkConversation,
|
|
8
10
|
type ChatgptWorkConversationBinding,
|
|
9
11
|
} from './chatgpt-work-binding-store';
|
|
10
12
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
13
|
+
const LEGACY_CONTROLLER_CHATGPT_SESSION_ID = 'forge-chatgpt-supercontroller';
|
|
14
|
+
export const DEFAULT_CHATGPT_AUTOMATION_MODEL = 'gpt-5.6';
|
|
15
|
+
export const DEFAULT_CHATGPT_AUTOMATION_REASONING = 'high';
|
|
16
|
+
export const DEFAULT_CHATGPT_AUTOMATION_TAB_POLICY = 'auto';
|
|
17
|
+
export const DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION = '@forge';
|
|
18
|
+
const CHATGPT_PROMPT_SELECTOR = 'div#prompt-textarea[contenteditable="true"]';
|
|
19
|
+
const CHATGPT_SEND_SELECTOR = '[data-testid="send-button"], button[aria-label*="Send"], button[data-testid*="send"]';
|
|
20
|
+
const CHATGPT_INTELLIGENCE_CONTROL_SELECTORS = [
|
|
21
|
+
'main button, main [role="button"]',
|
|
22
|
+
'button, [role="button"]',
|
|
23
|
+
] as const;
|
|
24
|
+
const CHATGPT_CAPABILITY_SLIDER_SELECTOR = '[role="slider"]';
|
|
25
|
+
const CHATGPT_CAPABILITY_MENUITEM_SELECTOR = '[role="menuitem"][aria-keyshortcuts~="ArrowLeft"][aria-keyshortcuts~="ArrowRight"]';
|
|
26
|
+
|
|
27
|
+
export type ChatgptAutomationReasoning = 'medium' | 'high' | 'xhigh';
|
|
28
|
+
export type ChatgptAutomationTabPolicy = 'auto' | 'reuse' | 'new';
|
|
29
|
+
export type ChatgptAutomationTabCleanupStatus = 'closed' | 'preserved_user_owned' | 'session_closed' | 'failed';
|
|
15
30
|
|
|
16
31
|
export interface WorkChatgptContinuationInput {
|
|
17
32
|
controllerHome: string;
|
|
@@ -22,6 +37,9 @@ export interface WorkChatgptContinuationInput {
|
|
|
22
37
|
title?: string;
|
|
23
38
|
browserSessionId?: string;
|
|
24
39
|
conversationUrl?: string;
|
|
40
|
+
model?: string;
|
|
41
|
+
reasoning?: ChatgptAutomationReasoning;
|
|
42
|
+
tabPolicy?: ChatgptAutomationTabPolicy;
|
|
25
43
|
timeoutMs?: number;
|
|
26
44
|
}
|
|
27
45
|
|
|
@@ -33,13 +51,46 @@ export interface WorkChatgptContinuationResult {
|
|
|
33
51
|
conversationId?: string;
|
|
34
52
|
localAlias?: string;
|
|
35
53
|
resumedFromBinding: boolean;
|
|
54
|
+
model: string;
|
|
55
|
+
reasoning: ChatgptAutomationReasoning;
|
|
56
|
+
tabPolicy: ChatgptAutomationTabPolicy;
|
|
57
|
+
executionPreferenceVerified: boolean;
|
|
58
|
+
tabCleanupStatus?: ChatgptAutomationTabCleanupStatus;
|
|
59
|
+
tabCleanupError?: { code: string; message: string };
|
|
36
60
|
error?: { code: string; message: string };
|
|
37
61
|
}
|
|
38
62
|
|
|
63
|
+
export interface StandaloneChatgptPromptInput {
|
|
64
|
+
controllerHome: string;
|
|
65
|
+
repoId: string;
|
|
66
|
+
scopeId: string;
|
|
67
|
+
prompt: string;
|
|
68
|
+
browserSessionId?: string;
|
|
69
|
+
conversationUrl?: string;
|
|
70
|
+
model?: string;
|
|
71
|
+
reasoning?: ChatgptAutomationReasoning;
|
|
72
|
+
tabPolicy?: ChatgptAutomationTabPolicy;
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const workflowToolAttributionInstruction = (workId: string): string => `Forge Workflow execution contract: first claim exact Work ${workId}. For every repository_command_execute and repository_safe_patch_apply call in this turn, pass work_id=${workId} explicitly. MCP transport sessions may change between tool calls; never omit this Work id.`;
|
|
77
|
+
|
|
39
78
|
function requestId(workId: string, actionId: string): string {
|
|
40
79
|
return `chatgpt-work:${workId}:${actionId}:${randomUUID()}`;
|
|
41
80
|
}
|
|
42
81
|
|
|
82
|
+
const CHATGPT_BROWSER_TRANSPORT_OVERRIDES = {
|
|
83
|
+
browser_mode: 'attach_preferred',
|
|
84
|
+
cdp_attach_fallback: 'fail_closed',
|
|
85
|
+
native_attach_mode: 'auto',
|
|
86
|
+
native_browser_candidates: ['chrome'],
|
|
87
|
+
} as const;
|
|
88
|
+
|
|
89
|
+
export function chatgptBrowserActionArgs(actionId: string, args: Record<string, unknown>): Record<string, unknown> {
|
|
90
|
+
if (actionId === 'configure' || actionId === 'list_sessions' || actionId === 'reconcile_sessions') return args;
|
|
91
|
+
return { ...args, ...CHATGPT_BROWSER_TRANSPORT_OVERRIDES };
|
|
92
|
+
}
|
|
93
|
+
|
|
43
94
|
async function controllerBrowserAction(
|
|
44
95
|
controllerHome: string,
|
|
45
96
|
workId: string,
|
|
@@ -57,7 +108,7 @@ async function controllerBrowserAction(
|
|
|
57
108
|
pluginId: 'browser',
|
|
58
109
|
actionId,
|
|
59
110
|
requestId: requestId(workId, actionId),
|
|
60
|
-
args,
|
|
111
|
+
args: chatgptBrowserActionArgs(actionId, args),
|
|
61
112
|
timeoutMs,
|
|
62
113
|
origin: { surface: 'schedule', actor: 'chatgpt-work-continuation' },
|
|
63
114
|
});
|
|
@@ -67,31 +118,547 @@ async function ensureControllerChatgptBrowser(controllerHome: string, workId: st
|
|
|
67
118
|
const repository = controllerPluginRepository(controllerHome);
|
|
68
119
|
const repoRoot = repository.canonicalRoot ?? repository.localRoot;
|
|
69
120
|
if (!repoRoot) throw new Error('CHATGPT_CONTROLLER_BROWSER_ROOT_UNAVAILABLE');
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
profile_mode: 'repo_local',
|
|
77
|
-
browser_channel: 'chromium',
|
|
78
|
-
cdp_attach_fallback: 'fail_closed',
|
|
79
|
-
native_attach_mode: 'auto',
|
|
80
|
-
native_browser_candidates: ['chrome'],
|
|
81
|
-
} : {}),
|
|
82
|
-
allowed_domains: allowedDomains,
|
|
83
|
-
});
|
|
121
|
+
// Browser configure is not a read: it persists configuration and closes managed
|
|
122
|
+
// contexts. Scheduled continuations must not disturb an already-enabled provider
|
|
123
|
+
// merely to prove it is available. Only enable it when the persisted authority is
|
|
124
|
+
// explicitly disabled; action-level transport overrides still fail closed later.
|
|
125
|
+
if (buildBrowserPluginManifest(0, undefined, repoRoot).enabled) return;
|
|
126
|
+
await controllerBrowserAction(controllerHome, workId, 'configure', { enabled: true });
|
|
84
127
|
}
|
|
85
128
|
|
|
86
129
|
function stringField(value: unknown): string | undefined {
|
|
87
130
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
88
131
|
}
|
|
89
132
|
|
|
133
|
+
function normalizeModel(value?: string): string {
|
|
134
|
+
const model = value?.trim().toLowerCase() || DEFAULT_CHATGPT_AUTOMATION_MODEL;
|
|
135
|
+
if (model === 'gpt-5.6' || model === 'gpt-5.6-sol' || model === '5.6' || model === '5.6s') return DEFAULT_CHATGPT_AUTOMATION_MODEL;
|
|
136
|
+
throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${value}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeReasoning(value?: ChatgptAutomationReasoning): ChatgptAutomationReasoning {
|
|
140
|
+
return value ?? DEFAULT_CHATGPT_AUTOMATION_REASONING;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeTabPolicy(value?: ChatgptAutomationTabPolicy): ChatgptAutomationTabPolicy {
|
|
144
|
+
return value ?? DEFAULT_CHATGPT_AUTOMATION_TAB_POLICY;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function stableChatgptWorkBrowserSessionId(repoId: string, workId: string): string {
|
|
148
|
+
const digest = createHash('sha256').update(`${repoId}\n${workId}`).digest('hex').slice(0, 20);
|
|
149
|
+
return `forge-chatgpt-work-${digest}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function stableStandaloneChatgptBrowserSessionId(repoId: string, scopeId: string): string {
|
|
153
|
+
const digest = createHash('sha256').update(`${repoId}\nstandalone\n${scopeId}`).digest('hex').slice(0, 20);
|
|
154
|
+
return `forge-chatgpt-standalone-${digest}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function resolveStandaloneChatgptBrowserSessionId(input: StandaloneChatgptPromptInput): string {
|
|
158
|
+
const policy = normalizeTabPolicy(input.tabPolicy);
|
|
159
|
+
const stable = stableStandaloneChatgptBrowserSessionId(input.repoId, input.scopeId);
|
|
160
|
+
if (policy === 'new') return `${stable}-${randomUUID().slice(0, 8)}`;
|
|
161
|
+
return input.browserSessionId?.trim() || stable;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function resolveChatgptWorkBrowserSessionId(input: {
|
|
165
|
+
repoId: string;
|
|
166
|
+
workId: string;
|
|
167
|
+
tabPolicy?: ChatgptAutomationTabPolicy;
|
|
168
|
+
explicitSessionId?: string;
|
|
169
|
+
boundSessionId?: string;
|
|
170
|
+
}): string {
|
|
171
|
+
const policy = normalizeTabPolicy(input.tabPolicy);
|
|
172
|
+
const stable = stableChatgptWorkBrowserSessionId(input.repoId, input.workId);
|
|
173
|
+
if (policy === 'new') return `${stable}-${randomUUID().slice(0, 8)}`;
|
|
174
|
+
const explicit = input.explicitSessionId?.trim();
|
|
175
|
+
if (explicit && explicit !== LEGACY_CONTROLLER_CHATGPT_SESSION_ID) return explicit;
|
|
176
|
+
const bound = input.boundSessionId?.trim();
|
|
177
|
+
if (bound && bound !== LEGACY_CONTROLLER_CHATGPT_SESSION_ID) return bound;
|
|
178
|
+
return stable;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface BrowserQueryMatch {
|
|
182
|
+
text?: string;
|
|
183
|
+
selectorHint?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function queryMatches(result: Record<string, unknown> | undefined): BrowserQueryMatch[] {
|
|
187
|
+
if (!result || !Array.isArray(result.matches)) return [];
|
|
188
|
+
return result.matches.filter((value): value is BrowserQueryMatch => Boolean(value) && typeof value === 'object');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function matchText(value: BrowserQueryMatch): string {
|
|
192
|
+
return typeof value.text === 'string' ? value.text.trim() : '';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function matchSelector(value: BrowserQueryMatch | undefined): string | undefined {
|
|
196
|
+
return typeof value?.selectorHint === 'string' && value.selectorHint.trim() ? value.selectorHint.trim() : undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function normalizeExecutionControlLabel(label: string | undefined): string {
|
|
200
|
+
return (label ?? '').trim().toLowerCase().replace(/[\s_-]+/g, '');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function chatgptAutomationReasoningLevelFromLabel(label: string | undefined): ChatgptAutomationReasoning | undefined {
|
|
204
|
+
const normalized = normalizeExecutionControlLabel(label);
|
|
205
|
+
if (!normalized) return undefined;
|
|
206
|
+
|
|
207
|
+
const exact = new Map<string, ChatgptAutomationReasoning>([
|
|
208
|
+
['medium', 'medium'], ['中', 'medium'], ['中等', 'medium'],
|
|
209
|
+
['high', 'high'], ['高', 'high'],
|
|
210
|
+
['xhigh', 'xhigh'], ['extrahigh', 'xhigh'], ['超高', 'xhigh'], ['极高', 'xhigh'],
|
|
211
|
+
]);
|
|
212
|
+
const exactLevel = exact.get(normalized);
|
|
213
|
+
if (exactLevel) return exactLevel;
|
|
214
|
+
|
|
215
|
+
const hasReasoningContext = ['reasoning', 'thinking', '推理', '思考']
|
|
216
|
+
.some((token) => normalized.includes(token));
|
|
217
|
+
if (!hasReasoningContext) return undefined;
|
|
218
|
+
if (['xhigh', 'extrahigh', '超高', '极高'].some((token) => normalized.includes(token))) return 'xhigh';
|
|
219
|
+
if (['medium', '中等'].some((token) => normalized.includes(token))) return 'medium';
|
|
220
|
+
if (['high', '高'].some((token) => normalized.includes(token))) return 'high';
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function modelLabelMatches(label: string | undefined, model: string): boolean {
|
|
225
|
+
if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL || !label) return false;
|
|
226
|
+
const normalized = normalizeExecutionControlLabel(label);
|
|
227
|
+
return normalized.includes('5.6sol') || normalized.includes('gpt5.6sol');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function reasoningLabelMatches(label: string | undefined, reasoning: ChatgptAutomationReasoning): boolean {
|
|
231
|
+
return chatgptAutomationReasoningLevelFromLabel(label) === reasoning;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function isReasoningControlLabel(label: string | undefined): boolean {
|
|
235
|
+
return reasoningLabelMatches(label, 'medium')
|
|
236
|
+
|| reasoningLabelMatches(label, 'high')
|
|
237
|
+
|| reasoningLabelMatches(label, 'xhigh');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function reasoningSliderValue(reasoning: ChatgptAutomationReasoning): number {
|
|
241
|
+
if (reasoning === 'medium') return 2;
|
|
242
|
+
if (reasoning === 'high') return 3;
|
|
243
|
+
return 4;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function findChatgptIntelligenceControl(
|
|
247
|
+
controllerHome: string,
|
|
248
|
+
workId: string,
|
|
249
|
+
browserSessionId: string,
|
|
250
|
+
timeoutMs?: number,
|
|
251
|
+
): Promise<BrowserQueryMatch | undefined> {
|
|
252
|
+
for (const selector of CHATGPT_INTELLIGENCE_CONTROL_SELECTORS) {
|
|
253
|
+
const result = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
254
|
+
session_id: browserSessionId,
|
|
255
|
+
selector,
|
|
256
|
+
// Prefer the composer/main region so sidebar history cannot crowd the
|
|
257
|
+
// reasoning control out of a bounded query. Keep a larger global fallback
|
|
258
|
+
// for ChatGPT layouts that place the control outside <main>.
|
|
259
|
+
limit: selector.startsWith('main ') ? 80 : 240,
|
|
260
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
261
|
+
}, timeoutMs);
|
|
262
|
+
const match = queryMatches(result).find((candidate) => {
|
|
263
|
+
const label = matchText(candidate);
|
|
264
|
+
return modelLabelMatches(label, DEFAULT_CHATGPT_AUTOMATION_MODEL) || isReasoningControlLabel(label);
|
|
265
|
+
});
|
|
266
|
+
if (match) return match;
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function chatgptAutomationControlWaitBudgets(timeoutMs?: number): { waitBudgetMs: number; probeTimeoutMs: number } {
|
|
272
|
+
const waitBudgetMs = Math.min(Math.max(timeoutMs ?? 30_000, 1_000), 30_000);
|
|
273
|
+
// One probe checks both the preferred <main> scope and the global fallback.
|
|
274
|
+
// Keep each selector query short enough that one slow pre-hydration pass cannot
|
|
275
|
+
// consume the whole readiness window before React exposes the reasoning control.
|
|
276
|
+
return { waitBudgetMs, probeTimeoutMs: Math.min(waitBudgetMs, 2_500) };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function waitForChatgptIntelligenceControl(
|
|
280
|
+
controllerHome: string,
|
|
281
|
+
workId: string,
|
|
282
|
+
browserSessionId: string,
|
|
283
|
+
timeoutMs?: number,
|
|
284
|
+
): Promise<BrowserQueryMatch | undefined> {
|
|
285
|
+
const { waitBudgetMs, probeTimeoutMs } = chatgptAutomationControlWaitBudgets(timeoutMs);
|
|
286
|
+
const deadline = Date.now() + waitBudgetMs;
|
|
287
|
+
do {
|
|
288
|
+
const control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, probeTimeoutMs);
|
|
289
|
+
if (control) return control;
|
|
290
|
+
if (Date.now() >= deadline) break;
|
|
291
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 500));
|
|
292
|
+
} while (Date.now() < deadline);
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export function chatgptAutomationPageFailure(
|
|
297
|
+
bodyText: string | undefined,
|
|
298
|
+
composerAvailable: boolean,
|
|
299
|
+
): 'CHATGPT_AUTOMATION_LOGIN_REQUIRED' | 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE' | undefined {
|
|
300
|
+
if (composerAvailable) return undefined;
|
|
301
|
+
const normalized = (bodyText ?? '').toLowerCase();
|
|
302
|
+
const loginMarkers = [
|
|
303
|
+
'log in', 'sign up', 'continue with google', 'continue with apple',
|
|
304
|
+
'登录', '登陆', '注册', '使用 google 继续', '使用 apple 继续',
|
|
305
|
+
];
|
|
306
|
+
return loginMarkers.some((marker) => normalized.includes(marker))
|
|
307
|
+
? 'CHATGPT_AUTOMATION_LOGIN_REQUIRED'
|
|
308
|
+
: 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function assertChatgptAutomationComposerReady(
|
|
312
|
+
controllerHome: string,
|
|
313
|
+
workId: string,
|
|
314
|
+
browserSessionId: string,
|
|
315
|
+
timeoutMs?: number,
|
|
316
|
+
): Promise<void> {
|
|
317
|
+
const composerResult = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
318
|
+
session_id: browserSessionId,
|
|
319
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
320
|
+
limit: 1,
|
|
321
|
+
timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
|
|
322
|
+
}, timeoutMs).catch(() => undefined);
|
|
323
|
+
const composerAvailable = queryMatches(composerResult).length > 0;
|
|
324
|
+
if (composerAvailable) return;
|
|
325
|
+
|
|
326
|
+
const bodyResult = await controllerBrowserAction(controllerHome, workId, 'get_text', {
|
|
327
|
+
session_id: browserSessionId,
|
|
328
|
+
selector: 'body',
|
|
329
|
+
max_chars: 4_000,
|
|
330
|
+
timeout_ms: Math.min(timeoutMs ?? 5_000, 5_000),
|
|
331
|
+
}, timeoutMs).catch(() => undefined);
|
|
332
|
+
const failure = chatgptAutomationPageFailure(stringField(bodyResult?.text), composerAvailable);
|
|
333
|
+
throw new Error(failure ?? 'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function ensureChatgptExecutionPreference(
|
|
337
|
+
controllerHome: string,
|
|
338
|
+
workId: string,
|
|
339
|
+
browserSessionId: string,
|
|
340
|
+
model: string,
|
|
341
|
+
reasoning: ChatgptAutomationReasoning,
|
|
342
|
+
timeoutMs?: number,
|
|
343
|
+
): Promise<boolean> {
|
|
344
|
+
if (model !== DEFAULT_CHATGPT_AUTOMATION_MODEL) throw new Error(`CHATGPT_AUTOMATION_MODEL_UNSUPPORTED:${model}`);
|
|
345
|
+
let control = await waitForChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs);
|
|
346
|
+
if (!control) {
|
|
347
|
+
await assertChatgptAutomationComposerReady(controllerHome, workId, browserSessionId, timeoutMs);
|
|
348
|
+
throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
|
|
349
|
+
}
|
|
350
|
+
// Current ChatGPT UI exposes the reasoning level (for example `高`) on the
|
|
351
|
+
// composer control instead of the model label. The model remains fail-closed
|
|
352
|
+
// through normalizeModel above; only the user-adjustable reasoning control is
|
|
353
|
+
// required to be observable in the page before unattended dispatch proceeds.
|
|
354
|
+
if (reasoningLabelMatches(matchText(control), reasoning)) return true;
|
|
355
|
+
let controlSelector = matchSelector(control);
|
|
356
|
+
if (!controlSelector) throw new Error('CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE');
|
|
357
|
+
|
|
358
|
+
const expanded = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
359
|
+
session_id: browserSessionId,
|
|
360
|
+
selector: controlSelector,
|
|
361
|
+
attribute: 'aria-expanded',
|
|
362
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
363
|
+
}, timeoutMs).catch(() => undefined);
|
|
364
|
+
if (stringField(expanded?.value) !== 'true') {
|
|
365
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
366
|
+
session_id: browserSessionId,
|
|
367
|
+
selector: controlSelector,
|
|
368
|
+
key: 'ArrowDown',
|
|
369
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
370
|
+
post_action_wait_ms: 150,
|
|
371
|
+
}, timeoutMs);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const currentValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
375
|
+
session_id: browserSessionId,
|
|
376
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
377
|
+
attribute: 'aria-valuenow',
|
|
378
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
379
|
+
}, timeoutMs).catch(() => undefined);
|
|
380
|
+
const currentValue = Number(stringField(currentValueResult?.value));
|
|
381
|
+
const targetValue = reasoningSliderValue(reasoning);
|
|
382
|
+
if (!Number.isInteger(currentValue) || currentValue < 0 || currentValue > 4) {
|
|
383
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_STATE_UNAVAILABLE:${reasoning}`);
|
|
384
|
+
}
|
|
385
|
+
const direction = targetValue > currentValue ? 'ArrowRight' : 'ArrowLeft';
|
|
386
|
+
for (let index = 0; index < Math.abs(targetValue - currentValue); index += 1) {
|
|
387
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
388
|
+
session_id: browserSessionId,
|
|
389
|
+
selector: CHATGPT_CAPABILITY_MENUITEM_SELECTOR,
|
|
390
|
+
key: direction,
|
|
391
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
392
|
+
post_action_wait_ms: 120,
|
|
393
|
+
}, timeoutMs);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const verifiedValueResult = await controllerBrowserAction(controllerHome, workId, 'get_attribute', {
|
|
397
|
+
session_id: browserSessionId,
|
|
398
|
+
selector: CHATGPT_CAPABILITY_SLIDER_SELECTOR,
|
|
399
|
+
attribute: 'aria-valuenow',
|
|
400
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
401
|
+
}, timeoutMs).catch(() => undefined);
|
|
402
|
+
if (Number(stringField(verifiedValueResult?.value)) !== targetValue) {
|
|
403
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
|
|
404
|
+
}
|
|
405
|
+
control = await findChatgptIntelligenceControl(controllerHome, workId, browserSessionId, timeoutMs) ?? control;
|
|
406
|
+
if (isReasoningControlLabel(matchText(control)) && !reasoningLabelMatches(matchText(control), reasoning)) {
|
|
407
|
+
throw new Error(`CHATGPT_AUTOMATION_REASONING_NOT_VERIFIED:${reasoning}`);
|
|
408
|
+
}
|
|
409
|
+
controlSelector = matchSelector(control) ?? controlSelector;
|
|
410
|
+
await controllerBrowserAction(controllerHome, workId, 'press', {
|
|
411
|
+
session_id: browserSessionId,
|
|
412
|
+
selector: controlSelector,
|
|
413
|
+
key: 'Escape',
|
|
414
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
415
|
+
post_action_wait_ms: 100,
|
|
416
|
+
}, timeoutMs).catch(() => undefined);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
90
420
|
function resultUrl(result: Record<string, unknown>): string | undefined {
|
|
91
421
|
return stringField(result.url)
|
|
92
422
|
?? stringField((result.session as Record<string, unknown> | undefined)?.url);
|
|
93
423
|
}
|
|
94
424
|
|
|
425
|
+
function resultSessionId(result: Record<string, unknown>): string | undefined {
|
|
426
|
+
return stringField(result.session_id)
|
|
427
|
+
?? stringField((result.session as Record<string, unknown> | undefined)?.sessionId);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function chatgptAutomationNavigationRequiresReplacement(error: unknown): boolean {
|
|
431
|
+
if (!(error instanceof Error)) return false;
|
|
432
|
+
return [
|
|
433
|
+
'BROWSER_AUTOMATION_BACKGROUND_NAVIGATION_REQUIRES_REPLACEMENT',
|
|
434
|
+
'PLUGIN_BROWSER_SESSION_STATE_LOST',
|
|
435
|
+
'PLUGIN_SESSION_NOT_FOUND',
|
|
436
|
+
].some((marker) => error.message.includes(marker));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function closeChatgptAutomationTabAfterDispatch(
|
|
440
|
+
controllerHome: string,
|
|
441
|
+
workId: string,
|
|
442
|
+
browserSessionId: string,
|
|
443
|
+
timeoutMs?: number,
|
|
444
|
+
): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
|
|
445
|
+
try {
|
|
446
|
+
const closed = await controllerBrowserAction(controllerHome, workId, 'close_page', {
|
|
447
|
+
session_id: browserSessionId,
|
|
448
|
+
}, Math.min(timeoutMs ?? 15_000, 15_000));
|
|
449
|
+
if (closed.preservedUserOwnedTab === true) return { status: 'preserved_user_owned' };
|
|
450
|
+
if (closed.resourceClosed === true) return { status: 'closed' };
|
|
451
|
+
return { status: 'session_closed' };
|
|
452
|
+
} catch (error) {
|
|
453
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
454
|
+
return {
|
|
455
|
+
status: 'failed',
|
|
456
|
+
error: {
|
|
457
|
+
code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_AUTOMATION_TAB_CLEANUP_FAILED',
|
|
458
|
+
message,
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Settles the browser resource for one completed Work-bound ChatGPT round.
|
|
466
|
+
* Conversation identity is durable in the Work binding and intentionally
|
|
467
|
+
* survives this ephemeral tab/session cleanup. Browser ownership policy keeps
|
|
468
|
+
* user-owned native tabs intact.
|
|
469
|
+
*/
|
|
470
|
+
export async function settleWorkChatgptAutomationTab(input: {
|
|
471
|
+
controllerHome: string;
|
|
472
|
+
workId: string;
|
|
473
|
+
browserSessionId: string;
|
|
474
|
+
timeoutMs?: number;
|
|
475
|
+
}): Promise<{ status: ChatgptAutomationTabCleanupStatus; error?: { code: string; message: string } }> {
|
|
476
|
+
return closeChatgptAutomationTabAfterDispatch(
|
|
477
|
+
input.controllerHome,
|
|
478
|
+
input.workId,
|
|
479
|
+
input.browserSessionId,
|
|
480
|
+
input.timeoutMs,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function isChatgptConversationUrl(value: string): boolean {
|
|
485
|
+
try {
|
|
486
|
+
const url = new URL(value);
|
|
487
|
+
return url.protocol === 'https:'
|
|
488
|
+
&& ['chatgpt.com', 'www.chatgpt.com', 'chat.openai.com'].includes(url.hostname)
|
|
489
|
+
&& /\/c\/[^/?#]+/.test(url.pathname);
|
|
490
|
+
} catch {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function navigateWorkConversation(
|
|
496
|
+
controllerHome: string,
|
|
497
|
+
workId: string,
|
|
498
|
+
browserSessionId: string,
|
|
499
|
+
targetUrl: string,
|
|
500
|
+
timeoutMs?: number,
|
|
501
|
+
): Promise<{ submissionTargetUrl: string; recoveredFromStaleBinding: boolean; browserSessionId: string }> {
|
|
502
|
+
const navigate = async (sessionId: string, url: string) => controllerBrowserAction(controllerHome, workId, 'navigate', {
|
|
503
|
+
session_id: sessionId,
|
|
504
|
+
url,
|
|
505
|
+
wait_until: 'domcontentloaded',
|
|
506
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
507
|
+
retries: 1,
|
|
508
|
+
}, timeoutMs);
|
|
509
|
+
const openReplacement = async (url: string): Promise<string> => {
|
|
510
|
+
const opened = await controllerBrowserAction(controllerHome, workId, 'open_page', {
|
|
511
|
+
url,
|
|
512
|
+
wait_until: 'domcontentloaded',
|
|
513
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
514
|
+
retries: 1,
|
|
515
|
+
}, timeoutMs);
|
|
516
|
+
const replacementSessionId = resultSessionId(opened);
|
|
517
|
+
if (!replacementSessionId) throw new Error('CHATGPT_AUTOMATION_REPLACEMENT_SESSION_NOT_CONFIRMED');
|
|
518
|
+
return replacementSessionId;
|
|
519
|
+
};
|
|
520
|
+
try {
|
|
521
|
+
await navigate(browserSessionId, targetUrl);
|
|
522
|
+
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId };
|
|
523
|
+
} catch (error) {
|
|
524
|
+
if (chatgptAutomationNavigationRequiresReplacement(error)) {
|
|
525
|
+
const replacementSessionId = await openReplacement(targetUrl);
|
|
526
|
+
return { submissionTargetUrl: targetUrl, recoveredFromStaleBinding: false, browserSessionId: replacementSessionId };
|
|
527
|
+
}
|
|
528
|
+
if (!isChatgptConversationUrl(targetUrl)) throw error;
|
|
529
|
+
try {
|
|
530
|
+
await navigate(browserSessionId, 'https://chatgpt.com/');
|
|
531
|
+
return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId };
|
|
532
|
+
} catch (fallbackError) {
|
|
533
|
+
if (!chatgptAutomationNavigationRequiresReplacement(fallbackError)) throw fallbackError;
|
|
534
|
+
const replacementSessionId = await openReplacement('https://chatgpt.com/');
|
|
535
|
+
return { submissionTargetUrl: 'https://chatgpt.com/', recoveredFromStaleBinding: true, browserSessionId: replacementSessionId };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function submitChatgptPrompt(
|
|
541
|
+
controllerHome: string,
|
|
542
|
+
workId: string,
|
|
543
|
+
browserSessionId: string,
|
|
544
|
+
prompt: string,
|
|
545
|
+
targetUrl: string,
|
|
546
|
+
timeoutMs?: number,
|
|
547
|
+
): Promise<string> {
|
|
548
|
+
await controllerBrowserAction(controllerHome, workId, 'fill', {
|
|
549
|
+
session_id: browserSessionId,
|
|
550
|
+
selector: CHATGPT_PROMPT_SELECTOR,
|
|
551
|
+
text: withForgePluginMention(prompt),
|
|
552
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
553
|
+
post_action_wait_ms: 100,
|
|
554
|
+
}, timeoutMs);
|
|
555
|
+
const sent = await controllerBrowserAction(controllerHome, workId, 'click', {
|
|
556
|
+
session_id: browserSessionId,
|
|
557
|
+
selector: CHATGPT_SEND_SELECTOR,
|
|
558
|
+
timeout_ms: timeoutMs ?? 60_000,
|
|
559
|
+
post_action_wait_ms: 1_000,
|
|
560
|
+
}, timeoutMs);
|
|
561
|
+
let observedUrl = resultUrl(sent) ?? targetUrl;
|
|
562
|
+
if (/\/c\/[^/?#]+/.test(observedUrl)) return observedUrl;
|
|
563
|
+
const deadline = Date.now() + Math.min(Math.max(timeoutMs ?? 8_000, 2_000), 8_000);
|
|
564
|
+
do {
|
|
565
|
+
const observed = await controllerBrowserAction(controllerHome, workId, 'query_all', {
|
|
566
|
+
session_id: browserSessionId,
|
|
567
|
+
selector: 'main',
|
|
568
|
+
limit: 1,
|
|
569
|
+
timeout_ms: Math.min(timeoutMs ?? 3_000, 3_000),
|
|
570
|
+
}, timeoutMs).catch(() => undefined);
|
|
571
|
+
observedUrl = observed ? resultUrl(observed) ?? observedUrl : observedUrl;
|
|
572
|
+
if (/\/c\/[^/?#]+/.test(observedUrl)) return observedUrl;
|
|
573
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
574
|
+
} while (Date.now() < deadline);
|
|
575
|
+
throw new Error(`CHATGPT_AUTOMATION_SUBMISSION_NOT_CONFIRMED:${observedUrl}`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export function withForgePluginMention(prompt: string): string {
|
|
579
|
+
const value = prompt.trim();
|
|
580
|
+
if (!value) throw new Error('CHATGPT_AUTOMATION_PROMPT_REQUIRED');
|
|
581
|
+
if (/^@forge(?:\s|$)/i.test(value)) return value;
|
|
582
|
+
return `${DEFAULT_CHATGPT_AUTOMATION_PLUGIN_MENTION} ${value}`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Dispatches a bounded standalone prompt through the same controller-owned
|
|
587
|
+
* ChatGPT browser path without creating or requiring a WorkContract. The scope
|
|
588
|
+
* id is only a stable browser correlation key (for example a Schedule id).
|
|
589
|
+
*/
|
|
590
|
+
export async function runStandaloneChatgptPrompt(input: StandaloneChatgptPromptInput): Promise<WorkChatgptContinuationResult> {
|
|
591
|
+
const model = normalizeModel(input.model);
|
|
592
|
+
const reasoning = normalizeReasoning(input.reasoning);
|
|
593
|
+
const tabPolicy = normalizeTabPolicy(input.tabPolicy);
|
|
594
|
+
const sessionId = resolveStandaloneChatgptBrowserSessionId(input);
|
|
595
|
+
const browserScopeId = `standalone:${input.scopeId}`;
|
|
596
|
+
const seedUrl = input.conversationUrl?.trim();
|
|
597
|
+
|
|
598
|
+
try {
|
|
599
|
+
await ensureControllerChatgptBrowser(input.controllerHome, browserScopeId);
|
|
600
|
+
const targetUrl = seedUrl ?? 'https://chatgpt.com/';
|
|
601
|
+
const navigation = await navigateWorkConversation(
|
|
602
|
+
input.controllerHome,
|
|
603
|
+
browserScopeId,
|
|
604
|
+
sessionId,
|
|
605
|
+
targetUrl,
|
|
606
|
+
input.timeoutMs,
|
|
607
|
+
);
|
|
608
|
+
const executionPreferenceVerified = await ensureChatgptExecutionPreference(
|
|
609
|
+
input.controllerHome,
|
|
610
|
+
browserScopeId,
|
|
611
|
+
navigation.browserSessionId,
|
|
612
|
+
model,
|
|
613
|
+
reasoning,
|
|
614
|
+
input.timeoutMs,
|
|
615
|
+
);
|
|
616
|
+
const observedUrl = await submitChatgptPrompt(
|
|
617
|
+
input.controllerHome,
|
|
618
|
+
browserScopeId,
|
|
619
|
+
navigation.browserSessionId,
|
|
620
|
+
input.prompt,
|
|
621
|
+
navigation.submissionTargetUrl,
|
|
622
|
+
input.timeoutMs,
|
|
623
|
+
);
|
|
624
|
+
const tabCleanup = await closeChatgptAutomationTabAfterDispatch(
|
|
625
|
+
input.controllerHome,
|
|
626
|
+
browserScopeId,
|
|
627
|
+
navigation.browserSessionId,
|
|
628
|
+
input.timeoutMs,
|
|
629
|
+
);
|
|
630
|
+
return {
|
|
631
|
+
status: 'dispatched',
|
|
632
|
+
provider: 'controller-browser',
|
|
633
|
+
browserSessionId: navigation.browserSessionId,
|
|
634
|
+
conversationUrl: observedUrl,
|
|
635
|
+
resumedFromBinding: false,
|
|
636
|
+
model,
|
|
637
|
+
reasoning,
|
|
638
|
+
tabPolicy,
|
|
639
|
+
executionPreferenceVerified,
|
|
640
|
+
tabCleanupStatus: tabCleanup.status,
|
|
641
|
+
...(tabCleanup.error ? { tabCleanupError: tabCleanup.error } : {}),
|
|
642
|
+
};
|
|
643
|
+
} catch (error) {
|
|
644
|
+
return {
|
|
645
|
+
status: 'failed',
|
|
646
|
+
provider: 'controller-browser',
|
|
647
|
+
browserSessionId: sessionId,
|
|
648
|
+
conversationUrl: seedUrl,
|
|
649
|
+
resumedFromBinding: false,
|
|
650
|
+
model,
|
|
651
|
+
reasoning,
|
|
652
|
+
tabPolicy,
|
|
653
|
+
executionPreferenceVerified: false,
|
|
654
|
+
error: {
|
|
655
|
+
code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_CONTROLLER_BROWSER_FAILED',
|
|
656
|
+
message: error instanceof Error ? error.message : String(error),
|
|
657
|
+
},
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
95
662
|
/**
|
|
96
663
|
* Dispatches a bounded resume prompt to one controller-owned ChatGPT Web tab.
|
|
97
664
|
* Chat history is transport context only. Forge Work/Plan/evidence remain authoritative.
|
|
@@ -100,7 +667,16 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
100
667
|
const store = { controllerHome: input.controllerHome, repoId: input.repoId };
|
|
101
668
|
const existing = getChatgptWorkConversationBinding(store, input.workId);
|
|
102
669
|
const seedUrl = input.conversationUrl?.trim() || existing?.conversationUrl;
|
|
103
|
-
const
|
|
670
|
+
const model = normalizeModel(input.model);
|
|
671
|
+
const reasoning = normalizeReasoning(input.reasoning);
|
|
672
|
+
const tabPolicy = normalizeTabPolicy(input.tabPolicy);
|
|
673
|
+
const sessionId = resolveChatgptWorkBrowserSessionId({
|
|
674
|
+
repoId: input.repoId,
|
|
675
|
+
workId: input.workId,
|
|
676
|
+
tabPolicy,
|
|
677
|
+
explicitSessionId: input.browserSessionId,
|
|
678
|
+
boundSessionId: existing?.latestBrowserSessionId,
|
|
679
|
+
});
|
|
104
680
|
let binding: ChatgptWorkConversationBinding | undefined = existing;
|
|
105
681
|
|
|
106
682
|
try {
|
|
@@ -118,44 +694,55 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
118
694
|
});
|
|
119
695
|
}
|
|
120
696
|
const targetUrl = binding?.conversationUrl ?? seedUrl ?? 'https://chatgpt.com/';
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
selector: CHATGPT_PROMPT_SELECTOR,
|
|
138
|
-
key: CHATGPT_SEND_KEY,
|
|
139
|
-
timeout_ms: input.timeoutMs ?? 60_000,
|
|
140
|
-
post_action_wait_ms: 1_500,
|
|
141
|
-
}, input.timeoutMs);
|
|
142
|
-
const observedUrl = resultUrl(sent) ?? targetUrl;
|
|
697
|
+
const navigation = await navigateWorkConversation(
|
|
698
|
+
input.controllerHome,
|
|
699
|
+
input.workId,
|
|
700
|
+
sessionId,
|
|
701
|
+
targetUrl,
|
|
702
|
+
input.timeoutMs,
|
|
703
|
+
);
|
|
704
|
+
const executionPreferenceVerified = await ensureChatgptExecutionPreference(
|
|
705
|
+
input.controllerHome,
|
|
706
|
+
input.workId,
|
|
707
|
+
navigation.browserSessionId,
|
|
708
|
+
model,
|
|
709
|
+
reasoning,
|
|
710
|
+
input.timeoutMs,
|
|
711
|
+
);
|
|
712
|
+
const observedUrl = await submitChatgptPrompt(input.controllerHome, input.workId, navigation.browserSessionId, `${workflowToolAttributionInstruction(input.workId)}\n\n${input.prompt}`, navigation.submissionTargetUrl, input.timeoutMs);
|
|
143
713
|
if (/\/c\/[^/?#]+/.test(observedUrl)) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
714
|
+
const observedIdentity = parseChatgptConversationIdentity(observedUrl);
|
|
715
|
+
binding = binding && binding.conversationId !== observedIdentity.conversationId
|
|
716
|
+
? rebindChatgptWorkConversation(store, {
|
|
717
|
+
workId: input.workId,
|
|
718
|
+
previousConversationId: binding.conversationId,
|
|
719
|
+
conversationUrl: observedUrl,
|
|
720
|
+
latestBrowserSessionId: navigation.browserSessionId,
|
|
721
|
+
localAlias: binding.localAlias ?? input.title,
|
|
722
|
+
})
|
|
723
|
+
: bindChatgptWorkConversation(store, {
|
|
724
|
+
workId: input.workId,
|
|
725
|
+
conversationUrl: observedUrl,
|
|
726
|
+
latestBrowserSessionId: navigation.browserSessionId,
|
|
727
|
+
localAlias: binding?.localAlias ?? input.title,
|
|
728
|
+
});
|
|
150
729
|
}
|
|
730
|
+
// Work-bound dispatch keeps the Forge-owned automation tab/session alive for
|
|
731
|
+
// the launched semantic Controller round. The exact Controller release path
|
|
732
|
+
// settles it after an explicit disposition. Closing here would race the new
|
|
733
|
+
// ChatGPT round and make dispatch success look like round completion.
|
|
151
734
|
return {
|
|
152
735
|
status: 'dispatched',
|
|
153
736
|
provider: 'controller-browser',
|
|
154
|
-
browserSessionId:
|
|
737
|
+
browserSessionId: navigation.browserSessionId,
|
|
155
738
|
conversationUrl: binding?.conversationUrl ?? observedUrl,
|
|
156
739
|
conversationId: binding?.conversationId,
|
|
157
740
|
localAlias: binding?.localAlias,
|
|
158
741
|
resumedFromBinding: Boolean(existing),
|
|
742
|
+
model,
|
|
743
|
+
reasoning,
|
|
744
|
+
tabPolicy,
|
|
745
|
+
executionPreferenceVerified,
|
|
159
746
|
};
|
|
160
747
|
} catch (error) {
|
|
161
748
|
return {
|
|
@@ -166,6 +753,10 @@ export async function runWorkChatgptContinuation(input: WorkChatgptContinuationI
|
|
|
166
753
|
conversationId: binding?.conversationId,
|
|
167
754
|
localAlias: binding?.localAlias,
|
|
168
755
|
resumedFromBinding: Boolean(existing),
|
|
756
|
+
model,
|
|
757
|
+
reasoning,
|
|
758
|
+
tabPolicy,
|
|
759
|
+
executionPreferenceVerified: false,
|
|
169
760
|
error: {
|
|
170
761
|
code: error instanceof Error && error.message.includes(':') ? error.message.split(':', 1)[0] : 'CHATGPT_CONTROLLER_BROWSER_FAILED',
|
|
171
762
|
message: error instanceof Error ? error.message : String(error),
|