openlanes 3.0.0 → 3.1.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.
- checksums.yaml +4 -4
- data/Brewfile +42 -0
- data/agents/bb8-resolver.md +49 -0
- data/agents/c3po-operator.md +62 -0
- data/agents/chewie-verifier.md +85 -0
- data/agents/hansolo-reviewer.md +20 -15
- data/agents/lando-broker.md +81 -19
- data/agents/leah-researcher.md +6 -6
- data/agents/luke-backend.md +41 -20
- data/agents/{palpatine-planner.md → luthen-rael-planner.md} +15 -13
- data/agents/palpatine-risk-assessor.md +112 -0
- data/agents/r2d2-mechanic.md +5 -1
- data/agents/rey-frontend.md +38 -19
- data/agents/yoda-writer.md +7 -7
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/plan.md +7 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/pull-requests.md +10 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/spec.md +5 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/verification/1/final.png +0 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/verification/1/report.md +7 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/repo/bin/greet +1 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/run.json +1 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/plan.md +7 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/pull-requests.md +10 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/spec.md +5 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/verification/1/final.png +0 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/verification/1/report.md +14 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/verification/1/step-01.png +0 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/repo/bin/greet +1 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/run.json +1 -0
- data/evals/cases/chewie-verifier/verify-medium.yml +48 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/implementation-plan.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/plan.md +3 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/pull-requests.md +12 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/spec.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/repo/lib/cart.rb +10 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/repo/spec/cart_spec.rb +9 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/run.json +1 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/implementation-plan.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/plan.md +3 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/pull-requests.md +15 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/spec.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/repo/lib/cart.rb +10 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/repo/spec/cart_spec.rb +9 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/run.json +1 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum.yml +76 -0
- data/evals/cases/luthen-rael-planner/plan-deep/fail/plan/plan.md +8 -0
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/plan.md +2 -2
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep.yml +1 -1
- data/evals/cases/luthen-rael-planner/plan-fast/fail/plan/plan.md +12 -0
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/plan.md +2 -2
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast.yml +1 -1
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/plan/plan.md +20 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/plan/pull-requests.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/plan/spec.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/repo/db/migrate/20261020120000_drop_legacy_email.rb +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/run.json +1 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/plan/plan.md +25 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/plan/pull-requests.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/plan/spec.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/repo/db/migrate/20261020120000_drop_legacy_email.rb +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/run.json +1 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium.yml +47 -0
- data/evals/cases/yoda-writer/spec-medium/pass/plan/spec.md +1 -1
- data/lib/openlanes/adapters/base.rb +24 -3
- data/lib/openlanes/adapters/capabilities.rb +29 -0
- data/lib/openlanes/adapters/claude.rb +32 -10
- data/lib/openlanes/adapters/codex.rb +9 -0
- data/lib/openlanes/adapters/pi.rb +10 -0
- data/lib/openlanes/adapters/stream.rb +145 -1
- data/lib/openlanes/adapters.rb +26 -2
- data/lib/openlanes/agents/agent.rb +4 -3
- data/lib/openlanes/agents/roster.rb +14 -2
- data/lib/openlanes/agents/routing.rb +43 -3
- data/lib/openlanes/apps/definition.rb +152 -0
- data/lib/openlanes/apps/framing.rb +114 -0
- data/lib/openlanes/apps/listing.rb +60 -0
- data/lib/openlanes/apps/ports.rb +168 -0
- data/lib/openlanes/apps/probe.rb +63 -0
- data/lib/openlanes/apps/process_spec.rb +39 -0
- data/lib/openlanes/apps/runner.rb +174 -0
- data/lib/openlanes/apps/status.rb +62 -0
- data/lib/openlanes/apps/supervisor.rb +277 -0
- data/lib/openlanes/apps/watcher.rb +50 -0
- data/lib/openlanes/apps.rb +41 -0
- data/lib/openlanes/cli/api/api.rb +10 -0
- data/lib/openlanes/cli/api/subcommands/docs.rb +63 -0
- data/lib/openlanes/cli/app/app.rb +31 -0
- data/lib/openlanes/cli/app/subcommands/start.rb +114 -0
- data/lib/openlanes/cli/app/subcommands/status.rb +38 -0
- data/lib/openlanes/cli/app/subcommands/stop.rb +44 -0
- data/lib/openlanes/cli/base.rb +89 -16
- data/lib/openlanes/cli/coverage/coverage.rb +68 -0
- data/lib/openlanes/cli/create/create.rb +11 -265
- data/lib/openlanes/cli/eval/eval.rb +4 -1
- data/lib/openlanes/cli/init/init.rb +39 -2
- data/lib/openlanes/cli/linear/subcommands/import.rb +1 -1
- data/lib/openlanes/cli/linear/subcommands/projects.rb +1 -1
- data/lib/openlanes/cli/list_plans/list_plans.rb +8 -2
- data/lib/openlanes/cli/mail/subcommands/ack.rb +52 -0
- data/lib/openlanes/cli/mail/subcommands/render.rb +31 -0
- data/lib/openlanes/cli/mail/subcommands/send.rb +26 -0
- data/lib/openlanes/cli/resync/subcommands/dirs.rb +53 -12
- data/lib/openlanes/cli/resync/subcommands/prs.rb +8 -69
- data/lib/openlanes/cli/review/review.rb +72 -0
- data/lib/openlanes/cli/run/run.rb +22 -454
- data/lib/openlanes/cli/setup/setup.rb +51 -0
- data/lib/openlanes/cli/start/start.rb +7 -64
- data/lib/openlanes/cli/state/state.rb +1 -1
- data/lib/openlanes/cli/state/subcommands/resume.rb +61 -0
- data/lib/openlanes/cli/state/subcommands/sign.rb +1 -1
- data/lib/openlanes/cli/unblock/unblock.rb +11 -205
- data/lib/openlanes/cli/web/web.rb +94 -8
- data/lib/openlanes/cli/worktree/worktree.rb +1 -1
- data/lib/openlanes/cli/worktree/worktree_prune.rb +77 -0
- data/lib/openlanes/cli.rb +37 -2
- data/lib/openlanes/coverage/cobertura.rb +49 -0
- data/lib/openlanes/coverage/config.rb +194 -0
- data/lib/openlanes/coverage/istanbul.rb +31 -0
- data/lib/openlanes/coverage/lcov.rb +38 -0
- data/lib/openlanes/coverage/report.rb +71 -0
- data/lib/openlanes/coverage/simplecov.rb +70 -0
- data/lib/openlanes/coverage.rb +33 -0
- data/lib/openlanes/engine/agent_locks.rb +231 -0
- data/lib/openlanes/engine/arbitration.rb +25 -0
- data/lib/openlanes/engine/board.rb +36 -5
- data/lib/openlanes/engine/conversions.rb +160 -0
- data/lib/openlanes/engine/coverage_gate.rb +217 -0
- data/lib/openlanes/engine/dispatcher.rb +1229 -116
- data/lib/openlanes/engine/event_journal/replay.rb +96 -0
- data/lib/openlanes/engine/event_journal.rb +148 -0
- data/lib/openlanes/engine/extensions.rb +271 -0
- data/lib/openlanes/engine/merging.rb +459 -0
- data/lib/openlanes/engine/narrator.rb +107 -0
- data/lib/openlanes/engine/plan_locks.rb +114 -0
- data/lib/openlanes/engine/pr_outcomes.rb +161 -0
- data/lib/openlanes/engine/pr_watch.rb +83 -0
- data/lib/openlanes/engine/progress_log.rb +38 -105
- data/lib/openlanes/engine/quality_question.rb +125 -0
- data/lib/openlanes/engine/rebase.rb +200 -0
- data/lib/openlanes/engine/run_workflow.rb +776 -0
- data/lib/openlanes/engine/runner.rb +186 -15
- data/lib/openlanes/engine/start_workflow.rb +77 -0
- data/lib/openlanes/engine/state_file.rb +101 -34
- data/lib/openlanes/engine/tally.rb +1 -1
- data/lib/openlanes/engine/verification/app_session.rb +104 -0
- data/lib/openlanes/engine/verification/driver.rb +314 -0
- data/lib/openlanes/engine/verification/outcome.rb +516 -0
- data/lib/openlanes/engine/verification/report.rb +95 -0
- data/lib/openlanes/engine/verification/round.rb +100 -0
- data/lib/openlanes/engine/verification/screenshots.rb +120 -0
- data/lib/openlanes/engine/verification.rb +57 -0
- data/lib/openlanes/engine.rb +30 -1
- data/lib/openlanes/execution/boundary.rb +212 -0
- data/lib/openlanes/execution/child.rb +4 -2
- data/lib/openlanes/execution/clock.rb +36 -6
- data/lib/openlanes/execution/control.rb +32 -0
- data/lib/openlanes/execution/executor.rb +114 -35
- data/lib/openlanes/execution/process_start.rb +63 -0
- data/lib/openlanes/execution/prompt.rb +236 -34
- data/lib/openlanes/execution/transcript.rb +159 -8
- data/lib/openlanes/execution.rb +21 -0
- data/lib/openlanes/lifecycle/adoption.rb +25 -8
- data/lib/openlanes/lifecycle/aging.rb +166 -0
- data/lib/openlanes/lifecycle/allocator.rb +216 -0
- data/lib/openlanes/lifecycle/brief.rb +32 -2
- data/lib/openlanes/lifecycle/completion.rb +12 -6
- data/lib/openlanes/lifecycle/create_workflow.rb +272 -0
- data/lib/openlanes/lifecycle/creator.rb +39 -11
- data/lib/openlanes/lifecycle/lanes.rb +1 -1
- data/lib/openlanes/lifecycle/resync.rb +170 -42
- data/lib/openlanes/lifecycle/unblock_workflow.rb +234 -0
- data/lib/openlanes/lifecycle/unblocker.rb +76 -2
- data/lib/openlanes/lifecycle.rb +37 -1
- data/lib/openlanes/linear/mapping.rb +4 -1
- data/lib/openlanes/linear/unit.rb +1 -1
- data/lib/openlanes/live/chat.rb +665 -0
- data/lib/openlanes/live/choice_converter.rb +166 -0
- data/lib/openlanes/live/choice_judge.rb +109 -0
- data/lib/openlanes/live/claims.rb +9 -1
- data/lib/openlanes/live/command.rb +13 -10
- data/lib/openlanes/live/commands.rb +221 -31
- data/lib/openlanes/live/copilot/brief.rb +128 -0
- data/lib/openlanes/live/copilot/cards.rb +226 -0
- data/lib/openlanes/live/copilot/operator.rb +259 -0
- data/lib/openlanes/live/copilot/slash.rb +82 -0
- data/lib/openlanes/live/copilot.rb +290 -0
- data/lib/openlanes/live/event_bus/base.rb +52 -0
- data/lib/openlanes/live/event_bus/memory_bus.rb +14 -0
- data/lib/openlanes/live/event_bus/redis_bus.rb +10 -0
- data/lib/openlanes/live/event_bus/transaction.rb +27 -0
- data/lib/openlanes/live/event_bus.rb +29 -0
- data/lib/openlanes/live/interviewer.rb +146 -0
- data/lib/openlanes/live/keys.rb +114 -0
- data/lib/openlanes/live/mail.rb +143 -0
- data/lib/openlanes/live/memory_store.rb +36 -1
- data/lib/openlanes/live/memory_subscriber.rb +40 -0
- data/lib/openlanes/live/news.rb +61 -0
- data/lib/openlanes/live/node.rb +20 -0
- data/lib/openlanes/live/outage.rb +225 -0
- data/lib/openlanes/live/outage_report.rb +89 -0
- data/lib/openlanes/live/publisher.rb +42 -2
- data/lib/openlanes/live/run_record.rb +39 -1
- data/lib/openlanes/live/run_state.rb +19 -2
- data/lib/openlanes/live/session.rb +38 -4
- data/lib/openlanes/live/snapshot.rb +103 -42
- data/lib/openlanes/live/stations.rb +12 -8
- data/lib/openlanes/live/store.rb +36 -1
- data/lib/openlanes/live/subscriber.rb +93 -0
- data/lib/openlanes/live.rb +71 -1
- data/lib/openlanes/plans/blocked_file.rb +117 -0
- data/lib/openlanes/plans/choices.rb +228 -0
- data/lib/openlanes/plans/dependencies.rb +304 -0
- data/lib/openlanes/plans/feature.rb +75 -16
- data/lib/openlanes/plans/journal.rb +169 -0
- data/lib/openlanes/plans/last_known_state.rb +82 -0
- data/lib/openlanes/plans/lock.rb +171 -0
- data/lib/openlanes/plans/mailbox.rb +145 -4
- data/lib/openlanes/plans/plan_state.rb +590 -9
- data/lib/openlanes/plans/pull_request_machine.rb +163 -0
- data/lib/openlanes/plans/review_label.rb +139 -0
- data/lib/openlanes/plans/spec.rb +239 -3
- data/lib/openlanes/plans/state_machine.rb +63 -30
- data/lib/openlanes/plans/status.rb +60 -11
- data/lib/openlanes/plans/subject.rb +4 -0
- data/lib/openlanes/plans/tree.rb +101 -10
- data/lib/openlanes/plans/verdict.rb +124 -0
- data/lib/openlanes/plans.rb +19 -0
- data/lib/openlanes/presentation/api_docs.rb +76 -0
- data/lib/openlanes/presentation/documentation.rb +105 -12
- data/lib/openlanes/presentation/pr_changes.rb +90 -0
- data/lib/openlanes/presentation/reporter.rb +66 -7
- data/lib/openlanes/presentation.rb +20 -1
- data/lib/openlanes/setup/check.rb +16 -0
- data/lib/openlanes/setup/machine.rb +179 -0
- data/lib/openlanes/setup.rb +34 -0
- data/lib/openlanes/support/clock.rb +57 -0
- data/lib/openlanes/support/config.rb +318 -6
- data/lib/openlanes/support/log.rb +206 -0
- data/lib/openlanes/support/refusal.rb +15 -0
- data/lib/openlanes/support/runtime.rb +61 -0
- data/lib/openlanes/support/spawner.rb +51 -0
- data/lib/openlanes/support/ui.rb +110 -21
- data/lib/openlanes/vcs/ci_gate.rb +31 -0
- data/lib/openlanes/vcs/github.rb +362 -12
- data/lib/openlanes/vcs/label_migration.rb +132 -0
- data/lib/openlanes/vcs/label_sync.rb +75 -0
- data/lib/openlanes/vcs/publisher.rb +287 -7
- data/lib/openlanes/vcs/refresh.rb +337 -0
- data/lib/openlanes/vcs/rename_date.rb +83 -0
- data/lib/openlanes/vcs/review.rb +66 -0
- data/lib/openlanes/vcs/worktree.rb +210 -14
- data/lib/openlanes/vcs.rb +18 -0
- data/lib/openlanes/version.rb +1 -1
- data/lib/openlanes/web/admin_auth.rb +104 -0
- data/lib/openlanes/web/app.rb +213 -15
- data/lib/openlanes/web/context.rb +33 -9
- data/lib/openlanes/web/demo.rb +39 -14
- data/lib/openlanes/web/demo_board.rb +63 -5
- data/lib/openlanes/web/events.rb +137 -9
- data/lib/openlanes/web/launcher.rb +70 -8
- data/lib/openlanes/web/listen.rb +133 -0
- data/lib/openlanes/web/plan_files.rb +136 -0
- data/lib/openlanes/web/request_log.rb +108 -0
- data/lib/openlanes/web/server.rb +31 -10
- data/lib/openlanes/web/stores.rb +11 -0
- data/lib/openlanes/web.rb +19 -4
- data/lib/openlanes.rb +45 -7
- data/openlanes.gemspec +9 -4
- data/schemas/journal-event.schema.json +72 -0
- data/schemas/plan-state.schema.json +163 -2
- data/skills/openlanes/SKILL.md +2 -2
- data/skills/openlanes-documents/SKILL.md +4 -4
- data/skills/openlanes-documents/references/blocked-template.md +2 -2
- data/skills/openlanes-documents/references/closing-templates.md +1 -1
- data/skills/openlanes-documents/references/plan-template.md +12 -4
- data/skills/openlanes-documents/references/retroactive-spec.md +1 -1
- data/skills/openlanes-run/SKILL.md +5 -5
- data/skills/openlanes-spec/SKILL.md +1 -1
- data/skills/openlanes-status/SKILL.md +1 -1
- data/web/dist/assets/favicon-BFJEKKAE.png +0 -0
- data/web/dist/assets/{index-Bp7W-ZTq.js → index-690ayLS4.js} +2 -2
- data/web/dist/assets/index-6ZmW4slI.js +199 -0
- data/web/dist/assets/{index-Byyxbh_z.js → index-B5KzlXp1.js} +2 -2
- data/web/dist/assets/{index-8dNhRmiO.js → index-BAo_v9bU.js} +2 -2
- data/web/dist/assets/{index-2xPC-eHt.js → index-BDm7g-xG.js} +2 -2
- data/web/dist/assets/{index-D0rmJN3h.js → index-BFH9bC7v.js} +2 -2
- data/web/dist/assets/{index-OR-odsl9.js → index-BVHPV7z4.js} +2 -2
- data/web/dist/assets/{index-C0VmkrO-.js → index-B_CPqjnA.js} +2 -2
- data/web/dist/assets/{index-DDI7zoAS.js → index-BhwBH5ct.js} +2 -2
- data/web/dist/assets/{index-BdR34cXC.js → index-Bq5AdwEt.js} +2 -2
- data/web/dist/assets/{index-uM88Njdj.js → index-CAW6ClM2.js} +2 -2
- data/web/dist/assets/index-Cp86u27u.css +1 -0
- data/web/dist/assets/{index-P3vSkiU-.js → index-Culm4pMO.js} +2 -2
- data/web/dist/assets/{index-DGLUuEXi.js → index-DElDJfp1.js} +2 -2
- data/web/dist/assets/{index-CYxcbpRM.js → index-Dja7osNW.js} +2 -2
- data/web/dist/assets/{index-popk9wbW.js → index-DjhU3FzU.js} +2 -2
- data/web/dist/assets/{index-mRV4XhJZ.js → index-Dutbxj4o.js} +2 -2
- data/web/dist/assets/{index-DB4QwuYn.js → index-RkDhPMDt.js} +2 -2
- data/web/dist/assets/{index-B7-O9ujQ.js → index-_mptZjlC.js} +2 -2
- data/web/dist/assets/logo-DXDCUiD8.png +0 -0
- data/web/dist/index.html +3 -3
- data/web/src/api/fixtures/board.json +203 -28
- metadata +1053 -881
- data/bin/factory-e2e +0 -157
- data/bin/plan-number +0 -164
- data/bin/setup +0 -26
- data/evals/cases/palpatine-planner/plan-deep/fail/plan/plan.md +0 -8
- data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan.md +0 -12
- data/web/dist/assets/favicon-C8pAuqqr.png +0 -0
- data/web/dist/assets/index-CI74IkWP.js +0 -198
- data/web/dist/assets/index-a4EHR-u4.css +0 -1
- data/web/dist/assets/logo-on-dark-DI9gCdN4.png +0 -0
- data/web/dist/assets/logo-on-light-BciwqxcJ.png +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/fail/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/fail/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/fail/run.json +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/plan-frontend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/run.json +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/plan/plan-frontend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/run.json +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/plan-frontend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/run.json +0 -0
- /data/exe/{depot → ol} +0 -0
|
@@ -37,6 +37,14 @@ module OpenLanes
|
|
|
37
37
|
# compacts its session from outside (see {Executor::Handle#compact!}).
|
|
38
38
|
WORDS = { "hold" => HOLD, "wrap_up" => WRAP_UP, "stop" => STOP }.freeze
|
|
39
39
|
|
|
40
|
+
# Redis stopped answering (W32): every agent stops at its next safe
|
|
41
|
+
# point and waits until the file says anything else. Its clock stands
|
|
42
|
+
# still meanwhile, so the wait costs it no time.
|
|
43
|
+
PAUSE = "PAUSE"
|
|
44
|
+
|
|
45
|
+
# Redis is back: carry on from where the PAUSE found you.
|
|
46
|
+
RESUME = "RESUME"
|
|
47
|
+
|
|
40
48
|
# Seconds between {#quit!} and the harness terminating whatever is still
|
|
41
49
|
# running. Long enough to write files and a handoff note; short enough
|
|
42
50
|
# that q means quit rather than "quit eventually".
|
|
@@ -46,6 +54,7 @@ module OpenLanes
|
|
|
46
54
|
@files = []
|
|
47
55
|
@quit = false
|
|
48
56
|
@interrupted = false
|
|
57
|
+
@paused = false
|
|
49
58
|
@deadline = nil
|
|
50
59
|
|
|
51
60
|
class << self
|
|
@@ -107,6 +116,9 @@ module OpenLanes
|
|
|
107
116
|
def quit!
|
|
108
117
|
stop!
|
|
109
118
|
@mutex.synchronize {
|
|
119
|
+
# Quitting overrides a pause: the clocks run again, so an agent
|
|
120
|
+
# that does not stop is still stopped.
|
|
121
|
+
@paused = false
|
|
110
122
|
@quit = true
|
|
111
123
|
@deadline ||= UI.monotonic + GRACE
|
|
112
124
|
}
|
|
@@ -127,6 +139,25 @@ module OpenLanes
|
|
|
127
139
|
}
|
|
128
140
|
end
|
|
129
141
|
|
|
142
|
+
# Every running agent waits where it is (W32).
|
|
143
|
+
#
|
|
144
|
+
# @return [void]
|
|
145
|
+
def pause!
|
|
146
|
+
@mutex.synchronize { @paused = true }
|
|
147
|
+
broadcast(PAUSE)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Every paused agent carries on.
|
|
151
|
+
#
|
|
152
|
+
# @return [void]
|
|
153
|
+
def resume!
|
|
154
|
+
@mutex.synchronize { @paused = false }
|
|
155
|
+
broadcast(RESUME)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# @return [Boolean] whether agents are paused; their clocks stand still
|
|
159
|
+
def paused? = @mutex.synchronize { @paused }
|
|
160
|
+
|
|
130
161
|
# @return [Boolean] whether `q` has been pressed
|
|
131
162
|
def quit? = @mutex.synchronize { @quit }
|
|
132
163
|
|
|
@@ -172,6 +203,7 @@ module OpenLanes
|
|
|
172
203
|
@files.each { |f| FileUtils.rm_f(f) }
|
|
173
204
|
@files.clear
|
|
174
205
|
@quit = false
|
|
206
|
+
@paused = false
|
|
175
207
|
@interrupted = false
|
|
176
208
|
@deadline = nil
|
|
177
209
|
}
|
|
@@ -15,9 +15,10 @@ module OpenLanes
|
|
|
15
15
|
#
|
|
16
16
|
# BEFORE — the agent is told, and `--disallowedTools` withholds the tools
|
|
17
17
|
# that would let it push.
|
|
18
|
-
# AFTER —
|
|
19
|
-
#
|
|
20
|
-
# one of them survives a model
|
|
18
|
+
# AFTER — {Boundary} checks that HEAD did not move and that no
|
|
19
|
+
# remote-tracking ref was moved by a push. A prompt is a request;
|
|
20
|
+
# a check is a guarantee, and only one of them survives a model
|
|
21
|
+
# deciding it knows better.
|
|
21
22
|
class Executor
|
|
22
23
|
# What one invocation did, and what it spent doing it.
|
|
23
24
|
#
|
|
@@ -124,12 +125,30 @@ module OpenLanes
|
|
|
124
125
|
# @return [String, nil] the CLI's session id, once its stream named it
|
|
125
126
|
attr_accessor :session
|
|
126
127
|
|
|
128
|
+
# @return [String, nil] the raw NDJSON trace the agent's stream is
|
|
129
|
+
# copied into, so the engine journal can name it and its offset
|
|
130
|
+
attr_accessor :trace
|
|
131
|
+
|
|
127
132
|
# @return [OpenLanes::Execution::Executor::Compaction, nil] the latest
|
|
128
133
|
# compaction asked for
|
|
129
134
|
def compaction = @mutex.synchronize { @compaction }
|
|
130
135
|
|
|
131
136
|
# @return [OpenLanes::Execution::Clock, nil]
|
|
132
|
-
|
|
137
|
+
attr_reader :clock
|
|
138
|
+
|
|
139
|
+
# The clock, once {Executor#call} has started it. Seconds granted
|
|
140
|
+
# before then (a round given more time, E1) go onto it now rather
|
|
141
|
+
# than being dropped for want of a clock.
|
|
142
|
+
#
|
|
143
|
+
# @param clock [OpenLanes::Execution::Clock, nil]
|
|
144
|
+
# @return [void]
|
|
145
|
+
def clock=(clock)
|
|
146
|
+
@mutex.synchronize do
|
|
147
|
+
@clock = clock
|
|
148
|
+
clock&.extend!(@pending) if @pending&.positive?
|
|
149
|
+
@pending = nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
133
152
|
|
|
134
153
|
# @return [String, nil]
|
|
135
154
|
attr_reader :control
|
|
@@ -184,9 +203,18 @@ module OpenLanes
|
|
|
184
203
|
child&.terminate
|
|
185
204
|
end
|
|
186
205
|
|
|
206
|
+
# More time on the clock, or on the clock to come.
|
|
207
|
+
#
|
|
187
208
|
# @param seconds [Integer]
|
|
188
|
-
# @return [
|
|
189
|
-
def extend!(seconds)
|
|
209
|
+
# @return [Boolean] false when the clock had already stopped the agent
|
|
210
|
+
def extend!(seconds)
|
|
211
|
+
@mutex.synchronize do
|
|
212
|
+
next @clock.extend!(seconds) if @clock
|
|
213
|
+
|
|
214
|
+
@pending = @pending.to_i + seconds
|
|
215
|
+
true
|
|
216
|
+
end
|
|
217
|
+
end
|
|
190
218
|
|
|
191
219
|
# Ask for the session to be compacted (W28): INTERRUPT, so the agent
|
|
192
220
|
# leaves a resume note and ends its turn, and SIGKILL after +grace+
|
|
@@ -290,7 +318,10 @@ module OpenLanes
|
|
|
290
318
|
def tools = @all.sum(&:tools)
|
|
291
319
|
|
|
292
320
|
# What the sessions already over spent, added to the running one's
|
|
293
|
-
# progress so the meter carries on rather than starting again.
|
|
321
|
+
# progress so the meter carries on rather than starting again. Their
|
|
322
|
+
# journal lines go first, for the same reason: the dispatcher counts
|
|
323
|
+
# its place in the list, and a list that started again at the
|
|
324
|
+
# continuation would have it skip the continuation's first entries.
|
|
294
325
|
#
|
|
295
326
|
# @param progress [OpenLanes::Execution::Transcript::Progress]
|
|
296
327
|
# @return [OpenLanes::Execution::Transcript::Progress]
|
|
@@ -298,7 +329,8 @@ module OpenLanes
|
|
|
298
329
|
before = @all[0...-1]
|
|
299
330
|
progress.with(up: progress.up + before.sum(&:up),
|
|
300
331
|
down: progress.down + before.sum(&:down),
|
|
301
|
-
subagents: progress.subagents + before.sum(&:spawned)
|
|
332
|
+
subagents: progress.subagents + before.sum(&:spawned),
|
|
333
|
+
journal: (before.flat_map { it.respond_to?(:journal) ? it.journal : [] } + progress.journal).freeze)
|
|
302
334
|
end
|
|
303
335
|
end
|
|
304
336
|
|
|
@@ -321,8 +353,13 @@ module OpenLanes
|
|
|
321
353
|
# as a regular expression that nothing referenced — a guard in the shape of
|
|
322
354
|
# a constant, enforcing nothing — which is exactly how `gh pr review` came
|
|
323
355
|
# to be reachable by an agent nobody had granted it to.
|
|
356
|
+
#
|
|
357
|
+
# `git rebase` is here because the harness owns the rebase onto the
|
|
358
|
+
# default branch ({Vcs::Refresh}): an agent resolving a stopped one only
|
|
359
|
+
# edits and stages, and a `--continue`, `--skip` or `--abort` of its own
|
|
360
|
+
# would move HEAD inside the window the after-check guards.
|
|
324
361
|
FORBIDDEN_COMMANDS = [
|
|
325
|
-
"git push", "git commit",
|
|
362
|
+
"git push", "git commit", "git rebase",
|
|
326
363
|
"gh pr create", "gh pr edit", "gh pr merge",
|
|
327
364
|
"gh pr review", "gh pr comment",
|
|
328
365
|
"gh release create"
|
|
@@ -335,7 +372,11 @@ module OpenLanes
|
|
|
335
372
|
# does not: an approval is reversible, visible, and attributable to the
|
|
336
373
|
# identity that made it. That difference is the whole line between
|
|
337
374
|
# `hansolo-reviewer` approving and `hansolo-reviewer` merging.
|
|
338
|
-
|
|
375
|
+
#
|
|
376
|
+
# A rebase is the harness's alone for the same reason a merge is: it
|
|
377
|
+
# rewrites the branch a pull request is built from, and the harness
|
|
378
|
+
# pushes that rewrite with a lease only it can check.
|
|
379
|
+
UNGRANTABLE = ["git push", "gh pr merge", "git rebase"].freeze
|
|
339
380
|
|
|
340
381
|
# How much of what the agent said survives into a one-line report.
|
|
341
382
|
REASON_LIMIT = 300
|
|
@@ -376,6 +417,11 @@ module OpenLanes
|
|
|
376
417
|
# and nothing here can tell the two apart.
|
|
377
418
|
CREDENTIAL_VARS = %w[ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN].freeze
|
|
378
419
|
|
|
420
|
+
# What `claude -p --resume <id>` prints for an id it has no session for,
|
|
421
|
+
# in the directory it was run from (2.1.x): this line on stderr, then a
|
|
422
|
+
# `result` event with `is_error`, zero turns and zero tokens, exit 1.
|
|
423
|
+
SESSION_MISSING = /No conversation found with session ID/
|
|
424
|
+
|
|
379
425
|
# @param env [Hash]
|
|
380
426
|
# @return [Array<String>] credential variables currently set
|
|
381
427
|
def self.foreign_credentials(env = ENV)
|
|
@@ -476,9 +522,15 @@ module OpenLanes
|
|
|
476
522
|
# @param handle [OpenLanes::Execution::Executor::Handle, nil] filled in for the caller
|
|
477
523
|
# @param partners [Array<OpenLanes::Agents::Agent>] the other agents working this
|
|
478
524
|
# plan right now, named in the prompt beside the plan's mailbox
|
|
525
|
+
# @param rebase [OpenLanes::Vcs::Refresh::Outcome, nil] a rebase stopped
|
|
526
|
+
# on conflicts: the invocation resolves it and does no plan work
|
|
527
|
+
# @param resume [String, nil] the previous round's session id (M1), to
|
|
528
|
+
# carry on with this round's prompt; ignored by a CLI that cannot
|
|
529
|
+
# resume, and dropped for a fresh start when the CLI cannot find it
|
|
479
530
|
# @return [OpenLanes::Execution::Executor::Result]
|
|
480
531
|
# @yieldparam progress [OpenLanes::Execution::Transcript::Progress]
|
|
481
|
-
def call(agent, subject, root: @root, round: 1, successor: nil, handle: nil, partners: [],
|
|
532
|
+
def call(agent, subject, root: @root, round: 1, successor: nil, handle: nil, partners: [], rebase: nil,
|
|
533
|
+
resume: nil, &on_progress)
|
|
482
534
|
started = UI.monotonic
|
|
483
535
|
if @dry_run
|
|
484
536
|
return Result.new(ok: true,
|
|
@@ -491,12 +543,14 @@ module OpenLanes
|
|
|
491
543
|
end
|
|
492
544
|
|
|
493
545
|
handle ||= Handle.new
|
|
494
|
-
before =
|
|
546
|
+
before = Boundary.snapshot(root)
|
|
495
547
|
trace = trace_path(agent, subject)
|
|
548
|
+
handle.trace = trace
|
|
496
549
|
profile = profile_for(agent, subject)
|
|
497
550
|
control = Control.register(@trace_dir, "#{subject.feature.ordinal}-#{agent.name}")
|
|
498
551
|
handle.control = control
|
|
499
|
-
|
|
552
|
+
resume = nil unless profile.adapter.resumes?
|
|
553
|
+
argv = invocation(agent, subject, root:, control:, round:, successor:, partners:, rebase:, resume:)
|
|
500
554
|
sessions = Sessions.new
|
|
501
555
|
child = launch(argv, root:, profile:, trace:, handle:, sessions:, &on_progress)
|
|
502
556
|
clock = Clock.new(seconds: timeout_for(agent),
|
|
@@ -507,6 +561,15 @@ module OpenLanes
|
|
|
507
561
|
|
|
508
562
|
begin
|
|
509
563
|
status = drain(child, sessions, handle)
|
|
564
|
+
if resume && !handle.killed && (lost = lost_session(status, sessions.current))
|
|
565
|
+
UI.log("session #{resume} #{lost}, starting #{agent.name} fresh",
|
|
566
|
+
level: :warn,
|
|
567
|
+
plan: subject.feature.ordinal.to_s,
|
|
568
|
+
agent: agent.name)
|
|
569
|
+
handle.session = nil
|
|
570
|
+
argv = invocation(agent, subject, root:, control:, round:, successor:, partners:, rebase:)
|
|
571
|
+
status = drain(launch(argv, root:, profile:, trace:, handle:, sessions:, &on_progress), sessions, handle)
|
|
572
|
+
end
|
|
510
573
|
while (compaction = handle.take_compaction)
|
|
511
574
|
resumed = settling(compaction, agent) do
|
|
512
575
|
carry_on(compaction, agent, subject, root:, profile:, trace:, handle:, sessions:, &on_progress)
|
|
@@ -541,7 +604,7 @@ module OpenLanes
|
|
|
541
604
|
pid: handle.pid)
|
|
542
605
|
end
|
|
543
606
|
|
|
544
|
-
violation =
|
|
607
|
+
violation = before.violation(root)
|
|
545
608
|
return failure(sessions, started, violation, pid: handle.pid) if violation
|
|
546
609
|
|
|
547
610
|
spent(sessions,
|
|
@@ -602,12 +665,15 @@ module OpenLanes
|
|
|
602
665
|
# @param round [Integer] which attempt on this plan this is
|
|
603
666
|
# @param successor [String, nil] who the agent names on its `next:` line
|
|
604
667
|
# @param partners [Array<OpenLanes::Agents::Agent>]
|
|
668
|
+
# @param rebase [OpenLanes::Vcs::Refresh::Outcome, nil]
|
|
669
|
+
# @param resume [String, nil] a session id to carry on
|
|
605
670
|
# @return [Array<String>]
|
|
606
|
-
def invocation(agent, subject, root: @root, control: nil, round: 1, successor: nil, partners: []
|
|
671
|
+
def invocation(agent, subject, root: @root, control: nil, round: 1, successor: nil, partners: [], rebase: nil,
|
|
672
|
+
resume: nil)
|
|
607
673
|
profile = profile_for(agent, subject)
|
|
608
674
|
profile.adapter.argv(
|
|
609
675
|
Adapters::Invocation.new(
|
|
610
|
-
prompt: prompt_for(agent, subject, root, control:, round:, successor:, partners:, profile:),
|
|
676
|
+
prompt: prompt_for(agent, subject, root, control:, round:, successor:, partners:, profile:, rebase:),
|
|
611
677
|
root:,
|
|
612
678
|
model: profile.model,
|
|
613
679
|
effort: profile.effort,
|
|
@@ -615,7 +681,8 @@ module OpenLanes
|
|
|
615
681
|
denied_tools: denied_for(agent),
|
|
616
682
|
denied_commands: denied_commands(agent),
|
|
617
683
|
network: agent.network,
|
|
618
|
-
lean: @lean
|
|
684
|
+
lean: @lean,
|
|
685
|
+
resume:
|
|
619
686
|
)
|
|
620
687
|
)
|
|
621
688
|
end
|
|
@@ -659,6 +726,33 @@ module OpenLanes
|
|
|
659
726
|
child
|
|
660
727
|
end
|
|
661
728
|
|
|
729
|
+
# Whether a resumed invocation failed because its session is gone,
|
|
730
|
+
# rather than at anything it did, and what to say about it. The message
|
|
731
|
+
# is the sure sign; a failure that spent nothing and called no tool is
|
|
732
|
+
# taken as the same thing, because a CLI that rewords the message must
|
|
733
|
+
# still not cost the round. Starting such a failure again fresh costs
|
|
734
|
+
# one more start-up at worst: an invocation that spent nothing has
|
|
735
|
+
# nothing to lose.
|
|
736
|
+
#
|
|
737
|
+
# That second case also catches an overload, an auth error or a network
|
|
738
|
+
# drop at start-up, which the fresh start will usually hit again. The
|
|
739
|
+
# line the CLI printed goes into the log beside the retry, so an outage
|
|
740
|
+
# reads as an outage rather than as a lost session.
|
|
741
|
+
#
|
|
742
|
+
# @param status [Process::Status]
|
|
743
|
+
# @param transcript [OpenLanes::Execution::Transcript]
|
|
744
|
+
# @return [String, nil] why the session counts as lost, nil when it does not
|
|
745
|
+
def lost_session(status, transcript)
|
|
746
|
+
return nil if status.success? && !transcript.failed?
|
|
747
|
+
|
|
748
|
+
said = [transcript.error, *transcript.plain].map(&:to_s).reject(&:empty?)
|
|
749
|
+
return "not found" if said.any? { SESSION_MISSING.match?(it) }
|
|
750
|
+
return nil unless transcript.up.zero? && transcript.down.zero? && transcript.tools.zero?
|
|
751
|
+
|
|
752
|
+
detail = said.empty? ? "exit #{status.exitstatus}" : said.last.lines.first.strip[0, 200]
|
|
753
|
+
"failed before spending anything (#{detail})"
|
|
754
|
+
end
|
|
755
|
+
|
|
662
756
|
# @return [Process::Status]
|
|
663
757
|
def drain(child, sessions, handle)
|
|
664
758
|
child.each_chunk do |out|
|
|
@@ -794,10 +888,12 @@ module OpenLanes
|
|
|
794
888
|
# @param round [Integer]
|
|
795
889
|
# @param successor [String, nil]
|
|
796
890
|
# @param partners [Array<OpenLanes::Agents::Agent>]
|
|
891
|
+
# @param rebase [OpenLanes::Vcs::Refresh::Outcome, nil]
|
|
797
892
|
# @return [String]
|
|
798
893
|
def prompt_for(agent, subject, root = @root, control: nil, round: 1, successor: nil, partners: [],
|
|
799
|
-
profile: profile_for(agent, subject))
|
|
894
|
+
profile: profile_for(agent, subject), rebase: nil)
|
|
800
895
|
Prompt.new(agent:,
|
|
896
|
+
rebase:,
|
|
801
897
|
subject:,
|
|
802
898
|
root:,
|
|
803
899
|
profile:,
|
|
@@ -827,23 +923,6 @@ module OpenLanes
|
|
|
827
923
|
handle.kill!(grace: 0, reason: :quit)
|
|
828
924
|
end
|
|
829
925
|
end
|
|
830
|
-
|
|
831
|
-
# @return [String, nil] current commit, or nil outside a repository
|
|
832
|
-
def head(root = @root)
|
|
833
|
-
out = `git -C #{root.shellescape} rev-parse HEAD 2>/dev/null`.strip
|
|
834
|
-
out.empty? ? nil : out
|
|
835
|
-
end
|
|
836
|
-
|
|
837
|
-
# @param before [String, nil]
|
|
838
|
-
# @return [String, nil] what boundary was crossed, or nil
|
|
839
|
-
def boundary_violation(before, root = @root)
|
|
840
|
-
return nil if before.nil?
|
|
841
|
-
|
|
842
|
-
after = head(root)
|
|
843
|
-
return "agent committed (HEAD moved #{before[0, 7]} → #{after[0, 7]}) — the boundary is docs plus code, no commits" if after != before
|
|
844
|
-
|
|
845
|
-
nil
|
|
846
|
-
end
|
|
847
926
|
end
|
|
848
927
|
end
|
|
849
928
|
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "etc"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module OpenLanes
|
|
8
|
+
module Execution
|
|
9
|
+
# When a process started, so a recovering engine can tell its own agent
|
|
10
|
+
# from a stranger that inherited the pid. Ruby's stdlib has no call for
|
|
11
|
+
# it: Linux answers through `/proc/<pid>/stat`, macOS through
|
|
12
|
+
# `ps -o lstart=`. The engine runs this, never an agent, so the autonomy
|
|
13
|
+
# boundary's withheld commands do not apply.
|
|
14
|
+
module ProcessStart
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# @param pid [Integer]
|
|
18
|
+
# @param proc_root [String] where `/proc` is, a fixture in specs
|
|
19
|
+
# @param runner [#capture2] runs `ps`, injected in specs
|
|
20
|
+
# @return [Time, nil] the start time, or nil where it cannot be read
|
|
21
|
+
def of(pid, proc_root: "/proc", runner: Open3)
|
|
22
|
+
return nil unless pid.is_a?(Integer) && pid.positive?
|
|
23
|
+
|
|
24
|
+
from_proc(pid, proc_root) || from_ps(pid, runner)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Field 22 of `stat` is the start in clock ticks after boot. The command
|
|
28
|
+
# name (field 2) may hold spaces and parentheses, so fields are counted
|
|
29
|
+
# from the last `)`.
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
def from_proc(pid, proc_root)
|
|
33
|
+
stat = File.join(proc_root, pid.to_s, "stat")
|
|
34
|
+
uptime = File.join(proc_root, "uptime")
|
|
35
|
+
return nil unless File.file?(stat) && File.file?(uptime)
|
|
36
|
+
|
|
37
|
+
ticks = File.read(stat).split(")").last.split[19].to_i
|
|
38
|
+
booted = Time.now - File.read(uptime).split.first.to_f
|
|
39
|
+
booted + (ticks / clock_ticks.to_f)
|
|
40
|
+
rescue SystemCallError, NoMethodError
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @api private
|
|
45
|
+
def from_ps(pid, runner)
|
|
46
|
+
out, status = runner.capture2("ps", "-o", "lstart=", "-p", pid.to_s)
|
|
47
|
+
return nil unless status.success?
|
|
48
|
+
|
|
49
|
+
text = out.strip
|
|
50
|
+
text.empty? ? nil : Time.parse(text)
|
|
51
|
+
rescue SystemCallError, ArgumentError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api private
|
|
56
|
+
def clock_ticks
|
|
57
|
+
Etc.sysconf(Etc::SC_CLK_TCK)
|
|
58
|
+
rescue StandardError
|
|
59
|
+
100
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|