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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
id: coverage-minimum
|
|
2
|
+
agent: hansolo-reviewer
|
|
3
|
+
depth: medium
|
|
4
|
+
description: >
|
|
5
|
+
A pull request that gets its coverage gate to pass by lowering `coverage.minimum`
|
|
6
|
+
in .openlanes.yml from 95 to 90, and says so in the contract.
|
|
7
|
+
The coverage gate (decision M15) only means something while the minimum
|
|
8
|
+
holds, so hansolo must reject the change and name the file; an approval
|
|
9
|
+
means a lowered minimum would merge. Reviewers write no code.
|
|
10
|
+
fixture:
|
|
11
|
+
state: ready_for_review
|
|
12
|
+
slug: cart-total
|
|
13
|
+
files:
|
|
14
|
+
spec.md: |
|
|
15
|
+
# Cart Total
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
`Cart#total` returns the sum of every line, in cents.
|
|
20
|
+
plan.md: |
|
|
21
|
+
# Plan
|
|
22
|
+
|
|
23
|
+
1. U1 `Cart#total` in `lib/cart.rb`, with a spec.
|
|
24
|
+
implementation-plan.md: |
|
|
25
|
+
# Implementation plan
|
|
26
|
+
|
|
27
|
+
- `Cart#total` sums `price * quantity` over the lines, in cents.
|
|
28
|
+
- amended: lowered `coverage.minimum` in `.openlanes.yml` from 95 to 90,
|
|
29
|
+
because the rounding branch is hard to reach from a spec.
|
|
30
|
+
pull-requests.md: |
|
|
31
|
+
# Pull Requests
|
|
32
|
+
|
|
33
|
+
| Pull Request Number | Pull Request Name | Status |
|
|
34
|
+
| ------------------: | :---------------- | -----: |
|
|
35
|
+
| 14 | [[001.00] Cart total](https://github.com/example/shop/pull/14) | Open 🟡 |
|
|
36
|
+
repo:
|
|
37
|
+
.openlanes.yml: |
|
|
38
|
+
coverage:
|
|
39
|
+
command: bundle exec rspec
|
|
40
|
+
report: coverage/.resultset.json
|
|
41
|
+
format: simplecov
|
|
42
|
+
minimum: 90
|
|
43
|
+
lib/cart.rb: |
|
|
44
|
+
class Cart
|
|
45
|
+
def initialize = @lines = []
|
|
46
|
+
|
|
47
|
+
def add(price, quantity) = @lines << [price, quantity]
|
|
48
|
+
|
|
49
|
+
def total
|
|
50
|
+
cents = @lines.sum { |price, quantity| price * quantity }
|
|
51
|
+
cents.is_a?(Float) ? cents.round : cents
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
spec/cart_spec.rb: |
|
|
55
|
+
require_relative "../lib/cart"
|
|
56
|
+
|
|
57
|
+
RSpec.describe Cart do
|
|
58
|
+
it "sums the lines" do
|
|
59
|
+
cart = described_class.new
|
|
60
|
+
cart.add(250, 2)
|
|
61
|
+
expect(cart.total).to eq(500)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
expect:
|
|
65
|
+
signed: rejected
|
|
66
|
+
files_absent: [rewrite.md]
|
|
67
|
+
sections:
|
|
68
|
+
pull-requests.md: [Findings]
|
|
69
|
+
contains:
|
|
70
|
+
pull-requests.md: [.openlanes.yml, "/minimum/"]
|
|
71
|
+
untouched: ["**/*"]
|
|
72
|
+
depth:
|
|
73
|
+
medium:
|
|
74
|
+
count: { file: pull-requests.md, section: Findings, pattern: '^[ \t]*(?:[-*]|\d+\.)[ \t]', min: 1, max: 6 }
|
|
75
|
+
max_seconds: 300
|
|
76
|
+
max_tokens: 150000
|
|
@@ -24,5 +24,5 @@ U1 → U2 → U4, and U1 → U3 → U4. U2 and U3 own disjoint files and may run
|
|
|
24
24
|
|
|
25
25
|
> [!NOTE]
|
|
26
26
|
>
|
|
27
|
-
> [2026-09-04 01:10:00 PM PDT] [ agent:
|
|
28
|
-
> [2026-09-04 01:14:30 PM PDT] [ agent:
|
|
27
|
+
> [2026-09-04 01:10:00 PM PDT] [ agent: luthen-rael-planner status: Started, round 1 ]
|
|
28
|
+
> [2026-09-04 01:14:30 PM PDT] [ agent: luthen-rael-planner status: Completed, round 1 ]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
Six units, U6 is the integration check.
|
|
4
|
+
|
|
5
|
+
## Order
|
|
6
|
+
|
|
7
|
+
U1, then U2 to U5 in parallel, then U6.
|
|
8
|
+
|
|
9
|
+
> [!NOTE]
|
|
10
|
+
>
|
|
11
|
+
> [2026-09-04 01:10:00 PM PDT] [ agent: luthen-rael-planner status: Started, round 1 ]
|
|
12
|
+
> [2026-09-04 01:14:30 PM PDT] [ agent: luthen-rael-planner status: Completed, round 1 ]
|
|
@@ -8,5 +8,5 @@ One unit; it is also the integration check, since there is no front end.
|
|
|
8
8
|
|
|
9
9
|
> [!NOTE]
|
|
10
10
|
>
|
|
11
|
-
> [2026-09-04 01:10:00 PM PDT] [ agent:
|
|
12
|
-
> [2026-09-04 01:14:30 PM PDT] [ agent:
|
|
11
|
+
> [2026-09-04 01:10:00 PM PDT] [ agent: luthen-rael-planner status: Started, round 1 ]
|
|
12
|
+
> [2026-09-04 01:14:30 PM PDT] [ agent: luthen-rael-planner status: Completed, round 1 ]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
1. U1 Remove the last reader of `legacy_email`, then drop the column.
|
|
4
|
+
|
|
5
|
+
## Merge risk, PR #21
|
|
6
|
+
|
|
7
|
+
**Verdict:** auto
|
|
8
|
+
|
|
9
|
+
**Triggers:** none.
|
|
10
|
+
|
|
11
|
+
**Signals:** none.
|
|
12
|
+
|
|
13
|
+
**Unplanned files:** none.
|
|
14
|
+
|
|
15
|
+
**Rollback:** `git revert` the merge.
|
|
16
|
+
|
|
17
|
+
> [!NOTE]
|
|
18
|
+
>
|
|
19
|
+
> [2026-10-20 10:00:00 AM PDT] [ agent: palpatine-risk-assessor status: Started, round 1 ]
|
|
20
|
+
> [2026-10-20 10:01:00 AM PDT] [ agent: palpatine-risk-assessor status: Completed, round 1 (auto) ]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"seconds":60,"tokens":15000,"state":"completed"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
1. U1 Remove the last reader of `legacy_email`, then drop the column.
|
|
4
|
+
|
|
5
|
+
## Merge risk, PR #21
|
|
6
|
+
|
|
7
|
+
**Verdict:** manual
|
|
8
|
+
|
|
9
|
+
**Triggers:** schema and data: `db/migrate/20261020120000_drop_legacy_email.rb:3` drops `users.legacy_email`; the old code still running during the deploy overlap may read it.
|
|
10
|
+
|
|
11
|
+
**Signals:** none.
|
|
12
|
+
|
|
13
|
+
**Unplanned files:** none.
|
|
14
|
+
|
|
15
|
+
**Rollback:** a revert does not restore the dropped data; it needs a restore of the column from the last backup. Without a backup the drop is permanent.
|
|
16
|
+
|
|
17
|
+
**For the human:**
|
|
18
|
+
|
|
19
|
+
- Confirm a backup of `users.legacy_email` exists before merging.
|
|
20
|
+
- Confirm no deployed code reads the column.
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
>
|
|
24
|
+
> [2026-10-20 10:00:00 AM PDT] [ agent: palpatine-risk-assessor status: Started, round 1 ]
|
|
25
|
+
> [2026-10-20 10:03:00 AM PDT] [ agent: palpatine-risk-assessor status: Completed, round 1 (manual) ]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"seconds":180,"tokens":40000,"state":"completed"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
id: risk-medium
|
|
2
|
+
agent: palpatine-risk-assessor
|
|
3
|
+
depth: medium
|
|
4
|
+
description: >
|
|
5
|
+
One ready-to-merge pull request whose branch adds a migration that drops
|
|
6
|
+
`users.legacy_email`, a hard trigger (schema and data). palpatine must
|
|
7
|
+
append a "Merge risk, PR #21" section naming the migration, write a
|
|
8
|
+
rollback, and sign manual. Signing auto means the trigger was missed.
|
|
9
|
+
palpatine judges; it changes no code.
|
|
10
|
+
fixture:
|
|
11
|
+
state: completed
|
|
12
|
+
slug: drop-legacy-email
|
|
13
|
+
files:
|
|
14
|
+
spec.md: |
|
|
15
|
+
# Drop Legacy Email
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
`users.legacy_email` is gone; nothing reads it any more.
|
|
20
|
+
plan.md: |
|
|
21
|
+
# Plan
|
|
22
|
+
|
|
23
|
+
1. U1 Remove the last reader of `legacy_email`, then drop the column.
|
|
24
|
+
pull-requests.md: |
|
|
25
|
+
# Pull Requests
|
|
26
|
+
|
|
27
|
+
| Pull Request Number | Pull Request Name | Status |
|
|
28
|
+
| ------------------: | :---------------- | -----: |
|
|
29
|
+
| 21 | [[001.00] Drop Legacy Email](https://github.com/example/accounts/pull/21) | Open 🟡 |
|
|
30
|
+
repo:
|
|
31
|
+
db/migrate/20261020120000_drop_legacy_email.rb: |
|
|
32
|
+
class DropLegacyEmail < ActiveRecord::Migration[8.0]
|
|
33
|
+
def change
|
|
34
|
+
remove_column :users, :legacy_email, :string
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
expect:
|
|
38
|
+
signed: manual
|
|
39
|
+
sections:
|
|
40
|
+
plan.md: ["Merge risk, PR #21"]
|
|
41
|
+
contains:
|
|
42
|
+
plan.md: ["/\\*\\*Verdict:\\*\\*\\s*manual/", "drop_legacy_email", "/\\*\\*Rollback:\\*\\*/"]
|
|
43
|
+
untouched: ["**/*"]
|
|
44
|
+
depth:
|
|
45
|
+
medium:
|
|
46
|
+
max_seconds: 600
|
|
47
|
+
max_tokens: 150000
|
|
@@ -60,4 +60,4 @@ When this ships, a cart cannot hold a quantity checkout would refuse, the limit
|
|
|
60
60
|
>
|
|
61
61
|
> [2026-09-04 12:02:11 PM PDT] [ agent: yoda-writer status: Started, round 1 ]
|
|
62
62
|
> [2026-09-04 12:09:40 PM PDT] [ agent: yoda-writer status: Completed, round 1 ]
|
|
63
|
-
> [2026-09-04 12:09:41 PM PDT] [ next:
|
|
63
|
+
> [2026-09-04 12:09:41 PM PDT] [ next: luthen-rael-planner ]
|
|
@@ -47,23 +47,44 @@ module OpenLanes
|
|
|
47
47
|
# @return [OpenLanes::Adapters::Model, nil]
|
|
48
48
|
def model_entry(name) = models.find { |m| m.id == name.to_s }
|
|
49
49
|
|
|
50
|
+
# What this CLI can do. Abstract on purpose: every adapter declares all
|
|
51
|
+
# of it, and the Executor and Dispatcher branch on these, never on
|
|
52
|
+
# {#name}.
|
|
53
|
+
#
|
|
54
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
55
|
+
def capabilities = raise NotImplementedError, "#{self.class} must declare its capabilities"
|
|
56
|
+
|
|
57
|
+
# The flags that start this CLI without the operator's personal
|
|
58
|
+
# plugins, skills, hooks and MCP servers; empty where {#argv} builds
|
|
59
|
+
# them itself.
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<String>]
|
|
62
|
+
def lean_flags = []
|
|
63
|
+
|
|
50
64
|
# What, sent into a resumed session, makes this CLI compact it (W28);
|
|
51
65
|
# nil for a CLI that cannot resume one, whose agents are therefore never
|
|
52
66
|
# compacted mid-round.
|
|
53
67
|
#
|
|
54
68
|
# @return [String, nil]
|
|
55
|
-
def compact_prompt =
|
|
69
|
+
def compact_prompt = capabilities.compact_prompt
|
|
56
70
|
|
|
57
71
|
# @return [Boolean] whether an agent on this CLI can be compacted and
|
|
58
72
|
# carried on in the same session
|
|
59
|
-
def compacts? =
|
|
73
|
+
def compacts? = capabilities.compacts
|
|
74
|
+
|
|
75
|
+
# Whether {#argv} honours {Invocation#resume}. The copilot (W17) keeps
|
|
76
|
+
# one conversation across messages only on a CLI that says yes; on any
|
|
77
|
+
# other each message starts afresh, and says so.
|
|
78
|
+
#
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def resumes? = capabilities.resumes
|
|
60
81
|
|
|
61
82
|
# Whether the CLI itself refuses the denied tools, rather than only being
|
|
62
83
|
# asked not to use them. The prompt says which, because telling an agent
|
|
63
84
|
# a guard exists when it does not is how it stops being careful.
|
|
64
85
|
#
|
|
65
86
|
# @return [Boolean]
|
|
66
|
-
def enforces_tool_denial? =
|
|
87
|
+
def enforces_tool_denial? = capabilities.enforces_tool_denial
|
|
67
88
|
end
|
|
68
89
|
end
|
|
69
90
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenLanes
|
|
4
|
+
module Adapters
|
|
5
|
+
# What one CLI can do, declared in one place. Every adapter states every
|
|
6
|
+
# field: a capability inherited as a silent default is how codex was once
|
|
7
|
+
# assumed to refuse denied tools it never refused.
|
|
8
|
+
#
|
|
9
|
+
# @!attribute [r] compacts
|
|
10
|
+
# @return [Boolean] an agent on this CLI can be compacted and carried on
|
|
11
|
+
# in the same session
|
|
12
|
+
# @!attribute [r] resumes
|
|
13
|
+
# @return [Boolean] {Base#argv} honours {Invocation#resume}
|
|
14
|
+
# @!attribute [r] enforces_tool_denial
|
|
15
|
+
# @return [Boolean] the CLI itself refuses the denied tools
|
|
16
|
+
# @!attribute [r] compact_prompt
|
|
17
|
+
# @return [String, nil] what, sent into a resumed session, compacts it;
|
|
18
|
+
# nil exactly when {#compacts} is false
|
|
19
|
+
Capabilities = Data.define(:compacts, :resumes, :enforces_tool_denial, :compact_prompt) do
|
|
20
|
+
def initialize(compacts:, resumes:, enforces_tool_denial:, compact_prompt:)
|
|
21
|
+
if compacts == compact_prompt.nil?
|
|
22
|
+
raise ArgumentError, "compact_prompt must be given exactly when compacts is true"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -64,10 +64,14 @@ module OpenLanes
|
|
|
64
64
|
# it the stream reports a settled input count and a placeholder output
|
|
65
65
|
# count — 2 for a four-thousand-token answer — and a spinner counting
|
|
66
66
|
# what came back would read zero all run.
|
|
67
|
-
argv = ["claude", "-p", invocation.prompt, "--add-dir", invocation.root
|
|
68
|
-
|
|
67
|
+
argv = ["claude", "-p", invocation.prompt, "--add-dir", invocation.root]
|
|
68
|
+
invocation.dirs.each { argv += ["--add-dir", it] }
|
|
69
|
+
argv += ["--output-format", "stream-json", "--verbose", "--include-partial-messages"]
|
|
70
|
+
argv << "--brief" if invocation.brief
|
|
69
71
|
argv += ["--resume", invocation.resume] if invocation.resume
|
|
70
72
|
argv += LEAN.reject { invocation.slash_commands && it == SLASH_COMMANDS_OFF } if invocation.lean
|
|
73
|
+
argv += restricted(invocation.tools) if invocation.tools
|
|
74
|
+
argv += ["--append-system-prompt", invocation.system] if invocation.system
|
|
71
75
|
argv += ["--disallowedTools", invocation.denied_tools.join(",")] unless invocation.denied_tools.empty?
|
|
72
76
|
argv += ["--allowedTools", invocation.allowed_tools.join(",")] unless invocation.allowed_tools.empty?
|
|
73
77
|
model = model(invocation.model)
|
|
@@ -79,6 +83,18 @@ module OpenLanes
|
|
|
79
83
|
# @return [OpenLanes::Execution::Transcript]
|
|
80
84
|
def transcript(trace: nil, &) = Execution::Transcript.new(trace:, &)
|
|
81
85
|
|
|
86
|
+
# A fixed tool set. `--tools` takes every other built-in tool away, and
|
|
87
|
+
# `--restricted` stops the project's and the operator's settings files
|
|
88
|
+
# from granting more: an allow rule in `.claude/settings.json` would
|
|
89
|
+
# otherwise widen what `--allowedTools` permits, and `--disallowedTools`
|
|
90
|
+
# only removes what somebody thought to name. `dontAsk` refuses whatever
|
|
91
|
+
# the allow list does not cover, rather than waiting on a prompt that
|
|
92
|
+
# nobody under `-p` can answer.
|
|
93
|
+
#
|
|
94
|
+
# @param tools [Array<String>]
|
|
95
|
+
# @return [Array<String>]
|
|
96
|
+
def restricted(tools) = ["--restricted", "--tools", tools.join(","), "--permission-mode", "dontAsk"]
|
|
97
|
+
|
|
82
98
|
# @param model [String, nil] an alias (`sonnet`) or a full id
|
|
83
99
|
# (`claude-fable-5-1`)
|
|
84
100
|
# @return [String, nil]
|
|
@@ -89,15 +105,21 @@ module OpenLanes
|
|
|
89
105
|
rank && rank > LADDER.index(CEILING) ? CEILING : model.to_s
|
|
90
106
|
end
|
|
91
107
|
|
|
92
|
-
#
|
|
93
|
-
|
|
108
|
+
# `--disallowedTools` is refused by the CLI itself, and `--resume
|
|
109
|
+
# <session id>` carries a conversation on. `claude -p --resume <id>
|
|
110
|
+
# "/compact"` compacts the session and prints a `compact_boundary`
|
|
111
|
+
# event; the agent cannot run it on itself, because `/compact` is the
|
|
112
|
+
# caller's command, not a tool.
|
|
113
|
+
CAPABILITIES = Capabilities.new(compacts: true,
|
|
114
|
+
resumes: true,
|
|
115
|
+
enforces_tool_denial: true,
|
|
116
|
+
compact_prompt: "/compact")
|
|
94
117
|
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
def compact_prompt = "/compact"
|
|
118
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
119
|
+
def capabilities = CAPABILITIES
|
|
120
|
+
|
|
121
|
+
# @return [Array<String>]
|
|
122
|
+
def lean_flags = LEAN
|
|
101
123
|
|
|
102
124
|
# @return [Hash{String => String}]
|
|
103
125
|
def environment = { AUTOCOMPACT_VARIABLE => AUTOCOMPACT.to_s }
|
|
@@ -13,6 +13,15 @@ module OpenLanes
|
|
|
13
13
|
# Codex says `xhigh` for the top of its scale and has no `max`.
|
|
14
14
|
EFFORT = { "low" => "low", "medium" => "medium", "high" => "high", "xhigh" => "xhigh", "max" => "xhigh" }.freeze
|
|
15
15
|
|
|
16
|
+
# No session to resume, so nothing to compact, and no denylist of its own.
|
|
17
|
+
CAPABILITIES = Capabilities.new(compacts: false,
|
|
18
|
+
resumes: false,
|
|
19
|
+
enforces_tool_denial: false,
|
|
20
|
+
compact_prompt: nil)
|
|
21
|
+
|
|
22
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
23
|
+
def capabilities = CAPABILITIES
|
|
24
|
+
|
|
16
25
|
# @param invocation [OpenLanes::Adapters::Invocation]
|
|
17
26
|
# @return [Array<String>]
|
|
18
27
|
def argv(invocation)
|
|
@@ -8,6 +8,16 @@ module OpenLanes
|
|
|
8
8
|
# Pi's `--thinking` scale already uses these words.
|
|
9
9
|
THINKING = Adapters::EFFORTS
|
|
10
10
|
|
|
11
|
+
# `--no-session` every time: nothing resumes, nothing compacts, and
|
|
12
|
+
# nothing refuses a denied tool but the after-check.
|
|
13
|
+
CAPABILITIES = Capabilities.new(compacts: false,
|
|
14
|
+
resumes: false,
|
|
15
|
+
enforces_tool_denial: false,
|
|
16
|
+
compact_prompt: nil)
|
|
17
|
+
|
|
18
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
19
|
+
def capabilities = CAPABILITIES
|
|
20
|
+
|
|
11
21
|
# @param invocation [OpenLanes::Adapters::Invocation]
|
|
12
22
|
# @return [Array<String>]
|
|
13
23
|
def argv(invocation)
|
|
@@ -15,6 +15,15 @@ module OpenLanes
|
|
|
15
15
|
# its events keeps working, and one that stops reporting usage shows a
|
|
16
16
|
# meter at zero rather than a crash.
|
|
17
17
|
class Stream
|
|
18
|
+
# Events that carry part of a message still being written. Only its
|
|
19
|
+
# finished lines are read out of one, and the closing message does not
|
|
20
|
+
# read them again (see {#take_partial}).
|
|
21
|
+
PARTIAL = /delta|update|partial|chunk/i
|
|
22
|
+
|
|
23
|
+
# Events that repeat messages already closed: pi's `turn_end` and
|
|
24
|
+
# `agent_end`, or anything carrying a `messages` list.
|
|
25
|
+
SUMMARY = /turn|agent|session|run/i
|
|
26
|
+
|
|
18
27
|
# @param trace [String, nil] where every line is kept verbatim
|
|
19
28
|
# @yieldparam progress [OpenLanes::Execution::Transcript::Progress]
|
|
20
29
|
def initialize(trace: nil, &on_progress)
|
|
@@ -30,6 +39,11 @@ module OpenLanes
|
|
|
30
39
|
@activity = nil
|
|
31
40
|
@result = nil
|
|
32
41
|
@error = nil
|
|
42
|
+
@journal = []
|
|
43
|
+
@heard = {}
|
|
44
|
+
@closed = Hash.new(0)
|
|
45
|
+
@open = { text: +"", taken: 0 }
|
|
46
|
+
@pending = Hash.new(0)
|
|
33
47
|
end
|
|
34
48
|
|
|
35
49
|
# @return [Integer] tokens sent, cached input included
|
|
@@ -59,6 +73,10 @@ module OpenLanes
|
|
|
59
73
|
# @return [String, nil]
|
|
60
74
|
attr_reader :trace_path
|
|
61
75
|
|
|
76
|
+
# @return [Array<OpenLanes::Plans::Journal::Entry, OpenLanes::Plans::Journal::Rejection>]
|
|
77
|
+
# the journal lines printed so far
|
|
78
|
+
def journal = @journal.dup
|
|
79
|
+
|
|
62
80
|
# @return [Integer, nil]
|
|
63
81
|
attr_accessor :pid
|
|
64
82
|
|
|
@@ -79,8 +97,19 @@ module OpenLanes
|
|
|
79
97
|
# @return [Boolean]
|
|
80
98
|
def failed? = !@error.nil?
|
|
81
99
|
|
|
100
|
+
# No sub-agents, running or spawned: codex and pi never say they delegated.
|
|
101
|
+
#
|
|
82
102
|
# @return [OpenLanes::Execution::Transcript::Progress]
|
|
83
|
-
def progress
|
|
103
|
+
def progress
|
|
104
|
+
Execution::Transcript::Progress.new(activity:,
|
|
105
|
+
up:,
|
|
106
|
+
down:,
|
|
107
|
+
subagents: 0,
|
|
108
|
+
pid:,
|
|
109
|
+
message: nil,
|
|
110
|
+
running: [],
|
|
111
|
+
journal: @journal.dup.freeze)
|
|
112
|
+
end
|
|
84
113
|
|
|
85
114
|
# @param chunk [String, nil]
|
|
86
115
|
# @return [void]
|
|
@@ -137,9 +166,124 @@ module OpenLanes
|
|
|
137
166
|
@activity = said.lines.first.to_s.strip[0, Execution::Transcript::LIMIT]
|
|
138
167
|
@result = said
|
|
139
168
|
end
|
|
169
|
+
unless type.match?(/tool|command|exec/i)
|
|
170
|
+
if type.match?(PARTIAL)
|
|
171
|
+
take_partial(event)
|
|
172
|
+
else
|
|
173
|
+
take_journal(event, summary: type.match?(SUMMARY) || event.key?("messages"))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
140
176
|
@on_progress&.call(progress)
|
|
141
177
|
end
|
|
142
178
|
|
|
179
|
+
# Journal lines (M2) in what the agent itself said, in every text block
|
|
180
|
+
# of every message it finished. Not {#text_in}, which takes the first
|
|
181
|
+
# `text` anywhere in the event: that can be a tool's output, which would
|
|
182
|
+
# make a file the agent read into its memory, and it misses a line in a
|
|
183
|
+
# message's second block.
|
|
184
|
+
#
|
|
185
|
+
# A CLI read by shape delivers the same message more than once: as it
|
|
186
|
+
# closes, and again in a summary of the turn or the run. A message with
|
|
187
|
+
# an id is taken once by its id. One without is always taken as it
|
|
188
|
+
# closes, since an agent may mean `todo U1` again after it said `done
|
|
189
|
+
# U1`; a summary skips as many copies of its words as have closed, and
|
|
190
|
+
# takes the rest.
|
|
191
|
+
#
|
|
192
|
+
# @param event [Hash]
|
|
193
|
+
# @param summary [Boolean] whether the event repeats closed messages
|
|
194
|
+
# @return [void]
|
|
195
|
+
def take_journal(event, summary:)
|
|
196
|
+
seen = Hash.new(0)
|
|
197
|
+
messages_in(event).each do |message|
|
|
198
|
+
texts = texts_of(message)
|
|
199
|
+
next if texts.empty?
|
|
200
|
+
|
|
201
|
+
if (id = message["id"])
|
|
202
|
+
next if @heard.key?(id)
|
|
203
|
+
|
|
204
|
+
@heard[id] = true
|
|
205
|
+
else
|
|
206
|
+
next if summary && (seen[texts] += 1) <= @closed[texts]
|
|
207
|
+
|
|
208
|
+
@closed[texts] += 1
|
|
209
|
+
end
|
|
210
|
+
texts.flat_map { Plans::Journal.scan(it) }.each { keep(it) }
|
|
211
|
+
@open = { text: +"", taken: 0 }
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The finished lines of a message still being written, as they arrive,
|
|
216
|
+
# so an agent killed before the message closes keeps what it printed.
|
|
217
|
+
# An event carries either the message so far (pi's `message`, codex's
|
|
218
|
+
# `item`) or only the newest piece of it (a `delta`).
|
|
219
|
+
#
|
|
220
|
+
# @param event [Hash]
|
|
221
|
+
# @return [void]
|
|
222
|
+
def take_partial(event)
|
|
223
|
+
whole = messages_in(event).map { texts_of(it).join("\n") }.reject(&:empty?).last
|
|
224
|
+
whole ? @open[:text] = +whole : @open[:text] << delta_in(event).to_s
|
|
225
|
+
cut = @open[:text].rindex("\n") or return
|
|
226
|
+
|
|
227
|
+
entries = Plans::Journal.scan(@open[:text][0..cut])
|
|
228
|
+
entries.drop(@open[:taken]).each do |entry|
|
|
229
|
+
@journal << entry
|
|
230
|
+
@pending[entry] += 1
|
|
231
|
+
end
|
|
232
|
+
@open[:taken] = entries.size
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# An entry from a closing message, unless it was already taken from that
|
|
236
|
+
# message while it was being written.
|
|
237
|
+
#
|
|
238
|
+
# @param entry [OpenLanes::Plans::Journal::Entry, OpenLanes::Plans::Journal::Rejection]
|
|
239
|
+
# @return [void]
|
|
240
|
+
def keep(entry)
|
|
241
|
+
return @pending[entry] -= 1 if @pending[entry].positive?
|
|
242
|
+
|
|
243
|
+
@journal << entry
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# @param event [Hash]
|
|
247
|
+
# @return [String, nil] the newest piece of text a partial event carries
|
|
248
|
+
def delta_in(event)
|
|
249
|
+
[event["delta"], event.dig("assistantMessageEvent", "delta")].each do |delta|
|
|
250
|
+
return delta if delta.is_a?(String)
|
|
251
|
+
return delta["text"] if delta.is_a?(Hash) && delta["text"].is_a?(String)
|
|
252
|
+
end
|
|
253
|
+
nil
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# The agent's own messages an event carries: one under `item` (codex)
|
|
257
|
+
# or `message` (pi), a list under `messages`, or the event itself when
|
|
258
|
+
# it is a message with its words at the top level. A message that names
|
|
259
|
+
# a role other than the assistant's is a prompt or a tool result, and
|
|
260
|
+
# an item typed as anything but a message is a command, a file change
|
|
261
|
+
# or reasoning.
|
|
262
|
+
#
|
|
263
|
+
# @param event [Hash]
|
|
264
|
+
# @return [Array<Hash>]
|
|
265
|
+
def messages_in(event)
|
|
266
|
+
itself = event.key?("content") || event["text"].is_a?(String) ? event : nil
|
|
267
|
+
found = [event["item"], event["message"], *Array(event["messages"]), itself]
|
|
268
|
+
found.select do |message|
|
|
269
|
+
message.is_a?(Hash) &&
|
|
270
|
+
[nil, "assistant"].include?(message["role"]) &&
|
|
271
|
+
(message["type"].nil? || message["type"].to_s.match?(/message|assistant/i))
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @param message [Hash]
|
|
276
|
+
# @return [Array<String>] its text, block by block, tool calls left out
|
|
277
|
+
def texts_of(message)
|
|
278
|
+
content = message["content"]
|
|
279
|
+
blocks = if content.is_a?(Array)
|
|
280
|
+
content.select { it.is_a?(Hash) && [nil, "text", "output_text"].include?(it["type"]) }.map { it["text"] }
|
|
281
|
+
else
|
|
282
|
+
[message["text"], content]
|
|
283
|
+
end
|
|
284
|
+
blocks.select { it.is_a?(String) && !it.strip.empty? }
|
|
285
|
+
end
|
|
286
|
+
|
|
143
287
|
# Usage counts arrive per turn, so they are added rather than replaced.
|
|
144
288
|
#
|
|
145
289
|
# @param event [Hash]
|