@moretea-labs/forge 1.6.0 → 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 +26 -0
- package/CLAUDE.md +2 -0
- package/README.md +2 -2
- package/README.zh-CN.md +1 -1
- 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/prompt-guard.sh +5 -81
- 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 +1 -1
- package/assets/reference-configs/document-generation.md +1 -1
- package/assets/reference-configs/harness-overview.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 +11 -36
- 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/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 +26 -6
- package/docs/operations/controller-performance-and-502.md +124 -0
- package/docs/operations/controller-reliability-runbook.md +1 -1
- package/docs/operations/features.md +2 -2
- 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/wiki/Architecture.md +6 -6
- package/docs/wiki/Implementation.md +2 -2
- package/docs/wiki/Integrations.md +2 -2
- 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-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-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/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/init-hook.ts +3 -3
- package/src/cli/commands/mcp.ts +9 -5
- package/src/cli/commands/plugin.ts +1 -1
- 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 +785 -469
- 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/installer/managed-entries.ts +15 -1
- package/src/cli/local-bridge/console-automations.ts +54 -78
- package/src/cli/local-bridge/console-view-models.ts +1 -35
- package/src/cli/local-bridge/facade-api.ts +0 -217
- package/src/cli/local-bridge/frontend/src/App.tsx +3 -3
- package/src/cli/local-bridge/frontend/src/features/automations/AutomationsView.tsx +23 -13
- package/src/cli/local-bridge/frontend/src/features/capabilities/CapabilitiesView.tsx +37 -4
- package/src/cli/local-bridge/frontend/src/features/overview/OverviewView.tsx +6 -4
- package/src/cli/local-bridge/frontend/src/lib/api.ts +1 -6
- package/src/cli/local-bridge/frontend/src/lib/routes.ts +1 -1
- package/src/cli/local-bridge/frontend/src/shell/Sidebar.tsx +2 -2
- package/src/cli/local-bridge/frontend/src/styles.css +1 -1
- package/src/cli/local-bridge/frontend/src/types.ts +5 -8
- package/src/cli/local-bridge/job-store.ts +1 -490
- package/src/cli/local-bridge/server.ts +0 -513
- package/src/cli/local-bridge/ui-dist/app.css +1 -1
- package/src/cli/local-bridge/ui-dist/app.js +3703 -3757
- 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/repository-tools.ts +247 -107
- package/src/cli/mcp/server.ts +537 -111
- 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 +101 -14
- package/src/cli/mcp/transports/session-registry.ts +43 -5
- package/src/cli/mcp/types.ts +3 -3
- package/src/cli/repositories/command-classifier.ts +101 -19
- 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/selected-path-actions.ts +17 -9
- package/src/cli/repositories/structured-git.ts +61 -3
- package/src/cli/repository/inspector.ts +553 -63
- 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 +47 -8
- package/src/runtime/control-plane/execution/edit-validation-coordinator.ts +86 -24
- package/src/runtime/control-plane/execution/session-store.ts +18 -0
- 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 +143 -8
- package/src/runtime/control-plane/execution/work-validation-reconciler.ts +22 -14
- package/src/runtime/control-plane/execution/work-verification-snapshot.ts +85 -8
- 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 +132 -1
- package/src/runtime/control-plane/facade/goal-workloop-access.ts +2 -0
- package/src/runtime/control-plane/facade/goal-workloop.ts +686 -129
- package/src/runtime/control-plane/facade/index.ts +1 -0
- package/src/runtime/control-plane/facade/plan-contract-store.ts +185 -20
- 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 +22 -5
- package/src/runtime/control-plane/facade/work-contract-store.ts +175 -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 +355 -116
- 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/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 +13 -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 +51 -4
- package/src/runtime/execution/process-runtime/check-result.ts +53 -0
- package/src/runtime/execution/process-runtime/check-scheduling.ts +114 -0
- package/src/runtime/execution/process-runtime/command-facade.ts +345 -40
- package/src/runtime/execution/process-runtime/gc.ts +38 -6
- package/src/runtime/execution/process-runtime/lightweight-managed.ts +1184 -0
- package/src/runtime/execution/process-runtime/persisted-check.ts +47 -4
- package/src/runtime/execution/process-runtime/resource-claims.ts +10 -6
- package/src/runtime/execution/process-runtime/runtime.ts +43 -3
- package/src/runtime/execution/process-runtime/store.ts +1 -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 +1192 -114
- package/src/runtime/gateway/mcp/process-tools.ts +83 -37
- package/src/runtime/gateway/mcp/router.ts +184 -82
- package/src/runtime/gateway/mcp/runtime-tool-definitions.ts +546 -0
- package/src/runtime/gateway/mcp/runtime-tools.ts +2224 -1589
- package/src/runtime/plugins/app-store-connect-adapter.ts +123 -19
- package/src/runtime/plugins/browser-adapter.ts +2901 -241
- package/src/runtime/plugins/browser-automation-service.ts +10 -0
- package/src/runtime/plugins/browser-handoff.ts +92 -2
- package/src/runtime/plugins/browser-macos-bridge.ts +582 -49
- 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 +8 -2
- package/src/runtime/plugins/external-adapter.ts +324 -3
- package/src/runtime/plugins/external-unix-socket.ts +7 -1
- package/src/runtime/plugins/first-party-registry.ts +2 -0
- package/src/runtime/plugins/github-adapter.ts +125 -0
- 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 +123 -6
- 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 +49 -8
- package/src/runtime/plugins/resend-adapter.ts +1 -1
- package/src/runtime/plugins/store.ts +269 -29
- package/src/runtime/plugins/types.ts +35 -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/maintenance-executor.ts +393 -28
- 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 +19 -0
- package/src/runtime/root/release-store.ts +48 -11
- package/src/runtime/root/runtime.ts +23 -5
- package/src/runtime/root/service.ts +100 -29
- package/src/runtime/root/types.ts +2 -0
- package/src/runtime/safe-tooling/ios-development.ts +321 -63
- 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 +700 -92
- 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 +81 -3
- package/src/runtime/workflow/schedules/engine.ts +287 -151
- 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 +2 -4
- package/src/runtime/workflow/schedules/work-continuation.ts +26 -13
- 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/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 -459
- package/docs/architecture/current/codegraph-context-provider.md +0 -238
- 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 -373
- package/docs/architecture/current/entity-model.md +0 -612
- 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 -169
- 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 -321
- 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 -93
- 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/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,11 +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';
|
|
16
|
-
import {
|
|
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';
|
|
17
21
|
import {
|
|
18
22
|
getOccurrence,
|
|
19
23
|
getSchedule,
|
|
@@ -25,8 +29,9 @@ import {
|
|
|
25
29
|
saveSchedule,
|
|
26
30
|
saveScheduleDecision,
|
|
27
31
|
} from './store';
|
|
28
|
-
import { applyScheduleFailure } from './settlement';
|
|
32
|
+
import { applyScheduleFailure, applyScheduleRetryableFailure } from './settlement';
|
|
29
33
|
import { classifyScheduledBrowserObservation, executeScheduledBrowserProbe } from './browser-probe';
|
|
34
|
+
import { executeScheduledGithubIssueWatch } from './github-issue-watch';
|
|
30
35
|
import type {
|
|
31
36
|
RepositorySchedule,
|
|
32
37
|
ScheduleDecisionType,
|
|
@@ -37,16 +42,24 @@ import type {
|
|
|
37
42
|
const execFileAsync = promisify(execFile);
|
|
38
43
|
const LIVE_MAINTENANCE_OPERATION = 'runtime_maintenance_apply';
|
|
39
44
|
const EXTERNAL_CONTROLLER_WAKE_OPERATION = 'external_controller_wake';
|
|
40
|
-
const CHATGPT_BROWSER_PROMPT_OPERATION = 'chatgpt_browser_prompt';
|
|
41
45
|
const BROWSER_PROBE_OPERATION = 'browser_probe';
|
|
46
|
+
const GITHUB_ISSUE_WATCH_OPERATION = 'github_issue_watch';
|
|
42
47
|
|
|
43
48
|
function isDeterministicSchedule(schedule: RepositorySchedule): boolean {
|
|
44
|
-
// Kernel
|
|
45
|
-
// ChatGPT owns semantic work; the Scheduler only dispatches the configured prompt.
|
|
49
|
+
// Kernel executes only typed, bounded dispatch operations.
|
|
46
50
|
return schedule.action.operation === LIVE_MAINTENANCE_OPERATION
|
|
47
51
|
|| schedule.action.operation === EXTERNAL_CONTROLLER_WAKE_OPERATION
|
|
48
|
-
|| schedule.action.operation ===
|
|
49
|
-
|| schedule.action.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;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
function normalizedWindow(minutes: number, at = Date.now()): string {
|
|
@@ -107,6 +120,12 @@ function cronWindowKey(schedule: RepositorySchedule, at = Date.now()): string {
|
|
|
107
120
|
}
|
|
108
121
|
|
|
109
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
|
+
}
|
|
110
129
|
switch (schedule.trigger.type) {
|
|
111
130
|
case 'cron':
|
|
112
131
|
return cronWindowKey(schedule, at);
|
|
@@ -229,11 +248,6 @@ async function triggerDue(
|
|
|
229
248
|
const due = condition.kind === 'job_succeeded' ? job?.status === 'succeeded' : terminal;
|
|
230
249
|
return { due, reason: due ? undefined : `Condition ${condition.kind} is not met.`, evidence: { jobId: condition.jobId, status: job?.status ?? 'missing' } };
|
|
231
250
|
}
|
|
232
|
-
const threshold = Math.max(1, condition.observationThreshold ?? 2);
|
|
233
|
-
const finding = listCandidateFindings(controllerHome, schedule.repoId, { includeTerminal: true, limit: 1000 })
|
|
234
|
-
.find((entry) => entry.semanticKey === condition.semanticKey);
|
|
235
|
-
const due = Boolean(finding && finding.observationCount >= threshold);
|
|
236
|
-
return { due, reason: due ? undefined : 'Candidate observation threshold is not met.', evidence: { semanticKey: condition.semanticKey, observations: finding?.observationCount ?? 0, threshold } };
|
|
237
251
|
}
|
|
238
252
|
default:
|
|
239
253
|
return { due: false, reason: 'Unsupported Schedule trigger.' };
|
|
@@ -351,6 +365,41 @@ function decideOccurrence(
|
|
|
351
365
|
return saveOccurrence(controllerHome, { ...occurrence, decision, decisionId, status, reason });
|
|
352
366
|
}
|
|
353
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
|
+
|
|
354
403
|
function externalControllerLaunchArgs(args: Record<string, unknown>, controllerType: string): string[] {
|
|
355
404
|
const launchArgs = Array.isArray(args.launch_args) ? args.launch_args.map(String) : [];
|
|
356
405
|
if (controllerType !== 'chatgpt') return launchArgs;
|
|
@@ -365,90 +414,6 @@ function externalControllerLaunchArgs(args: Record<string, unknown>, controllerT
|
|
|
365
414
|
return launchArgs;
|
|
366
415
|
}
|
|
367
416
|
|
|
368
|
-
async function executeChatgptBrowserPrompt(
|
|
369
|
-
controllerHome: string,
|
|
370
|
-
schedule: RepositorySchedule,
|
|
371
|
-
occurrence: ScheduleOccurrence,
|
|
372
|
-
timestamp: string,
|
|
373
|
-
args: Record<string, unknown>,
|
|
374
|
-
): Promise<ScheduleOccurrence> {
|
|
375
|
-
const prompt = typeof args.prompt === 'string' ? args.prompt.trim() : '';
|
|
376
|
-
if (!prompt) {
|
|
377
|
-
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'CHATGPT_BROWSER_PROMPT_REQUIRED');
|
|
378
|
-
}
|
|
379
|
-
const model = typeof args.model === 'string' ? args.model : 'gpt-5.6';
|
|
380
|
-
const reasoning = args.reasoning === 'medium' || args.reasoning === 'xhigh' ? args.reasoning : 'high';
|
|
381
|
-
const tabPolicy = args.tab_policy === 'reuse' || args.tab_policy === 'new' ? args.tab_policy : 'auto';
|
|
382
|
-
const dispatching = decideOccurrence(
|
|
383
|
-
controllerHome,
|
|
384
|
-
schedule,
|
|
385
|
-
occurrence,
|
|
386
|
-
'execute',
|
|
387
|
-
'running',
|
|
388
|
-
'Forge Scheduler is dispatching the configured prompt through the controller-owned ChatGPT browser session.',
|
|
389
|
-
occurrenceDecisionEvidence({ operation: CHATGPT_BROWSER_PROMPT_OPERATION, model, reasoning, tabPolicy }),
|
|
390
|
-
);
|
|
391
|
-
const result = await runScheduledChatgptPrompt({
|
|
392
|
-
controllerHome,
|
|
393
|
-
repoId: schedule.repoId,
|
|
394
|
-
automationId: schedule.scheduleId,
|
|
395
|
-
prompt,
|
|
396
|
-
title: schedule.name,
|
|
397
|
-
browserSessionId: typeof args.browser_session_id === 'string' ? args.browser_session_id : undefined,
|
|
398
|
-
conversationUrl: typeof args.conversation_url === 'string' ? args.conversation_url : undefined,
|
|
399
|
-
model,
|
|
400
|
-
reasoning,
|
|
401
|
-
tabPolicy,
|
|
402
|
-
timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
|
|
403
|
-
});
|
|
404
|
-
if (result.status === 'failed') {
|
|
405
|
-
const reason = result.error?.message ?? 'ChatGPT browser prompt dispatch failed.';
|
|
406
|
-
const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
|
|
407
|
-
outcome: 'failed',
|
|
408
|
-
decision: 'execute',
|
|
409
|
-
reason,
|
|
410
|
-
handoff: {
|
|
411
|
-
title: `ChatGPT scheduled prompt ${occurrence.occurrenceId} failed`,
|
|
412
|
-
summary: 'Forge recorded the trigger but could not dispatch the configured ChatGPT prompt.',
|
|
413
|
-
reason,
|
|
414
|
-
creationReason: 'repeated_infrastructure_failure',
|
|
415
|
-
blockingDecision: 'Repair ChatGPT/browser readiness before unattended prompt dispatch resumes.',
|
|
416
|
-
recommendedDecision: 'Inspect browser attachment and ChatGPT execution preference verification, then retrigger this automation.',
|
|
417
|
-
recommendedPrompt: `Inspect failed ChatGPT prompt occurrence ${occurrence.occurrenceId} for schedule ${schedule.scheduleId}.`,
|
|
418
|
-
statusSummary: 'Scheduled ChatGPT prompt dispatch failed.',
|
|
419
|
-
blockedBy: ['chatgpt_browser_prompt_failed'],
|
|
420
|
-
attemptedActions: [`schedule:${schedule.scheduleId}`, `browser-session:${result.browserSessionId}`],
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
424
|
-
saveSchedule(controllerHome, { ...latest, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
425
|
-
return failed.occurrence ?? saveOccurrence(controllerHome, { ...dispatching, status: 'failed', reason });
|
|
426
|
-
}
|
|
427
|
-
const latest = getSchedule(controllerHome, schedule.repoId, schedule.scheduleId);
|
|
428
|
-
const durableConversationUrl = result.conversationUrl && /\/c\/[^/?#]+/.test(result.conversationUrl)
|
|
429
|
-
? result.conversationUrl
|
|
430
|
-
: undefined;
|
|
431
|
-
saveSchedule(controllerHome, {
|
|
432
|
-
...latest,
|
|
433
|
-
action: durableConversationUrl && tabPolicy !== 'new'
|
|
434
|
-
? {
|
|
435
|
-
...latest.action,
|
|
436
|
-
arguments: { ...latest.action.arguments, conversation_url: durableConversationUrl },
|
|
437
|
-
}
|
|
438
|
-
: latest.action,
|
|
439
|
-
lastTriggeredAt: timestamp,
|
|
440
|
-
lastOccurrenceId: occurrence.occurrenceId,
|
|
441
|
-
consecutiveFailures: 0,
|
|
442
|
-
nextEligibleAt: undefined,
|
|
443
|
-
pausedReason: undefined,
|
|
444
|
-
});
|
|
445
|
-
return saveOccurrence(controllerHome, {
|
|
446
|
-
...dispatching,
|
|
447
|
-
status: 'succeeded',
|
|
448
|
-
reason: `ChatGPT prompt dispatched via ${result.browserSessionId}${result.reusedSession ? ' (reused)' : ' (new session)'}; execution preference ${result.executionPreferenceVerified ? 'verified' : 'not verified'}.`,
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
417
|
async function executeExternalControllerWake(
|
|
453
418
|
controllerHome: string,
|
|
454
419
|
schedule: RepositorySchedule,
|
|
@@ -456,6 +421,7 @@ async function executeExternalControllerWake(
|
|
|
456
421
|
timestamp: string,
|
|
457
422
|
args: Record<string, unknown>,
|
|
458
423
|
extraEvidence: Record<string, unknown> = {},
|
|
424
|
+
options: { transientWakeFailure?: boolean } = {},
|
|
459
425
|
): Promise<ScheduleOccurrence> {
|
|
460
426
|
const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
|
|
461
427
|
const controllerType = typeof args.controller_type === 'string' ? args.controller_type.trim() : 'chatgpt';
|
|
@@ -476,7 +442,10 @@ async function executeExternalControllerWake(
|
|
|
476
442
|
return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} is terminal (${work.status}); automatic continuation stopped.`);
|
|
477
443
|
}
|
|
478
444
|
const existingOwner = getControllerSession(workStore, workId);
|
|
479
|
-
|
|
445
|
+
const occurrenceNowMs = Date.parse(timestamp);
|
|
446
|
+
if (existingOwner && controllerSessionBlocksRecovery(workStore, workId, {
|
|
447
|
+
nowMs: Number.isFinite(occurrenceNowMs) ? occurrenceNowMs : Date.now(),
|
|
448
|
+
})) {
|
|
480
449
|
saveSchedule(controllerHome, { ...schedule, lastTriggeredAt: timestamp, lastOccurrenceId: occurrence.occurrenceId });
|
|
481
450
|
return decideOccurrence(controllerHome, schedule, occurrence, 'nothing_to_do', 'skipped', `Work ${workId} already has an active Controller ${existingOwner.controllerId}.`);
|
|
482
451
|
}
|
|
@@ -512,37 +481,81 @@ async function executeExternalControllerWake(
|
|
|
512
481
|
if (controllerType === 'chatgpt') {
|
|
513
482
|
const reasoning = args.reasoning === 'medium' || args.reasoning === 'xhigh' ? args.reasoning : 'high';
|
|
514
483
|
const tabPolicy = args.tab_policy === 'reuse' || args.tab_policy === 'new' ? args.tab_policy : 'auto';
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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
|
+
}
|
|
544
557
|
}
|
|
545
|
-
const launched = launchSuperController({
|
|
558
|
+
const launched = await launchSuperController({
|
|
546
559
|
work: { controllerHome, repoId: schedule.repoId },
|
|
547
560
|
handoff: { controllerHome, repoId: schedule.repoId },
|
|
548
561
|
}, {
|
|
@@ -573,10 +586,35 @@ async function executeExternalControllerWake(
|
|
|
573
586
|
});
|
|
574
587
|
} catch (error) {
|
|
575
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
|
+
}
|
|
576
613
|
const failed = applyScheduleFailure(controllerHome, schedule.scheduleId, schedule.repoId, occurrence.occurrenceId, {
|
|
577
614
|
outcome: 'failed',
|
|
578
615
|
decision: 'execute',
|
|
579
616
|
reason,
|
|
617
|
+
...(failureClass === 'user_action_required' ? { pauseReason: reason } : {}),
|
|
580
618
|
handoff: {
|
|
581
619
|
title: `External Controller wake ${occurrence.occurrenceId} failed`,
|
|
582
620
|
summary: 'Forge recorded the schedule trigger but could not wake the configured external Controller.',
|
|
@@ -602,6 +640,9 @@ export async function evaluateSchedule(
|
|
|
602
640
|
force = false,
|
|
603
641
|
triggerContext?: ScheduleTriggerContext,
|
|
604
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;
|
|
605
646
|
if (!schedule.enabled && !force) return undefined;
|
|
606
647
|
const due = await triggerDue(controllerHome, schedule, force, triggerContext);
|
|
607
648
|
if (!due.due) return undefined;
|
|
@@ -803,24 +844,92 @@ export async function evaluateSchedule(
|
|
|
803
844
|
return executeExternalControllerWake(controllerHome, schedule, occurrence, timestamp, args);
|
|
804
845
|
}
|
|
805
846
|
|
|
806
|
-
if (schedule.action.operation ===
|
|
807
|
-
|
|
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;
|
|
913
|
+
}
|
|
808
914
|
}
|
|
809
915
|
|
|
810
916
|
if (schedule.action.operation === BROWSER_PROBE_OPERATION) {
|
|
811
917
|
const workId = typeof args.work_id === 'string' ? args.work_id.trim() : '';
|
|
812
|
-
|
|
918
|
+
const keepaliveOnly = args.keepalive_only === true;
|
|
919
|
+
if (!workId && !keepaliveOnly) {
|
|
813
920
|
return decideOccurrence(controllerHome, schedule, occurrence, 'operation_blocked', 'skipped', 'SCHEDULE_BROWSER_PROBE_WORK_ID_REQUIRED');
|
|
814
921
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
+
}
|
|
824
933
|
}
|
|
825
934
|
|
|
826
935
|
try {
|
|
@@ -847,17 +956,44 @@ export async function evaluateSchedule(
|
|
|
847
956
|
pausedReason: undefined,
|
|
848
957
|
});
|
|
849
958
|
if (args.wake_on_auth_required !== false) {
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
? args.
|
|
854
|
-
:
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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
|
+
);
|
|
859
995
|
}
|
|
860
|
-
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', `Browser watcher requires authentication: ${probe.authReason ?? 'login marker matched'}`, observationEvidence);
|
|
996
|
+
return decideOccurrence(controllerHome, observedSchedule, occurrence, 'operation_blocked', 'skipped', `Browser ${workId ? 'watcher' : 'keepalive'} requires authentication: ${probe.authReason ?? 'login marker matched'}`, observationEvidence);
|
|
861
997
|
}
|
|
862
998
|
|
|
863
999
|
if (probe.status === 'keepalive') {
|