Commit 8d4ed80
Harness runtime replacement (#652)
* Add harness session runtime for training and evaluation
* Handle MCP tool errors and lazy-load BrowserGym exports
* Refine experimental harness APIs and reward forwarding
* docs: mention environment_factory in harness tutorial
* test: cover harness review regressions
* feat(harness): rollout collection + openenv collect CLI (#560)
* refactor(harness): move harness.py into harness/ package
Preserves the public API (`from openenv.core.harness import X` keeps
working) while making room for a ``collect`` submodule that layers
synthetic-dataset generation on top of the runtime primitives from
RFC 005 / PR #471. Relative imports in the module are adjusted from
``.client_types`` → ``..client_types``.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(harness): add rollout collection module
Introduces ``openenv.core.harness.collect``, a thin layer on top of the
harness runtime from RFC 005 / PR #471 for generating synthetic datasets
from deployed environments:
- ``EpisodeRecord`` — serializable view of one rollout + its verification.
Uses ``_resolve_env_reward`` so any mismatch between a tool-result
reward and ``verify.env_reward`` raises, preserving the "rewards in
env" invariant end-to-end.
- ``RolloutSerializer`` — append-only JSONL writer with a
``metadata.json`` sidecar. ``collected_episode_ids()`` enables resume.
- ``CollectRunner.run()`` — orchestrates N episodes: session.create →
harness.run_white_box → verify → optional rubric filter → serialize.
Returns a ``CollectResult`` with aggregate stats.
- ``build_model_step(llm_client)`` — adapts any ``LLMClient`` (OpenAI,
Anthropic, and any OpenAI-compatible endpoint such as vLLM, TGI,
Ollama, HF Inference, Together, Groq, Fireworks) into a ``ModelStep``
for the white-box harness.
- ``push_to_hf_hub(output_dir, repo_id)`` — uploads ``results.jsonl``,
``metadata.json``, and an auto-generated dataset card to the Hub. The
card's YAML front-matter tells the HF Dataset Viewer to treat the
JSONL as ``split=train`` instead of trying to merge it with the
metadata sidecar.
37 new tests (EpisodeRecord, RolloutSerializer, CollectRunner,
build_model_step, push_to_hf_hub, build_dataset_readme).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(envs/openspiel): add TTT harness session factory
``envs/openspiel_env/harness.py`` exposes an ``OpenSpielSessionFactory``
that wraps ``OpenSpielEnv`` (via ``StepEnvSessionAdapter``) and lets a
harness drive any OpenSpiel game — tic_tac_toe initially — through a
single ``play_move(action_id)`` MCP-style tool.
- Initial prompt renders legal actions plus a human-readable board for
TTT so the LLM can reason about positions without needing to decode
the 27-float info_state tensor.
- ``render_tic_tac_toe_board`` decodes the OpenSpiel TTT info_state
(empty/X/O planes) into a 3x3 grid. Empty cells show their action_id
so the prompt doubles as an action legend.
- Follows the pattern established by ``envs/browsergym_env/harness.py``
in PR #471 — no changes to the underlying env, client, or protocol.
Tests cover the board rendering, tool dispatch, reset-kwargs forwarding,
and an end-to-end collect run against a scripted client exercising the
full ``MCPHarnessAdapter`` → ``CollectRunner`` → JSONL pipeline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add openenv collect command
Wraps ``CollectRunner`` + ``OpenSpielSessionFactory`` into a single
command so a deployed OpenEnv environment can produce a dataset with
one call:
openenv collect openspiel:tic_tac_toe \\
--base-url https://user-space.hf.space \\
--output-dir /tmp/ttt-sft-v1 \\
-n 200 --provider openai --model gpt-5-mini \\
--push-to-hub user/ttt-sft-v1
Flags in short:
- ``--provider scripted | openai | anthropic`` — teacher selection.
Scripted picks the first legal action and requires no API key,
making ``openenv collect`` smoke-testable out of the box.
- ``--llm-endpoint / --llm-port`` — point at any OpenAI-compatible
endpoint (vLLM, TGI, Ollama, HF Inference, Together, Groq, ...).
- ``--push-to-hub REPO`` — upload the directory as a dataset after
collect; ``--private``/``--commit-message`` available.
- ``--resume / --no-resume`` — ``CollectRunner`` skips ``episode_ids``
already serialized on disk.
- ``--keep-losses`` — by default filters rollouts with reward < 0 so
the output is SFT-ready.
Env dispatch is via ``"family:variant"`` strings (e.g.
``openspiel:tic_tac_toe``). Unknown families raise a typer
``BadParameter`` with the supported set.
7 new CLI tests mocking ``CollectRunner`` + ``OpenSpielEnv`` so the
dispatch logic (scripted vs hosted provider, push-to-hub, filter
defaults, bad ``--env``) is exercised without network.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(harness): add README and runnable examples
- ``src/openenv/core/harness/README.md`` documents the collect module:
quick-start (CLI + programmatic), output schema, and design notes
covering the "thin envs" / "rewards in env" / "provider-agnostic
teacher" choices.
- ``examples/ttt_collect_demo.py`` — scripted teacher against either a
built-in fake OpenSpiel client or a real deployed server (``--base-url``).
Runs with zero setup for pipeline smoke-testing.
- ``examples/ttt_collect_with_llm.py`` — provider-agnostic example that
picks between hosted OpenAI/Anthropic (via ``--provider``) and any
OpenAI-compatible self-hosted endpoint (via ``--llm-endpoint``), using
the same collect pipeline unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address collect feedback
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: burtenshaw <ben.burtenshaw@gmail.com>
* fix: address browsergym harness review comments
* fix: format collect files
* envs/opencode_env: OpenCode coding-agent harness primitive (#603)
* feat(opencode_env): OpenCode harness primitive (M1.1-M1.5)
Harness primitive for running the OpenCode CLI agent in a sandbox against
any OpenAI-compatible endpoint. Stacked on the PR #471 harness-runtime
branch; implements the ResourceSession / ResourceSessionFactory contracts.
Modes:
- black_box: opencode talks directly to the configured endpoint. Verified
end-to-end against real OpenAI (gpt-4o-mini) inside an E2B sandbox; the
agent produces a correct fizzbuzz and the verifier scores reward=1.0.
- transparent_proxy: a per-session FastAPI proxy runs inside the sandbox,
forwards /v1/chat/completions to the configured upstream with
logprobs=true injected, captures per-turn (messages, completion tokens,
logprobs, finish reason) to a JSON-lines trace, and strips logprobs
from what opencode sees. Handles both unary and streaming (SSE). Caps
max_tokens and auto-translates to max_completion_tokens for gpt-5.x/o*
models.
Components:
- config.py: OpenCodeConfig (generic provider/base_url/api_key/model
fields supporting OpenAI, Anthropic, and OpenAI-compatible endpoints;
proxy tuning knobs; sandbox_home override for non-E2B backends).
- opencode_runtime.py: pure builders for opencode.json, install/run
shell commands, and env vars.
- task.py: OpenCodeTask pydantic model (instruction + optional
setup_shell + file uploads + opaque metadata); coerces from str/dict.
- sandbox/base.py: SandboxBackend / SandboxHandle / BgJob Protocols.
- sandbox/e2b.py: E2BSandboxBackend with a threaded BgJob wrapper that
provides timeout support over E2B's CommandHandle.
- harness.py: OpenCodeSession + OpenCodeSessionFactory; in Mode B,
installs proxy deps, uploads the proxy module, starts it as a bg job
on localhost:7000, rewrites opencode.json to point at the proxy, and
forces @ai-sdk/openai-compatible so routing goes through
/v1/chat/completions.
- interception.py: InterceptionProxy (FastAPI, unary + streaming),
per-turn trace capture, CLI entry point for sandbox-side execution.
Tests: 37 unit tests plus 3 live integration tests gated on
E2B_API_KEY / OPENAI_API_KEY.
Known limitation: OpenAI's gpt-5.x chat family refuses logprob requests,
so Mode B live validation against OpenAI requires gpt-4o-mini or older.
vLLM (the intended training-time upstream) returns logprobs natively.
* feat(opencode_env): live vLLM validation + post-rollout summary
End-to-end Mode B now verified against a live vLLM tunnel:
- Qwen/Qwen3.5-4B on 2x A100 (tp=2, 16K ctx) via `vllm serve`
- Cloudflared tunnel exposes the endpoint publicly
- E2B sandbox runs opencode against the tunnel through the in-sandbox proxy
- Proxy captures 4 turns, 36 tokens with real per-token logprobs
- Agent produces correct fizzbuzz.py, verifier scores reward=1.0
- 55.86s total (sandbox + install + 4 LLM turns + verify)
Changes:
- config: add proxy_disable_thinking flag; plumbed through harness -> proxy
via --disable-thinking CLI arg.
- interception: inject chat_template_kwargs.enable_thinking=false on
forwarded requests when enabled (Qwen3/Qwen3.5 tokenizer hook); also
split the request handler into unary + streaming paths (SSE) and
auto-translate max_tokens -> max_completion_tokens for gpt-5.x/o*
models.
- sandbox/e2b: E2B's commands.run(background=True) has a default
server-side timeout=60 that kills long-running opencode bg jobs;
pass timeout=0 to disable it.
- live_watch: RolloutSummary / collect_rollout_summary /
print_rollout_summary — post-rollout structured report reading proxy
trace, opencode event log, and the workdir listing + file contents.
- tests/test_harness_live_vllm: end-to-end Mode B test against a live
vLLM tunnel (gated on VLLM_TUNNEL_URL + E2B_API_KEY), asserts
logprobs are captured with shape matching completion tokens.
* docs(opencode_env): README with Mode A + Mode B quickstarts and full config
* feat(opencode_env): retries, error surfacing, model override
Fixes the server-mode rollout path. Under load E2B can return sandboxes
that aren't yet accepting commands, curl-install can transiently fail,
and opencode's internal title-generation call can emit a stripped model
id (e.g. ``Qwen3.5-4B`` instead of ``Qwen/Qwen3.5-4B``) that vLLM
rejects with 404. Previously the proxy silently swallowed upstream error
bodies and returned an empty event-stream, which opencode interpreted as
an empty assistant turn.
Changes:
- harness: ``_wait_for_sandbox_ready`` probes ``echo ok`` up to 15x
before issuing commands.
- harness: ``_exec_with_retry`` wraps install / pip-deps / extra-setup
with exponential backoff, up to 3 attempts, bailing on deterministic
errors (non-empty stderr).
- interception: ``ProxyConfig.model_override`` rewrites the ``model``
field on every forwarded request to the exact upstream id, bypassing
opencode's provider-prefix quirks. Plumbed through as
``--model-override`` on the CLI.
- interception: ``_proxy_streaming`` now inspects upstream status before
committing to an SSE response — non-2xx returns a JSON error response
to opencode AND logs the full upstream body to proxy.log, so the
caller sees the real failure reason.
- harness: ``_start_proxy`` passes ``--model-override`` built from
``config.model`` so the upstream always sees the right id.
- harness: proxy deps install now uses ``_exec_with_retry`` too.
Verified via local uvicorn: fizzbuzz + fibonacci tasks both succeed
end-to-end through the server path in 19-20s with reward=1.0, 3-4
productive turns, and real per-token logprobs captured on every turn.
* feat(opencode_env): add serve driver + opencode_client (Phase 2b primitive)
Adds the infrastructure for driving opencode via its HTTP server instead
of the CLI. This is the Phase 2b foundation — fine-grained MCP tools in
the consumer server wrap these primitives.
Changes:
- opencode_client.OpenCodeServerClient: typed httpx wrapper over the
OpenAPI spec at /doc. Sync and async methods for create_session,
send_message / send_prompt_async, list_messages, get_session,
get_all_status, abort, plus stream_events / astream_events (SSE) and
a wait_for_ready helper. Base64 basic-auth when
OPENCODE_SERVER_PASSWORD is set.
- harness.OpenCodeSession: new ``driver: Literal["cli", "serve"]`` field.
driver="cli" is today's `opencode run` path. driver="serve" stores
serve_public_url + serve_client + serve_session_id on the session.
start_agent() dispatches on driver; wait_for_completion() polls
/session/:id for idle when driver="serve". New abort() method hits
/session/:id/abort for cancellation.
- harness.OpenCodeSessionFactory: new ``driver`` constructor arg plumbs
through to create(). _start_serve() runs opencode serve bound to
0.0.0.0:4096 as a bg job, probes it internally via curl, then uses
the sandbox backend's get_host(4096) to build a public URL (E2B
returns https://4096-<sandbox_id>.e2b.app). Fails fast if the backend
doesn't support get_host.
- tests/test_opencode_client.py: 7 unit tests covering URL/method/body
shape, auth header, prompt text extraction, abort bool, limit param,
wait_for_ready polling, SSE event helpers. Uses httpx MockTransport
patched via monkeypatch — no live opencode serve needed.
- tests/test_harness.py: _FakeSandbox now responds to the health-probe
"echo ok" command so the existing factory tests work after the
Phase-1 reliability layer landed.
Verified: 34 unit tests pass (7 new + 27 existing), driver=cli path
unchanged. End-to-end E2B spike confirms:
sandbox_id assigned in 0.4s
opencode install 2.5s
opencode serve --port 4096 --hostname 0.0.0.0 listening in 1s
sandbox.get_host(4096) returns https://4096-<id>.e2b.app
external /doc returns HTTP 200 with OpenAPI spec
external POST /session returns real session metadata
Next: wire 4 new MCP tools (start_rollout / get_state / abort_rollout /
finalize_rollout) in the consumer env + SSE endpoint for live rollout
events. Ship to HF Space.
* fix(opencode_env): bump proxy-start wait from 10s to 60s
Uvicorn+fastapi cold boot inside E2B can take >10s under load; the tight
probe loop was producing false 'proxy did not start within 10s' errors.
60s cap at 0.5s intervals keeps the retry fast while tolerating the slow
path.
* fix(opencode_env): cwd into workdir, fix idle detection, preserve reasoning
Three audit fixes surfaced by end-to-end testing through the deployed
env server:
1. `_start_serve` now `cd`s into workdir_path(config) before launching
opencode serve. Without this, the agent writes files to $HOME and
RolloutResult.workdir_files (reading /home/user/workdir) comes back
empty — the "rollout succeeded but nothing appeared" symptom.
2. `wait_for_completion` idle check was `status.get("idle")` but
opencode's /session/status returns `{"type":"idle"}`, not
`{"idle":true}`. Every serve-driver rollout silently timed out at
agent_timeout_s. Now checks `status.get("type") == "idle"` and adds
structured logging on every tick.
3. Interception proxy now preserves `delta.reasoning` on streaming
chunks and surfaces it as `message.reasoning` on the assembled
response. HF Router's Qwen3.5 thinking mode returns reasoning as a
separate field from content; previously it was dropped.
4. `upstream_model` no longer strips the Qwen/ org prefix — full
`config.model` is forwarded as the model-override so both vLLM
(served as `Qwen/Qwen3.5-4B`) and HF Router (requires
`Qwen/<repo>:<provider>`) work.
5. Structured logging at every factory.create phase so operators can
see exactly which step is stuck (sandbox, bootstrap, proxy, serve,
wait_for_completion).
* test(opencode_env): drop live integration tests requiring external secrets
The four live tests (OpenAI / vLLM / mode-B / E2B) required
OPENAI_API_KEY, VLLM_URL, or E2B_API_KEY to execute and were
development-time fixtures rather than CI checks. The core functionality
is already covered offline by test_harness.py (end-to-end factory
lifecycle against a mock sandbox + mock OpenAI endpoint),
test_interception.py (proxy forward + per-turn record assembly),
test_opencode_client.py (serve client over httpx mocks), and
test_sandbox_base.py (E2BSandboxBackend key-required unit).
* feat(opencode_env): deployable env with HF Space, Gradio UI, and 3-endpoint catalog
Wraps the existing OpenCode harness primitive in a deployable OpenEnv
environment that can run as an HF Space, exposing a single MCP
``run_rollout`` tool plus a Gradio web UI at /web.
Highlights
----------
- Single MCP tool ``run_rollout`` accepting a uniform Task shape
(instruction + setup[] + verify[] bash commands), reward = passed_verify
/ total or override via /home/user/logs/verifier/reward.txt.
- Endpoint shorthand catalog (``vllm`` / ``openai`` / ``hf_router``) that
resolves base_url / api_key / model from env vars + sane defaults.
- In-sandbox FastAPI proxy (``transparent_proxy`` mode) injects
logprobs=true and captures per-token logprobs for GRPO training.
- Optional ``black_box`` mode skips the proxy for SFT / eval rollouts.
- Pre-baked E2B template (``opencode-rl``) drops sandbox cold start
from ~2min to ~6s by shipping opencode + proxy deps in the image.
- Streaming Gradio UI: /run handler is a generator that yields a live
phase log (sandbox boot → setup → agent → verify → collect) so the
user sees progress instead of a spinner.
- HF Space deployed at AdithyaSK/opencode-env, end-to-end verified
against vLLM, OpenAI, and HF Router (all 3 reward=1.0 on the
binary_search smoke task).
Layout
------
envs/opencode_env/
{client.py, models.py, __init__.py} # HTTP client + pydantic
{config.py, harness.py, opencode_runtime.py,
task.py} # primitive (CLI-only)
server/{app.py, opencode_environment.py,
gradio_ui.py, catalog.py, Dockerfile} # FastAPI + Gradio + MCP
sandbox/{base.py, e2b.py, interception.py,
build_template.py} # E2B + proxy + template
{pyproject.toml, openenv.yaml, uv.lock,
README.md, .dockerignore, .gitignore}
Removed (CLI-only refactor)
---------------------------
- harness.py: dropped the ``opencode serve`` driver path (~270 LOC).
- Deleted opencode_client.py, live_watch.py, env-local tests/.
CI / tests
----------
- New tests/envs/test_opencode_env.py: 14 unit tests (no E2B, no LLM,
no network) covering catalog resolution, model serialization, and
task coercion. Plus one @pytest.mark.integration test that runs
opencode end-to-end against the deployed Space (skipped by default).
- sandbox/__init__.py: e2b import wrapped in try/except so the package
loads cleanly without e2b installed (CI-friendly).
- Added opencode-env to .github/workflows/docker-build.yml matrix so
the image is built and pushed to GHCR alongside other envs.
openenv-core dependency
-----------------------
Currently pinned to the ``opencode-harness`` branch via git because
PyPI's ``openenv-core`` (0.2.x) does not yet ship the
``openenv.core.harness`` module that this env imports. Switch to
``openenv-core[core]>=0.2.2`` once RFC 5 / PR #471 ships in a
published release. The intended end-state is documented inline in
pyproject.toml.
* docs(opencode_env): add examples/opencode_env_simple.py
Minimal end-to-end example: hits the deployed HF Space, runs a
binary_search rollout via the MCP run_rollout tool, prints the
reward + per-turn logprobs + the file the agent produced.
Mirrors the per-env convention in ``examples/`` (echo_mcp_demo.py /
coding_env_inference.py / atari_simple.py etc.). Defaults point at
``https://adithyask-opencode-env.hf.space``; override with
``OPENCODE_ENV_SPACE`` to target a different Space or local container.
Requires ``OPENAI_API_KEY`` in the environment (passed in the
request body, no Space secret required). Swap ``endpoint="openai"``
for ``"vllm"`` or ``"hf_router"`` to exercise the other backends.
* fix: secure opencode proxy command
---------
Co-authored-by: burtenshaw <ben.burtenshaw@gmail.com>
* fix: address ci docs and formatting
* feat(tutorials): add SFT warm-up tutorial with reasoning_gym collect support (#636)
* feat(collect): add reasoning_gym support + --dataset-config + --system-prompt
Extends `openenv collect` to support reasoning_gym environments:
- Register ReasoningGymSessionFactory in _build_session_factory
- Add --dataset-config option (JSON string) for env-specific config
- Add --system-prompt option to override the default system prompt
- Add ReasoningGymSessionFactory in envs/reasoning_gym_env/harness.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(harness): generate random seed per episode when none provided
reasoning_gym server requires seed when dataset_name is specified.
CollectRunner never passes a seed, so generate one per episode to
ensure variety across collected rollouts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(llm_client): use max_completion_tokens for OpenAI client
Newer OpenAI models (gpt-5-mini, o1, o3) reject max_tokens and require
max_completion_tokens instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(llm_client): use max_completion_tokens only for OpenAI API, not self-hosted
Newer OpenAI models require max_completion_tokens; self-hosted OpenAI-compatible
endpoints (vLLM, Ollama, TGI) only support max_tokens. Add use_max_completion_tokens
flag to OpenAIClient, enabled automatically by create_llm_client for the openai
provider and left off for self-hosted endpoints via --llm-endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(llm_client): omit temperature for OpenAI API newer models
gpt-5-mini and other newer OpenAI models only accept the default
temperature (1) and reject any explicit value. Omit temperature
entirely when use_max_completion_tokens is set; self-hosted endpoints
are unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(collect): add rich progress bar to CollectRunner
Shows episode count, collected count, running avg reward, and elapsed
time during collection — previously the loop ran silently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(tutorials): add SFT warm-up tutorial with end-to-end validated notebook
Adds docs/source/tutorials/sft-warmup.md and examples/sft_warmup.ipynb.
Tutorial collects rollouts via CollectRunner Python API, pushes to Hub,
filters by reward, and fine-tunes Qwen3-1.7B with SFTTrainer. Validated
end-to-end: 0% → 64% format compliance, 4% → 60% accuracy on chain_sum.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(tutorials,collect): address PR review findings
- Scope use_max_completion_tokens to models that require it (gpt-5-mini,
o1, o3, o4-mini) — was incorrectly applied to all OpenAI models,
breaking gpt-4o and other standard models
- Remove duplicate YOUR_HF_USERNAME re-declaration in tutorial section 10
- Add missing asyncio import in tutorial section 10 code block
- Fix prose: max_seq_length → max_length (correct SFTConfig field name)
- Fix usort import order in collect.py and harness/collect.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(collect,tutorials): address Greptile review findings
- Move _MAX_COMPLETION_TOKENS_PREFIXES to module level as frozenset
- Use prefix matching for versioned model names (o1-2024-12-17, etc.)
- Replace asyncio.run() with await in tutorial section 10 (Jupyter compat)
- Use json.dumps for tool_call text in to_qwen3_messages (safe escaping)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: burtenshaw <ben.burtenshaw@gmail.com>
* fix: address harness review feedback
* fix: format harness lint
* fix: address harness review comments
---------
Co-authored-by: Sergio Paniego Blanco <sergiopaniegoblanco@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Adithya S K <adithyaskolavi@gmail.com>1 parent 5237225 commit 8d4ed80
30 files changed
Lines changed: 7452 additions & 10 deletions
File tree
- docs/source/tutorials
- envs
- browsergym_env
- openspiel_env
- reasoning_gym_env
- examples
- src/openenv
- cli
- commands
- core
- harness
- tests
- core
- envs
- scripts
- test_cli
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
39 | 40 | | |
40 | 41 | | |
41 | 42 | | |
0 commit comments