Repository navigation
Commit d3b6ff4
feat(scenarios): run-scenarios --driver codex and --driver external:COMMAND, with the driver on each gallery app page (#31265)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XSm2B8fDTCJGABZvwCSJJvu)_
Fixes #31159
Clause-②: yes
Metadata-protocol: none
## 维护者速读
- 改了什么:场景执行器新增两种驱动。`--driver codex` 用本机 Codex CLI(`codex exec
--json`)跑卡片,只认 ChatGPT 套餐登录;`--driver external:命令` 让任何能按约定读写 JSON
的命令接进来(每次调用一个进程,stdin 进 JSON、stdout 出 JSON,约定写在包 README)。画廊每个应用页多一行 `-
Driver:`,写明驱动名和版本;回放记录写明原录制的驱动。
- 为什么改:维护者原话「前面的建议都同意,立卡」(epic cloud#3600):同一张卡要能横向比较不同 agent
工具。WorkBuddy 只做核查:没找到无头模式加结构化输出的入口,所以不接,也不做 UI 桥。
- 风险与回滚:只动 `packages/qa/scenarios`,默认驱动 `claude` 的行为不变(只是把进程启动函数挪到
`drivers/process.ts` 共用)。最大的风险是人物 API key 泄露:key 只写进 0600
文件(外部命令拿到的是文件路径;Codex 是 `$CODEX_HOME` 下的临时 profile),用完即删,从不进 argv、环境变量或
stdin;端到端测试把整张 crm 卡经外部命令跑完,再在运行记录、录制、评分卡和画廊导出里逐文件搜 key。回滚即 revert 本 PR。
- 先看:`packages/qa/scenarios/src/runner/drivers/external.ts`(外部约定与 key
文件)、`packages/qa/scenarios/src/runner/drivers/codex.ts`(登录守卫、profile
文件、JSONL
解析)、`packages/qa/scenarios/test/runner/external-driver.test.ts`(端到端 key
搜索)。
- 两分钟验证:`pnpm --filter '@objectstack/scenarios^...' build && pnpm
--filter @objectstack/scenarios exec vitest run
test/runner/external-driver.test.ts test/runner/codex-driver.test.ts`,预期
2 个文件 20/20 通过。
- 需要你做的:无,席位落地。
## What changes
- **`--driver external:COMMAND`** (`src/runner/drivers/external.ts`,
`ExternalDriver`): the `AgentDriver` seam as a process contract,
`objectstack-scenario-driver/1`.
- One process per call. The call is one JSON object on stdin, the answer
one JSON object on stdout, logs on stderr (the record keeps the last
2,000 characters). Three calls: `info` once at start (the command names
itself and its version: the record's driver is `external:NAME`, its
version the one it gives; a command that cannot answer is refused before
any card runs), `author` (`step`, `prompt`, `cwd`, `tier`, `limits`;
runs in `cwd`) and `ask` (`step`, `prompt`, `mcp: { url, configFile }`,
`limits`; runs in the config file's directory).
- The answer is strict (`ExternalTurnSchema`: `replyText`,
`toolCalls[{name, input, output, isError}]`, optional `model` and
`numTurns`; an unknown key is refused). Exit 0 with that shape is the
turn. A non-zero exit, a signal or the runner's timeout is a failed turn
`no-result(exit N)`; stdout that is not one JSON object of that shape is
`invalid-output(…)` with `isError`. A failed authoring turn leaves its
Build half not measured (`authorNotMeasured`), a failed persona turn is
graded on its empty reply. Never an empty pass.
- ⛔ The key: `ask` writes
`{"mcpServers":{"app":{"type":"http","url":…,"headers":{"Authorization":"Bearer
KEY"}}}}` (the Claude driver's `--mcp-config` shape) to a 0600 file in a
fresh temp directory, passes its PATH in `mcp.configFile`, and deletes
the directory when the call returns. The key is never on argv, in the
environment or on stdin.
- `limits` carries `timeoutMs` (`--author-timeout` for `author`, 5 min
for `ask`; the runner kills the call then) and, when given, `maxTurns`
and `model`. The command may ignore them; the record states them
(`DriverInfo`, `meta.process.maxTurns` / `requestedModel`).
- The command is split on whitespace (no shell); a word starting `./` or
`../`, and a first word holding `/`, resolves against the directory the
runner starts in. The runner removes `ANTHROPIC_API_KEY`,
`ANTHROPIC_AUTH_TOKEN`, `OPENAI_API_KEY` and `CODEX_API_KEY` from the
command's environment; it cannot see the command's login, so
`authMethod` reads `not verified (an external command: …)`.
- **`--driver codex`** (`src/runner/drivers/codex.ts`, `CodexDriver`),
built from the installed CLI (measurements below):
- Login: refuses to start while `OPENAI_API_KEY` or `CODEX_API_KEY` is
set, or when `codex login status` says anything but `Logged in using
ChatGPT` (`assertCodexSubscriptionLogin`, pure, the counterpart of
`assertSubscriptionLogin`); every turn also passes `-c
forced_login_method="chatgpt"`, and both variables are removed from
every child.
- A turn: `codex exec --json --ephemeral --skip-git-repo-check -c
forced_login_method="chatgpt" -c approval_policy="never" -p PROFILE -s …
-C DIR [-m MODEL] -`, the prompt on stdin. Authoring: `-s
workspace-write -c sandbox_workspace_write.network_access=true` in the
project (the Claude driver's Bash has the network too). Persona turns:
`-s read-only` in an empty temp directory.
- MCP and the key: a profile layer
`$CODEX_HOME/scenario-runner-HEX16.config.toml`, mode 0600, deleted in
`finally`, holding `[mcp_servers."app"]` with `url` and `http_headers =
{ Authorization = "Bearer …" }`, plus `enabled = false` for every server
of the user's own config (`codex mcp list --json`, read once at start),
the counterpart of `--strict-mcp-config`. argv carries the profile's
NAME only. A user server already named `app` is refused at start.
- `parseCodexJsonl` reads the events into the same `ParsedTurn`:
`agent_message` texts joined into `replyText`; `mcp_tool_call` → the
app's own tool name (`rawName` `mcp__SERVER__TOOL`), output the result's
text (or `structured_content`, or the error message), `isError` on
`failed`; `command_execution`, `file_change`, `web_search` kept as calls
by their item type; a call started and never completed keeps `output:
undefined`; `turn.completed` → `success`; `turn.failed` → `turn.failed:
MESSAGE` with `isError`; a top-level `error` event is a reconnect
notice, not the end.
- Flags: `--model`, `--author-model`, `--ask-model` → `-m` per turn
kind; `--author-timeout` is the runner's own clock. `codex exec` has no
turn cap, so `--author-max-turns` and `--ask-max-turns` are refused at
`makeDriver` (as the recorded driver refuses every live flag).
- **The driver on record** (Ask 4): `DriverInfo` gains `replayOf?: {
driver, version? }`, which `RecordedDriver.info()` fills from the
recording (a recording of a replay keeps the original). The gallery
exporter writes `- Driver: NAME VERSION` on every app page
(`driverText`, exported from `./export`); a replay reads
`RECORDED-DRIVER VERSION (re-graded from a recording of that run)`,
never the recording's path; an old replay record with no `replayOf` says
it does not name its driver. The record schema reads
`env.driver.version` / `replayOf` (loose, both optional).
- **Shared process runner**: `runCli` and `noResult` moved from
`driver.ts` to `src/runner/drivers/process.ts` (`runTurnProcess`, now
taking the child environment), unchanged otherwise; the tool-result text
reader moved there too (`contentText`) and `transcript.ts` uses it.
`process.ts` is not re-exported from `./runner`.
- `makeDriver`: `codex` and `external:COMMAND` (an empty command
refused); the unknown-driver message lists `claude, codex,
external:COMMAND, recorded:FILE`. `ApiBillingRefusedError` takes an
optional `{ tool, login }` (default Claude Code's, message unchanged).
- README: the Codex section and the external contract (stdin shapes for
`info`/`author`/`ask`, the stdout shape, exit codes, timeout, limits,
the key file), and the WorkBuddy finding. Changeset `patch` with both
declaration lines.
### Public surface (Clause-②: yes, additive)
Nothing removed or renamed. Added to `@objectstack/scenarios/runner`:
`CodexDriver`, `CodexDriverOptions`, `CODEX_API_BILLING_ENV`,
`CODEX_TURN_CONFIG`, `CODEX_APP_SERVER`, `codexSubscriptionOnlyEnv`,
`assertCodexSubscriptionLogin`, `codexProfileToml`, `parseCodexJsonl`,
`ExternalDriver`, `ExternalDriverOptions`, `EXTERNAL_DRIVER_CONTRACT`,
`EXTERNAL_STRIPPED_ENV`, `ExternalLimits`, `ExternalInfoInput`,
`ExternalAuthorInput`, `ExternalAskInput`, `ExternalInfoSchema`,
`ExternalToolCallSchema`, `ExternalTurnSchema`, `ExternalTurn`,
`parseExternalCommand`; the optional member `DriverInfo.replayOf`; an
optional second parameter on `ApiBillingRefusedError`. Added to
`./export`: `driverText`. CLI: `--driver codex`, `--driver
external:COMMAND`.
### Ask 2: what was measured on the Codex CLI
Installed `@openai/codex@0.167.0` (npm, `codex-cli 0.167.0`, linux-x64)
into a scratch prefix in this container. No model turn ran: no login was
used, and the container's proxy refuses OpenAI's hosts (`HTTP CONNECT
failed with status 403`), so every `exec` below stopped at the model
call.
<details><summary>Readings, each with its source</summary>
- `codex --help`, `codex exec --help` (installed binary): `exec` "Run
Codex non-interactively"; `[PROMPT]` "If not provided as an argument (or
if `-` is used), instructions are read from stdin"; `--json` "Print
events to stdout as JSONL"; `-m, --model`; `-s, --sandbox
read-only|workspace-write|danger-full-access`; `-C, --cd`;
`--skip-git-repo-check`; `--ephemeral` "Run without persisting session
files to disk"; `-p, --profile` "Layer $CODEX_HOME/NAME.config.toml on
top of the base user config"; `--ignore-user-config`; `-c key=value`;
`--strict-config`. No turn-cap flag; `max_turns` and its spellings
appear nowhere in the binary's strings.
- JSONL event shapes: `codex-rs/exec/src/exec_events.rs` at tag
`rust-v0.167.0` (raw.githubusercontent.com): `thread.started
{thread_id}`, `turn.started`, `turn.completed {usage}`, `turn.failed
{error{message}}`, `item.started|updated|completed {item{id,type,…}}`,
`error {message}`; item types `agent_message {text}`, `mcp_tool_call
{server, tool, arguments, result{content, structured_content},
error{message}, status}`, `command_execution {command,
aggregated_output, exit_code, status}`, `file_change`, `web_search`,
`todo_list`, `reasoning`, `error`. No event carries the served model.
Seen live: `thread.started`, `turn.started`, then `error` events
`Reconnecting... 2/5 (…)` while the turn continued, so a top-level
`error` is read as a notice. The docs the repository ships
(`docs/exec.md`, `docs/config.md`, `docs/authentication.md` at that tag)
only point to developers.openai.com, which this container cannot reach
(connect refused): the config reference was not read; every config key
below was measured instead.
- MCP from a profile: `$CODEX_HOME/scen.config.toml` with
`[mcp_servers.app] url = "http://127.0.0.1:39123/mcp"`, `http_headers =
{ Authorization = "Bearer FAKEKEY123" }`: `codex -p scen mcp list
--json` shows it `streamable_http` with that header; `codex exec --json
-p scen …` POSTed `initialize` to a local listener with `authorization:
Bearer FAKEKEY123` (`clientInfo.name` `codex-mcp-client`). A project
`.codex/config.toml` in the working directory was not read by `mcp list`
(untrusted directory), so the driver does not use one.
- The user's servers: with a base `config.toml` server `other` (port
39124) and the profile's `app` (39123), plain `exec -p scen` contacted
both; `--ignore-user-config -p scen` contacted neither (it drops the
profile too); `[mcp_servers.other] enabled = false` in the profile →
`mcp list` shows it `enabled: false`, and `exec` contacted `app` only.
The key appeared in no file under `$CODEX_HOME` but the profile.
- Login: `codex login --with-api-key` (a fake key) → `codex login
status` prints on stderr `API key configured - sk-fake-***-0000`, exit
0; an empty `CODEX_HOME` → `Not logged in`, exit 1, also with
`CODEX_API_KEY` or `OPENAI_API_KEY` set; the binary's strings hold
`Logged in using ChatGPT` (and `… using access token`, `… personal
access token`, `… workload identity`, `… Amazon Bedrock …`). With `-c
forced_login_method="chatgpt"`, the API-key login reads `Not logged in`,
exit 1.
- Config keys under `exec --strict-config -c …`:
`forced_login_method="chatgpt"`, `approval_policy="never"` and
`sandbox_workspace_write.network_access=true` are accepted;
`forced_login_method="bogus"` → "expected `chatgpt` or `api`",
`approval_policy="bogus"` → "expected one of `untrusted`, `on-failure`,
`on-request`, `granular`, `never`", `sandbox_workspace_write.bogus_key`
and `bogus_top` → "unknown configuration field".
- Not measured: a real `codex exec` turn end to end (no ChatGPT login
here, and live model runs are paused by the maintainer, cloud#3600), so
a `turn.completed` stream was not seen live; its shape is the source's.
The stand-in in `codex-driver.test.ts` replays those shapes.
</details>
### Ask 3: WorkBuddy
Checked on 2026-11-07; no wiring, no UI bridge.
- npm search `workbuddy`: the one package calling itself a WorkBuddy
command line, `@workbuddy/cli-vnext@1.0.153` (maintainer
`workbuddy_developer`, `developer@workbuddy.com`), read from its tarball
without running it: a REST client for a WorkBuddy tenant API
(`commander`; commands `auth`, `profiles`, `use`, `whoami`; options
`--base-url`, `--client-id`, `--output json|table|raw`), with no agent,
no prompt mode and no MCP client. Its only URL is
`https://yourcompany.workbuddy.com`; it is not Tencent's WorkBuddy.
- Tencent's WorkBuddy (CN and the international WorkBuddy AI) is a
desktop app: the community `dsh-workbuddy-provider` README (npm)
describes reusing "WorkBuddy 桌面 App" credentials on macOS, and the app's
model catalogue is fetched with "内置 CLI 版本号" (a built-in CLI version),
but no published, documented headless mode with structured output and an
MCP client was found.
- So the PR says no, per the card. If one appears, it is a wrapper on
`--driver external:` (README), not runner code.
## Tests
- **`test/runner/external-driver.test.ts`** (new, 8 cases), against
`test/runner/fixtures/fake-external-driver.mjs`, a stand-in command that
speaks the contract and reports what it saw of the key (a SHA-256, never
the key):
- `info` names the driver and version; a command exiting 4, printing
prose, or missing is refused at construction with the reason.
- `author`: the exact stdin object, the call running in the project, the
answer read into the turn (MCP prefix dropped, `isError` kept, `model`,
`numTurns`, `success`), the project files snapshotted.
- `ask`: the config file is 0600, holds the URL and the key, the call
runs in its directory, the key is on neither argv, environment nor
stdin, and the file and its directory are gone afterwards.
- Failed calls: exit 3 → `no-result(exit 3)` and not measured; prose →
`invalid-output(stdout is not one JSON object)`; another shape →
`invalid-output(…)` (is_error); a hang → killed at the timeout, `author
timed out after 0.03 min`.
- Command parsing, `external:` with no command refused, the
unknown-driver list; every flag in `limits` and on the record.
- **End to end, the key pin**: the real `crm_account_with_contacts` card
through `runCard` (both modes), each scripted turn sent through the
stand-in command (the exporter fixture's in-memory app; `runCrm` gains
`keyPrefix` and `wrap`), every persona and the admin issued a key
`sk-scenario-e2e-key-9f27c1d4` + the persona id. Every ask read one of
those keys from a 0600 file and from nowhere else, and each file is
gone. Then the run record (where the CLI writes it), the recording, the
scorecard (`renderScorecard`) and the full gallery export
(`exportGallery`, which exported the card) are written, and every
written file is searched for the key prefix: none holds it. The record
names `external:fake-agent 1.2.3`, the app page reads `- Driver:
external:fake-agent 1.2.3`; a replay of that recording names `replayOf`
and its page reads `(re-graded from a recording of that run)` without
the recording's path.
- **`test/runner/codex-driver.test.ts`** (new, 12 cases), against a
stand-in `codex` script (it answers `--version`, `login status` on
stderr, `mcp list --json`, and for `exec` reads the profile it is
pointed at):
- `parseCodexJsonl` on the 0.167.0 shapes (pairing, failed MCP call,
command execution, a call cut short, torn last line, notice not an end,
`turn.failed`).
- The cost guard: `OPENAI_API_KEY` / `CODEX_API_KEY` set, an API-key
login, no login, an empty status, a non-plan login (its masked
credential not echoed) refused; the billing variables stripped.
- `codexProfileToml` exact text (TOML-escaped key and names).
- The driver: version and login on record; refused at construction for
an API-key login, no login and `OPENAI_API_KEY`; an ask turn's argv
(`exec --json --ephemeral --skip-git-repo-check`, the two `-c`
overrides, `-s read-only`, `-`), a 190 KB prompt on stdin, the profile
0600 with the key, the key on neither argv nor environment, the profile
file gone and `$CODEX_HOME` empty afterwards, no billing variable in the
child; an author turn's `workspace-write`, `-C`, the network override
and the user's server turned off; a user server named `app` refused;
timeout, exit-without-result and `turn.failed` each not measured;
`--model` / `--author-model` / `--ask-model` → `-m`; both turn-cap flags
refused.
<details><summary>Ablations (each restored: blob == HEAD, git diff HEAD
empty)</summary>
Both through `scripts/ablation-replace.mjs` (anchor x1 → x0, replacement
x0 → x1) on commit `fb1d76f7`. The suites import `src/` directly, so no
dist leg.
- **Ablation 1, the key written into the record** (`external.ts` `ask`:
`return await this.call(input, dir);` → the turn's `meta.model` set to
`task.mcp.apiKey`): the end-to-end case → `1 failed | 7 skipped`,
`expected [ …(7) ] to deeply equal []`, naming the run record,
`recording.json`, `scorecard.md`, and the gallery's `README.md`,
`run-record.json`, `scorecard.json`, `scorecard.md`.
- **Ablation 2, an API-key Codex login accepted** (`codex.ts`: the
ChatGPT test widened to `/Logged in using ChatGPT|API key configured/`):
`codex-driver.test.ts -t "cost guard|refuses an API-key login"` → `2
failed | 2 passed`, both "expected function to throw an error, but it
didn't".
- Restore leg: both files → `Test Files 2 passed (2)`, `Tests 20 passed
(20)`.
</details>
## Acceptance notes
- **The external driver's cost claim.** `DriverInfo.apiBilled` is typed
`false`, and the runner cannot see an external command's login, so the
record says `authMethod: not verified (…)` and the README states the
cost ruling applies to the command; the four billing variables are
stripped from its environment. Whether an external command must also be
refused when one of them is set in the caller's environment (as the two
built-in drivers are) was not in the rulings; this PR strips and does
not refuse.
- **No served model for Codex.** The JSONL names none, so a Codex turn's
`model` is absent and the scorecard reads `model not reported (asked for
…)` rather than guessing; `modelMismatch` is never set for it.
- **Codex writes into `$CODEX_HOME`.** The profile layer is the only
key-in-a-file route the CLI was measured to read (`-p`); the file is
0600, uniquely named, and removed in `finally`. A run killed by SIGKILL
could leave one behind; it holds a persona key of a local dev app that
is torn down with the run.
- **The external stand-in's e2e uses the fixture's in-memory app.** The
command answers the turn the scripted model gave; the double applies the
turn's writes to the in-memory app in-process. Every turn's bytes still
cross the process boundary, and the key reaches the command only through
the file.
- `codex mcp list --json` prints the user's other servers' headers to
the runner's memory once at start; only `name` is read and nothing is
logged.
- Out of scope, untouched: any live model run (paused, cloud#3600), a
WorkBuddy or other UI bridge, `orchestrate.ts`, the gallery `index.json`
schema (the driver is on the app page, as Ask 4 says; `run-record.json`
already carries `env.driver`).
## Verification
All on head `fb1d76f7` (base `origin/main` `169ff7e5`, which carries
#31221 as `fba4c00a` and #31204 as `0d0c377b`), worktree
`objectstack-issue-31159`.
- `pnpm --filter '@objectstack/scenarios^...' build` (under the verify
lock) → `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/scenarios build && pnpm --filter
@objectstack/scenarios test` → `Test Files 30 passed (30)`, `Tests 573
passed (573)`, `VERDICT command-exit 0`.
- `pnpm --filter @objectstack/scenarios typecheck` (`tsc --noEmit`,
tests included) → exit 0.
- `node --stack-size=4000 node_modules/eslint/bin/eslint.js
packages/qa/scenarios --no-inline-config` → exit 0 (the package has no
`lint` script).
- The built entries load: `require('dist/runner.cjs')` (141 keys;
`ExternalDriver`, `CodexDriver` functions), `dist/export.cjs`
(`driverText`), `dist/index.cjs`, `dist/cli.cjs`, and
`import('dist/runner.js')` (`EXTERNAL_DRIVER_CONTRACT` =
`objectstack-scenario-driver/1`).
- `GITHUB_EVENT_NAME=pull_request GITHUB_EVENT_PATH=EVENT_WITH_THIS_BODY
node scripts/check-changeset-no-major.mjs --base origin/main` → exit 0
(`Clause-②: yes`, `Metadata-protocol: none`, `patch`).
<details><summary>Derived gates: 60 families, 58 run green, 2 NOT
MEASURED, then 1 re-measured</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (tree at `fb1d76f7`) → 60 commands, each run
with its exit captured before any pipe; `--ran` → "60 derived famil(ies)
accounted for — 58 run, 2 NOT-MEASURED".
- `node scripts/check-plugin-teardown-shape.mjs --self-test` → exit 3
(shallow clone, control commit missing). After `git fetch --depth=1
origin 621a487…`, rerun → exit 0, "48 cases pass".
- `pnpm check:dual-build-cjs-loads` → exit 3, PREREQUISITE NOT MET
(needs every package's `dist/`; this worktree built the scenarios
closure only). NOT MEASURED; the package's own CJS and ESM entries were
loaded directly instead (above). CI runs it.
- Beyond the derivation: `pnpm --filter @objectstack/spec run
check:error-code-provenance` → exit 0 (this diff adds no error-code
literal).
- Every other family exit 0, among them
`check:cross-package-test-inputs`, `check:test-source-alias`,
`check:dts-closure`, `check:published-files`,
`check:undeclared-dep-imports`, `check:type-check-debt`,
`check:type-check-coverage`, `check:nul-bytes`,
`check-changeset-no-major`, `check-empty-changeset`,
`check-adr-0087-registration`.
- The full record: `pm-orders/devx/31159-gates-ran.txt` (project files).
</details>
- `test:repo`, the dogfood battery and the full CI farm are CI's (no `!`
in the title, no `(narrowing)` arm).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5mx64pxVGCbKfuxEwi)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent ea62933 commit d3b6ff4
15 files changed
Lines changed: 1496 additions & 112 deletions
File tree
- .changeset
- packages/qa/scenarios
- src
- export
- runner
- drivers
- test
- export
- runner
- fixtures
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
138 | 138 | | |
139 | 139 | | |
140 | 140 | | |
| 141 | + | |
| 142 | + | |
141 | 143 | | |
142 | 144 | | |
143 | 145 | | |
| |||
196 | 198 | | |
197 | 199 | | |
198 | 200 | | |
199 | | - | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
200 | 204 | | |
201 | 205 | | |
202 | 206 | | |
| |||
255 | 259 | | |
256 | 260 | | |
257 | 261 | | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
128 | 141 | | |
129 | 142 | | |
130 | 143 | | |
| |||
135 | 148 | | |
136 | 149 | | |
137 | 150 | | |
| 151 | + | |
138 | 152 | | |
139 | 153 | | |
140 | 154 | | |
| |||
324 | 338 | | |
325 | 339 | | |
326 | 340 | | |
327 | | - | |
| 341 | + | |
328 | 342 | | |
329 | 343 | | |
330 | 344 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
60 | 65 | | |
61 | 66 | | |
62 | 67 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
14 | 21 | | |
15 | 22 | | |
16 | 23 | | |
| |||
56 | 63 | | |
57 | 64 | | |
58 | 65 | | |
| 66 | + | |
| 67 | + | |
59 | 68 | | |
60 | 69 | | |
61 | 70 | | |
| |||
189 | 198 | | |
190 | 199 | | |
191 | 200 | | |
192 | | - | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
193 | 224 | | |
194 | 225 | | |
195 | 226 | | |
| |||
0 commit comments