@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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { resolveTrustedExecutable } from '../../shared/trusted-executable';
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
|
|
7
|
+
export interface GithubIssueWatchIssue {
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
state: 'open' | 'closed';
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
url: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GithubIssueWatchState {
|
|
16
|
+
schemaVersion: 1;
|
|
17
|
+
observedAt: string;
|
|
18
|
+
signatures: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GithubIssueWatchObservation {
|
|
22
|
+
status: 'baseline' | 'unchanged' | 'changed';
|
|
23
|
+
shouldWake: boolean;
|
|
24
|
+
changedOpenIssues: GithubIssueWatchIssue[];
|
|
25
|
+
issues: GithubIssueWatchIssue[];
|
|
26
|
+
nextState: GithubIssueWatchState;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stringValue(value: unknown): string | undefined {
|
|
30
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function boundedNumber(value: unknown, fallback: number, min: number, max: number): number {
|
|
34
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
35
|
+
? Math.max(min, Math.min(Math.trunc(value), max))
|
|
36
|
+
: fallback;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function issueSignature(issue: GithubIssueWatchIssue): string {
|
|
40
|
+
return `${issue.state}:${issue.updatedAt}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeIssue(value: unknown): GithubIssueWatchIssue | undefined {
|
|
44
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
45
|
+
const record = value as Record<string, unknown>;
|
|
46
|
+
const number = typeof record.number === 'number' && Number.isFinite(record.number) ? Math.trunc(record.number) : undefined;
|
|
47
|
+
const title = stringValue(record.title);
|
|
48
|
+
const state = record.state === 'open' || record.state === 'closed' ? record.state : undefined;
|
|
49
|
+
const updatedAt = stringValue(record.updatedAt);
|
|
50
|
+
const url = stringValue(record.url);
|
|
51
|
+
if (!number || !title || !state || !updatedAt || !url) return undefined;
|
|
52
|
+
return { number, title, state, updatedAt, url };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function normalizeGithubIssueWatchState(value: unknown): GithubIssueWatchState | undefined {
|
|
56
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
57
|
+
const record = value as Record<string, unknown>;
|
|
58
|
+
const signaturesValue = record.signatures;
|
|
59
|
+
if (!signaturesValue || typeof signaturesValue !== 'object' || Array.isArray(signaturesValue)) return undefined;
|
|
60
|
+
const signatures = Object.fromEntries(Object.entries(signaturesValue as Record<string, unknown>)
|
|
61
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === 'string'));
|
|
62
|
+
return {
|
|
63
|
+
schemaVersion: 1,
|
|
64
|
+
observedAt: stringValue(record.observedAt) ?? new Date(0).toISOString(),
|
|
65
|
+
signatures,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function classifyGithubIssueWatchObservation(
|
|
70
|
+
issues: GithubIssueWatchIssue[],
|
|
71
|
+
previousState: GithubIssueWatchState | undefined,
|
|
72
|
+
options: { since?: string; wakeOnFirstObservation?: boolean; observedAt?: string } = {},
|
|
73
|
+
): GithubIssueWatchObservation {
|
|
74
|
+
const observedAt = options.observedAt ?? new Date().toISOString();
|
|
75
|
+
const signatures = Object.fromEntries(issues.map((issue) => [String(issue.number), issueSignature(issue)]));
|
|
76
|
+
const firstObservation = !previousState;
|
|
77
|
+
const sinceMs = options.since ? Date.parse(options.since) : Number.NaN;
|
|
78
|
+
const changedOpenIssues = issues.filter((issue) => {
|
|
79
|
+
if (issue.state !== 'open') return false;
|
|
80
|
+
if (!firstObservation) return previousState.signatures[String(issue.number)] !== issueSignature(issue);
|
|
81
|
+
if (options.wakeOnFirstObservation === true) return true;
|
|
82
|
+
return Number.isFinite(sinceMs) && Date.parse(issue.updatedAt) > sinceMs;
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
status: firstObservation ? 'baseline' : changedOpenIssues.length > 0 ? 'changed' : 'unchanged',
|
|
86
|
+
shouldWake: changedOpenIssues.length > 0,
|
|
87
|
+
changedOpenIssues,
|
|
88
|
+
issues,
|
|
89
|
+
nextState: { schemaVersion: 1, observedAt, signatures },
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const DEFAULT_GH_EXECUTABLE_CANDIDATES = [
|
|
94
|
+
'/opt/homebrew/bin/gh',
|
|
95
|
+
'/usr/local/bin/gh',
|
|
96
|
+
'/usr/bin/gh',
|
|
97
|
+
] as const;
|
|
98
|
+
|
|
99
|
+
export function resolveGithubIssueWatchExecutable(
|
|
100
|
+
args: Record<string, unknown>,
|
|
101
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
102
|
+
preferredPaths: readonly string[] = DEFAULT_GH_EXECUTABLE_CANDIDATES,
|
|
103
|
+
): string {
|
|
104
|
+
const configured = stringValue(args.gh_executable) ?? stringValue(env.FORGE_GH_EXECUTABLE);
|
|
105
|
+
const resolved = resolveTrustedExecutable({
|
|
106
|
+
name: 'gh',
|
|
107
|
+
configured,
|
|
108
|
+
preferredPaths,
|
|
109
|
+
env,
|
|
110
|
+
});
|
|
111
|
+
if (resolved.configuredInvalid) {
|
|
112
|
+
throw new Error('SCHEDULE_GITHUB_ISSUE_WATCH_GH_EXECUTABLE_INVALID: gh_executable/FORGE_GH_EXECUTABLE must be an absolute executable path.');
|
|
113
|
+
}
|
|
114
|
+
if (!resolved.executable) {
|
|
115
|
+
throw new Error('SCHEDULE_GITHUB_ISSUE_WATCH_GH_EXECUTABLE_NOT_FOUND: GitHub CLI was not found in trusted Homebrew/system locations or PATH.');
|
|
116
|
+
}
|
|
117
|
+
return resolved.executable;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function executeScheduledGithubIssueWatch(input: {
|
|
121
|
+
repoRoot: string;
|
|
122
|
+
args: Record<string, unknown>;
|
|
123
|
+
observedAt?: string;
|
|
124
|
+
}): Promise<GithubIssueWatchObservation> {
|
|
125
|
+
const repository = stringValue(input.args.github_repository);
|
|
126
|
+
if (!repository || !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) {
|
|
127
|
+
throw new Error('SCHEDULE_GITHUB_ISSUE_WATCH_REPOSITORY_REQUIRED');
|
|
128
|
+
}
|
|
129
|
+
const limit = boundedNumber(input.args.issue_limit, 50, 10, 100);
|
|
130
|
+
const timeoutMs = boundedNumber(input.args.timeout_ms, 15_000, 1_000, 60_000);
|
|
131
|
+
try {
|
|
132
|
+
const ghExecutable = resolveGithubIssueWatchExecutable(input.args);
|
|
133
|
+
const result = await execFileAsync(ghExecutable, [
|
|
134
|
+
'api',
|
|
135
|
+
'-H', 'Accept: application/vnd.github+json',
|
|
136
|
+
`repos/${repository}/issues?state=all&sort=updated&direction=desc&per_page=${limit}`,
|
|
137
|
+
'--jq', 'map(select(.pull_request|not)) | map({number,title,state,updatedAt:.updated_at,url:.html_url})',
|
|
138
|
+
], {
|
|
139
|
+
cwd: input.repoRoot,
|
|
140
|
+
encoding: 'utf8',
|
|
141
|
+
timeout: timeoutMs,
|
|
142
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
143
|
+
});
|
|
144
|
+
const parsed = JSON.parse(String(result.stdout)) as unknown;
|
|
145
|
+
if (!Array.isArray(parsed)) throw new Error('GitHub issue response was not an array.');
|
|
146
|
+
const issues = parsed.map(normalizeIssue).filter((issue): issue is GithubIssueWatchIssue => Boolean(issue));
|
|
147
|
+
const previousState = normalizeGithubIssueWatchState(input.args.issue_watch_state);
|
|
148
|
+
return classifyGithubIssueWatchObservation(issues, previousState, {
|
|
149
|
+
since: stringValue(input.args.issue_watch_since),
|
|
150
|
+
wakeOnFirstObservation: input.args.wake_on_first_observation === true,
|
|
151
|
+
observedAt: input.observedAt,
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
155
|
+
if (message.startsWith('SCHEDULE_GITHUB_ISSUE_WATCH_GH_EXECUTABLE_')) throw error;
|
|
156
|
+
throw new Error(`SCHEDULE_GITHUB_ISSUE_WATCH_FAILED: ${message}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -11,14 +11,21 @@ import {
|
|
|
11
11
|
|
|
12
12
|
const TERMINAL_OCCURRENCE_STATUSES = new Set<ScheduleOccurrence['status']>(['succeeded', 'failed', 'shadowed', 'skipped']);
|
|
13
13
|
|
|
14
|
-
function
|
|
15
|
-
const shouldPause = nextFailures >= schedule.policy.maxFailures;
|
|
14
|
+
function computeScheduleBackoff(schedule: ReturnType<typeof getSchedule>, nextFailures: number): string {
|
|
16
15
|
const backoffBase = Math.max(1, schedule.policy.backoffBaseMinutes ?? schedule.policy.cooldownMinutes ?? 1);
|
|
17
16
|
const backoffMax = Math.max(backoffBase, schedule.policy.backoffMaxMinutes ?? 24 * 60);
|
|
18
|
-
|
|
17
|
+
// Persistent readiness failures may span hours or days. Cap the exponent so
|
|
18
|
+
// the calculation stays bounded even while the observable failure count grows.
|
|
19
|
+
const exponent = Math.min(16, Math.max(0, nextFailures - 1));
|
|
20
|
+
const backoffMinutes = Math.min(backoffMax, backoffBase * (2 ** exponent));
|
|
21
|
+
return new Date(Date.now() + backoffMinutes * 60_000).toISOString();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function computeScheduleFailureState(schedule: ReturnType<typeof getSchedule>, nextFailures: number): Pick<ReturnType<typeof getSchedule>, 'consecutiveFailures' | 'nextEligibleAt' | 'enabled' | 'pausedReason'> {
|
|
25
|
+
const shouldPause = nextFailures >= schedule.policy.maxFailures;
|
|
19
26
|
return {
|
|
20
27
|
consecutiveFailures: nextFailures,
|
|
21
|
-
nextEligibleAt:
|
|
28
|
+
nextEligibleAt: computeScheduleBackoff(schedule, nextFailures),
|
|
22
29
|
enabled: shouldPause ? false : schedule.enabled,
|
|
23
30
|
pausedReason: shouldPause ? 'Maximum consecutive failures reached.' : undefined,
|
|
24
31
|
};
|
|
@@ -68,6 +75,43 @@ export function applyScheduleFailure(
|
|
|
68
75
|
return { schedule: nextSchedule, occurrence: nextOccurrence };
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
export function applyScheduleRetryableFailure(
|
|
79
|
+
controllerHome: string,
|
|
80
|
+
scheduleId: string,
|
|
81
|
+
repoId: string,
|
|
82
|
+
occurrenceId: string,
|
|
83
|
+
options: {
|
|
84
|
+
outcome: 'failed' | 'skipped';
|
|
85
|
+
decision: ScheduleDecisionType;
|
|
86
|
+
reason: string;
|
|
87
|
+
countFailure?: boolean;
|
|
88
|
+
},
|
|
89
|
+
): { schedule: ReturnType<typeof getSchedule>; occurrence?: ScheduleOccurrence } {
|
|
90
|
+
const schedule = getSchedule(controllerHome, repoId, scheduleId);
|
|
91
|
+
const occurrence = getOccurrence(controllerHome, repoId, occurrenceId);
|
|
92
|
+
let nextOccurrence = occurrence;
|
|
93
|
+
if (occurrence && !TERMINAL_OCCURRENCE_STATUSES.has(occurrence.status)) {
|
|
94
|
+
nextOccurrence = saveOccurrence(controllerHome, {
|
|
95
|
+
...occurrence,
|
|
96
|
+
status: options.outcome,
|
|
97
|
+
decision: options.decision,
|
|
98
|
+
reason: options.reason,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const countFailure = options.countFailure !== false;
|
|
102
|
+
const nextFailures = countFailure ? schedule.consecutiveFailures + 1 : schedule.consecutiveFailures;
|
|
103
|
+
const nextSchedule = saveSchedule(controllerHome, {
|
|
104
|
+
...schedule,
|
|
105
|
+
consecutiveFailures: nextFailures,
|
|
106
|
+
nextEligibleAt: computeScheduleBackoff(schedule, Math.max(1, nextFailures)),
|
|
107
|
+
// Retryable readiness/semantic-wait failures never turn a durable schedule
|
|
108
|
+
// into a manual-resume task. Respect a concurrent explicit pause, however.
|
|
109
|
+
enabled: schedule.enabled,
|
|
110
|
+
pausedReason: schedule.enabled ? undefined : schedule.pausedReason,
|
|
111
|
+
});
|
|
112
|
+
return { schedule: nextSchedule, occurrence: nextOccurrence };
|
|
113
|
+
}
|
|
114
|
+
|
|
71
115
|
export function markScheduledExecutionRunning(controllerHome: string, job: ExecutionJob): void {
|
|
72
116
|
if (job.type !== 'scheduled-occurrence') return;
|
|
73
117
|
const occurrenceId = typeof job.payload.occurrenceId === 'string' ? job.payload.occurrenceId : undefined;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'crypto';
|
|
2
|
-
import { existsSync, readdirSync } from 'fs';
|
|
2
|
+
import { existsSync, readdirSync, rmSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { repositoryControllerRoot } from '../../../cli/repositories/controller-home';
|
|
5
5
|
import { withControllerLock } from '../../../cli/repositories/locks';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
createHandoffItem,
|
|
8
|
+
getHandoffItem,
|
|
9
|
+
listHandoffItems,
|
|
10
|
+
resolveHandoffItem,
|
|
11
|
+
} from '../../control-plane/facade/handoff-inbox-store';
|
|
7
12
|
import { readJsonFile, writeJsonAtomic } from '../../shared/json-files';
|
|
8
13
|
import { appendRuntimeEvent } from '../../evidence/event-ledger';
|
|
9
14
|
import type { RepositorySchedule, ScheduleDecision, ScheduleOccurrence } from './types';
|
|
@@ -159,17 +164,120 @@ export function listSchedules(controllerHome: string, repoId: string): Repositor
|
|
|
159
164
|
} catch { return []; }
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
export function deleteSchedule(controllerHome: string, repoId: string, scheduleId: string): RepositorySchedule {
|
|
168
|
+
return withControllerLock(controllerHome, { scope: 'task', repoId, taskId: `schedule-${scheduleId}` }, `delete-schedule:${scheduleId}`, () => {
|
|
169
|
+
const current = getSchedule(controllerHome, repoId, scheduleId);
|
|
170
|
+
if (current.enabled) throw new Error(`SCHEDULE_DELETE_REQUIRES_PAUSED: ${scheduleId}`);
|
|
171
|
+
if (listActiveOccurrences(controllerHome, repoId, scheduleId).length > 0) {
|
|
172
|
+
throw new Error(`SCHEDULE_DELETE_ACTIVE_OCCURRENCE: ${scheduleId}`);
|
|
173
|
+
}
|
|
174
|
+
rmSync(schedulePath(controllerHome, repoId, scheduleId), { force: true });
|
|
175
|
+
const requestRecordPath = requestPath(controllerHome, repoId, current.requestId);
|
|
176
|
+
if (existsSync(requestRecordPath)) {
|
|
177
|
+
const record = readJsonFile<ScheduleRequestRecord>(requestRecordPath);
|
|
178
|
+
if (record.scheduleId === scheduleId) rmSync(requestRecordPath, { force: true });
|
|
179
|
+
}
|
|
180
|
+
appendRuntimeEvent(controllerHome, {
|
|
181
|
+
repoId,
|
|
182
|
+
entityType: 'schedule',
|
|
183
|
+
entityId: scheduleId,
|
|
184
|
+
eventType: 'schedule_deleted',
|
|
185
|
+
requestId: current.requestId,
|
|
186
|
+
revision: current.revision + 1,
|
|
187
|
+
data: { name: current.name },
|
|
188
|
+
});
|
|
189
|
+
return current;
|
|
190
|
+
}, 10_000);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function scheduleFailureClass(reason: string): string {
|
|
194
|
+
const trimmed = reason.trim();
|
|
195
|
+
const explicitCode = trimmed.match(/^([A-Z][A-Z0-9_]{2,})(?::|\b)/)?.[1];
|
|
196
|
+
if (explicitCode) return explicitCode;
|
|
197
|
+
const prefix = (trimmed.split(':', 1)[0] ?? trimmed)
|
|
198
|
+
.replace(/\s+/g, ' ')
|
|
199
|
+
.trim()
|
|
200
|
+
.toLowerCase()
|
|
201
|
+
.slice(0, 120);
|
|
202
|
+
return prefix || 'unknown_failure';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function activeScheduleFailureHandoff(
|
|
206
|
+
controllerHome: string,
|
|
207
|
+
repoId: string,
|
|
208
|
+
scheduleId: string,
|
|
209
|
+
creationReason: ScheduleOccurrenceHandoffInput['creationReason'],
|
|
210
|
+
reason: string,
|
|
211
|
+
) {
|
|
212
|
+
const failureClass = scheduleFailureClass(reason);
|
|
213
|
+
return listHandoffItems({ controllerHome, repoId, status: 'active', limit: 100 }).find((item) => (
|
|
214
|
+
item.currentState?.taskId === scheduleId
|
|
215
|
+
&& item.creationReason === creationReason
|
|
216
|
+
&& scheduleFailureClass(item.reason) === failureClass
|
|
217
|
+
));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function stableScheduleFailureHandoffId(
|
|
221
|
+
controllerHome: string,
|
|
222
|
+
repoId: string,
|
|
223
|
+
scheduleId: string,
|
|
224
|
+
creationReason: ScheduleOccurrenceHandoffInput['creationReason'],
|
|
225
|
+
reason: string,
|
|
226
|
+
occurrenceId: string,
|
|
227
|
+
): string {
|
|
228
|
+
const digest = createHash('sha256')
|
|
229
|
+
.update(`${scheduleId}:${creationReason}:${scheduleFailureClass(reason)}`)
|
|
230
|
+
.digest('hex')
|
|
231
|
+
.slice(0, 20);
|
|
232
|
+
const baseId = `schedule-failure-${digest}`;
|
|
233
|
+
if (!getHandoffItem({ controllerHome, repoId }, baseId)) return baseId;
|
|
234
|
+
// A previous failure streak may already have resolved the stable item. Handoff
|
|
235
|
+
// records are immutable apart from lifecycle status, so use one new epoch id;
|
|
236
|
+
// subsequent failures in this streak will discover and reuse the active item.
|
|
237
|
+
const epoch = createHash('sha256').update(occurrenceId).digest('hex').slice(0, 8);
|
|
238
|
+
return `${baseId}-${epoch}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resolveRecoveredScheduleFailureHandoffs(controllerHome: string, occurrence: ScheduleOccurrence): void {
|
|
242
|
+
try {
|
|
243
|
+
const active = listHandoffItems({ controllerHome, repoId: occurrence.repoId, status: 'active', limit: 100 })
|
|
244
|
+
.filter((item) => (
|
|
245
|
+
item.currentState?.taskId === occurrence.scheduleId
|
|
246
|
+
&& item.creationReason === 'repeated_infrastructure_failure'
|
|
247
|
+
));
|
|
248
|
+
for (const item of active) {
|
|
249
|
+
try {
|
|
250
|
+
resolveHandoffItem(
|
|
251
|
+
{ controllerHome, repoId: occurrence.repoId },
|
|
252
|
+
item.id,
|
|
253
|
+
{
|
|
254
|
+
decision: `Schedule ${occurrence.scheduleId} recovered on occurrence ${occurrence.occurrenceId}.`,
|
|
255
|
+
resolver: 'schedule-runtime',
|
|
256
|
+
},
|
|
257
|
+
);
|
|
258
|
+
} catch {
|
|
259
|
+
// The succeeded occurrence remains authoritative. Inbox cleanup is
|
|
260
|
+
// best-effort and must never turn schedule recovery into a failure.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
// Preserve successful Schedule settlement even if Inbox storage is unavailable.
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
162
268
|
export function saveOccurrence(controllerHome: string, occurrence: ScheduleOccurrence): ScheduleOccurrence {
|
|
163
|
-
|
|
269
|
+
const next = withControllerLock(controllerHome, { scope: 'task', repoId: occurrence.repoId, taskId: `schedule-${occurrence.scheduleId}` }, `save-occurrence:${occurrence.occurrenceId}`, () => {
|
|
164
270
|
const existingPath = occurrencePath(controllerHome, occurrence.repoId, occurrence.occurrenceId);
|
|
165
271
|
const previous = existsSync(existingPath) ? readJsonFile<ScheduleOccurrence>(existingPath) : undefined;
|
|
166
|
-
const
|
|
167
|
-
writeJsonAtomic(existingPath,
|
|
168
|
-
upsertOccurrenceIndexUnlocked(controllerHome,
|
|
169
|
-
const schedule = getSchedule(controllerHome,
|
|
170
|
-
appendRuntimeEvent(controllerHome, { repoId:
|
|
171
|
-
return
|
|
272
|
+
const saved = { ...occurrence, revision: (previous?.revision ?? occurrence.revision ?? 0) + 1, updatedAt: new Date().toISOString() };
|
|
273
|
+
writeJsonAtomic(existingPath, saved);
|
|
274
|
+
upsertOccurrenceIndexUnlocked(controllerHome, saved);
|
|
275
|
+
const schedule = getSchedule(controllerHome, saved.repoId, saved.scheduleId);
|
|
276
|
+
appendRuntimeEvent(controllerHome, { repoId: saved.repoId, entityType: 'occurrence', entityId: saved.occurrenceId, eventType: `occurrence_${saved.status}`, requestId: `${schedule.requestId}:${saved.windowKey}`, revision: saved.revision, correlationId: saved.scheduleId, data: { decision: saved.decision, jobId: saved.jobId, reason: saved.reason } });
|
|
277
|
+
return saved;
|
|
172
278
|
}, 10_000);
|
|
279
|
+
if (next.status === 'succeeded') resolveRecoveredScheduleFailureHandoffs(controllerHome, next);
|
|
280
|
+
return next;
|
|
173
281
|
}
|
|
174
282
|
|
|
175
283
|
export function getOccurrence(controllerHome: string, repoId: string, occurrenceId: string): ScheduleOccurrence | undefined {
|
|
@@ -184,7 +292,20 @@ export function recordScheduleOccurrenceHandoff(
|
|
|
184
292
|
input: ScheduleOccurrenceHandoffInput,
|
|
185
293
|
): ScheduleOccurrence {
|
|
186
294
|
if (occurrence.handoffId) return occurrence;
|
|
187
|
-
const
|
|
295
|
+
const reusable = input.creationReason === 'repeated_infrastructure_failure'
|
|
296
|
+
? activeScheduleFailureHandoff(controllerHome, occurrence.repoId, schedule.scheduleId, input.creationReason, input.reason)
|
|
297
|
+
: undefined;
|
|
298
|
+
if (reusable) return saveOccurrence(controllerHome, { ...occurrence, handoffId: reusable.id });
|
|
299
|
+
const handoffId = input.creationReason === 'repeated_infrastructure_failure'
|
|
300
|
+
? stableScheduleFailureHandoffId(
|
|
301
|
+
controllerHome,
|
|
302
|
+
occurrence.repoId,
|
|
303
|
+
schedule.scheduleId,
|
|
304
|
+
input.creationReason,
|
|
305
|
+
input.reason,
|
|
306
|
+
occurrence.occurrenceId,
|
|
307
|
+
)
|
|
308
|
+
: `schedule-${occurrence.occurrenceId}`;
|
|
188
309
|
const evidenceRefs: NonNullable<ScheduleOccurrenceHandoffInput['evidenceRefs']> = [
|
|
189
310
|
{ title: `Occurrence ${occurrence.occurrenceId}`, summary: occurrence.reason ?? input.reason, detailLevel: 'summary' as const },
|
|
190
311
|
];
|
|
@@ -10,10 +10,8 @@ export type ScheduleTriggerType =
|
|
|
10
10
|
| 'manual';
|
|
11
11
|
|
|
12
12
|
export interface ScheduleCondition {
|
|
13
|
-
kind: 'repository_clean' | 'job_succeeded' | 'job_terminal'
|
|
13
|
+
kind: 'repository_clean' | 'job_succeeded' | 'job_terminal';
|
|
14
14
|
jobId?: string;
|
|
15
|
-
semanticKey?: string;
|
|
16
|
-
observationThreshold?: number;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
export interface ScheduleTrigger {
|
|
@@ -73,6 +71,11 @@ export interface RepositorySchedule {
|
|
|
73
71
|
consecutiveNoops?: number;
|
|
74
72
|
nextEligibleAt?: string;
|
|
75
73
|
pausedReason?: string;
|
|
74
|
+
/** Last deterministic external observation for browser or issue watchers. */
|
|
75
|
+
lastObservationAt?: string;
|
|
76
|
+
lastObservationFingerprint?: string;
|
|
77
|
+
lastObservationChangedAt?: string;
|
|
78
|
+
lastObservationStatus?: 'baseline' | 'unchanged' | 'changed' | 'keepalive' | 'auth_required';
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
export type ScheduleDecisionType =
|
|
@@ -19,9 +19,11 @@ import type {
|
|
|
19
19
|
} from './types';
|
|
20
20
|
|
|
21
21
|
export type ContinuationControllerType = 'chatgpt' | 'codex' | 'claude' | 'grok';
|
|
22
|
+
export type WorkScheduleMode = 'continuation' | 'browser_watch' | 'browser_keepalive';
|
|
22
23
|
|
|
23
24
|
export interface WorkContinuationScheduleInput {
|
|
24
|
-
workId
|
|
25
|
+
workId?: string;
|
|
26
|
+
scheduleMode?: WorkScheduleMode;
|
|
25
27
|
controllerType?: ContinuationControllerType;
|
|
26
28
|
executable?: string;
|
|
27
29
|
launchArgs?: string[];
|
|
@@ -30,6 +32,18 @@ export interface WorkContinuationScheduleInput {
|
|
|
30
32
|
browserSessionId?: string;
|
|
31
33
|
conversationUrl?: string;
|
|
32
34
|
continuationPrompt?: string;
|
|
35
|
+
probeUrl?: string;
|
|
36
|
+
probeBrowserSessionId?: string;
|
|
37
|
+
probeSelector?: string;
|
|
38
|
+
probeMaxChars?: number;
|
|
39
|
+
probeTimeoutMs?: number;
|
|
40
|
+
includeTerms?: string[];
|
|
41
|
+
ignorePatterns?: string[];
|
|
42
|
+
loginUrlTerms?: string[];
|
|
43
|
+
loginTextTerms?: string[];
|
|
44
|
+
wakeOnFirstObservation?: boolean;
|
|
45
|
+
wakeOnAuthRequired?: boolean;
|
|
46
|
+
authRequiredPrompt?: string;
|
|
33
47
|
scheduleName?: string;
|
|
34
48
|
requestId?: string;
|
|
35
49
|
triggerType?: ScheduleTriggerType;
|
|
@@ -70,7 +84,7 @@ function activeWork(controllerHome: string, repoId: string, workId: string): Wor
|
|
|
70
84
|
}
|
|
71
85
|
|
|
72
86
|
function continuationSchedule(schedule: RepositorySchedule): RepositorySchedule {
|
|
73
|
-
if (schedule.action.operation
|
|
87
|
+
if (!['external_controller_wake', 'browser_probe'].includes(schedule.action.operation)) {
|
|
74
88
|
throw new Error(`SCHEDULE_NOT_WORK_CONTINUATION: ${schedule.scheduleId}`);
|
|
75
89
|
}
|
|
76
90
|
return schedule;
|
|
@@ -92,8 +106,9 @@ function normalizedTrigger(input: WorkContinuationScheduleInput): ScheduleTrigge
|
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
function wakeArguments(input: WorkContinuationScheduleInput, controllerType: ContinuationControllerType): Record<string, unknown> {
|
|
109
|
+
const workId = input.workId?.trim();
|
|
95
110
|
return {
|
|
96
|
-
work_id:
|
|
111
|
+
...(workId ? { work_id: workId } : {}),
|
|
97
112
|
controller_type: controllerType,
|
|
98
113
|
...(input.executable?.trim() ? { executable: input.executable.trim() } : {}),
|
|
99
114
|
...(input.launchArgs ? { launch_args: input.launchArgs.map(String) } : {}),
|
|
@@ -105,55 +120,120 @@ function wakeArguments(input: WorkContinuationScheduleInput, controllerType: Con
|
|
|
105
120
|
};
|
|
106
121
|
}
|
|
107
122
|
|
|
123
|
+
function probeArguments(input: WorkContinuationScheduleInput, controllerType: ContinuationControllerType, keepaliveOnly: boolean): Record<string, unknown> {
|
|
124
|
+
if (!input.probeUrl?.trim() && !input.probeBrowserSessionId?.trim()) {
|
|
125
|
+
throw new Error('SCHEDULE_BROWSER_PROBE_TARGET_REQUIRED');
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
...wakeArguments(input, controllerType),
|
|
129
|
+
...(input.probeUrl?.trim() ? { probe_url: input.probeUrl.trim() } : {}),
|
|
130
|
+
...(input.probeBrowserSessionId?.trim() ? { probe_session_id: input.probeBrowserSessionId.trim() } : {}),
|
|
131
|
+
...(input.probeSelector?.trim() ? { selector: input.probeSelector.trim() } : {}),
|
|
132
|
+
...(input.probeMaxChars !== undefined ? { max_chars: Math.max(256, Math.min(Math.trunc(input.probeMaxChars), 100_000)) } : {}),
|
|
133
|
+
...(input.probeTimeoutMs !== undefined ? { timeout_ms: Math.max(1_000, Math.min(Math.trunc(input.probeTimeoutMs), 120_000)) } : {}),
|
|
134
|
+
...(input.includeTerms ? { include_terms: input.includeTerms.map(String).filter(Boolean) } : {}),
|
|
135
|
+
...(input.ignorePatterns ? { ignore_patterns: input.ignorePatterns.map(String).filter(Boolean) } : {}),
|
|
136
|
+
...(input.loginUrlTerms ? { login_url_terms: input.loginUrlTerms.map(String).filter(Boolean) } : {}),
|
|
137
|
+
...(input.loginTextTerms ? { login_text_terms: input.loginTextTerms.map(String).filter(Boolean) } : {}),
|
|
138
|
+
...(input.wakeOnFirstObservation === true ? { wake_on_first_observation: true } : {}),
|
|
139
|
+
...(input.wakeOnAuthRequired === false ? { wake_on_auth_required: false } : {}),
|
|
140
|
+
...(input.authRequiredPrompt?.trim() ? { auth_required_prompt: input.authRequiredPrompt.trim() } : {}),
|
|
141
|
+
keepalive_only: keepaliveOnly,
|
|
142
|
+
wake_on_change: !keepaliveOnly,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
108
146
|
export function createWorkContinuationSchedule(
|
|
109
147
|
controllerHome: string,
|
|
110
148
|
repoId: string,
|
|
111
149
|
input: WorkContinuationScheduleInput,
|
|
112
|
-
): { schedule: RepositorySchedule; work
|
|
113
|
-
const work = activeWork(controllerHome, repoId, input.workId);
|
|
150
|
+
): { schedule: RepositorySchedule; work?: WorkContract } {
|
|
114
151
|
const controllerType = input.controllerType ?? 'chatgpt';
|
|
152
|
+
const scheduleMode = input.scheduleMode ?? 'continuation';
|
|
153
|
+
const requestedWorkId = input.workId?.trim();
|
|
154
|
+
if (!requestedWorkId && scheduleMode !== 'browser_keepalive') throw new Error('WORK_ID_REQUIRED');
|
|
155
|
+
if (!requestedWorkId && scheduleMode === 'browser_keepalive' && controllerType !== 'chatgpt') throw new Error('STANDALONE_BROWSER_KEEPALIVE_CHATGPT_REQUIRED');
|
|
156
|
+
const work = requestedWorkId ? activeWork(controllerHome, repoId, requestedWorkId) : undefined;
|
|
115
157
|
const trigger = normalizedTrigger(input);
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
const operation = scheduleMode === 'continuation' ? 'external_controller_wake' : 'browser_probe';
|
|
159
|
+
const actionArguments = scheduleMode === 'continuation'
|
|
160
|
+
? wakeArguments(input, controllerType)
|
|
161
|
+
: probeArguments(input, controllerType, scheduleMode === 'browser_keepalive');
|
|
162
|
+
assertAutomatedOperationAllowed(operation, actionArguments);
|
|
163
|
+
const name = input.scheduleName?.trim()
|
|
164
|
+
|| (scheduleMode === 'browser_watch' ? `Watch external state for Work ${work!.workId}`
|
|
165
|
+
: scheduleMode === 'browser_keepalive'
|
|
166
|
+
? (work ? `Keep browser session alive for Work ${work.workId}` : 'Keep browser session alive')
|
|
167
|
+
: `Continue Work ${work!.workId}`);
|
|
168
|
+
const policy = {
|
|
169
|
+
maxActiveOccurrences: 1,
|
|
170
|
+
maxFailures: input.maxFailures !== undefined ? Math.max(1, Math.trunc(input.maxFailures)) : 3,
|
|
171
|
+
cooldownMinutes: input.cooldownMinutes !== undefined ? Math.max(0, Math.trunc(input.cooldownMinutes)) : 120,
|
|
172
|
+
dailyBudgetMinutes: input.dailyBudgetMinutes !== undefined ? Math.max(1, Math.trunc(input.dailyBudgetMinutes)) : 180,
|
|
173
|
+
shadowMode: input.shadowMode !== false,
|
|
174
|
+
backoffBaseMinutes: input.backoffBaseMinutes !== undefined ? Math.max(1, Math.trunc(input.backoffBaseMinutes)) : 5,
|
|
175
|
+
backoffMaxMinutes: input.backoffMaxMinutes !== undefined ? Math.max(1, Math.trunc(input.backoffMaxMinutes)) : 24 * 60,
|
|
176
|
+
};
|
|
177
|
+
const stopConditions = input.stopConditions ?? (work ? ['work_terminal', 'human_review_required', 'external_blocker'] : []);
|
|
178
|
+
|
|
179
|
+
// A durable Work has one authoritative continuation lane. Changing cadence,
|
|
180
|
+
// prompt, controller, or policy updates that lane instead of minting another
|
|
181
|
+
// Workflow. Browser watches/keepalives are intentionally excluded because one
|
|
182
|
+
// Work may legitimately observe several independent external targets.
|
|
183
|
+
if (scheduleMode === 'continuation') {
|
|
184
|
+
const existing = listSchedules(controllerHome, repoId)
|
|
185
|
+
.filter((candidate) => candidate.action.operation === 'external_controller_wake')
|
|
186
|
+
.filter((candidate) => candidate.action.arguments?.work_id === work!.workId);
|
|
187
|
+
const authoritative = [...existing].reverse().find((candidate) => candidate.enabled) ?? existing.at(-1);
|
|
188
|
+
if (authoritative) {
|
|
189
|
+
const schedule = saveSchedule(controllerHome, {
|
|
190
|
+
...authoritative,
|
|
191
|
+
name,
|
|
192
|
+
enabled: true,
|
|
193
|
+
trigger,
|
|
194
|
+
policy,
|
|
195
|
+
action: { operation, target: 'runtime', arguments: actionArguments, resourceClaims: [] },
|
|
196
|
+
stopConditions,
|
|
197
|
+
pausedReason: undefined,
|
|
198
|
+
});
|
|
199
|
+
for (const duplicate of existing) {
|
|
200
|
+
if (duplicate.scheduleId === schedule.scheduleId || !duplicate.enabled) continue;
|
|
201
|
+
saveSchedule(controllerHome, {
|
|
202
|
+
...duplicate,
|
|
203
|
+
enabled: false,
|
|
204
|
+
pausedReason: `Superseded by authoritative continuation ${schedule.scheduleId} for Work ${work!.workId}.`,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return { schedule, work };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
119
211
|
const semantic = JSON.stringify({
|
|
120
212
|
repoId,
|
|
121
|
-
workId: work
|
|
213
|
+
workId: work?.workId,
|
|
122
214
|
controllerType,
|
|
215
|
+
scheduleMode,
|
|
123
216
|
trigger,
|
|
124
217
|
name,
|
|
125
218
|
actionArguments,
|
|
126
|
-
policy
|
|
127
|
-
|
|
128
|
-
cooldownMinutes: input.cooldownMinutes,
|
|
129
|
-
dailyBudgetMinutes: input.dailyBudgetMinutes,
|
|
130
|
-
shadowMode: input.shadowMode,
|
|
131
|
-
backoffBaseMinutes: input.backoffBaseMinutes,
|
|
132
|
-
backoffMaxMinutes: input.backoffMaxMinutes,
|
|
133
|
-
},
|
|
134
|
-
stopConditions: input.stopConditions,
|
|
219
|
+
policy,
|
|
220
|
+
stopConditions,
|
|
135
221
|
});
|
|
136
222
|
const requestId = input.requestId?.trim()
|
|
137
|
-
||
|
|
223
|
+
|| (work
|
|
224
|
+
? `work-continuation:${repoId}:${work.workId}:${createHash('sha256').update(semantic).digest('hex').slice(0, 16)}`
|
|
225
|
+
: `browser-keepalive:${repoId}:${createHash('sha256').update(semantic).digest('hex').slice(0, 16)}`);
|
|
138
226
|
const schedule = createSchedule(controllerHome, {
|
|
139
227
|
requestId,
|
|
140
228
|
repoId,
|
|
141
229
|
name,
|
|
142
230
|
enabled: true,
|
|
143
231
|
trigger,
|
|
144
|
-
policy
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
shadowMode: input.shadowMode !== false,
|
|
150
|
-
backoffBaseMinutes: input.backoffBaseMinutes !== undefined ? Math.max(1, Math.trunc(input.backoffBaseMinutes)) : 5,
|
|
151
|
-
backoffMaxMinutes: input.backoffMaxMinutes !== undefined ? Math.max(1, Math.trunc(input.backoffMaxMinutes)) : 24 * 60,
|
|
152
|
-
},
|
|
153
|
-
// The schedule only wakes a Controller. The Controller acquires Work/resource
|
|
154
|
-
// ownership separately, so the wake itself must not claim repository writes.
|
|
155
|
-
action: { operation: 'external_controller_wake', target: 'runtime', arguments: actionArguments, resourceClaims: [] },
|
|
156
|
-
stopConditions: input.stopConditions ?? ['work_terminal', 'human_review_required', 'external_blocker'],
|
|
232
|
+
policy,
|
|
233
|
+
// Work-bound schedules only perform a deterministic wake or a bounded read-only
|
|
234
|
+
// browser probe. Controller/Work write ownership is acquired separately.
|
|
235
|
+
action: { operation, target: 'runtime', arguments: actionArguments, resourceClaims: [] },
|
|
236
|
+
stopConditions,
|
|
157
237
|
});
|
|
158
238
|
return { schedule, work };
|
|
159
239
|
}
|
|
@@ -164,7 +244,7 @@ export function listWorkContinuationSchedules(
|
|
|
164
244
|
options: WorkContinuationScheduleListOptions = {},
|
|
165
245
|
): { schedules: RepositorySchedule[]; occurrences?: ScheduleOccurrence[] } {
|
|
166
246
|
const schedules = listSchedules(controllerHome, repoId)
|
|
167
|
-
.filter((entry) => entry.action.operation
|
|
247
|
+
.filter((entry) => ['external_controller_wake', 'browser_probe'].includes(entry.action.operation))
|
|
168
248
|
.filter((entry) => !options.workId || entry.action.arguments?.work_id === options.workId);
|
|
169
249
|
if (!options.includeOccurrences) return { schedules };
|
|
170
250
|
const scheduleIds = new Set(schedules.map((entry) => entry.scheduleId));
|
|
@@ -204,7 +284,13 @@ export function resumeWorkContinuationSchedule(controllerHome: string, repoId: s
|
|
|
204
284
|
const schedule = continuationSchedule(getSchedule(controllerHome, repoId, scheduleId));
|
|
205
285
|
const workId = typeof schedule.action.arguments?.work_id === 'string' ? schedule.action.arguments.work_id : '';
|
|
206
286
|
activeWork(controllerHome, repoId, workId);
|
|
207
|
-
return saveSchedule(controllerHome, {
|
|
287
|
+
return saveSchedule(controllerHome, {
|
|
288
|
+
...schedule,
|
|
289
|
+
enabled: true,
|
|
290
|
+
pausedReason: undefined,
|
|
291
|
+
consecutiveFailures: 0,
|
|
292
|
+
nextEligibleAt: undefined,
|
|
293
|
+
});
|
|
208
294
|
}
|
|
209
295
|
|
|
210
296
|
export async function triggerWorkContinuationSchedule(
|