@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
|
@@ -9,10 +9,15 @@ import {
|
|
|
9
9
|
previewAutomaticRuntimeMaintenance,
|
|
10
10
|
type RuntimeMaintenanceActionId,
|
|
11
11
|
} from '../../recovery/maintenance-executor';
|
|
12
|
-
import {
|
|
13
|
-
import { getControllerSession, getWorkContract, isTerminalWorkContractStatus, listHandoffItems } from '../../control-plane/facade';
|
|
12
|
+
import { appendWorkEvidence, controllerSessionBlocksRecovery, getControllerSession, getWorkContract, isTerminalWorkContractStatus, listHandoffItems } from '../../control-plane/facade';
|
|
14
13
|
import { launchSuperController } from '../../control-plane/launcher/thin-launcher';
|
|
15
14
|
import { getExternalControllerLaunchReservation } from '../../control-plane/launcher/launch-reservation-store';
|
|
15
|
+
import { runStandaloneChatgptPrompt, runWorkChatgptContinuation } from '../../control-plane/launcher/chatgpt-work-continuation';
|
|
16
|
+
import {
|
|
17
|
+
beginInitialControllerRoundDispatch,
|
|
18
|
+
buildControllerRoundRelayPrompt,
|
|
19
|
+
finishControllerRoundRelayDispatch,
|
|
20
|
+
} from '../../control-plane/facade/controller-round-relay';
|
|
16
21
|
import {
|
|
17
22
|
getOccurrence,
|
|
18
23
|
getSchedule,
|
|
@@ -24,7 +29,9 @@ import {
|
|
|
24
29
|
saveSchedule,
|
|
25
30
|
saveScheduleDecision,
|
|
26
31
|
} from './store';
|
|
27
|
-
import { applyScheduleFailure } from './settlement';
|
|
32
|
+
import { applyScheduleFailure, applyScheduleRetryableFailure } from './settlement';
|
|
33
|
+
import { classifyScheduledBrowserObservation, executeScheduledBrowserProbe } from './browser-probe';
|
|
34
|
+
import { executeScheduledGithubIssueWatch } from './github-issue-watch';
|
|
28
35
|
import type {
|
|
29
36
|
RepositorySchedule,
|
|
30
37
|
ScheduleDecisionType,
|
|
@@ -35,12 +42,24 @@ import type {
|
|
|
35
42
|
const execFileAsync = promisify(execFile);
|
|
36
43
|
const LIVE_MAINTENANCE_OPERATION = 'runtime_maintenance_apply';
|
|
37
44
|
const EXTERNAL_CONTROLLER_WAKE_OPERATION = 'external_controller_wake';
|
|
45
|
+
const BROWSER_PROBE_OPERATION = 'browser_probe';
|
|
46
|
+
const GITHUB_ISSUE_WATCH_OPERATION = 'github_issue_watch';
|
|
38
47
|
|
|
39
48
|
function isDeterministicSchedule(schedule: RepositorySchedule): boolean {
|
|
40
|
-
// Kernel
|
|
41
|
-
// Semantic / model-backed schedules remain external-controller handoffs.
|
|
49
|
+
// Kernel executes only typed, bounded dispatch operations.
|
|
42
50
|
return schedule.action.operation === LIVE_MAINTENANCE_OPERATION
|
|
43
|
-
|| schedule.action.operation === EXTERNAL_CONTROLLER_WAKE_OPERATION
|
|
51
|
+
|| schedule.action.operation === EXTERNAL_CONTROLLER_WAKE_OPERATION
|
|
52
|
+
|| schedule.action.operation === BROWSER_PROBE_OPERATION
|
|
53
|
+
|| schedule.action.operation === GITHUB_ISSUE_WATCH_OPERATION;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function retiredScheduleReason(schedule: RepositorySchedule): string | undefined {
|
|
57
|
+
if (schedule.action.operation === 'chatgpt_browser_prompt') return 'RETIRED_WORK_FREE_PROMPT_SCHEDULE';
|
|
58
|
+
if (schedule.action.operation === 'assistant_routine_execute') return 'RETIRED_PERSONAL_ASSISTANT_SCHEDULE';
|
|
59
|
+
if ((schedule.trigger.condition as { kind?: string } | undefined)?.kind === 'candidate_observation_threshold') {
|
|
60
|
+
return 'RETIRED_CANDIDATE_FINDING_SCHEDULE';
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
function normalizedWindow(minutes: number, at = Date.now()): string {
|
|
@@ -101,6 +120,12 @@ function cronWindowKey(schedule: RepositorySchedule, at = Date.now()): string {
|
|
|
101
120
|
}
|
|
102
121
|
|
|
103
122
|
function triggerWindowKey(schedule: RepositorySchedule, context: ScheduleTriggerContext | undefined, at = Date.now()): string {
|
|
123
|
+
// Explicit control-plane triggers use a separate identity namespace from timer/cron windows.
|
|
124
|
+
// A caller-supplied event/request id is the stable retry key; otherwise each manual trigger is distinct.
|
|
125
|
+
if (context?.source === 'manual' && schedule.trigger.type !== 'manual') {
|
|
126
|
+
const stableManualKey = context.eventId?.trim();
|
|
127
|
+
return `manual:${stableManualKey || randomUUID()}`;
|
|
128
|
+
}
|
|
104
129
|
switch (schedule.trigger.type) {
|
|
105
130
|
case 'cron':
|
|
106
131
|
return cronWindowKey(schedule, at);
|
|
@@ -223,19 +248,15 @@ async function triggerDue(
|
|
|
223
248
|
const due = condition.kind === 'job_succeeded' ? job?.status === 'succeeded' : terminal;
|
|
224
249
|
return { due, reason: due ? undefined : `Condition ${condition.kind} is not met.`, evidence: { jobId: condition.jobId, status: job?.status ?? 'missing' } };
|
|
225
250
|
}
|
|
226
|
-
const threshold = Math.max(1, condition.observationThreshold ?? 2);
|
|
227
|
-
const finding = listCandidateFindings(controllerHome, schedule.repoId, { includeTerminal: true, limit: 1000 })
|
|
228
|
-
.find((entry) => entry.semanticKey === condition.semanticKey);
|
|
229
|
-
const due = Boolean(finding && finding.observationCount >= threshold);
|
|
230
|
-
return { due, reason: due ? undefined : 'Candidate observation threshold is not met.', evidence: { semanticKey: condition.semanticKey, observations: finding?.observationCount ?? 0, threshold } };
|
|
231
251
|
}
|
|
232
252
|
default:
|
|
233
253
|
return { due: false, reason: 'Unsupported Schedule trigger.' };
|
|
234
254
|
}
|
|
235
255
|
}
|
|
236
256
|
|
|
237
|
-
function
|
|
238
|
-
if (schedule.action.operation !== EXTERNAL_CONTROLLER_WAKE_OPERATION
|
|
257
|
+
function workBoundScheduleWorkId(schedule: RepositorySchedule): string | undefined {
|
|
258
|
+
if (schedule.action.operation !== EXTERNAL_CONTROLLER_WAKE_OPERATION
|
|
259
|
+
&& schedule.action.operation !== BROWSER_PROBE_OPERATION) return undefined;
|
|
239
260
|
const value = schedule.action.arguments?.work_id;
|
|
240
261
|
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
241
262
|
}
|
|
@@ -251,7 +272,7 @@ function executionJobBelongsToWork(job: ReturnType<typeof listExecutionJobs>[num
|
|
|
251
272
|
|
|
252
273
|
async function stopReason(controllerHome: string, schedule: RepositorySchedule): Promise<string | undefined> {
|
|
253
274
|
const projection = readRepositoryProjection(controllerHome, schedule.repoId);
|
|
254
|
-
const workId =
|
|
275
|
+
const workId = workBoundScheduleWorkId(schedule);
|
|
255
276
|
const work = workId ? getWorkContract({ controllerHome, repoId: schedule.repoId }, workId) : undefined;
|
|
256
277
|
if (schedule.stopConditions.includes('human_review_required')) {
|
|
257
278
|
if (workId) {
|
|
@@ -344,12 +365,284 @@ function decideOccurrence(
|
|
|
344
365
|
return saveOccurrence(controllerHome, { ...occurrence, decision, decisionId, status, reason });
|
|
345
366
|
}
|
|
346
367
|
|
|
368
|
+
export type ChatgptWakeFailureClass = 'retryable_readiness' | 'semantic_wait' | 'user_action_required' | 'ordinary_failure';
|
|
369
|
+
|
|
370
|
+
export function classifyChatgptWakeFailure(reason: string): ChatgptWakeFailureClass {
|
|
371
|
+
const normalized = reason.toUpperCase();
|
|
372
|
+
if (
|
|
373
|
+
normalized.includes('CHATGPT_AUTOMATION_LOGIN_REQUIRED')
|
|
374
|
+
|| normalized.includes('PLUGIN_BROWSER_JAVASCRIPT_PERMISSION_REQUIRED')
|
|
375
|
+
|| normalized.includes('AUTHENTICATION_REQUIRED')
|
|
376
|
+
|| normalized.includes('CONSENT_REQUIRED')
|
|
377
|
+
) return 'user_action_required';
|
|
378
|
+
if (normalized.includes('REPEATED_STATE:')) return 'semantic_wait';
|
|
379
|
+
const retryableMarkers = [
|
|
380
|
+
'PLUGIN_BROWSER_ATTACH_UNAVAILABLE',
|
|
381
|
+
'PLUGIN_BROWSER_DEPENDENCY_UNAVAILABLE',
|
|
382
|
+
'PLUGIN_BROWSER_NODE_HOST_UNAVAILABLE',
|
|
383
|
+
'PLUGIN_BROWSER_NODE_UNAVAILABLE',
|
|
384
|
+
'PLUGIN_BROWSER_ACTIVE_TAB_UNAVAILABLE',
|
|
385
|
+
'PLUGIN_BROWSER_SESSION_STATE_LOST',
|
|
386
|
+
'PLUGIN_SESSION_NOT_FOUND',
|
|
387
|
+
'PLUGIN_BROWSER_NATIVE_FOREGROUND_ACTIVATOR_UNAVAILABLE',
|
|
388
|
+
'CHATGPT_CONTROLLER_BROWSER_ROOT_UNAVAILABLE',
|
|
389
|
+
'CHATGPT_CONTROLLER_BROWSER_FAILED',
|
|
390
|
+
'CHATGPT_AUTOMATION_COMPOSER_UNAVAILABLE',
|
|
391
|
+
'CHATGPT_AUTOMATION_INTELLIGENCE_CONTROL_UNAVAILABLE',
|
|
392
|
+
'CHATGPT_AUTOMATION_REPLACEMENT_SESSION_NOT_CONFIRMED',
|
|
393
|
+
'HTTP 502',
|
|
394
|
+
'ECONNREFUSED',
|
|
395
|
+
'ECONNRESET',
|
|
396
|
+
'ETIMEDOUT',
|
|
397
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
398
|
+
];
|
|
399
|
+
if (retryableMarkers.some((marker) => normalized.includes(marker))) return 'retryable_readiness';
|
|
400
|
+
return 'ordinary_failure';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function externalControllerLaunchArgs(args: Record<string, unknown>, controllerType: string): string[] {
|
|
404
|
+
const launchArgs = Array.isArray(args.launch_args) ? args.launch_args.map(String) : [];
|
|
405
|
+
if (controllerType !== 'chatgpt') return launchArgs;
|
|
406
|
+
const explicit = new Set(launchArgs.filter((value) => value.startsWith('--')).map((value) => value.split('=', 1)[0]));
|
|
407
|
+
const add = (flag: string, value: unknown) => {
|
|
408
|
+
if (explicit.has(flag) || typeof value !== 'string' || !value.trim()) return;
|
|
409
|
+
launchArgs.push(flag, value.trim());
|
|
410
|
+
};
|
|
411
|
+
add('--model', args.model);
|
|
412
|
+
add('--reasoning', args.reasoning);
|
|
413
|
+
add('--tab-policy', args.tab_policy);
|
|
414
|
+
return launchArgs;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function executeExternalControllerWake(
|
|
418
|
+
controllerHome: string,
|
|
419
|
+
schedule: RepositorySchedule,
|
|
420
|
+
occurrence: ScheduleOccurrence,
|
|
421
|
+
timestamp: string,
|
|
422
|
+
args: Record<string, unknown>,
|
|
423
|
+
extraEvidence: Record<string, unknown> = {},
|
|
424
|
+
options: { transientWakeFailure?: boolean } = {},
|
|
425
|
+
): Promise<ScheduleOccurrence> {
|
|
426
|
+
const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
|
|
427
|
+
const controllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : 'chatgpt';
|
|
428
|
+
if (!workId) {
|
|
429
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'EXTERNAL_CONTROLLER_WAKE_WORK_ID_REQUIRED');
|
|
430
|
+
}
|
|
431
|
+
if (!['chatgpt', 'codex', 'claude', 'grok'].includes(controllerType)) {
|
|
432
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'EXTERNAL_CONTROLLER_WAKE_TYPE_INVALID');
|
|
433
|
+
}
|
|
434
|
+
const workStore = { controllerHome, repoId: schedule.repoId };
|
|
435
|
+
const work = getWorkContract(workStore, workId);
|
|
436
|
+
if (!work) {
|
|
437
|
+
saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} no longer exists.`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
438
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `EXTERNAL_CONTROLLER_WAKE_WORK_NOT_FOUND:${workId}`);
|
|
439
|
+
}
|
|
440
|
+
if (isTerminalWorkContractStatus(work.status)) {
|
|
441
|
+
saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} is terminal (${work.status}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
442
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); automatic continuation stopped.`);
|
|
443
|
+
}
|
|
444
|
+
const existingOwner = getControllerSession(workStore, workId);
|
|
445
|
+
const occurrenceNowMs = Date.parse(timestamp);
|
|
446
|
+
if (existingOwner && controllerSessionBlocksRecovery(workStore, workId, {
|
|
447
|
+
nowMs: Number.isFinite(occurrenceNowMs) ? occurrenceNowMs : Date.now(),
|
|
448
|
+
})) {
|
|
449
|
+
saveSchedule(controllerHome, { ...schedule, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
450
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} already has an active Controller ${existingOwner.controllerId}.`);
|
|
451
|
+
}
|
|
452
|
+
const launchReservation = getExternalControllerLaunchReservation(workStore, workId);
|
|
453
|
+
if (launchReservation) {
|
|
454
|
+
saveSchedule(controllerHome, { ...schedule, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
455
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} already has a pending external Controller launch ${launchReservation.reservationId}.`);
|
|
456
|
+
}
|
|
457
|
+
const wakeDecision = decideOccurrence(
|
|
458
|
+
controllerHome,
|
|
459
|
+
schedule,
|
|
460
|
+
occurrence,
|
|
461
|
+
'execute',
|
|
462
|
+
'running',
|
|
463
|
+
'Deterministic external Controller wake is starting through Thin Launcher.',
|
|
464
|
+
occurrenceDecisionEvidence({
|
|
465
|
+
operation: schedule.action.operation,
|
|
466
|
+
workId,
|
|
467
|
+
controllerType,
|
|
468
|
+
...(controllerType === 'chatgpt' ? {
|
|
469
|
+
model: typeof args.model === 'string' ? args.model : 'gpt-5.6',
|
|
470
|
+
reasoning: typeof args.reasoning === 'string' ? args.reasoning : 'high',
|
|
471
|
+
tabPolicy: typeof args.tab_policy === 'string' ? args.tab_policy : 'auto',
|
|
472
|
+
} : {}),
|
|
473
|
+
...extraEvidence,
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
try {
|
|
477
|
+
const repository = getRepository(schedule.repoId, controllerHome, { includeRemoved: true });
|
|
478
|
+
const continuationPrompt = typeof args.continuation_prompt === 'string'
|
|
479
|
+
? args.continuation_prompt
|
|
480
|
+
: `Scheduled continuation ${occurrence.occurrenceId} from ${schedule.scheduleId}. Read current Forge state and continue only the bounded Work objective.`;
|
|
481
|
+
if (controllerType === 'chatgpt') {
|
|
482
|
+
const reasoning = args.reasoning === 'medium' || args.reasoning === 'xhigh' ? args.reasoning : 'high';
|
|
483
|
+
const tabPolicy = args.tab_policy === 'reuse' || args.tab_policy === 'new' ? args.tab_policy : 'auto';
|
|
484
|
+
let relay;
|
|
485
|
+
try {
|
|
486
|
+
relay = beginInitialControllerRoundDispatch(workStore, {
|
|
487
|
+
workId,
|
|
488
|
+
identity: {
|
|
489
|
+
controllerId: `schedule:${schedule.scheduleId}`,
|
|
490
|
+
principalId: 'forge-scheduler',
|
|
491
|
+
controllerInstanceId: 'forge-runtime-scheduler',
|
|
492
|
+
sessionId: occurrence.occurrenceId,
|
|
493
|
+
},
|
|
494
|
+
requirementId: work.requirementId,
|
|
495
|
+
browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
|
|
496
|
+
conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
|
|
497
|
+
});
|
|
498
|
+
if (relay.status === 'blocked') throw new Error(`CONTROLLER_RELAY_LAUNCH_BLOCKED: ${relay.blockedReason ?? relay.relayScopeId}`);
|
|
499
|
+
const relayPrompt = `${buildControllerRoundRelayPrompt(workStore, relay)}\n\nScheduled continuation hint: ${continuationPrompt}`;
|
|
500
|
+
const dispatched = await runWorkChatgptContinuation({
|
|
501
|
+
controllerHome,
|
|
502
|
+
repoId: schedule.repoId,
|
|
503
|
+
repoRoot: repository.canonicalRoot,
|
|
504
|
+
workId,
|
|
505
|
+
prompt: relayPrompt,
|
|
506
|
+
title: schedule.name,
|
|
507
|
+
browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
|
|
508
|
+
conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
|
|
509
|
+
model: typeof args.model === 'string' ? args.model : 'gpt-5.6',
|
|
510
|
+
reasoning,
|
|
511
|
+
tabPolicy,
|
|
512
|
+
timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
|
|
513
|
+
});
|
|
514
|
+
if (dispatched.status === 'failed') throw new Error(dispatched.error?.message ?? 'CHATGPT_WORK_CONTINUATION_FAILED');
|
|
515
|
+
const completedRelay = finishControllerRoundRelayDispatch(workStore, {
|
|
516
|
+
workId,
|
|
517
|
+
ok: true,
|
|
518
|
+
browserSessionId: dispatched.browserSessionId,
|
|
519
|
+
conversationUrl: dispatched.conversationUrl,
|
|
520
|
+
});
|
|
521
|
+
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
522
|
+
saveSchedule(controllerHome, {
|
|
523
|
+
...latest,
|
|
524
|
+
lastTriggeredAt: timestamp,
|
|
525
|
+
lastOccurrenceId: occurrence.occurrenceId,
|
|
526
|
+
consecutiveFailures: 0,
|
|
527
|
+
nextEligibleAt: undefined,
|
|
528
|
+
pausedReason: undefined,
|
|
529
|
+
});
|
|
530
|
+
const succeededOccurrence = saveOccurrence(controllerHome, {
|
|
531
|
+
...wakeDecision,
|
|
532
|
+
status: 'succeeded',
|
|
533
|
+
reason: `ChatGPT dispatch action succeeded via ${dispatched.browserSessionId}; relay ${completedRelay?.relayScopeId ?? relay.relayScopeId} remains dispatched until the new Controller claims Work ${workId}.`,
|
|
534
|
+
});
|
|
535
|
+
appendWorkEvidence(workStore, workId, {
|
|
536
|
+
evidenceId: succeededOccurrence.occurrenceId,
|
|
537
|
+
title: 'scheduled ChatGPT continuation dispatched',
|
|
538
|
+
summary: `Schedule ${schedule.scheduleId} timer occurrence ${succeededOccurrence.occurrenceId} dispatched the Work-bound ChatGPT continuation successfully.`,
|
|
539
|
+
detailLevel: 'summary',
|
|
540
|
+
});
|
|
541
|
+
return succeededOccurrence;
|
|
542
|
+
} catch (error) {
|
|
543
|
+
if (relay) {
|
|
544
|
+
finishControllerRoundRelayDispatch(workStore, {
|
|
545
|
+
workId,
|
|
546
|
+
ok: false,
|
|
547
|
+
error: error instanceof Error ? error.message : String(error),
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
if (error instanceof Error && error.message.startsWith('CONTROLLER_RELAY_ROUND_ALREADY_OPEN:')) {
|
|
551
|
+
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
552
|
+
saveSchedule(controllerHome, { ...latest, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
553
|
+
return decideOccurrence(controllerHome, schedule, wakeDecision, 'nothing_to_do', 'skipped', `Work ${workId} already has an open ChatGPT controller round awaiting claim or disposition.`);
|
|
554
|
+
}
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const launched = await launchSuperController({
|
|
559
|
+
work: { controllerHome, repoId: schedule.repoId },
|
|
560
|
+
handoff: { controllerHome, repoId: schedule.repoId },
|
|
561
|
+
}, {
|
|
562
|
+
controllerType: controllerType as 'codex' | 'claude' | 'grok',
|
|
563
|
+
executable: typeof args.executable === 'string' ? args.executable : undefined,
|
|
564
|
+
args: externalControllerLaunchArgs(args, controllerType),
|
|
565
|
+
workId,
|
|
566
|
+
handoffId: typeof args.handoff_id === 'string' ? args.handoff_id : undefined,
|
|
567
|
+
browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
|
|
568
|
+
conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
|
|
569
|
+
launchReservationMs: typeof args.launch_reservation_ms === 'number' ? args.launch_reservation_ms : typeof args.lease_ms === 'number' ? args.lease_ms : undefined,
|
|
570
|
+
continuationPrompt,
|
|
571
|
+
cwd: repository.canonicalRoot,
|
|
572
|
+
});
|
|
573
|
+
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
574
|
+
saveSchedule(controllerHome, {
|
|
575
|
+
...latest,
|
|
576
|
+
lastTriggeredAt: timestamp,
|
|
577
|
+
lastOccurrenceId: occurrence.occurrenceId,
|
|
578
|
+
consecutiveFailures: 0,
|
|
579
|
+
nextEligibleAt: undefined,
|
|
580
|
+
pausedReason: undefined,
|
|
581
|
+
});
|
|
582
|
+
return saveOccurrence(controllerHome, {
|
|
583
|
+
...wakeDecision,
|
|
584
|
+
status: 'succeeded',
|
|
585
|
+
reason: `External Controller wake started ${launched.controllerType} pid=${String(launched.pid ?? 'unknown')} for Work ${workId}.`,
|
|
586
|
+
});
|
|
587
|
+
} catch (error) {
|
|
588
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
589
|
+
const failureClass = controllerType === 'chatgpt' ? classifyChatgptWakeFailure(reason) : 'ordinary_failure';
|
|
590
|
+
if (options.transientWakeFailure || failureClass === 'retryable_readiness' || failureClass === 'semantic_wait') {
|
|
591
|
+
const semanticWait = failureClass === 'semantic_wait';
|
|
592
|
+
const deferred = applyScheduleRetryableFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
|
|
593
|
+
outcome: semanticWait ? 'skipped' : 'failed',
|
|
594
|
+
decision: semanticWait ? 'nothing_to_do' : 'execute',
|
|
595
|
+
reason: semanticWait
|
|
596
|
+
? `Controller wake deferred until durable state changes; schedule remains active: ${reason}`
|
|
597
|
+
: `Controller wake deferred by transient readiness; schedule remains active with bounded backoff: ${reason}`,
|
|
598
|
+
countFailure: !semanticWait,
|
|
599
|
+
});
|
|
600
|
+
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
601
|
+
saveSchedule(controllerHome, {
|
|
602
|
+
...latest,
|
|
603
|
+
lastTriggeredAt: timestamp,
|
|
604
|
+
lastOccurrenceId: occurrence.occurrenceId,
|
|
605
|
+
});
|
|
606
|
+
return deferred.occurrence ?? saveOccurrence(controllerHome, {
|
|
607
|
+
...wakeDecision,
|
|
608
|
+
status: semanticWait ? 'skipped' : 'failed',
|
|
609
|
+
decision: semanticWait ? 'nothing_to_do' : 'execute',
|
|
610
|
+
reason,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
|
|
614
|
+
outcome: 'failed',
|
|
615
|
+
decision: 'execute',
|
|
616
|
+
reason,
|
|
617
|
+
...(failureClass === 'user_action_required' ? { pauseReason: reason } : {}),
|
|
618
|
+
handoff: {
|
|
619
|
+
title: `External Controller wake ${occurrence.occurrenceId} failed`,
|
|
620
|
+
summary: 'Forge recorded the schedule trigger but could not wake the configured external Controller.',
|
|
621
|
+
reason,
|
|
622
|
+
creationReason: 'repeated_infrastructure_failure',
|
|
623
|
+
blockingDecision: 'Repair the external Controller/browser launch path or update the saved session reference.',
|
|
624
|
+
recommendedDecision: 'Inspect launcher/browser readiness, then retrigger this bounded continuation.',
|
|
625
|
+
recommendedPrompt: `Resume Work ${workId} manually, inspect failed wake occurrence ${occurrence.occurrenceId}, and repair the configured external Controller launch path before unattended continuation resumes.`,
|
|
626
|
+
statusSummary: 'Scheduled external Controller wake failed.',
|
|
627
|
+
blockedBy: ['external_controller_wake_failed'],
|
|
628
|
+
attemptedActions: [`schedule:${schedule.scheduleId}`, `work:${workId}`, `controller:${controllerType}`],
|
|
629
|
+
},
|
|
630
|
+
});
|
|
631
|
+
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
632
|
+
saveSchedule(controllerHome, { ...latest, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
633
|
+
return failed.occurrence ?? saveOccurrence(controllerHome, { ...wakeDecision, status: 'failed', reason });
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
347
637
|
export async function evaluateSchedule(
|
|
348
638
|
controllerHome: string,
|
|
349
639
|
schedule: RepositorySchedule,
|
|
350
640
|
force = false,
|
|
351
641
|
triggerContext?: ScheduleTriggerContext,
|
|
352
642
|
): Promise<ScheduleOccurrence | undefined> {
|
|
643
|
+
// Preserve retired schedule records as evidence, but do not create a new
|
|
644
|
+
// occurrence or resume a controller/external action from their old payload.
|
|
645
|
+
if (retiredScheduleReason(schedule)) return undefined;
|
|
353
646
|
if (!schedule.enabled && !force) return undefined;
|
|
354
647
|
const due = await triggerDue(controllerHome, schedule, force, triggerContext);
|
|
355
648
|
if (!due.due) return undefined;
|
|
@@ -548,76 +841,212 @@ export async function evaluateSchedule(
|
|
|
548
841
|
|
|
549
842
|
const args = schedule.action.arguments ?? {};
|
|
550
843
|
if (schedule.action.operation === EXTERNAL_CONTROLLER_WAKE_OPERATION) {
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
844
|
+
return executeExternalControllerWake(controllerHome, schedule, occurrence, timestamp, args);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (schedule.action.operation === GITHUB_ISSUE_WATCH_OPERATION) {
|
|
848
|
+
try {
|
|
849
|
+
const repository = getRepository(schedule.repoId, controllerHome, { includeRemoved: true });
|
|
850
|
+
const observation = await executeScheduledGithubIssueWatch({ repoRoot: repository.canonicalRoot, args, observedAt: timestamp });
|
|
851
|
+
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
852
|
+
const evidence = occurrenceDecisionEvidence({
|
|
853
|
+
githubRepository: typeof args.github_repository === 'string' ? args.github_repository : undefined,
|
|
854
|
+
observationStatus: observation.status,
|
|
855
|
+
observedIssueCount: observation.issues.length,
|
|
856
|
+
changedIssueNumbers: observation.changedOpenIssues.map((issue) => issue.number),
|
|
857
|
+
});
|
|
858
|
+
const persistObservation = () => saveSchedule(controllerHome, {
|
|
859
|
+
...getSchedule(controllerHome, schedule.repoId, schedule.scheduleId),
|
|
860
|
+
action: {
|
|
861
|
+
...latest.action,
|
|
862
|
+
arguments: { ...(latest.action.arguments ?? {}), issue_watch_state: observation.nextState, issue_watch_since: undefined },
|
|
863
|
+
},
|
|
864
|
+
lastTriggeredAt: timestamp,
|
|
865
|
+
lastOccurrenceId: occurrenceId,
|
|
866
|
+
lastObservationAt: timestamp,
|
|
867
|
+
lastObservationStatus: observation.status,
|
|
868
|
+
lastObservationChangedAt: observation.shouldWake ? timestamp : latest.lastObservationChangedAt,
|
|
869
|
+
consecutiveFailures: 0,
|
|
870
|
+
consecutiveNoops: observation.shouldWake ? 0 : (latest.consecutiveNoops ?? 0) + 1,
|
|
871
|
+
nextEligibleAt: undefined,
|
|
872
|
+
pausedReason: undefined,
|
|
873
|
+
});
|
|
874
|
+
if (!observation.shouldWake) {
|
|
875
|
+
const observedSchedule = persistObservation();
|
|
876
|
+
const reason = observation.status === 'baseline'
|
|
877
|
+
? 'GitHub issue watcher baseline recorded; no Controller wake was emitted.'
|
|
878
|
+
: 'GitHub issue watcher found no new, reopened, or updated open issue.';
|
|
879
|
+
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'nothing_to_do', 'skipped', reason, evidence);
|
|
880
|
+
}
|
|
881
|
+
const issueSummary = observation.changedOpenIssues.slice(0, 10)
|
|
882
|
+
.map((issue) => `#${issue.number} ${issue.title}`)
|
|
883
|
+
.join('; ');
|
|
884
|
+
const wakeArgs = {
|
|
885
|
+
...args,
|
|
886
|
+
continuation_prompt: typeof args.continuation_prompt === 'string'
|
|
887
|
+
? `${args.continuation_prompt}\n\nGitHub issue watcher detected: ${issueSummary}`
|
|
888
|
+
: `GitHub issue watcher detected actionable issue changes: ${issueSummary}. Read current Work/Plan/evidence and process these issues through the bounded repair workflow.`,
|
|
889
|
+
};
|
|
890
|
+
const wake = await executeExternalControllerWake(controllerHome, schedule, occurrence, timestamp, wakeArgs, evidence, { transientWakeFailure: true });
|
|
891
|
+
if (wake.status !== 'failed') persistObservation();
|
|
892
|
+
return wake;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
895
|
+
const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
|
|
896
|
+
outcome: 'failed',
|
|
897
|
+
decision: 'execute',
|
|
898
|
+
reason,
|
|
899
|
+
handoff: {
|
|
900
|
+
title: `GitHub issue watcher occurrence ${occurrence.occurrenceId} failed`,
|
|
901
|
+
summary: 'The local GitHub issue poll failed before Forge could compare issue state.',
|
|
902
|
+
reason,
|
|
903
|
+
creationReason: 'repeated_infrastructure_failure',
|
|
904
|
+
blockingDecision: 'Repair GitHub CLI authentication/connectivity before unattended issue monitoring resumes.',
|
|
905
|
+
recommendedDecision: 'Restore gh read access, then retrigger the watcher.',
|
|
906
|
+
recommendedPrompt: `Inspect GitHub issue watcher occurrence ${occurrence.occurrenceId} and restore the local read-only gh issue polling path.`,
|
|
907
|
+
statusSummary: 'GitHub issue watcher poll failed.',
|
|
908
|
+
blockedBy: ['github_issue_watch_failed'],
|
|
909
|
+
attemptedActions: [`schedule:${schedule.scheduleId}`, 'gh:api:issues'],
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
return failed.occurrence ?? occurrence;
|
|
568
913
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (schedule.action.operation === BROWSER_PROBE_OPERATION) {
|
|
917
|
+
const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
|
|
918
|
+
const keepaliveOnly = args.keepalive_only === true;
|
|
919
|
+
if (!workId && !keepaliveOnly) {
|
|
920
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'SCHEDULE_BROWSER_PROBE_WORK_ID_REQUIRED');
|
|
573
921
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
922
|
+
if (workId) {
|
|
923
|
+
const workStore = { controllerHome, repoId: schedule.repoId };
|
|
924
|
+
const work = getWorkContract(workStore, workId);
|
|
925
|
+
if (!work) {
|
|
926
|
+
saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} no longer exists.`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
|
|
927
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', `SCHEDULE_BROWSER_PROBE_WORK_NOT_FOUND:${workId}`);
|
|
928
|
+
}
|
|
929
|
+
if (isTerminalWorkContractStatus(work.status)) {
|
|
930
|
+
saveSchedule(controllerHome, { ...schedule, enabled: false, pausedReason: `Work ${workId} is terminal (${work.status}).`, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
|
|
931
|
+
return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); browser watcher stopped before probing.`);
|
|
932
|
+
}
|
|
578
933
|
}
|
|
579
|
-
|
|
580
|
-
controllerHome,
|
|
581
|
-
schedule,
|
|
582
|
-
occurrence,
|
|
583
|
-
'execute',
|
|
584
|
-
'running',
|
|
585
|
-
'Deterministic external Controller wake is starting through Thin Launcher.',
|
|
586
|
-
occurrenceDecisionEvidence({ operation: schedule.action.operation, workId, controllerType }),
|
|
587
|
-
);
|
|
934
|
+
|
|
588
935
|
try {
|
|
589
936
|
const repository = getRepository(schedule.repoId, controllerHome, { includeRemoved: true });
|
|
590
|
-
const
|
|
591
|
-
work: { controllerHome, repoId: schedule.repoId },
|
|
592
|
-
handoff: { controllerHome, repoId: schedule.repoId },
|
|
593
|
-
}, {
|
|
594
|
-
controllerType: controllerType as 'chatgpt' | 'codex' | 'claude' | 'grok',
|
|
595
|
-
executable: typeof args.executable === 'string' ? args.executable : undefined,
|
|
596
|
-
args: Array.isArray(args.launch_args) ? args.launch_args.map(String) : [],
|
|
597
|
-
workId,
|
|
598
|
-
handoffId: typeof args.handoff_id === 'string' ? args.handoff_id : undefined,
|
|
599
|
-
browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
|
|
600
|
-
conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
|
|
601
|
-
launchReservationMs: typeof args.launch_reservation_ms === 'number' ? args.launch_reservation_ms : typeof args.lease_ms === 'number' ? args.lease_ms : undefined,
|
|
602
|
-
continuationPrompt: typeof args.continuation_prompt === 'string'
|
|
603
|
-
? args.continuation_prompt
|
|
604
|
-
: `Scheduled continuation ${occurrence.occurrenceId} from ${schedule.scheduleId}. Read current Forge state and continue only the bounded Work objective.`,
|
|
605
|
-
cwd: repository.canonicalRoot,
|
|
606
|
-
});
|
|
937
|
+
const probe = await executeScheduledBrowserProbe({ controllerHome, repository, occurrenceId, args });
|
|
607
938
|
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
608
|
-
|
|
939
|
+
const observationEvidence = occurrenceDecisionEvidence({
|
|
940
|
+
probeStatus: probe.status,
|
|
941
|
+
url: probe.url,
|
|
942
|
+
projectedLineCount: probe.projectedLineCount,
|
|
943
|
+
observedChars: probe.observedChars,
|
|
944
|
+
truncated: probe.truncated,
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
if (probe.status === 'auth_required') {
|
|
948
|
+
const observedSchedule = saveSchedule(controllerHome, {
|
|
949
|
+
...latest,
|
|
950
|
+
lastTriggeredAt: timestamp,
|
|
951
|
+
lastOccurrenceId: occurrenceId,
|
|
952
|
+
lastObservationAt: timestamp,
|
|
953
|
+
lastObservationStatus: 'auth_required',
|
|
954
|
+
consecutiveFailures: 0,
|
|
955
|
+
nextEligibleAt: undefined,
|
|
956
|
+
pausedReason: undefined,
|
|
957
|
+
});
|
|
958
|
+
if (args.wake_on_auth_required !== false) {
|
|
959
|
+
const authPrompt = typeof args.auth_required_prompt === 'string'
|
|
960
|
+
? args.auth_required_prompt
|
|
961
|
+
: typeof args.continuation_prompt === 'string'
|
|
962
|
+
? args.continuation_prompt
|
|
963
|
+
: `Scheduled browser watcher ${schedule.scheduleId} detected that authentication is required (${probe.authReason ?? 'login marker'}). Inspect the external dependency and request user login only if it cannot be restored safely.`;
|
|
964
|
+
if (workId) {
|
|
965
|
+
const wakeArgs = { ...args, continuation_prompt: authPrompt };
|
|
966
|
+
return executeExternalControllerWake(controllerHome, observedSchedule, occurrence, timestamp, wakeArgs, { ...observationEvidence, authReason: probe.authReason });
|
|
967
|
+
}
|
|
968
|
+
const controllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : 'chatgpt';
|
|
969
|
+
if (controllerType !== 'chatgpt') {
|
|
970
|
+
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', 'STANDALONE_BROWSER_KEEPALIVE_CHATGPT_REQUIRED', observationEvidence);
|
|
971
|
+
}
|
|
972
|
+
const dispatched = await runStandaloneChatgptPrompt({
|
|
973
|
+
controllerHome,
|
|
974
|
+
repoId: schedule.repoId,
|
|
975
|
+
scopeId: `schedule:${schedule.scheduleId}`,
|
|
976
|
+
prompt: authPrompt,
|
|
977
|
+
browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
|
|
978
|
+
conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
|
|
979
|
+
});
|
|
980
|
+
if (dispatched.status === 'failed') throw new Error(dispatched.error?.message ?? 'CHATGPT_STANDALONE_PROMPT_FAILED');
|
|
981
|
+
return decideOccurrence(
|
|
982
|
+
controllerHome,
|
|
983
|
+
observedSchedule,
|
|
984
|
+
occurrence,
|
|
985
|
+
'execute',
|
|
986
|
+
'succeeded',
|
|
987
|
+
'Standalone browser keepalive auth-required prompt dispatched to ChatGPT.',
|
|
988
|
+
{
|
|
989
|
+
...observationEvidence,
|
|
990
|
+
authReason: probe.authReason,
|
|
991
|
+
browserSessionId: dispatched.browserSessionId,
|
|
992
|
+
conversationUrl: dispatched.conversationUrl,
|
|
993
|
+
},
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', `Browser ${workId ? 'watcher' : 'keepalive'} requires authentication: ${probe.authReason ?? 'login marker matched'}`, observationEvidence);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (probe.status === 'keepalive') {
|
|
1000
|
+
const observedSchedule = saveSchedule(controllerHome, {
|
|
1001
|
+
...latest,
|
|
1002
|
+
lastTriggeredAt: timestamp,
|
|
1003
|
+
lastOccurrenceId: occurrenceId,
|
|
1004
|
+
lastObservationAt: timestamp,
|
|
1005
|
+
lastObservationStatus: 'keepalive',
|
|
1006
|
+
consecutiveFailures: 0,
|
|
1007
|
+
consecutiveNoops: 0,
|
|
1008
|
+
nextEligibleAt: undefined,
|
|
1009
|
+
pausedReason: undefined,
|
|
1010
|
+
});
|
|
1011
|
+
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'execute', 'succeeded', 'Browser session keepalive refreshed successfully.', observationEvidence);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const observation = classifyScheduledBrowserObservation(
|
|
1015
|
+
latest.lastObservationFingerprint,
|
|
1016
|
+
probe.fingerprint,
|
|
1017
|
+
args.wake_on_first_observation === true,
|
|
1018
|
+
);
|
|
1019
|
+
const changed = observation.status === 'changed';
|
|
1020
|
+
const shouldWake = args.wake_on_change !== false && observation.shouldWake;
|
|
1021
|
+
const observationStatus: RepositorySchedule['lastObservationStatus'] = observation.status;
|
|
1022
|
+
const observedSchedule = saveSchedule(controllerHome, {
|
|
609
1023
|
...latest,
|
|
610
1024
|
lastTriggeredAt: timestamp,
|
|
611
1025
|
lastOccurrenceId: occurrenceId,
|
|
1026
|
+
lastObservationAt: timestamp,
|
|
1027
|
+
lastObservationFingerprint: probe.fingerprint,
|
|
1028
|
+
lastObservationChangedAt: changed ? timestamp : latest.lastObservationChangedAt,
|
|
1029
|
+
lastObservationStatus: observationStatus,
|
|
612
1030
|
consecutiveFailures: 0,
|
|
1031
|
+
consecutiveNoops: shouldWake ? 0 : (latest.consecutiveNoops ?? 0) + 1,
|
|
613
1032
|
nextEligibleAt: undefined,
|
|
614
1033
|
pausedReason: undefined,
|
|
615
1034
|
});
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
status
|
|
619
|
-
|
|
620
|
-
|
|
1035
|
+
|
|
1036
|
+
if (!shouldWake) {
|
|
1037
|
+
const reason = observation.status === 'baseline'
|
|
1038
|
+
? 'Browser watcher baseline recorded; no Controller wake was emitted.'
|
|
1039
|
+
: 'Browser watcher observation is unchanged; no Controller wake was emitted.';
|
|
1040
|
+
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'nothing_to_do', 'skipped', reason, { ...observationEvidence, observationStatus });
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
const wakeArgs = {
|
|
1044
|
+
...args,
|
|
1045
|
+
continuation_prompt: typeof args.continuation_prompt === 'string'
|
|
1046
|
+
? args.continuation_prompt
|
|
1047
|
+
: `Scheduled browser watcher ${schedule.scheduleId} detected a changed external observation. Read the current external state, correlate it with Work ${workId}, and continue only the bounded Work objective.`,
|
|
1048
|
+
};
|
|
1049
|
+
return executeExternalControllerWake(controllerHome, observedSchedule, occurrence, timestamp, wakeArgs, { ...observationEvidence, observationStatus });
|
|
621
1050
|
} catch (error) {
|
|
622
1051
|
const reason = error instanceof Error ? error.message : String(error);
|
|
623
1052
|
const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
|
|
@@ -625,21 +1054,21 @@ export async function evaluateSchedule(
|
|
|
625
1054
|
decision: 'execute',
|
|
626
1055
|
reason,
|
|
627
1056
|
handoff: {
|
|
628
|
-
title: `
|
|
629
|
-
summary: '
|
|
1057
|
+
title: `Browser watcher occurrence ${occurrence.occurrenceId} failed`,
|
|
1058
|
+
summary: 'A bounded browser probe failed before Forge could compare the external observation.',
|
|
630
1059
|
reason,
|
|
631
1060
|
creationReason: 'repeated_infrastructure_failure',
|
|
632
|
-
blockingDecision: 'Repair
|
|
633
|
-
recommendedDecision: 'Inspect
|
|
634
|
-
recommendedPrompt: `
|
|
635
|
-
statusSummary: 'Scheduled
|
|
636
|
-
blockedBy: ['
|
|
637
|
-
attemptedActions: [`schedule:${schedule.scheduleId}`, `work:${workId}`,
|
|
1061
|
+
blockingDecision: 'Repair browser/session readiness or update the watcher target before unattended probing resumes.',
|
|
1062
|
+
recommendedDecision: 'Inspect the browser plugin/session and retrigger the watcher after the target is readable.',
|
|
1063
|
+
recommendedPrompt: `Inspect browser watcher schedule ${schedule.scheduleId} for Work ${workId}, repair the failed browser probe, then retrigger one bounded occurrence.`,
|
|
1064
|
+
statusSummary: 'Scheduled browser watcher failed.',
|
|
1065
|
+
blockedBy: ['browser_probe_failed'],
|
|
1066
|
+
attemptedActions: [`schedule:${schedule.scheduleId}`, `work:${workId}`, 'operation:browser_probe'],
|
|
638
1067
|
},
|
|
639
1068
|
});
|
|
640
1069
|
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
641
1070
|
saveSchedule(controllerHome, { ...latest, lastTriggeredAt: timestamp, lastOccurrenceId: occurrenceId });
|
|
642
|
-
return failed.occurrence ?? saveOccurrence(controllerHome, { ...
|
|
1071
|
+
return failed.occurrence ?? saveOccurrence(controllerHome, { ...occurrence, status: 'failed', decision: 'execute', reason });
|
|
643
1072
|
}
|
|
644
1073
|
}
|
|
645
1074
|
|