agents-relay 2.0.168 → 2.0.170
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/README.md +13 -19
- package/dist/adapters.js +3 -9
- package/dist/blockers.js +3 -2
- package/dist/cli-help.js +3 -3
- package/dist/cli-job-create.js +2 -4
- package/dist/cli-support.js +5 -16
- package/dist/cli.js +9 -11
- package/dist/dashboard-html.js +5 -9
- package/dist/dashboard-style.js +0 -1
- package/dist/execution-recovery.js +1 -1
- package/dist/force-restate-validation.js +11 -1
- package/dist/interactive-resume.js +1 -1
- package/dist/job-actions.js +1 -1
- package/dist/job-pause.js +1 -1
- package/dist/legacy-blocked-task-migration.js +1 -1
- package/dist/local-authority-store.js +0 -57
- package/dist/local-git-merge.js +1 -2
- package/dist/local-merge-delivery.js +1 -1
- package/dist/markers.js +5 -6
- package/dist/planned-review-head.js +2 -2
- package/dist/planner-runtime.js +3 -2
- package/dist/planner.js +2 -7
- package/dist/pool.js +7 -36
- package/dist/reconciler-review.js +4 -5
- package/dist/reconciler.js +11 -36
- package/dist/relay-create.js +3 -3
- package/dist/relay-job-snapshot-reads.js +6 -2
- package/dist/relay-store.js +2 -2
- package/dist/relay-workflow-operations.js +2 -2
- package/dist/relayd-control.js +9 -7
- package/dist/relayd-utilities.js +0 -4
- package/dist/relayd.js +7 -24
- package/dist/runtime-status.js +0 -19
- package/dist/single-job-task-control.js +1 -1
- package/dist/store.js +1 -1
- package/dist/task-actions.js +3 -3
- package/dist/task-recovery.js +2 -2
- package/dist/task-transitions.js +61 -0
- package/dist/troubleshooter-fallback.js +1 -1
- package/dist/troubleshooter.js +1 -4
- package/dist/types.js +1 -2
- package/dist/unblock.js +5 -9
- package/dist/worker-rejection.js +1 -1
- package/dist/workflow/authoritative-conditions.js +1 -1
- package/dist/workflow/blocker-resolution-v2.js +2 -4
- package/dist/workflow/contracts.js +0 -1
- package/dist/workflow/definitions-v2.js +3 -6
- package/dist/workflow/durable-transition.js +1 -1
- package/dist/workflow/force-restate-validation-v2.js +2 -1
- package/dist/workflow/orchestrator-rescue.js +1 -1
- package/dist/workflow/planner-orchestration-v2.js +2 -5
- package/dist/workflow/planner-trigger-v2.js +1 -2
- package/dist/workflow/review-v2.js +0 -1
- package/dist/workflow/task-reconcile.js +1 -7
- package/dist/workflow-effect-failures.js +1 -1
- package/dist/workspace.js +1 -1
- package/package.json +3 -4
- package/skills/agents-relay/SKILL.md +6 -6
- package/skills/agents-relay/agents/planner.agent.md +3 -3
- package/skills/agents-relay/contracts/orchestrator-workflows.md +2 -2
- package/skills/agents-relay/contracts/task-job-contract.md +5 -5
- package/workflow/blocker-resolution.mmd +1 -7
- package/workflow/force-restate-validation.mmd +7 -2
- package/workflow/planner-orchestration.mmd +2 -5
- package/workflow/planner-trigger.mmd +2 -9
- package/workflow/review-lifecycle.mmd +2 -8
- package/dist/e2e-injection.js +0 -76
- package/dist/scheduled-jobs.js +0 -67
- package/dist/scheduler.js +0 -214
- package/dist/workflow/schedule-cycle-v2.js +0 -25
- package/dist/workflow/scheduled-run-v2.js +0 -30
- package/workflow/schedule-cycle.mmd +0 -23
- package/workflow/scheduled-run.mmd +0 -59
|
@@ -104,29 +104,29 @@ The ChatGPT adapter uses Relay's `chatgpt-worker` agent contract and Browser Wor
|
|
|
104
104
|
|
|
105
105
|
ChatGPT thread identity is diagnostic only. It is not persisted as a resumable task handle, and Relay does not poll, resume, retrieve a result from, or delete a ChatGPT conversation as part of task lifecycle.
|
|
106
106
|
|
|
107
|
-
##
|
|
107
|
+
## Objective-driven Planner
|
|
108
108
|
|
|
109
109
|
Direct orchestrator-created Tasks do not require a Plan. The orchestrator may create the initial batch with `task create`; those Tasks remain top-level unless the orchestrator explicitly gives them normal task parentage. When planner-driven decomposition is desired instead, use `agents-relay plan --repo OWNER/REPO --pr N --id JOB_ID [--prompt TEXT | --prompt-file PATH|-]`.
|
|
110
110
|
|
|
111
|
-
For explicit `plan` and
|
|
111
|
+
For explicit `plan` and automatic continuation, Relay invokes the same planner agent as a pure decision function. The planner receives durable Job/Task state, previous planner rounds, and resolved execution context, then returns structured `{ objective_status, assessment, next_tasks }` output. The planner does not call Relay CLI and does not persist Tasks itself.
|
|
112
112
|
|
|
113
113
|
Each successful planner invocation is one real `plan-N` planning round. Relay persists that planner result, then creates returned work Tasks with provenance back to that planning round. `parentTaskId` expresses semantic ownership among work Tasks; it is not inherited from the Plan and must never point to a `plan-N` record. `dependencies` independently express execution prerequisites. Historical `planner-N` records remain readable. Only planner execution creates planner rounds; Relay never manufactures synthetic `plan-*` records for direct Task creation. In the dashboard, planner rounds remain provenance/grouping metadata, not semantic parents.
|
|
114
114
|
|
|
115
|
-
A zero-task
|
|
115
|
+
A zero-task DRAFT Job remains idle until explicit first planning or direct Task creation. After initial work settles, every Job automatically replans until the latest successful Plan is `satisfied`; there is no fixed/autonomous mode or Job scheduler.
|
|
116
116
|
|
|
117
117
|
Planner-created child Tasks remain descriptive agent work and must include the output contract and `storySize`; worker requirements are optional soft routing facts unless an explicit runtime constraint is known. Execution Router chooses the complete runtime route afterward.
|
|
118
118
|
|
|
119
119
|
## Durable truth
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
SQLite is the sole local durable lifecycle authority. GitHub PR/Job/Task markers are collaboration projections. Correlated model-worker events are inputs to Graph-selected, SQLite-committed transitions; runtime events, process exits, browser receipts, and conversation text are not themselves completion proof.
|
|
122
122
|
|
|
123
123
|
Keep secrets, credentials, private prompts, and large private payloads out of durable markers and events.
|
|
124
124
|
|
|
125
125
|
### Automatic task troubleshooting
|
|
126
126
|
|
|
127
|
-
When an ACTIVE Job contains a current-run `FAILED` task, reconciliation may create one P0-priority `troubleshooter` recovery task for that exact failed attempt. `BLOCKED` is resolved only through authoritative unblock, which records evidence and transitions the Task to `UNBLOCKED`. The recovery task is routed to a local Codex-capable worker with repository/tool access and runs under the bundled Troubleshooter agent contract.
|
|
127
|
+
When an ACTIVE Job contains a current-run `FAILED` task, reconciliation may create one P0-priority `troubleshooter` recovery task for that exact failed attempt. `BLOCKED` is resolved only through authoritative unblock, which records evidence and transitions the Task to `UNBLOCKED`. The recovery task is routed to a local Codex-capable worker with repository/tool access and runs under the bundled Troubleshooter agent contract. Historical ordinary unblock evidence is Planner recovery context; only an effectively blocked Troubleshooter can be authoritatively resumed. Each Planner round is durably represented by an `agents-relay:plan:v1` PR comment created before generated Task comments and updated in place with its assessment and generated Task IDs. Troubleshooter diagnosis is event-first: it must inspect the failed task's task-scoped lifecycle event history (including ordering, duplicate/missing/late events, source and phase data) before classifying root cause or retrying.
|
|
128
128
|
|
|
129
|
-
The Troubleshooter diagnoses before retrying. A task-local or transient blocker is repaired narrowly; a shared worker/runtime/tool defect is repaired through the owning repository's P0/self-job workflow. Only after the blocker is removed does it retry the original task and verify durable progress. Historical failures
|
|
129
|
+
The Troubleshooter diagnoses before retrying. A task-local or transient blocker is repaired narrowly; a shared worker/runtime/tool defect is repaired through the owning repository's P0/self-job workflow. Only after the blocker is removed does it retry the original task and verify durable progress. Historical resolved failures do not spawn duplicate recovery work, and deterministic reconciliation does not duplicate a Troubleshooter task for the same failed attempt.
|
|
130
130
|
|
|
131
131
|
## Persistent service bootstrap
|
|
132
132
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Planner Agent
|
|
2
2
|
|
|
3
|
-
You are the default objective planner for Agents Relay
|
|
3
|
+
You are the default objective planner for all Agents Relay Jobs.
|
|
4
4
|
|
|
5
5
|
Before producing tasks, read and follow `../contracts/task-job-contract.md`.
|
|
6
6
|
|
|
@@ -25,10 +25,10 @@ For every non-satisfied objective:
|
|
|
25
25
|
|
|
26
26
|
### Objective decomposition and convergence
|
|
27
27
|
|
|
28
|
-
- **Never bundle independently testable acceptance layers into one giant Task.** Break objectives into separately verifiable deliverables (for example, contract tests, real-provider compliance,
|
|
28
|
+
- **Never bundle independently testable acceptance layers into one giant Task.** Break objectives into separately verifiable deliverables (for example, contract tests, real-provider compliance, L1 Graph branches, L2 role evidence, and L3 production-ingress SQLite replay, and integration evidence). Give each Task concrete acceptance checks and an observable output.
|
|
29
29
|
- Split a large Task when its remaining work spans distinct implementation areas, independently testable failure paths, or more than one meaningful review/recovery cycle. Put shared prerequisites in earlier Tasks and express true dependencies explicitly; independent work may run in parallel.
|
|
30
30
|
- For a failed Task with repeated deterministic acceptance gaps, do not reissue the identical broad assignment. Identify the *missing acceptance cases*, create focused same-Job repair Tasks with valid `same_job_dependency` resolutions, and specify how their evidence unblocks the original Task. Preserve the original failed Task and failure history.
|
|
31
|
-
- For event-driven workflows, separate basic ingress/persistence checks from
|
|
31
|
+
- For event-driven workflows, separate basic ingress/persistence checks from L3 production Reconciler/Graph/SQLite multi-stage replay scenarios. Require negative-path, multi-failure recovery and no-false-completion evidence before claiming full objective coverage.
|
|
32
32
|
- In `assessment`, show the entire remaining objective as a dependency graph, clearly distinguishing already proven work, the executable batch, subsequent verification, and Relay-owned review/merge. Never report overall `satisfied` merely because one batch has passed.
|
|
33
33
|
|
|
34
34
|
|
|
@@ -20,7 +20,7 @@ job create --initial-plan
|
|
|
20
20
|
|
|
21
21
|
Without `--initial-plan`, `job create` remains create-only and establishes durable Job/PR state without execution. `agents-relay plan` remains available for an existing create-only/adopted Job. Unless the user explicitly requested **create-only / no execution**, the orchestrator should use `job create --initial-plan` rather than issuing two separate commands.
|
|
22
22
|
|
|
23
|
-
The initial decomposition belongs to that durable Planner round. The orchestrator must not hand-author the initial child Task batch as a substitute. If initial planning fails, the create command fails visibly after preserving the durable Job/Plan evidence; the orchestrator must not silently invent Tasks.
|
|
23
|
+
The initial decomposition belongs to that durable Planner round. The orchestrator must not hand-author the initial child Task batch as a substitute. If initial planning fails, the create command fails visibly after preserving the durable Job/Plan evidence; the orchestrator must not silently invent Tasks. Every Job follows this same objective-driven lifecycle. After a completed implementation batch, Relay automatically creates subsequent Plan rounds until the latest successful Plan reports `objective_status: satisfied`; there is no fixed/autonomous execution mode or Job scheduling.
|
|
24
24
|
|
|
25
25
|
## P0 / self job workflow
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ state and hand subsequent execution to Relay.
|
|
|
48
48
|
|
|
49
49
|
## Merge completion vs deployment evidence
|
|
50
50
|
|
|
51
|
-
For a
|
|
51
|
+
For a managed Job, the canonical Job lifecycle Graph owns completion. A satisfied Plan, successful implementation evidence, applicable SHA-bound review, and a verified local merge/delivery receipt are required. Remote GitHub `pr.merged` facts are audit-only and never independently complete a local Job. Deployment code must not invent another completion gate after durable Job completion.
|
|
52
52
|
|
|
53
53
|
Post-merge publication and deployment activity is separate from Job lifecycle. Events or evidence such as `package.published`, published version metadata, service restart, and deployment verification may be attached after the Job is already `COMPLETED`; they must not hold the Job open, reopen it, or redefine completion. If post-merge deployment needs remediation, record that as separate deployment/recovery work rather than mutating the completed implementation Job lifecycle.
|
|
54
54
|
|
|
@@ -77,7 +77,7 @@ The Job owns the meaning of the tag. Its trusted Job marker declares a human-rea
|
|
|
77
77
|
```json
|
|
78
78
|
{
|
|
79
79
|
"tag": {
|
|
80
|
-
"description": "
|
|
80
|
+
"description": "L3 verification outcome. Set pass only after authoritative SQLite replay checks pass; set fail when a required check fails.",
|
|
81
81
|
"values": {
|
|
82
82
|
"pass": "✅",
|
|
83
83
|
"fail": "❌"
|
|
@@ -109,9 +109,9 @@ Relay validates `data.tag` against `Job.tag.values`. If the value is allowed, Re
|
|
|
109
109
|
|
|
110
110
|
The dashboard always keeps the normal lifecycle badge. When a valid Task tag is present, it additionally renders the display string from `Job.tag.values` after the lifecycle status. For the example above this appears as `SUCCEEDED ✅`.
|
|
111
111
|
|
|
112
|
-
A tag must describe something specific to the Job contract, not Relay lifecycle. Examples include
|
|
112
|
+
A tag must describe something specific to the Job contract, not Relay lifecycle. Examples include a verified L3 result (`pass` / `fail`), publication result, validation classification, or another small Job-defined outcome. The Job description must tell the worker when to set the tag and what each allowed value means. Jobs without a `tag` declaration behave exactly as before.
|
|
113
113
|
|
|
114
|
-
For
|
|
114
|
+
For a validation Job, for example, the Job defines `pass -> ✅` and `fail -> ❌`; each L3 verification Task reports `data.tag` after the isolated replay and evidence checks, while Task lifecycle independently records the worker outcome.
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
### Task resolution classifications
|
|
@@ -139,9 +139,9 @@ SHA-bound final reviewer. For example, `video-director + market + style ->
|
|
|
139
139
|
review-chair -> unified revision plan` is a project pattern, not a hard-coded
|
|
140
140
|
Relay reviewer list.
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
Every Job is objective-driven. After a direct initial Task batch or a Planner-produced batch settles, reconciliation evaluates the canonical Planner Graph and creates the next durable `plan-N` round until the latest successful Plan returns `objective_status: satisfied`. There is no fixed/autonomous mode, no Job schedule, no cron slot, and no Scheduler entity. The daemon performs startup recovery and wake-driven reconciliation from SQLite. A satisfied Plan alone does not complete a Job: successful implementation evidence, SHA-bound review when enabled, and verified local merge/delivery remain mandatory.
|
|
143
143
|
|
|
144
|
-
|
|
144
|
+
A newly created zero-task DRAFT Job waits for explicit initial planning (`plan` or `--initial-plan`) or direct work creation; this is only a start gate, not a distinct execution mode. Failed or blocked Tasks require durable Troubleshooter/owner recovery and never allow a fabricated completion. Duplicate events and daemon restarts must not create duplicate Plan rounds.
|
|
145
145
|
|
|
146
146
|
|
|
147
147
|
## Task evidence and head identity
|
|
@@ -6,7 +6,6 @@ flowchart LR
|
|
|
6
6
|
%% relay: initial waiting
|
|
7
7
|
waiting["state: waiting"]
|
|
8
8
|
checkTarget["state: check_target"]
|
|
9
|
-
checkAutonomous["state: check_autonomous"]
|
|
10
9
|
checkEffective["state: check_effective"]
|
|
11
10
|
checkAlreadyUnblocked["state: check_already_unblocked"]
|
|
12
11
|
checkDraft["state: check_draft"]
|
|
@@ -19,7 +18,6 @@ resumed["state: resumed"]
|
|
|
19
18
|
|
|
20
19
|
resolutionAllowed["guard: resolutionAllowed"]
|
|
21
20
|
targetMatches["guard: targetMatches"]
|
|
22
|
-
autonomousJob["guard: autonomousJob"]
|
|
23
21
|
effectivelyBlocked["guard: effectivelyBlocked"]
|
|
24
22
|
alreadyUnblocked["guard: alreadyUnblocked"]
|
|
25
23
|
isDraft["guard: isDraft"]
|
|
@@ -35,11 +33,7 @@ resolutionAllowed -->|true| checkTarget
|
|
|
35
33
|
|
|
36
34
|
checkTarget -->|on: blocker.resolved| targetMatches
|
|
37
35
|
targetMatches -->|false| obsolete
|
|
38
|
-
targetMatches -->|true|
|
|
39
|
-
|
|
40
|
-
checkAutonomous -->|on: blocker.resolved| autonomousJob
|
|
41
|
-
autonomousJob -->|true| checkEffective
|
|
42
|
-
autonomousJob -->|false| checkDraft
|
|
36
|
+
targetMatches -->|true| checkEffective
|
|
43
37
|
|
|
44
38
|
checkEffective -->|on: blocker.resolved| effectivelyBlocked
|
|
45
39
|
effectivelyBlocked -->|true| wait
|
|
@@ -8,12 +8,14 @@ idle["state: idle"]
|
|
|
8
8
|
checkHistoryMode["state: check_history_mode"]
|
|
9
9
|
checkHistory["state: check_history"]
|
|
10
10
|
checkNormalHistory["state: check_normal_history"]
|
|
11
|
+
checkSatisfied["state: check_satisfied"]
|
|
11
12
|
checkForward["state: check_forward"]
|
|
12
13
|
commit["action: plan.apply.commit"]
|
|
13
14
|
rejected["state: rejected"]
|
|
14
15
|
committed["state: committed"]
|
|
15
16
|
forceRestate["guard: forceRestateRequested"]
|
|
16
17
|
historyConsistent["guard: historyConsistencyValid"]
|
|
18
|
+
objectiveSatisfied["guard: objectiveSatisfied"]
|
|
17
19
|
forwardConverged["guard: forwardConvergenceValid"]
|
|
18
20
|
normalHistoryClean["guard: normalHistoryMutationAbsent"]
|
|
19
21
|
rejectHistory["action: plan.apply.rejectHistory"]
|
|
@@ -24,12 +26,15 @@ checkHistoryMode -->|on: plan.apply| forceRestate
|
|
|
24
26
|
forceRestate -->|true| checkHistory
|
|
25
27
|
forceRestate -->|false| checkNormalHistory
|
|
26
28
|
checkNormalHistory -->|on: plan.apply| normalHistoryClean
|
|
27
|
-
normalHistoryClean -->|true|
|
|
29
|
+
normalHistoryClean -->|true| checkSatisfied
|
|
28
30
|
normalHistoryClean -->|false| rejectHistory
|
|
29
31
|
checkHistory -->|on: plan.apply| historyConsistent
|
|
30
|
-
historyConsistent -->|true|
|
|
32
|
+
historyConsistent -->|true| checkSatisfied
|
|
31
33
|
historyConsistent -->|false| rejectHistory
|
|
32
34
|
rejectHistory --> rejected
|
|
35
|
+
checkSatisfied -->|on: plan.apply| objectiveSatisfied
|
|
36
|
+
objectiveSatisfied -->|true| commit
|
|
37
|
+
objectiveSatisfied -->|false| checkForward
|
|
33
38
|
checkForward -->|on: plan.apply| forwardConverged
|
|
34
39
|
forwardConverged -->|true| commit
|
|
35
40
|
forwardConverged -->|false| rejectForward
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Planner Orchestration
|
|
3
|
-
description: Guards
|
|
3
|
+
description: Guards objective-driven planning rounds, interrupted plans, and planner recovery.
|
|
4
4
|
---
|
|
5
5
|
flowchart LR
|
|
6
6
|
%% relay: initial idle
|
|
@@ -21,7 +21,6 @@ wait["state: wait"]
|
|
|
21
21
|
recovered["state: recovered"]
|
|
22
22
|
created["state: created"]
|
|
23
23
|
|
|
24
|
-
autonomous["guard: autonomousJob"]
|
|
25
24
|
legacyPending["guard: legacyPlanningPending"]
|
|
26
25
|
recoverablePending["guard: recoverablePendingPlan"]
|
|
27
26
|
terminalFailure["guard: terminalPlanFailure"]
|
|
@@ -39,9 +38,7 @@ recoverAction["action: planner.recoverPending"]
|
|
|
39
38
|
createRecovery["action: planner.createRecovery"]
|
|
40
39
|
createAction["action: planner.createRound"]
|
|
41
40
|
|
|
42
|
-
idle -->|on: planner.reconcile|
|
|
43
|
-
autonomous -->|false| wait
|
|
44
|
-
autonomous -->|true| checkLegacy
|
|
41
|
+
idle -->|on: planner.reconcile| checkLegacy
|
|
45
42
|
|
|
46
43
|
checkLegacy -->|on: planner.reconcile| legacyPending
|
|
47
44
|
legacyPending -->|true| wait
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Planner Trigger
|
|
3
|
-
description: Classifies planning requests as manual, unblock,
|
|
3
|
+
description: Classifies planning requests as manual, unblock, initial, or continuation.
|
|
4
4
|
---
|
|
5
5
|
flowchart LR
|
|
6
6
|
%% relay: initial idle
|
|
7
7
|
idle["state: idle"]
|
|
8
8
|
checkUnblock["state: check_unblock"]
|
|
9
|
-
checkSchedule["state: check_schedule"]
|
|
10
9
|
checkInitial["state: check_initial"]
|
|
11
10
|
manual["state: manual"]
|
|
12
11
|
unblock["state: unblock"]
|
|
13
|
-
schedule["state: schedule"]
|
|
14
12
|
initial["state: initial"]
|
|
15
13
|
continuation["state: continuation"]
|
|
16
14
|
|
|
17
15
|
manualGuard["guard: manualPlan"]
|
|
18
16
|
unblockGuard["guard: hasUnblock"]
|
|
19
|
-
scheduleGuard["guard: scheduledRun"]
|
|
20
17
|
initialGuard["guard: initialPlan"]
|
|
21
18
|
|
|
22
19
|
idle -->|on: plan.requested| manualGuard
|
|
@@ -25,11 +22,7 @@ manualGuard -->|false| checkUnblock
|
|
|
25
22
|
|
|
26
23
|
checkUnblock -->|on: plan.requested| unblockGuard
|
|
27
24
|
unblockGuard -->|true| unblock
|
|
28
|
-
unblockGuard -->|false|
|
|
29
|
-
|
|
30
|
-
checkSchedule -->|on: plan.requested| scheduleGuard
|
|
31
|
-
scheduleGuard -->|true| schedule
|
|
32
|
-
scheduleGuard -->|false| checkInitial
|
|
25
|
+
unblockGuard -->|false| checkInitial
|
|
33
26
|
|
|
34
27
|
checkInitial -->|on: plan.requested| initialGuard
|
|
35
28
|
initialGuard -->|true| initial
|
|
@@ -13,7 +13,6 @@ changes["state: CHANGES_REQUIRED"]
|
|
|
13
13
|
blocked["state: BLOCKED"]
|
|
14
14
|
stale["state: STALE"]
|
|
15
15
|
|
|
16
|
-
unscheduledNone["guard: unscheduled"]
|
|
17
16
|
reviewEnabledNone["guard: reviewEnabled"]
|
|
18
17
|
jobActiveNone["guard: jobActive"]
|
|
19
18
|
implementationCompleteNone["guard: implementationComplete"]
|
|
@@ -21,7 +20,6 @@ headAvailableNone["guard: headAvailable"]
|
|
|
21
20
|
completionGateNone["guard: completionGateSatisfied"]
|
|
22
21
|
noActiveReviewNone["guard: noActiveReviewTask"]
|
|
23
22
|
noApprovedReviewNone["guard: noApprovedCurrentReview"]
|
|
24
|
-
unscheduledStale["guard: unscheduled"]
|
|
25
23
|
reviewEnabledStale["guard: reviewEnabled"]
|
|
26
24
|
jobActiveStale["guard: jobActive"]
|
|
27
25
|
implementationCompleteStale["guard: implementationComplete"]
|
|
@@ -49,9 +47,7 @@ fixChanges["action: review.createFix"]
|
|
|
49
47
|
mergePending["action: review.requestMerge"]
|
|
50
48
|
mergeStarted["action: review.requestMerge"]
|
|
51
49
|
|
|
52
|
-
none -->|on: reconcile|
|
|
53
|
-
unscheduledNone -->|true| reviewEnabledNone
|
|
54
|
-
unscheduledNone -->|false| none
|
|
50
|
+
none -->|on: reconcile| reviewEnabledNone
|
|
55
51
|
reviewEnabledNone -->|true| jobActiveNone
|
|
56
52
|
reviewEnabledNone -->|false| none
|
|
57
53
|
jobActiveNone -->|true| implementationCompleteNone
|
|
@@ -68,9 +64,7 @@ noApprovedReviewNone -->|true| createNone
|
|
|
68
64
|
noApprovedReviewNone -->|false| none
|
|
69
65
|
createNone --> pending
|
|
70
66
|
|
|
71
|
-
stale -->|on: reconcile|
|
|
72
|
-
unscheduledStale -->|true| reviewEnabledStale
|
|
73
|
-
unscheduledStale -->|false| stale
|
|
67
|
+
stale -->|on: reconcile| reviewEnabledStale
|
|
74
68
|
reviewEnabledStale -->|true| jobActiveStale
|
|
75
69
|
reviewEnabledStale -->|false| stale
|
|
76
70
|
jobActiveStale -->|true| implementationCompleteStale
|
package/dist/e2e-injection.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
const legacy = {
|
|
2
|
-
quota_exceeded: { method: 'worker.codex.launch', action: 'return', execution: 'skip', mock: 'worker_quota' },
|
|
3
|
-
model_not_found: { method: 'worker.codex.launch', action: 'return', execution: 'skip', mock: 'worker_model_not_found' },
|
|
4
|
-
provider_unavailable: { method: 'worker.codex.launch', action: 'return', execution: 'skip', mock: 'worker_provider_unavailable' },
|
|
5
|
-
github_projection_failure: { method: 'github.projectLocalMerge', action: 'throw', execution: 'skip', error: { code: 'SERVICE_UNAVAILABLE', message: 'E2E transient GitHub projection transport failure', status: 503 } },
|
|
6
|
-
};
|
|
7
|
-
export function parseTestInjection(value) {
|
|
8
|
-
if (typeof value === 'string')
|
|
9
|
-
return legacy[value];
|
|
10
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
|
-
return undefined;
|
|
12
|
-
const v = value;
|
|
13
|
-
if (v.method !== 'worker.codex.launch' && v.method !== 'github.projectLocalMerge' && v.method !== 'browserWorkspace.command')
|
|
14
|
-
return undefined;
|
|
15
|
-
if (v.action !== 'throw' && v.action !== 'return' && v.action !== 'pass')
|
|
16
|
-
return undefined;
|
|
17
|
-
if (v.execution !== 'skip' && v.execution !== 'after' && v.execution !== 'run')
|
|
18
|
-
return undefined;
|
|
19
|
-
const error = v.error;
|
|
20
|
-
if (v.action === 'throw' && (!error || typeof error !== 'object' || typeof error.message !== 'string' || typeof error.code !== 'string'))
|
|
21
|
-
return undefined;
|
|
22
|
-
const mock = v.mock;
|
|
23
|
-
if (v.action === 'return' && (v.method !== 'worker.codex.launch' || !['worker_quota', 'worker_model_not_found', 'worker_provider_unavailable'].includes(String(mock))))
|
|
24
|
-
return undefined;
|
|
25
|
-
if (v.method === 'worker.codex.launch' && v.action !== 'pass' && (v.action !== 'return' || v.execution !== 'skip'))
|
|
26
|
-
return undefined;
|
|
27
|
-
if (v.action === 'pass' && v.execution !== 'run')
|
|
28
|
-
return undefined;
|
|
29
|
-
if (v.action !== 'pass' && v.execution === 'run')
|
|
30
|
-
return undefined;
|
|
31
|
-
return { method: v.method, action: v.action, execution: v.execution, error: error, mock: mock };
|
|
32
|
-
}
|
|
33
|
-
export function authorizedInjection(repository, raw, env = process.env) {
|
|
34
|
-
return env.E2E_REPO && repository === env.E2E_REPO ? parseTestInjection(raw) : undefined;
|
|
35
|
-
}
|
|
36
|
-
function mockWorker(name) {
|
|
37
|
-
const message = name === 'worker_quota' ? 'rate_limit_exceeded: usage limit reached' : name === 'worker_model_not_found' ? 'model_not_found: model is unavailable' : 'provider_unavailable: provider refused the request';
|
|
38
|
-
const promise = Promise.reject(new Error(message));
|
|
39
|
-
promise.catch(() => { });
|
|
40
|
-
return { id: 'e2e-injected', launched: Promise.resolve(), promise, cancel: () => { }, processExit: { exitCode: 1, signal: null, stderr: message, terminationReason: null } };
|
|
41
|
-
}
|
|
42
|
-
/** Caller must supply a configuration already consumed durably before this invocation. */
|
|
43
|
-
export function withTestInjection(method, config, execute) {
|
|
44
|
-
if (!config || config.method !== method || config.action === 'pass')
|
|
45
|
-
return execute();
|
|
46
|
-
const apply = (original) => {
|
|
47
|
-
if (config.action === 'throw') {
|
|
48
|
-
const error = Object.assign(new Error(config.error.message), { code: config.error.code, status: config.error.status });
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
if (method === 'worker.codex.launch' && config.mock)
|
|
52
|
-
return mockWorker(config.mock);
|
|
53
|
-
return original;
|
|
54
|
-
};
|
|
55
|
-
return config.execution === 'after' ? Promise.resolve(execute()).then(apply) : apply();
|
|
56
|
-
}
|
|
57
|
-
/** Shared atomic consumer, scoped solely by E2E_REPO and exact method. */
|
|
58
|
-
export async function consumeMethodInjection(relay, repository, jobId, method) {
|
|
59
|
-
if (!process.env.E2E_REPO || process.env.E2E_REPO !== repository || !relay.mutateJob)
|
|
60
|
-
return undefined;
|
|
61
|
-
let config;
|
|
62
|
-
await relay.mutateJob(jobId, job => {
|
|
63
|
-
if (job.repository !== repository)
|
|
64
|
-
return job;
|
|
65
|
-
const task = job.tasks.find(t => authorizedInjection(job.repository, t.testInjection)?.method === method);
|
|
66
|
-
if (!task)
|
|
67
|
-
return job;
|
|
68
|
-
config = authorizedInjection(job.repository, task.testInjection);
|
|
69
|
-
delete task.testInjection;
|
|
70
|
-
task.updatedAt = new Date(Math.max(Date.now(), Date.parse(task.updatedAt) + 1)).toISOString();
|
|
71
|
-
return job;
|
|
72
|
-
});
|
|
73
|
-
if (config && (await relay.loadJob(jobId))?.tasks.some(t => authorizedInjection(repository, t.testInjection)?.method === method))
|
|
74
|
-
throw Error(`E2E injection consumption not committed: ${jobId}`);
|
|
75
|
-
return config;
|
|
76
|
-
}
|
package/dist/scheduled-jobs.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { nextScheduleRun, scheduleBucket } from './scheduler.js';
|
|
2
|
-
import { dispatchScheduledRun } from './workflow/scheduled-run-v2.js';
|
|
3
|
-
/** Local-first schedule entrypoint: immutable Job ID selects the SQLite store. */
|
|
4
|
-
export async function runScheduledBucketFromStore(store, ref, engine, now = new Date()) {
|
|
5
|
-
// SQLite authority provides an atomic claim. The fallback retains the
|
|
6
|
-
// existing in-memory test adapter, but cannot be used for production claims.
|
|
7
|
-
if (store.claimScheduledSlot) {
|
|
8
|
-
if (!store.finishScheduledSlot)
|
|
9
|
-
throw new Error('Atomic scheduled completion is required');
|
|
10
|
-
const claimed = await store.claimScheduledSlot(ref.jobId, ref.bucket, now);
|
|
11
|
-
if (claimed.status !== 'completed')
|
|
12
|
-
return claimed;
|
|
13
|
-
const slot = claimed.job.schedule?.currentRunSlot;
|
|
14
|
-
if (!slot)
|
|
15
|
-
throw new Error('Claimed schedule slot missing durable currentRunSlot');
|
|
16
|
-
await engine.reconcile(ref.jobId);
|
|
17
|
-
await engine.idle();
|
|
18
|
-
const job = await store.finishScheduledSlot(ref.jobId, slot, now);
|
|
19
|
-
return { job, status: 'completed' };
|
|
20
|
-
}
|
|
21
|
-
let job = await store.load(ref.jobId);
|
|
22
|
-
if (!job.schedule)
|
|
23
|
-
throw new Error('Scheduled job ' + job.id + ' has no schedule metadata');
|
|
24
|
-
const schedule = job.schedule;
|
|
25
|
-
const slot = new Date(schedule.nextRunAt);
|
|
26
|
-
const active = job.tasks.some(task => ['QUEUED', 'RUNNING'].includes(task.state));
|
|
27
|
-
let claimed = false;
|
|
28
|
-
let status = 'skipped-idempotent';
|
|
29
|
-
const decision = await dispatchScheduledRun({
|
|
30
|
-
slotDue: scheduleBucket(slot) === ref.bucket && slot.getTime() <= now.getTime(),
|
|
31
|
-
slotUnclaimed: !schedule.lastScheduledSlot || new Date(schedule.lastScheduledSlot).getTime() < slot.getTime(),
|
|
32
|
-
jobActive: job.state === 'ACTIVE', hasActiveTasks: active,
|
|
33
|
-
concurrencySkip: schedule.concurrencyPolicy === 'skip', concurrencyQueue: schedule.concurrencyPolicy === 'queue',
|
|
34
|
-
runMissed: schedule.missedRunPolicy === 'run',
|
|
35
|
-
}, async (actionId) => {
|
|
36
|
-
if (actionId === 'schedule.skipActiveAdvance') {
|
|
37
|
-
schedule.lastScheduledSlot = slot.toISOString();
|
|
38
|
-
schedule.nextRunAt = nextScheduleRun(schedule, now).toISOString();
|
|
39
|
-
status = 'skipped-active';
|
|
40
|
-
}
|
|
41
|
-
else if (actionId === 'schedule.claimFromSlot' || actionId === 'schedule.claimFromNow') {
|
|
42
|
-
schedule.lastScheduledSlot = slot.toISOString();
|
|
43
|
-
schedule.currentRunSlot = slot.toISOString();
|
|
44
|
-
schedule.nextRunAt = nextScheduleRun(schedule, actionId === 'schedule.claimFromSlot' ? slot : now).toISOString();
|
|
45
|
-
claimed = true;
|
|
46
|
-
status = 'completed';
|
|
47
|
-
}
|
|
48
|
-
else
|
|
49
|
-
throw new Error('Unknown local schedule action: ' + actionId);
|
|
50
|
-
job.updatedAt = now.toISOString();
|
|
51
|
-
await store.saveJob(job);
|
|
52
|
-
return {};
|
|
53
|
-
});
|
|
54
|
-
if (!decision.accepted || !claimed)
|
|
55
|
-
return { job, status };
|
|
56
|
-
await engine.reconcile(job.id);
|
|
57
|
-
await engine.idle();
|
|
58
|
-
job = await store.load(job.id);
|
|
59
|
-
if (!job.schedule)
|
|
60
|
-
throw new Error('Scheduled job lost schedule metadata');
|
|
61
|
-
job.schedule.lastRunAt = now.toISOString();
|
|
62
|
-
// A claim and its completion can share the same wall-clock timestamp.
|
|
63
|
-
// Advance the snapshot revision to satisfy SQLite's stale-write guard.
|
|
64
|
-
job.updatedAt = new Date(Math.max(now.getTime(), Date.parse(job.updatedAt) + 1)).toISOString();
|
|
65
|
-
await store.saveJob(job);
|
|
66
|
-
return { job, status: 'completed' };
|
|
67
|
-
}
|