From 403b62abf828a46dc54384d8c4d5d2ba70af6315 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:44:08 -0400 Subject: [PATCH 01/47] C0: import slack-coding-agent scaffold Bring in the standalone TypeScript scaffold (three typed seams + skeleton router + CLI + Biome/tsconfig/CI) as the project baseline. `npm run check` and `npm run build` are green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/audit.yml | 16 + .github/workflows/ci.yml | 19 + .gitignore | 8 + README.md | 57 ++- biome.json | 39 ++ package-lock.json | 737 ++++++++++++++++++++++++++++++++++++ package.json | 30 ++ src/backend/index.ts | 32 ++ src/cli/index.ts | 56 +++ src/platform/index.ts | 25 ++ src/router/index.ts | 68 ++++ src/sandbox/index.ts | 36 ++ src/types.ts | 23 ++ tsconfig.build.json | 7 + tsconfig.json | 32 ++ 15 files changed, 1184 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/audit.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 biome.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/backend/index.ts create mode 100644 src/cli/index.ts create mode 100644 src/platform/index.ts create mode 100644 src/router/index.ts create mode 100644 src/sandbox/index.ts create mode 100644 src/types.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..9d5857b --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,16 @@ +name: Audit + +on: + schedule: + - cron: "0 6 * * 1" # weekly, Mondays 06:00 UTC + workflow_dispatch: + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm audit --omit=dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4258c8c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci --ignore-scripts + - run: npm run check + - run: npm run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ffb14d --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +*.log +.env +.env.* +!.env.example +.DS_Store +coverage/ diff --git a/README.md b/README.md index 7b5d644..0302bde 100644 --- a/README.md +++ b/README.md @@ -1 +1,56 @@ -# mishu +# Slack Coding Agent — starter + +A **standalone** TypeScript scaffold for the router in the design spec: mention the bot in a +Slack thread → it works in an isolated sandbox → it reports back in-thread. Independent project; +conventions are **learned from pi-mom, but no code is shared or imported** (spec §10). + +## Prerequisites + +- Node **≥ 22** (the project is ESM / `NodeNext`). + +## Commands (all verified on Node 22) + +| Command | What it does | +| --- | --- | +| `npm install` | Install the dev toolchain. | +| `npm run dev` | Run the CLI under `tsx watch` (pass args: `npm run dev -- --sandbox=sbx ./data`). | +| `npm run build` | Type-check and emit `dist/` (`tsc -p tsconfig.build.json`). | +| `npm start` | Run the built CLI (`node dist/cli/index.js`). | +| `npm run check` | **CI verify:** Biome (lint + format + import-organize) + `tsc --noEmit`. | +| `npm run check:fix` | Apply Biome fixes, then type-check. | +| `npm run lint` / `format` / `typecheck` | The individual steps. | + +## Layout (by seam — spec §3) + +``` +src/ + platform/ PlatformAdapter — Slack Socket Mode ingress/egress + sandbox/ SandboxProvider — sbx microVM lifecycle (exec = the logged transport) + backend/ CodingBackend — Codex / Claude Code / Pi (turnArgs + parseResult) + router/ Router — dedupe, idle/running/pending, dispatch, boundary logging + cli/ entrypoint — `--sandbox= ` (pi-mom-style) + types.ts shared types +.github/workflows/ ci.yml (check + build), audit.yml (weekly npm audit) +biome.json single lint + format config (Biome 2.x) +tsconfig.json base TS config (strict, ESM/NodeNext) +tsconfig.build.json build config (emits dist/, excludes tests) +``` + +## Conventions (re-implemented from pi-mom's — spec §10) + +- **TypeScript**, `strict`, **ESM (`NodeNext`)** — relative imports use `.js` extensions (e.g. `import { Router } from "./router/index.js"`). +- **Biome** is the single lint + format + import-organize tool. `$schema` points at the **local** `node_modules` schema so it always matches the installed Biome version (avoids the "schema does not match CLI version" error). +- **tsconfig pair:** `tsconfig.json` (base — editor + `tsc --noEmit`) and `tsconfig.build.json` (emits `dist/`, excludes tests). +- **CI:** GitHub Actions on push/PR → `npm ci --ignore-scripts` → `check` → `build`; plus a weekly `npm audit --omit=dev`. + +## Implementation checklist (next steps) + +The seams are typed interfaces with skeleton wiring (`Router` shows the dispatch shape). Fill them in roughly this order — section refs are to the design spec: + +1. **PlatformAdapter** (`src/platform`) — Slack Socket Mode. **ACK the envelope immediately (<3s), run the handler async**; user-facing acks are reactions, not replies (§3, §4.1). Add `@slack/socket-mode` + `@slack/web-api`. +2. **SandboxProvider** (`src/sandbox`) — `sbx` microVM: `create` (`--clone`), `exec` (**wrap in `bash -c`**), `getFile`/`putFile`, `stop`/`destroy`, `list` (`sbx ls`). (§4.3, §4.4, §4.5) +3. **CodingBackend** (`src/backend`) — Codex first: `turnArgs` + `parseResult` + `captureSessionId`. Drive with `--json`, **capture stderr too, keep reading until exit** (§4.5, §4.9, §9.14–§9.15). +4. **Router** (`src/router`) — idle/running/pending coalescing, `ts` dedupe, boundary logging, and per-thread `~/.agent-state` (`transcript.jsonl` + `session`) (§4.1, §4.2, §4.6, §4.9). +5. **Wire it** in `src/cli` — construct the three implementations + `Router`, then `router.start()`. + +> Runtime Slack/agent dependencies are intentionally **not** included yet, so the scaffold installs and builds with the toolchain alone. Add them as you implement each seam. diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..edd9dae --- /dev/null +++ b/biome.json @@ -0,0 +1,39 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": true, + "includes": ["**", "!**/dist"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always", + "trailingCommas": "all" + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..129e6eb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,737 @@ +{ + "name": "slack-coding-agent", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "slack-coding-agent", + "version": "0.0.1", + "license": "MIT", + "bin": { + "slack-coding-agent": "dist/cli/index.js" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.0", + "@types/node": "^22.10.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz", + "integrity": "sha512-x9ajFh1zChVybCiM3TN6OD4phAqLgtPZjFrZF+aTMYCPjwBO+k529TX7PPsAqtGNLeV4UgzwQnowEgS7bGmzcA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.16", + "@biomejs/cli-darwin-x64": "2.4.16", + "@biomejs/cli-linux-arm64": "2.4.16", + "@biomejs/cli-linux-arm64-musl": "2.4.16", + "@biomejs/cli-linux-x64": "2.4.16", + "@biomejs/cli-linux-x64-musl": "2.4.16", + "@biomejs/cli-win32-arm64": "2.4.16", + "@biomejs/cli-win32-x64": "2.4.16" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.16.tgz", + "integrity": "sha512-wxPvu4XOA85YJk9ixSWUmq/QBHbid85BISbOAqqBM/5xQpPk9ayjk5375tOlSC0BeCwNSbPFafQBm+vBumXq0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.16.tgz", + "integrity": "sha512-xFCqGPwYusQJp4N4NJLi1XJiZqjwFdjhT+KqtNy+Ug3qgfczqnTa6MSDvxJF6TkuDLoYJItMapz6tAf7kCekFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.16.tgz", + "integrity": "sha512-2kFb4//jxfZaP6D+Rj5VkHkxgyD9EoRAVBEQb8PKRv+s4NO2zYNJKXFaJmK1CmhufJOWEfpHKaRbOja7qjmdhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.16.tgz", + "integrity": "sha512-oYxnW0ARfJkr72ezzF2OR8N/rtkgLUQeYtF8cFhVswbknHxtTcmzSsanVJP8yQKnGpGpc2ck6c5zLvHahL6Cbg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.16.tgz", + "integrity": "sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.16.tgz", + "integrity": "sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.16.tgz", + "integrity": "sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.16", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.16.tgz", + "integrity": "sha512-Kp85jgoBHa05gix6UIRjfCDiUV3w/8VIdZ247VyyO2gEjaw12WEVhdIjlxp/AMzXxqxQwbxNTDVZ3Mwd2RG5rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..4fb8a5a --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "slack-coding-agent", + "version": "0.0.1", + "private": true, + "type": "module", + "description": "Slack-native coding agent — a router that delegates Slack threads to a coding-agent CLI in an isolated sandbox.", + "license": "MIT", + "bin": { + "slack-coding-agent": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + }, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "dev": "tsx watch src/cli/index.ts", + "start": "node dist/cli/index.js", + "typecheck": "tsc --noEmit", + "lint": "biome lint .", + "format": "biome format --write .", + "check": "biome check . && tsc --noEmit", + "check:fix": "biome check --write . && tsc --noEmit" + }, + "devDependencies": { + "@biomejs/biome": "^2.4.0", + "@types/node": "^22.10.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + } +} diff --git a/src/backend/index.ts b/src/backend/index.ts new file mode 100644 index 0000000..5fee270 --- /dev/null +++ b/src/backend/index.ts @@ -0,0 +1,32 @@ +import type { SandboxHandle } from "../sandbox/index.js"; + +/** + * CodingBackend — per-agent knowledge: the ONLY place agent-format details + * live. See spec §3, §4.9. Transport (SandboxProvider.exec) is router-owned + * and format-agnostic; only turnArgs / parseResult / events know the agent's + * format. + * + * v0: Codex. Then Claude Code, then Pi (the `pi` CLI — a third-party binary + * run inside the sandbox, not a code dependency; see spec §10). + */ +export interface TurnResult { + finalText: string; + ok: boolean; +} + +export interface CodingBackend { + /** e.g. ~/.codex, ~/.claude, ~/.pi/agent */ + configHome(): string; + + /** Build the CLI invocation. sessionId omitted → fresh session, else resume (§4.5). */ + turnArgs(message: string, sessionId?: string): string[]; + + /** Interpret the raw captured stream into the result to relay (agent-specific, §4.9). */ + parseResult(captured: string): TurnResult; + + /** After a fresh turn, read the new session's id (Codex: newest rollout filename) (§4.5). */ + captureSessionId(handle: SandboxHandle): Promise; + + /** OPTIONAL: normalized events to enrich logs; router degrades to raw bytes if absent. */ + events?(captured: string): unknown[]; +} diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..fd5b3b5 --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,56 @@ +#!/usr/bin/env node +/** + * CLI entrypoint. Shape mirrors pi-mom's convention (spec §10): + * + * slack-coding-agent --sandbox= + * e.g. slack-coding-agent --sandbox=sbx ./data + * + * Slack Socket Mode env (see spec §4.7, §4.8): + * APP_SLACK_APP_TOKEN xapp-... (app-level token → Socket Mode) + * APP_SLACK_BOT_TOKEN xoxb-... (bot token) + * plus the coding-agent / LLM credentials, injected proxy-side (§4.7). + */ + +interface Args { + sandbox: string; + dataDir: string; +} + +function parseArgs(argv: string[]): Args | null { + let sandbox = ""; + const positional: string[] = []; + for (const arg of argv) { + if (arg.startsWith("--sandbox=")) { + sandbox = arg.slice("--sandbox=".length); + } else if (arg === "-h" || arg === "--help") { + return null; + } else { + positional.push(arg); + } + } + const dataDir = positional[0]; + if (!sandbox || !dataDir) { + return null; + } + return { sandbox, dataDir }; +} + +function usage(): void { + console.log("Usage: slack-coding-agent --sandbox= "); + console.log(" e.g. slack-coding-agent --sandbox=sbx ./data"); +} + +function main(): void { + const args = parseArgs(process.argv.slice(2)); + if (!args) { + usage(); + process.exit(1); + } + + console.log(`[slack-coding-agent] sandbox=${args.sandbox} dataDir=${args.dataDir}`); + console.log( + "Scaffold OK. Implement PlatformAdapter, SandboxProvider, CodingBackend, then wire Router.start() (see spec §3, §4).", + ); +} + +main(); diff --git a/src/platform/index.ts b/src/platform/index.ts new file mode 100644 index 0000000..b560f74 --- /dev/null +++ b/src/platform/index.ts @@ -0,0 +1,25 @@ +import type { Mention, ThreadId, ThreadMessage } from "../types.js"; + +/** + * PlatformAdapter — ingress/egress of human conversation. See spec §3, §4.1. + * + * v0: Slack via Socket Mode. The implementation MUST acknowledge the Socket + * Mode envelope immediately (<3s) and invoke the handler asynchronously — it + * must never block the ACK on sandbox/turn work (spec §4.1). Acks to the user + * are reactions, not replies (§4.1/§4.2). + * + * Later platforms (Discord, Teams, Linear comments) implement the same shape. + */ +export interface PlatformAdapter { + /** Register the mention handler. The adapter ACKs the transport, then calls this. */ + onMention(handler: (mention: Mention) => void): void; + + /** Whole thread (sinceTs omitted) or only messages after sinceTs — the delta (§4.2). */ + fetchThread(thread: ThreadId, sinceTs?: string): Promise; + + postReply(thread: ThreadId, text: string): Promise; + uploadFile(thread: ThreadId, path: string): Promise; + + addReaction(channel: string, ts: string, emoji: string): Promise; + removeReaction(channel: string, ts: string, emoji: string): Promise; +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..42971c4 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,68 @@ +import type { CodingBackend } from "../backend/index.js"; +import type { PlatformAdapter } from "../platform/index.js"; +import type { SandboxProvider } from "../sandbox/index.js"; +import type { Mention } from "../types.js"; + +/** Per-thread coordination state, held in router memory only. See spec §3, §4.1. */ +type TurnState = "idle" | "running"; + +interface ThreadState { + state: TurnState; + pending: boolean; +} + +/** + * Router — plumbing, NOT an LLM (spec §6). It owns: immediate-ACK ingress (in + * the adapter), idempotent dispatch, the idle/running/pending state machine + + * coalescing (§4.1), boundary logging (§4.9), and turn dispatch. It interprets + * the agent's output only via CodingBackend.parseResult. + * + * This is a skeleton: the seam wiring is real; the full turn lifecycle + * (coalescing, boundary logging, session-id capture, eviction) is TODO. + */ +export class Router { + /** Transient coordination state (§3, bucket 2) — lost on restart, by design. */ + private readonly threads = new Map(); + /** Idempotent dispatch on ts (§4.1). A real impl TTLs this to the retry window. */ + private readonly seenTs = new Set(); + + constructor( + private readonly platform: PlatformAdapter, + private readonly sandbox: SandboxProvider, + private readonly backend: CodingBackend, + ) {} + + start(): void { + this.platform.onMention((mention) => { + this.onMention(mention).catch((err) => console.error("[router] turn failed", err)); + }); + } + + private async onMention(mention: Mention): Promise { + // 1. Idempotent dispatch on ts — drop already-accepted triggers (§4.1). + if (this.seenTs.has(mention.ts)) { + return; + } + this.seenTs.add(mention.ts); + + // 2. idle/running/pending state machine + coalescing (§4.1). + // TODO: if running, set pending and return; coalesce on completion. + const key = `${mention.thread.channel}:${mention.thread.threadTs}`; + const thread = this.threads.get(key) ?? { state: "idle", pending: false }; + thread.state = "running"; + this.threads.set(key, thread); + + // 3. Dispatch (minimal happy path). A real impl resolves-or-creates the + // sandbox by deterministic name (§4.1, §4.3), drives the turn so its + // stream-reader/logger outlives the dispatch (§4.1, §4.9), persists the + // session id (§4.5), and acks with a reaction first (§4.1/§4.2). + const sandboxName = `t-${key.replace(/[^a-zA-Z0-9_-]/g, "_")}`; + const handle = await this.sandbox.create(sandboxName, ""); + const argv = this.backend.turnArgs(mention.text); + const result = await this.sandbox.exec(handle, argv); // raw stream — log at the boundary (§4.9) + const { finalText, ok } = this.backend.parseResult(result.stdout); + await this.platform.postReply(mention.thread, ok ? finalText : "Turn failed."); + + thread.state = "idle"; + } +} diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts new file mode 100644 index 0000000..03cedb7 --- /dev/null +++ b/src/sandbox/index.ts @@ -0,0 +1,36 @@ +/** + * SandboxProvider — isolated execution-environment lifecycle. See spec §3, §4.4. + * + * v0: Docker Sandboxes (sbx) microVM. `exec` is the transport the router logs + * at the agent boundary (§4.9); commands should be wrapped in `bash -c` so the + * sandbox's persistent environment is loaded (§4.5). + * + * Fallback: plain Docker + iptables egress. Cloud/teams later: e2b / Daytona / + * self-hosted Firecracker. + */ +export interface SandboxHandle { + /** Deterministic, reversible name = f(threadId). See spec §4.1. */ + name: string; +} + +export interface ExecResult { + stdout: string; + stderr: string; + exitCode: number; +} + +export interface SandboxProvider { + create(name: string, repoRef: string): Promise; + + /** Drive the agent CLI per turn; the router streams and logs this raw output (§4.9). */ + exec(handle: SandboxHandle, argv: string[]): Promise; + + getFile(handle: SandboxHandle, path: string): Promise; + putFile(handle: SandboxHandle, path: string, contents: Uint8Array): Promise; + + stop(handle: SandboxHandle): Promise; + destroy(handle: SandboxHandle): Promise; + + /** The registry — `sbx ls`. See spec §4.1, §4.6. */ + list(): Promise; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..9569aca --- /dev/null +++ b/src/types.ts @@ -0,0 +1,23 @@ +/** Shared types for the seams. See spec §3. */ + +/** A Slack thread identity: channel + root-message ts. */ +export interface ThreadId { + channel: string; + threadTs: string; +} + +/** A normalized inbound mention handed to the router. See spec §4.1. */ +export interface Mention { + thread: ThreadId; + /** ts of the triggering message — used for idempotent dispatch (§4.1). */ + ts: string; + user: string; + text: string; +} + +/** One message in a thread's transcript. See spec §4.2. */ +export interface ThreadMessage { + ts: string; + user: string; + text: string; +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..3276af0 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false + }, + "exclude": ["node_modules", "dist", "src/**/*.test.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7cc8243 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "target": "ES2023", + "lib": ["ES2023"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "moduleDetection": "force", + "types": ["node"], + + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "verbatimModuleSyntax": true, + "isolatedModules": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} From c594c9c5b02f8621850a97f3d5974c902a90c2c0 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:46:09 -0400 Subject: [PATCH 02/47] C1: wire up Vitest + fold tests into the check gate - vitest + @vitest/coverage-v8 (globals, node env, v8 coverage) - vitest.config.ts (offline unit tests) and vitest.live.config.ts (SBX_LIVE-style lane via *.live.test.ts, passWithNoTests, excluded from CI) - tsconfig types += vitest/globals so test files type-check under tsc --noEmit - scripts: test / test:watch / test:cov / test:live; check now runs biome --error-on-warnings + tsc --noEmit + vitest run - trivial wiring test; build still excludes *.test.ts from dist/ Co-Authored-By: Claude Opus 4.8 (1M context) --- package-lock.json | 1628 +++++++++++++++++++++++++++++++++++++++-- package.json | 12 +- src/types.test.ts | 16 + tsconfig.json | 2 +- vitest.config.ts | 20 + vitest.live.config.ts | 18 + 6 files changed, 1615 insertions(+), 81 deletions(-) create mode 100644 src/types.test.ts create mode 100644 vitest.config.ts create mode 100644 vitest.live.config.ts diff --git a/package-lock.json b/package-lock.json index 129e6eb..16e1c0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,75 @@ "devDependencies": { "@biomejs/biome": "^2.4.0", "@types/node": "^22.10.0", + "@vitest/coverage-v8": "^4.1.7", "tsx": "^4.19.0", - "typescript": "^5.7.0" + "typescript": "^5.7.0", + "vitest": "^4.1.7" }, "engines": { "node": ">=22" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@biomejs/biome": { "version": "2.4.16", "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.16.tgz", @@ -184,6 +246,40 @@ "node": ">=14.21.3" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", @@ -626,112 +722,1490 @@ "node": ">=18" } }, - "node_modules/@types/node": { - "version": "22.19.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", - "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" }, - "engines": { - "node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "esbuild": "~0.28.0" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=14.17" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", + "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.7", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.7", + "vitest": "4.1.7" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", + "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/tsx": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index 4fb8a5a..b2ec3be 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,19 @@ "typecheck": "tsc --noEmit", "lint": "biome lint .", "format": "biome format --write .", - "check": "biome check . && tsc --noEmit", - "check:fix": "biome check --write . && tsc --noEmit" + "test": "vitest run", + "test:watch": "vitest", + "test:cov": "vitest run --coverage", + "test:live": "vitest run --config vitest.live.config.ts", + "check": "biome check --error-on-warnings . && tsc --noEmit && vitest run", + "check:fix": "biome check --write --error-on-warnings . && tsc --noEmit && vitest run" }, "devDependencies": { "@biomejs/biome": "^2.4.0", "@types/node": "^22.10.0", + "@vitest/coverage-v8": "^4.1.7", "tsx": "^4.19.0", - "typescript": "^5.7.0" + "typescript": "^5.7.0", + "vitest": "^4.1.7" } } diff --git a/src/types.test.ts b/src/types.test.ts new file mode 100644 index 0000000..d23890f --- /dev/null +++ b/src/types.test.ts @@ -0,0 +1,16 @@ +import type { Mention } from "./types.js"; + +// Proves the Vitest wiring: globals (describe/it/expect) resolve via +// `vitest/globals` in tsconfig, and the shared types type-check in a test file. +describe("test wiring", () => { + it("runs vitest with globals and type-checks shared types", () => { + const mention: Mention = { + thread: { channel: "C0ABCDEF", threadTs: "1748600000.123456" }, + ts: "1748600000.123456", + user: "U123", + text: "hello", + }; + expect(mention.thread.channel).toBe("C0ABCDEF"); + expect(mention.ts).toBe(mention.thread.threadTs); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 7cc8243..a6b7c1b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "moduleDetection": "force", - "types": ["node"], + "types": ["node", "vitest/globals"], "strict": true, "noUncheckedIndexedAccess": true, diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..daa834d --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "vitest/config"; + +/** + * Default test run: offline unit tests only. Live tests (`*.live.test.ts`, + * which need the sbx daemon / Docker / creds — spec §9) are excluded here and + * run via `npm run test:live` (see vitest.live.config.ts). + */ +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["src/**/*.test.ts"], + exclude: ["**/node_modules/**", "**/dist/**", "src/**/*.live.test.ts"], + coverage: { + provider: "v8", + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts", "src/**/*.live.test.ts", "src/**/fixtures/**"], + }, + }, +}); diff --git a/vitest.live.config.ts b/vitest.live.config.ts new file mode 100644 index 0000000..cc5c11b --- /dev/null +++ b/vitest.live.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "vitest/config"; + +/** + * Live lane — only `*.live.test.ts`, which exercise the real sbx daemon / Docker + * / Codex (spec §9). NOT part of `npm run check` or CI; run on demand with + * `npm run test:live` once the environment is up (Tier 1, see the plan). Passes + * with no test files so the script is safe before any live tests exist. + */ +export default defineConfig({ + test: { + globals: true, + environment: "node", + include: ["src/**/*.live.test.ts"], + passWithNoTests: true, + testTimeout: 120_000, + hookTimeout: 120_000, + }, +}); From 56078900ec823880575e07214f014baa72aa408c Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:48:52 -0400 Subject: [PATCH 03/47] C2: deterministic, reversible sandbox naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/sandbox-name.ts — name = "t--", a pure function of the thread id (spec §4.1) so the router finds/reverses sandboxes with no persisted mapping. Reconciles a spec bug: §4.1's sample replaces "."->"_", but sbx v0.31.1 `--name` rejects "_" (allows [A-Za-z0-9.+-]); periods are legal, so we keep the ".". A charset test guards against reintroducing the "_". Includes a charset/length hash fallback (t-<16 hex>) and parseSandboxName that namespaces our sandboxes away from utility ones (_login-tmp -> null). 15 tests: round-trip, injectivity, charset, hash fallback, namespacing. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/sandbox-name.test.ts | 127 ++++++++++++++++++++++++++++++++ src/router/sandbox-name.ts | Bin 0 -> 3537 bytes 2 files changed, 127 insertions(+) create mode 100644 src/router/sandbox-name.test.ts create mode 100644 src/router/sandbox-name.ts diff --git a/src/router/sandbox-name.test.ts b/src/router/sandbox-name.test.ts new file mode 100644 index 0000000..ad5b591 --- /dev/null +++ b/src/router/sandbox-name.test.ts @@ -0,0 +1,127 @@ +import type { ThreadId } from "../types.js"; +import { + chooseSandboxName, + hashSandboxName, + isCharsetSafe, + isHashName, + parseSandboxName, + SANDBOX_PREFIX, + sandboxName, +} from "./sandbox-name.js"; + +const SAMPLES: ThreadId[] = [ + { channel: "C0ABCDEF", threadTs: "1748600000.123456" }, + { channel: "G12345678", threadTs: "1700000000.000100" }, + { channel: "D0XYZ", threadTs: "1.2" }, + { channel: "C0ABCDEFGHIJ", threadTs: "1748600000.999999" }, +]; + +describe("sandboxName", () => { + it("encodes as t--, keeping the '.' (spec §4.1)", () => { + expect(sandboxName({ channel: "C0ABCDEF", threadTs: "1748600000.123456" })).toBe( + "t-C0ABCDEF-1748600000.123456", + ); + }); + + it("NEVER substitutes '.' -> '_' (the spec's charset bug)", () => { + for (const thread of SAMPLES) { + const name = sandboxName(thread); + expect(name).not.toContain("_"); + expect(name).toContain("."); + } + }); + + it("only ever emits sbx-legal characters [A-Za-z0-9.+-]", () => { + for (const thread of SAMPLES) { + expect(isCharsetSafe(sandboxName(thread))).toBe(true); + } + }); +}); + +describe("parseSandboxName (round-trip + namespacing)", () => { + it("round-trips every sample: parse(name(t)) === t", () => { + for (const thread of SAMPLES) { + expect(parseSandboxName(sandboxName(thread))).toEqual(thread); + } + }); + + it("is injective: distinct threads -> distinct names", () => { + const names = new Set(SAMPLES.map(sandboxName)); + expect(names.size).toBe(SAMPLES.length); + }); + + it("returns null for foreign / utility / malformed names", () => { + for (const name of [ + "_login-tmp", // sbx utility sandbox + "codex-myrepo", // default sbx name shape + "t-", // prefix only + "t-C0ABCDEF", // no thread_ts + "t-C0ABCDEF-notats", // thread_ts not numeric + "t-C0ABCDEF-1748600000", // missing fractional part + "C0ABCDEF-1748600000.1", // missing prefix + ]) { + expect(parseSandboxName(name)).toBeNull(); + } + }); + + it("returns null for hash names (caller reads ~/.agent-state/thread)", () => { + const hashed = hashSandboxName(SAMPLES[0] as ThreadId); + expect(parseSandboxName(hashed)).toBeNull(); + }); +}); + +describe("hash fallback", () => { + const thread = SAMPLES[0] as ThreadId; + + it("is deterministic, charset-safe, and recognized as a hash name", () => { + const a = hashSandboxName(thread); + const b = hashSandboxName(thread); + expect(a).toBe(b); + expect(a.startsWith(SANDBOX_PREFIX)).toBe(true); + expect(isCharsetSafe(a)).toBe(true); + expect(isHashName(a)).toBe(true); + expect(a).not.toContain("_"); + }); + + it("maps distinct threads to distinct hashes", () => { + expect(hashSandboxName(SAMPLES[0] as ThreadId)).not.toBe( + hashSandboxName(SAMPLES[1] as ThreadId), + ); + }); + + it("avoids collisions across the channel/ts boundary", () => { + // Without a separator, ("ab","c") and ("a","bc") could collide. + expect(hashSandboxName({ channel: "AB", threadTs: "1.2" })).not.toBe( + hashSandboxName({ channel: "A", threadTs: "B1.2" }), + ); + }); +}); + +describe("chooseSandboxName", () => { + const thread = SAMPLES[0] as ThreadId; + + it("uses the plain reversible name when it fits", () => { + const name = chooseSandboxName(thread); + expect(name).toBe(sandboxName(thread)); + expect(parseSandboxName(name)).toEqual(thread); + }); + + it("falls back to a hash name when the plain name exceeds maxLen", () => { + const name = chooseSandboxName(thread, 10); + expect(isHashName(name)).toBe(true); + expect(name).toBe(hashSandboxName(thread)); + }); +}); + +describe("isHashName / isCharsetSafe", () => { + it("distinguishes hash names from plain names", () => { + expect(isHashName("t-0123456789abcdef")).toBe(true); + expect(isHashName("t-C0ABCDEF-1748600000.123456")).toBe(false); + expect(isHashName("t-0123456789ABCDEF")).toBe(false); // uppercase != hex digest + }); + + it("flags underscores as charset-unsafe", () => { + expect(isCharsetSafe("t-C0ABCDEF-1748600000_123456")).toBe(false); + expect(isCharsetSafe("t-C0ABCDEF-1748600000.123456")).toBe(true); + }); +}); diff --git a/src/router/sandbox-name.ts b/src/router/sandbox-name.ts new file mode 100644 index 0000000000000000000000000000000000000000..449737099ad20ca695bc3812a5edd66a81ca2ae6 GIT binary patch literal 3537 zcmai1ZEo8}5bbZDVju(w6^oP|w+8CO0hGk84cLld3x(0dH7Rl>ZA~o+?$VO#DCi-2 zhu)({=}G!#m!zaPaevs5xXbx^Z{CdLG|vh{_Y@Z*GU7PW6I#-!$fnd%St1VNVv(Dy z^>RzPn`V&^DaCl%p%OrFj~O#l4-eX!dO@Fu@Jm~31VBsE2pmxZ8FsbV8DMcIf9t{EE+$)rO% zvrmewglmc;MI)&airAvN_Dnb4?_@-w9?mJ%A#LLvE*jf2X(q5jky=pBacd(Innrn! zh&yZ#IaE0bL{^kaaV}MAaD(Io4Wsy$Oh(e^I?hp6*dS%Xm6!>Uvo(*?-*3`CIXi!U zG3fON^zWa4Q9OwXEev_6pd3jA5{)CNv>^`vZYS8=Md=P)HV#bkeGVX`C{44OW*3=C z7t~6HL2Y!0R5=~m50gbc5eiR`r_2)mkd`Qu94piuSpuZ`d&|Zee(3qXM*bf={*S@7 ze;u}6ltmd!xLZ*FJ(Vhf@i;3);IIV5D2fFcMVjVFzAiS~OfsPHDP!i6U{j>aAy{il<{4A6yR{|ewp-$i>wekmzkBok>gN37 z=%qu5X? zIb%??R1FI}4%ar%xD;BWf}i(x+AnE;k1z6JoK2^h;({P<1I~z|`>ucKr_oTPl}&~? zLbxMAi5Nv?Y6AL&iMGInJCGaaMvf;&D3T+#rG!X8LZ&7=vB7gfmhD_<%YJSL5TFo< zUZ9;&>RGgK)!Z!Ro2#3C@9gO2tao*Ddelcb_xGHRSb~Q&mo1=8H?aGMYzDj?3b*;6 zACk90#dg3*It1Sy_bx7v1~(T+aM%59lhNBxx|RPPngL}aZzW%C3lzG|_^;5JN{RHf z|Ei(xYY?&Va(>!7>DNAt@2%WiZ?}E#W3v4*z`ypIKdz?VRFgU3TY~JU;M6ocfIw8wd=`oh=IUG|ekE({71~iiEfTjsfR1d=0fW&D zt$mENwUUfVsG>}`EZRUR@kz17u&vc(4L^5-Xe^ZRwZY!fbzVYZSY4Y!`?Kt}%qvcI zSHtxoHM!p|p>bG9YzdvUZ_(3D_FlVf`?3ri6& zL*DX=lxAb8d^64MOkjIVIA~Q!c_E+~LpFUOUIM8&se)32d)ojEMHWx`} JWn#%A=0E%0mw^BP literal 0 HcmV?d00001 From c480e9c7f92564ceff315d56abb97adc27e741da Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:50:58 -0400 Subject: [PATCH 04/47] C3: pure per-thread state machine + coalescing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/state-machine.ts — total reducer (state, event) -> {state, effects} over idle | running | runningPending (spec §4.1). No payload: Slack is the queue, so mid-turn mentions only flip pending and the dispatcher recomputes the delta covering all of them. Coalescing falls out: N mentions during a turn produce exactly ONE follow-up dispatch. 26 tests cover every state x event cell, the coalescing count, ack-before-dispatch ordering, and a no-double-dispatch invariant over an interleaved sequence. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/state-machine.test.ts | 135 +++++++++++++++++++++++++++++++ src/router/state-machine.ts | 56 +++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 src/router/state-machine.test.ts create mode 100644 src/router/state-machine.ts diff --git a/src/router/state-machine.test.ts b/src/router/state-machine.test.ts new file mode 100644 index 0000000..ac48065 --- /dev/null +++ b/src/router/state-machine.test.ts @@ -0,0 +1,135 @@ +import { + type FsmEffect, + type FsmEvent, + INITIAL_STATE, + reduce, + type ThreadFsmState, +} from "./state-machine.js"; + +const mention: FsmEvent = { kind: "mention" }; +const turnFinished: FsmEvent = { kind: "turnFinished" }; + +/** Fold a sequence of events from a start state, collecting every effect. */ +function run( + start: ThreadFsmState, + events: FsmEvent[], +): { state: ThreadFsmState; effects: FsmEffect[] } { + let state = start; + const effects: FsmEffect[] = []; + for (const event of events) { + const t = reduce(state, event); + state = t.state; + effects.push(...t.effects); + } + return { state, effects }; +} + +describe("reduce — every state x event cell (§4.1)", () => { + it("idle + mention -> running [ackRunning, dispatchTurn]", () => { + expect(reduce("idle", mention)).toEqual({ + state: "running", + effects: ["ackRunning", "dispatchTurn"], + }); + }); + + it("running + mention -> runningPending [ackPending] (no dispatch)", () => { + expect(reduce("running", mention)).toEqual({ + state: "runningPending", + effects: ["ackPending"], + }); + }); + + it("runningPending + mention -> runningPending [ackPending] (idempotent)", () => { + expect(reduce("runningPending", mention)).toEqual({ + state: "runningPending", + effects: ["ackPending"], + }); + }); + + it("running + turnFinished -> idle [goIdle]", () => { + expect(reduce("running", turnFinished)).toEqual({ state: "idle", effects: ["goIdle"] }); + }); + + it("runningPending + turnFinished -> running [dispatchTurn] (coalesced follow-up)", () => { + expect(reduce("runningPending", turnFinished)).toEqual({ + state: "running", + effects: ["dispatchTurn"], + }); + }); + + it("idle + turnFinished -> idle [] (defensive no-op)", () => { + expect(reduce("idle", turnFinished)).toEqual({ state: "idle", effects: [] }); + }); +}); + +describe("coalescing (§4.1)", () => { + it("dispatches exactly once per turn regardless of mentions arriving mid-turn", () => { + // 1 initial mention + 5 mid-turn mentions, then the turn finishes, then the + // coalesced follow-up finishes. Expect exactly 2 dispatches total. + const events: FsmEvent[] = [ + mention, // -> running, dispatch #1 + mention, // -> pending + mention, + mention, + mention, + mention, // still pending (idempotent) + turnFinished, // -> running, dispatch #2 (coalesced) + turnFinished, // -> idle, no dispatch + ]; + const { state, effects } = run(INITIAL_STATE, events); + expect(effects.filter((e) => e === "dispatchTurn")).toHaveLength(2); + expect(state).toBe("idle"); + }); + + it("a single mention with no mid-turn mentions yields exactly one dispatch", () => { + const { state, effects } = run(INITIAL_STATE, [mention, turnFinished]); + expect(effects.filter((e) => e === "dispatchTurn")).toHaveLength(1); + expect(state).toBe("idle"); + }); + + it("acks every mention but only dispatches when idle (👀 on each, §4.1)", () => { + const { effects } = run(INITIAL_STATE, [mention, mention, mention]); + const acks = effects.filter((e) => e === "ackRunning" || e === "ackPending"); + expect(acks).toHaveLength(3); // one 👀 per mention + expect(effects.filter((e) => e === "dispatchTurn")).toHaveLength(1); // only the first dispatched + }); + + it("orders the ack before the dispatch on idle+mention", () => { + const { effects } = reduce("idle", mention); + expect(effects.indexOf("ackRunning")).toBeLessThan(effects.indexOf("dispatchTurn")); + }); +}); + +describe("invariants", () => { + it("never dispatches a turn while one is already in flight", () => { + // Drive an interleaving of mentions and completions; a turnFinished clears + // the in-flight turn, after which the FSM may dispatch the coalesced one. + let state = INITIAL_STATE; + let inFlight = false; + const seq: FsmEvent[] = [ + mention, + mention, + turnFinished, + mention, + turnFinished, + turnFinished, + mention, + turnFinished, + ]; + for (const event of seq) { + const t = reduce(state, event); + if (event.kind === "turnFinished") { + inFlight = false; // the in-flight turn completed + } + for (const eff of t.effects) { + if (eff === "dispatchTurn") { + expect(inFlight).toBe(false); // never dispatch while a turn is in flight + inFlight = true; + } + } + state = t.state; + } + expect(state).toBe("idle"); + expect(inFlight).toBe(false); + }); +}); diff --git a/src/router/state-machine.ts b/src/router/state-machine.ts new file mode 100644 index 0000000..0394b4e --- /dev/null +++ b/src/router/state-machine.ts @@ -0,0 +1,56 @@ +/** + * Per-thread coordination state machine (spec §4.1). + * + * Pure, total reducer `(state, event) -> {state, effects}`. No timers, no I/O, + * no message payload — "Slack is the queue" (§4.1), so the FSM only tracks + * whether a turn is running and whether one more is pending; the dispatcher + * recomputes the delta covering every coalesced mention. + * + * Held in router memory only (transient bucket, §3): lost on restart, which is + * fine — an in-flight turn is abandoned and re-driven idempotently on the next + * mention. + * + * State table (§4.1): + * idle + mention -> running [ackRunning, dispatchTurn] + * running + mention -> runningPending [ackPending] (set pending, no dispatch) + * runningPending + mention -> runningPending [ackPending] (idempotent) + * running + turnFinished -> idle [goIdle] + * runningPending + turnFinished -> running [dispatchTurn] (the ONE coalesced follow-up) + * idle + turnFinished -> idle [] (defensive no-op) + */ + +/** `runningPending` is the spec's "running + pending". */ +export type ThreadFsmState = "idle" | "running" | "runningPending"; + +export type FsmEvent = { kind: "mention" } | { kind: "turnFinished" }; + +/** + * An instruction for the router to execute. `ackRunning`/`ackPending` both add + * the 👀 reaction (§4.1) but are distinct so the router can log the dispatch + * path vs the coalesce path differently (§4.9). + */ +export type FsmEffect = "ackRunning" | "ackPending" | "dispatchTurn" | "goIdle"; + +export interface Transition { + state: ThreadFsmState; + effects: FsmEffect[]; +} + +export const INITIAL_STATE: ThreadFsmState = "idle"; + +export function reduce(state: ThreadFsmState, event: FsmEvent): Transition { + switch (state) { + case "idle": + return event.kind === "mention" + ? { state: "running", effects: ["ackRunning", "dispatchTurn"] } + : { state: "idle", effects: [] }; + case "running": + return event.kind === "mention" + ? { state: "runningPending", effects: ["ackPending"] } + : { state: "idle", effects: ["goIdle"] }; + case "runningPending": + return event.kind === "mention" + ? { state: "runningPending", effects: ["ackPending"] } + : { state: "running", effects: ["dispatchTurn"] }; + } +} From 8f3393a37a7f64db93e4e7a85d11f1a72eb17ed3 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:52:02 -0400 Subject: [PATCH 05/47] C4: TTL dedupe set for idempotent dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/dedupe.ts — keyed by the trigger's Slack `ts`, sized to Slack's ~5-min retry window (§4.1). In-memory transient state; clock is injected so eviction is testable without sleeping. 7 tests: presence within window, exact-boundary eviction, no pre-TTL eviction, size(). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/dedupe.test.ts | 63 +++++++++++++++++++++++++++++++++++++++ src/router/dedupe.ts | 55 ++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 src/router/dedupe.test.ts create mode 100644 src/router/dedupe.ts diff --git a/src/router/dedupe.test.ts b/src/router/dedupe.test.ts new file mode 100644 index 0000000..f1a1d5d --- /dev/null +++ b/src/router/dedupe.test.ts @@ -0,0 +1,63 @@ +import { createTtlSet, DEFAULT_DEDUPE_TTL_MS } from "./dedupe.js"; + +describe("createTtlSet", () => { + it("reports added keys as present within the TTL window", () => { + const clock = 1000; + const set = createTtlSet(1000, () => clock); + expect(set.has("ts-1")).toBe(false); + set.add("ts-1"); + expect(set.has("ts-1")).toBe(true); + }); + + it("tracks distinct keys independently", () => { + let clock = 0; + const set = createTtlSet(1000, () => clock); + set.add("a"); + clock = 1; // keep the same window + expect(set.has("a")).toBe(true); + expect(set.has("b")).toBe(false); + set.add("b"); + expect(set.has("a")).toBe(true); + expect(set.has("b")).toBe(true); + }); + + it("does NOT evict before the TTL elapses", () => { + let clock = 0; + const set = createTtlSet(1000, () => clock); + set.add("a"); + clock = 999; // one tick before expiry + expect(set.has("a")).toBe(true); + }); + + it("evicts exactly at the TTL boundary", () => { + let clock = 0; + const set = createTtlSet(1000, () => clock); + set.add("a"); + clock = 1000; // current - inserted === ttl -> expired + expect(set.has("a")).toBe(false); + }); + + it("size() counts only non-expired keys", () => { + let clock = 0; + const set = createTtlSet(1000, () => clock); + set.add("a"); // inserted at 0 + clock = 500; + set.add("b"); // inserted at 500 + expect(set.size()).toBe(2); + clock = 1000; // "a" expires (1000-0>=1000), "b" survives (1000-500<1000) + expect(set.size()).toBe(1); + expect(set.has("a")).toBe(false); + expect(set.has("b")).toBe(true); + }); + + it("defaults the TTL to at least Slack's ~5-min retry window", () => { + expect(DEFAULT_DEDUPE_TTL_MS).toBeGreaterThanOrEqual(5 * 60_000); + }); + + it("defaults the clock to wall time (smoke: present right after add)", () => { + const set = createTtlSet(); // real clock, 5-min TTL + set.add("ts-now"); + expect(set.has("ts-now")).toBe(true); + expect(set.size()).toBe(1); + }); +}); diff --git a/src/router/dedupe.ts b/src/router/dedupe.ts new file mode 100644 index 0000000..09d9068 --- /dev/null +++ b/src/router/dedupe.ts @@ -0,0 +1,55 @@ +/** + * Idempotent-dispatch dedupe (spec §4.1). + * + * Immediate Socket Mode ACK stops the retry storm but duplicates still arrive (a + * lost ACK inside the retry window, or a reconnect race). The router dedupes on + * the mention's Slack `ts`: a `ts` already accepted is dropped. + * + * This is a TTL set keyed by `ts`, sized to cover Slack's ~5-min retry window. + * It lives in router memory (transient bucket, §3) and is lost on restart, which + * is fine — retries don't outlive that window. The clock is injected so the TTL + * is testable without sleeping. + */ + +/** Slack retries an unacked event for ~5 minutes (immediately, then ~1m, ~5m). */ +export const DEFAULT_DEDUPE_TTL_MS = 5 * 60_000; + +export interface TtlSet { + /** True iff `key` was added within the TTL window. Evicts expired keys first. */ + has(key: string): boolean; + /** Record `key` as seen at the current time. */ + add(key: string): void; + /** Count of non-expired keys (test introspection). */ + size(): number; +} + +export function createTtlSet( + ttlMs: number = DEFAULT_DEDUPE_TTL_MS, + now: () => number = Date.now, +): TtlSet { + const insertedAt = new Map(); + + const evict = (current: number): void => { + for (const [key, ts] of insertedAt) { + if (current - ts >= ttlMs) { + insertedAt.delete(key); + } + } + }; + + return { + has(key: string): boolean { + evict(now()); + return insertedAt.has(key); + }, + add(key: string): void { + const current = now(); + evict(current); + insertedAt.set(key, current); + }, + size(): number { + evict(now()); + return insertedAt.size; + }, + }; +} From b900d9d3e1ce145396e4967fe7a5dbf385c1519f Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:55:26 -0400 Subject: [PATCH 06/47] C5: agent-state pure functions (transcript ledger + delta) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/agent-state.ts — pure layer of the per-thread durable state (spec §4.2/§4.6): highWaterMark (lexicographic Slack-ts ordering), computeDelta (after-hwm, minus bot posts), JSONL serialize/parse, and the ~/.agent-state path constants. Documents the §4.2 "minus the trigger" reconciliation: the previous trigger is already <= hwm (in the ledger) and the current trigger carries the request and must be fed (§4.1 "the delta covers all of them"), so the only author dropped is the bot itself. 13 tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/agent-state.test.ts | 113 +++++++++++++++++++++++++++++++++ src/router/agent-state.ts | 96 ++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 src/router/agent-state.test.ts create mode 100644 src/router/agent-state.ts diff --git a/src/router/agent-state.test.ts b/src/router/agent-state.test.ts new file mode 100644 index 0000000..dc90c33 --- /dev/null +++ b/src/router/agent-state.test.ts @@ -0,0 +1,113 @@ +import type { ThreadMessage } from "../types.js"; +import { + computeDelta, + highWaterMark, + parseTranscript, + serializeTranscript, +} from "./agent-state.js"; + +const msg = (ts: string, user: string, text: string): ThreadMessage => ({ ts, user, text }); + +describe("highWaterMark", () => { + it("returns null for an empty ledger (turn-1 signal)", () => { + expect(highWaterMark([])).toBeNull(); + }); + + it("returns the largest ts regardless of input order", () => { + expect( + highWaterMark([ + msg("1748600000.500000", "U1", "b"), + msg("1748600000.100000", "U1", "a"), + msg("1748600000.900000", "U1", "c"), + ]), + ).toBe("1748600000.900000"); + }); + + it("compares Slack ts lexicographically (= chronologically)", () => { + // micro-level ordering + expect( + highWaterMark([msg("1748600000.000009", "U", "x"), msg("1748600000.000010", "U", "y")]), + ).toBe("1748600000.000010"); + // across the seconds boundary + expect( + highWaterMark([msg("1748600000.999999", "U", "x"), msg("1748600001.000000", "U", "y")]), + ).toBe("1748600001.000000"); + }); +}); + +describe("computeDelta", () => { + const fetched = [ + msg("1748600000.100000", "U1", "old request"), + msg("1748600000.200000", "BOT", "old bot reply"), + msg("1748600000.300000", "U2", "teammate note"), + msg("1748600000.400000", "U1", "@bot new request"), // the current trigger + ]; + + it("turn 1 (hwm null) returns the whole thread minus bot posts (primer)", () => { + const delta = computeDelta(fetched, null, { botUser: "BOT" }); + expect(delta.map((m) => m.text)).toEqual(["old request", "teammate note", "@bot new request"]); + }); + + it("follow-up excludes <= hwm and bot posts, but INCLUDES the current trigger", () => { + const delta = computeDelta(fetched, "1748600000.200000", { botUser: "BOT" }); + // strictly after hwm: teammate note + the new request; bot reply (if any) dropped + expect(delta.map((m) => m.text)).toEqual(["teammate note", "@bot new request"]); + }); + + it("treats ts === hwm as already seen (exclusive boundary)", () => { + const delta = computeDelta(fetched, "1748600000.300000", { botUser: "BOT" }); + expect(delta.map((m) => m.text)).toEqual(["@bot new request"]); + }); + + it("drops the bot's own later posts so the HWM never lands on a bot reply", () => { + const withBotReply = [ + msg("1748600000.400000", "U1", "request"), + msg("1748600000.500000", "BOT", "result reply"), + ]; + const delta = computeDelta(withBotReply, "1748600000.350000", { botUser: "BOT" }); + expect(delta.map((m) => m.text)).toEqual(["request"]); + }); + + it("without a botUser, keeps every message after the hwm", () => { + const delta = computeDelta(fetched, "1748600000.250000"); + expect(delta).toHaveLength(2); + }); +}); + +describe("serializeTranscript / parseTranscript", () => { + const messages = [ + msg("1748600000.100000", "U1", "hello"), + msg("1748600000.200000", "U2", 'has "quotes" and a\nnewline'), + ]; + + it("round-trips messages (incl. quotes and embedded newlines)", () => { + expect(parseTranscript(serializeTranscript(messages))).toEqual(messages); + }); + + it("serializes one JSON object per line with a trailing newline", () => { + const text = serializeTranscript(messages); + expect(text.endsWith("\n")).toBe(true); + expect(text.trimEnd().split("\n")).toHaveLength(2); + }); + + it("serializes only {ts, user, text}, dropping any extra fields", () => { + const dirty = [ + { ts: "1.1", user: "U", text: "hi", extra: "drop me" }, + ] as unknown as ThreadMessage[]; + expect(JSON.parse(serializeTranscript(dirty).trim())).toEqual({ + ts: "1.1", + user: "U", + text: "hi", + }); + }); + + it("returns '' for an empty ledger and [] when parsing it back", () => { + expect(serializeTranscript([])).toBe(""); + expect(parseTranscript("")).toEqual([]); + }); + + it("tolerates blank lines and a missing trailing newline", () => { + const text = '{"ts":"1.1","user":"U","text":"a"}\n\n{"ts":"1.2","user":"U","text":"b"}'; + expect(parseTranscript(text)).toEqual([msg("1.1", "U", "a"), msg("1.2", "U", "b")]); + }); +}); diff --git a/src/router/agent-state.ts b/src/router/agent-state.ts new file mode 100644 index 0000000..7db64c8 --- /dev/null +++ b/src/router/agent-state.ts @@ -0,0 +1,96 @@ +import type { ThreadMessage } from "../types.js"; + +/** + * Per-thread durable state — pure layer (spec §4.2/§4.6). + * + * The thread's seen-message ledger is `~/.agent-state/transcript.jsonl` in the + * sandbox: one JSON line per message the agent has been fed, `{ts, user, text}`. + * It is both the **high-water mark** (largest `ts`) and a durable audit of + * exactly what the agent saw. This file holds only the *pure* logic over message + * arrays; the sbx-backed read/write store lives in agent-state.store.ts (§4.6). + * + * Paths are relative to the sandbox user's `$HOME` (resolved by the store). + */ +export const AGENT_STATE_DIR = ".agent-state"; +export const TRANSCRIPT_PATH = `${AGENT_STATE_DIR}/transcript.jsonl`; +export const SESSION_PATH = `${AGENT_STATE_DIR}/session`; +export const THREAD_PATH = `${AGENT_STATE_DIR}/thread`; + +/** + * Largest `ts` in the ledger, or `null` when empty (which is how the router + * knows it's turn 1 — §4.2). + * + * Slack `ts` is `"<10-digit secs>.<6-digit micros>"`, zero-padded, so plain + * string comparison is chronological (verified by test). This holds until the + * seconds field gains an 11th digit (year 2286). + */ +export function highWaterMark(transcript: ThreadMessage[]): string | null { + let max: string | null = null; + for (const message of transcript) { + if (max === null || message.ts > max) { + max = message.ts; + } + } + return max; +} + +export interface DeltaOptions { + /** The bot's own Slack user id; its posts are never fed back to the agent. */ + botUser?: string; +} + +/** + * The messages to feed this turn: everything after the high-water mark, minus + * the bot's own posts (§4.2). + * + * - Turn 1 (`hwm === null`): the whole thread minus bot posts — the primer. + * - Follow-up: messages with `ts > hwm` minus bot posts — the exact delta. + * + * Note on §4.2's "minus the trigger": the *previous* turn's trigger is already + * excluded by `ts > hwm` (it's in the ledger). The *current* trigger carries the + * user's new request and MUST be included — §4.1 says "the delta covers all of + * them [the mentions]". So the only author we drop is the bot itself. + */ +export function computeDelta( + fetched: ThreadMessage[], + hwm: string | null, + opts: DeltaOptions = {}, +): ThreadMessage[] { + const { botUser } = opts; + return fetched.filter((message) => { + if (botUser !== undefined && message.user === botUser) { + return false; + } + if (hwm !== null && message.ts <= hwm) { + return false; + } + return true; + }); +} + +/** Serialize messages to JSONL (one `{ts, user, text}` per line, trailing \n). */ +export function serializeTranscript(messages: ThreadMessage[]): string { + if (messages.length === 0) { + return ""; + } + return `${messages + .map((m) => JSON.stringify({ ts: m.ts, user: m.user, text: m.text })) + .join("\n")}\n`; +} + +/** + * Parse JSONL back to messages, tolerating a trailing newline and blank lines. + * Malformed lines throw (the ledger has a single writer and is append-only, so + * corruption is a real signal, not something to silently swallow). + */ +export function parseTranscript(text: string): ThreadMessage[] { + const messages: ThreadMessage[] = []; + for (const line of text.split("\n")) { + if (line.trim() === "") { + continue; + } + const parsed = JSON.parse(line) as ThreadMessage; + messages.push({ ts: parsed.ts, user: parsed.user, text: parsed.text }); + } + return messages; +} From fe5461f25205bc9a7927ad580bcbdd18ac8c2f08 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 16:58:21 -0400 Subject: [PATCH 07/47] =?UTF-8?q?C6:=20boundary=20logging=20=E2=80=94=20bu?= =?UTF-8?q?ilders=20+=20best-effort=20sinks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/log.ts — format-agnostic boundary logging (spec §4.9): a common envelope with in/out/router directions and turnId correlation; pure builders (turn.in, turn.out.chunk, turn.exit, router.*); summary vs verbose levels that cap/hash large prompts and argv so whole-thread primers and tool-output bursts can't flood the log. Sinks are best-effort and never throw — a full disk or slow shipper must never stall a turn: createJsonlSink (injected writer), createRecordingSink (tests), nullSink, and a safeWrite wrapper. 17 tests, incl. a guard that no credential-shaped field is ever emitted (§4.7). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/log.test.ts | 190 +++++++++++++++++++++++++++++++++++ src/router/log.ts | 222 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 src/router/log.test.ts create mode 100644 src/router/log.ts diff --git a/src/router/log.test.ts b/src/router/log.test.ts new file mode 100644 index 0000000..163f918 --- /dev/null +++ b/src/router/log.test.ts @@ -0,0 +1,190 @@ +import { + buildOutChunk, + buildRouterEvent, + buildTurnExit, + buildTurnIn, + capArgv, + capForSummary, + createJsonlSink, + createRecordingSink, + hashString, + type LogContext, + type LogSink, + nullSink, + RouterKind, + safeWrite, + summarizePrompt, +} from "./log.js"; + +const ctx: LogContext = { + threadId: "t-C0ABCDEF-1748600000.123456", + channel: "C0ABCDEF", + thread_ts: "1748600000.123456", + sandbox: "t-C0ABCDEF-1748600000.123456", + turnId: "turn-1", +}; +const TS = "2026-05-31T00:00:00.000Z"; + +describe("envelope shape & correlation fields", () => { + it("every builder carries the common envelope (§4.9)", () => { + const env = buildRouterEvent(ctx, TS, RouterKind.MentionReceived, { user: "U1" }); + expect(env).toMatchObject({ + ts: TS, + threadId: ctx.threadId, + channel: ctx.channel, + thread_ts: ctx.thread_ts, + sandbox: ctx.sandbox, + turnId: ctx.turnId, + direction: "router", + kind: "mention.received", + user: "U1", + }); + }); + + it("assigns the right direction per builder", () => { + expect( + buildTurnIn(ctx, TS, { + sessionId: null, + argv: ["codex"], + cwd: "/w", + prompt: "p", + level: "summary", + }).direction, + ).toBe("in"); + expect(buildOutChunk(ctx, TS, { stream: "stdout", chunk: "x" }).direction).toBe("out"); + expect( + buildTurnExit(ctx, TS, { exitCode: 0, durationMs: 5, finalSnippet: "done", ok: true }) + .direction, + ).toBe("out"); + expect(buildRouterEvent(ctx, TS, RouterKind.AckReaction).direction).toBe("router"); + }); +}); + +describe("buildTurnIn — summary vs verbose", () => { + const longPrompt = "x".repeat(1000); + + it("summary caps argv and hashes/sizes the prompt (no full prompt)", () => { + const env = buildTurnIn(ctx, TS, { + sessionId: null, + argv: ["codex", "exec", longPrompt], + cwd: "/w", + prompt: longPrompt, + level: "summary", + }); + expect(env.kind).toBe("turn.in"); + expect(env.resume).toBe(false); + expect(env.sessionId).toBeNull(); + // prompt is summarized, not embedded whole + expect(env.prompt).toEqual({ + bytes: 1000, + sha256: hashString(longPrompt), + head: capForSummary(longPrompt, 200), + }); + // the giant prompt-arg in argv is capped too + const argv = env.argv as string[]; + expect(argv[2]?.length).toBeLessThan(longPrompt.length); + }); + + it("verbose embeds the full prompt and full argv", () => { + const env = buildTurnIn(ctx, TS, { + sessionId: "sess-123", + argv: ["codex", "exec", "resume", "sess-123", longPrompt], + cwd: "/w", + prompt: longPrompt, + level: "verbose", + }); + expect(env.resume).toBe(true); + expect(env.sessionId).toBe("sess-123"); + expect(env.prompt).toBe(longPrompt); + expect((env.argv as string[])[4]).toBe(longPrompt); + }); +}); + +describe("buildTurnExit / buildOutChunk", () => { + it("turn.exit carries exit/duration/ok and caps the snippet", () => { + const env = buildTurnExit(ctx, TS, { + exitCode: 1, + durationMs: 1234, + finalSnippet: "y".repeat(2000), + ok: false, + }); + expect(env).toMatchObject({ kind: "turn.exit", exitCode: 1, durationMs: 1234, ok: false }); + expect((env.finalSnippet as string).length).toBeLessThan(2000); + }); + + it("turn.out.chunk carries the raw stream + bytes", () => { + const env = buildOutChunk(ctx, TS, { stream: "stderr", chunk: "boom" }); + expect(env).toMatchObject({ kind: "turn.out.chunk", stream: "stderr", chunk: "boom" }); + }); +}); + +describe("safeguards", () => { + it("capForSummary truncates only when over the limit", () => { + expect(capForSummary("short", 10)).toBe("short"); + expect(capForSummary("abcdefghij", 3)).toBe("abc…(+7 chars)"); + }); + + it("hashString is deterministic 64-hex", () => { + expect(hashString("a")).toBe(hashString("a")); + expect(hashString("a")).toMatch(/^[0-9a-f]{64}$/); + }); + + it("summarizePrompt counts utf8 bytes (not chars)", () => { + expect(summarizePrompt("é").bytes).toBe(2); // 2 utf-8 bytes + }); + + it("capArgv shortens long elements", () => { + expect(capArgv(["short", "z".repeat(500)], 10)[1]).toContain("…"); + }); + + it("never emits credential-shaped fields (§4.7)", () => { + const env = buildTurnIn(ctx, TS, { + sessionId: null, + argv: ["codex"], + cwd: "/w", + prompt: "p", + level: "verbose", + }); + for (const key of Object.keys(env)) { + expect(key).not.toMatch(/token|secret|password|apikey|credential/i); + } + }); +}); + +describe("sinks — best-effort, never throwing", () => { + it("recording sink collects records", () => { + const sink = createRecordingSink(); + sink.write(buildRouterEvent(ctx, TS, RouterKind.AckReaction)); + expect(sink.records).toHaveLength(1); + expect(sink.records[0]?.kind).toBe("ack.reaction"); + }); + + it("jsonl sink serializes one line per record", () => { + const lines: string[] = []; + const sink = createJsonlSink((line) => lines.push(line)); + sink.write(buildRouterEvent(ctx, TS, RouterKind.SandboxStop, { reason: "idle" })); + expect(lines).toHaveLength(1); + expect(lines[0]?.endsWith("\n")).toBe(true); + expect(JSON.parse(lines[0] as string)).toMatchObject({ kind: "sandbox.stop", reason: "idle" }); + }); + + it("jsonl sink swallows a throwing writer (logging never fails a turn)", () => { + const sink = createJsonlSink(() => { + throw new Error("disk full"); + }); + expect(() => sink.write(buildRouterEvent(ctx, TS, RouterKind.Error))).not.toThrow(); + }); + + it("safeWrite swallows a throwing sink", () => { + const bad: LogSink = { + write() { + throw new Error("boom"); + }, + }; + expect(() => safeWrite(bad, buildRouterEvent(ctx, TS, RouterKind.Error))).not.toThrow(); + }); + + it("nullSink is a no-op", () => { + expect(() => nullSink.write(buildRouterEvent(ctx, TS, RouterKind.AckReaction))).not.toThrow(); + }); +}); diff --git a/src/router/log.ts b/src/router/log.ts new file mode 100644 index 0000000..8fb3a02 --- /dev/null +++ b/src/router/log.ts @@ -0,0 +1,222 @@ +import { createHash } from "node:crypto"; + +/** + * Boundary logging (spec §4.9). + * + * The router logs both edges of every turn at the agent boundary, treating the + * agent's output as opaque bytes — format-agnostic, so it survives Codex schema + * changes and catches the agent misbehaving (e.g. silent empty output, §9.14). + * + * - `direction: "in"` — router -> agent (argv, target session, prompt) + * - `direction: "out"` — agent -> router (raw stdout/stderr chunks; exit summary) + * - `direction: "router"` — the router's own lifecycle actions + * + * Two levels: always-on **summaries** (cap/hash large prompts and output) and a + * **verbose** toggle for full raw bytes. Logs are a sink, never read back for + * correctness, and writes are best-effort — they must never stall or fail a turn. + * Credentials are proxy-side and never enter argv/prompt, so they are never + * logged (§4.7). + */ + +export type Direction = "in" | "out" | "router"; +export type LogLevel = "summary" | "verbose"; + +/** Stable per-turn/per-thread correlation fields carried by every record. */ +export interface LogContext { + threadId: string; + channel: string; + thread_ts: string; + sandbox: string; + turnId: string; +} + +interface EnvelopeBase extends LogContext { + ts: string; + direction: Direction; + kind: string; +} + +/** Common envelope + kind-specific payload spread at the top level (§4.9). */ +export type LogEnvelope = EnvelopeBase & Record; + +/** Router-direction record kinds — the gaps raw agent I/O can't explain (§4.9). */ +export const RouterKind = { + MentionReceived: "mention.received", + MentionDeduped: "mention.deduped", + AckReaction: "ack.reaction", + QueuedPending: "queued.pending", + SandboxCreate: "sandbox.create", + SandboxStart: "sandbox.start", + SandboxStop: "sandbox.stop", + TurnAbandoned: "turn.abandoned", + ResultRelayed: "result.relayed", + FileUploaded: "file.uploaded", + Error: "error", +} as const; + +const SUMMARY_HEAD_CHARS = 200; +const SUMMARY_SNIPPET_CHARS = 500; + +// --------------------------------------------------------------------------- +// Pure safeguards +// --------------------------------------------------------------------------- + +/** Truncate long strings for summaries, noting how much was dropped. */ +export function capForSummary(text: string, maxChars: number): string { + if (text.length <= maxChars) { + return text; + } + return `${text.slice(0, maxChars)}…(+${text.length - maxChars} chars)`; +} + +export function hashString(text: string): string { + return createHash("sha256").update(text).digest("hex"); +} + +/** Size + hash + head of a (possibly huge) prompt — never the whole thing. */ +export function summarizePrompt(prompt: string): { + bytes: number; + sha256: string; + head: string; +} { + return { + bytes: Buffer.byteLength(prompt, "utf8"), + sha256: hashString(prompt), + head: capForSummary(prompt, SUMMARY_HEAD_CHARS), + }; +} + +/** Cap each argv element so a giant prompt-arg can't flood a summary record. */ +export function capArgv(argv: string[], maxChars: number = SUMMARY_HEAD_CHARS): string[] { + return argv.map((arg) => capForSummary(arg, maxChars)); +} + +// --------------------------------------------------------------------------- +// Pure record builders +// --------------------------------------------------------------------------- + +function base(ctx: LogContext, ts: string, direction: Direction, kind: string): EnvelopeBase { + return { + ts, + threadId: ctx.threadId, + channel: ctx.channel, + thread_ts: ctx.thread_ts, + sandbox: ctx.sandbox, + turnId: ctx.turnId, + direction, + kind, + }; +} + +export interface TurnInInput { + /** Target session id: null => fresh turn, else resume (§4.5). */ + sessionId: string | null; + argv: string[]; + cwd: string; + prompt: string; + level: LogLevel; +} + +export function buildTurnIn(ctx: LogContext, ts: string, input: TurnInInput): LogEnvelope { + const { sessionId, argv, cwd, prompt, level } = input; + if (level === "verbose") { + return { + ...base(ctx, ts, "in", "turn.in"), + resume: sessionId !== null, + sessionId, + argv, + cwd, + prompt, + }; + } + return { + ...base(ctx, ts, "in", "turn.in"), + resume: sessionId !== null, + sessionId, + argv: capArgv(argv), + cwd, + prompt: summarizePrompt(prompt), + }; +} + +export function buildOutChunk( + ctx: LogContext, + ts: string, + input: { stream: "stdout" | "stderr"; chunk: string }, +): LogEnvelope { + return { ...base(ctx, ts, "out", "turn.out.chunk"), stream: input.stream, chunk: input.chunk }; +} + +export interface TurnExitInput { + exitCode: number; + durationMs: number; + finalSnippet: string; + ok: boolean; +} + +export function buildTurnExit(ctx: LogContext, ts: string, input: TurnExitInput): LogEnvelope { + return { + ...base(ctx, ts, "out", "turn.exit"), + exitCode: input.exitCode, + durationMs: input.durationMs, + finalSnippet: capForSummary(input.finalSnippet, SUMMARY_SNIPPET_CHARS), + ok: input.ok, + }; +} + +export function buildRouterEvent( + ctx: LogContext, + ts: string, + kind: string, + payload: Record = {}, +): LogEnvelope { + return { ...base(ctx, ts, "router", kind), ...payload }; +} + +// --------------------------------------------------------------------------- +// Sinks (best-effort, never throwing) +// --------------------------------------------------------------------------- + +export interface LogSink { + write(env: LogEnvelope): void; +} + +/** Discards everything (default when logging is off / in tests). */ +export const nullSink: LogSink = { write() {} }; + +/** In-memory sink for tests. */ +export function createRecordingSink(): LogSink & { records: LogEnvelope[] } { + const records: LogEnvelope[] = []; + return { + records, + write(env) { + records.push(env); + }, + }; +} + +/** + * Serialize each record to a JSONL line and hand it to `writeLine` (which does + * the actual stdout/file I/O, injected by the composition root). Swallows all + * errors — a full disk or slow shipper must never stall a turn (§4.9). + */ +export function createJsonlSink(writeLine: (line: string) => void): LogSink { + return { + write(env) { + try { + writeLine(`${JSON.stringify(env)}\n`); + } catch { + // best-effort: logging is never allowed to fail a turn + } + }, + }; +} + +/** Write to a sink without ever letting a misbehaving sink throw into a turn. */ +export function safeWrite(sink: LogSink, env: LogEnvelope): void { + try { + sink.write(env); + } catch { + // best-effort + } +} From 6979567ba3738c306597f1c103b6cdbb0fa30ed0 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:01:35 -0400 Subject: [PATCH 08/47] C7: pure sbx argv builders + shell quoting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/sandbox/sbx-argv.ts — argv construction for the sbx CLI (verified vs v0.31.1, spec §4.4/§4.5), isolated from the I/O shell so it's testable without the daemon. Bakes in the load-bearing rules: --clone on create (never the host tree); exec wrapped in `bash -c` with `--` and NO `-i` (§4.5/§9.15); a PTY wrap option for codex's headless empty-output regression (§9.14). Includes ls --json parsing (shape confirmed live), cp/stop/rm, and secret ls/set/rm for onboarding. shellQuote/shellJoin use POSIX single-quoting; a test round-trips a hostile argv table (newlines, $(), backticks, quotes, emoji) through real bash. 18 tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/sandbox/sbx-argv.test.ts | 203 ++++++++++++++++++++++++++++++++ src/sandbox/sbx-argv.ts | 221 +++++++++++++++++++++++++++++++++++ 2 files changed, 424 insertions(+) create mode 100644 src/sandbox/sbx-argv.test.ts create mode 100644 src/sandbox/sbx-argv.ts diff --git a/src/sandbox/sbx-argv.test.ts b/src/sandbox/sbx-argv.test.ts new file mode 100644 index 0000000..bd51f12 --- /dev/null +++ b/src/sandbox/sbx-argv.test.ts @@ -0,0 +1,203 @@ +import { execFileSync } from "node:child_process"; +import { + cpArgv, + createArgv, + execAgentArgv, + execArgv, + lsArgv, + lsNamesArgv, + parseLsJson, + ptyCommand, + remotePath, + rmArgv, + secretLsArgv, + secretRmArgv, + secretSetArgv, + shellJoin, + shellQuote, + stopArgv, +} from "./sbx-argv.js"; + +describe("createArgv", () => { + it("always uses --clone and the codex agent by default (§4.4)", () => { + expect(createArgv("t-C0-1.2", "/repo")).toEqual([ + "create", + "--clone", + "--name", + "t-C0-1.2", + "codex", + "/repo", + ]); + }); + + it("threads template + kits and can disable clone", () => { + expect( + createArgv("box", "/repo", { + agent: "claude", + clone: false, + template: "myreg/codex-snowball", + kits: ["./kit-a", "./kit-b"], + }), + ).toEqual([ + "create", + "--name", + "box", + "--template", + "myreg/codex-snowball", + "--kit", + "./kit-a", + "--kit", + "./kit-b", + "claude", + "/repo", + ]); + }); +}); + +describe("execArgv", () => { + it("wraps in `bash -c`, uses `--`, and never passes -i (§4.5/§9.15)", () => { + expect(execArgv("box", "codex exec --json")).toEqual([ + "exec", + "box", + "--", + "bash", + "-c", + "codex exec --json", + ]); + expect(execArgv("box", "x")).not.toContain("-i"); + }); + + it("supports login shell, workdir, env, and the PTY mitigation (§9.14)", () => { + expect(execArgv("box", "cmd", { login: true })).toContain("-lc"); + expect(execArgv("box", "cmd", { workdir: "/repo" })).toEqual([ + "exec", + "-w", + "/repo", + "box", + "--", + "bash", + "-c", + "cmd", + ]); + expect(execArgv("box", "cmd", { env: { FOO: "bar" } })).toEqual([ + "exec", + "-e", + "FOO=bar", + "box", + "--", + "bash", + "-c", + "cmd", + ]); + const pty = execArgv("box", "codex exec", { pty: true }); + expect(pty[pty.length - 1]).toBe(ptyCommand("codex exec")); + }); + + it("execAgentArgv quotes an agent argv into the bash -c string", () => { + expect(execAgentArgv("box", ["codex", "exec", "fix the bug"])).toEqual([ + "exec", + "box", + "--", + "bash", + "-c", + "codex exec 'fix the bug'", + ]); + }); +}); + +describe("shellQuote / shellJoin", () => { + it("leaves safe words bare and quotes the rest", () => { + expect(shellQuote("codex")).toBe("codex"); + expect(shellQuote("--json")).toBe("--json"); + expect(shellQuote("/home/u/.codex")).toBe("/home/u/.codex"); + expect(shellQuote("")).toBe("''"); + expect(shellQuote("a b")).toBe("'a b'"); + expect(shellQuote("$HOME")).toBe("'$HOME'"); + expect(shellQuote("it's")).toBe("'it'\\''s'"); + }); + + // The strong guarantee: bash must reparse shellJoin(argv) back into argv. + const reparse = (argv: string[]): string[] => { + const out = execFileSync("bash", ["-c", `printf '%s\\0' ${shellJoin(argv)}`], { + encoding: "utf8", + }); + const parts = out.split("\0"); + parts.pop(); // trailing "" after the final NUL + return parts; + }; + + it("round-trips a hostile argv table through real bash", () => { + const table: string[][] = [ + ["codex", "exec", "--json"], + ["a b", "it's", 'say "hi"'], + ["use $HOME", "back`tick`", "semi;colon", "and && or", "pipe|x"], + ["line\nbreak", "tab\there", "glob*?[x]", "-n", "--", ""], + ["fix the bug: $(rm -rf /)", "emoji 🚀", "{brace}", "~tilde"], + ]; + for (const argv of table) { + expect(reparse(argv)).toEqual(argv); + } + }); +}); + +describe("ls + parseLsJson", () => { + it("builds ls argv variants", () => { + expect(lsArgv()).toEqual(["ls", "--json"]); + expect(lsNamesArgv()).toEqual(["ls", "-q"]); + }); + + it("parses a top-level array", () => { + const json = JSON.stringify([ + { name: "t-C0-1.2", status: "running", agent: "codex", workspace: "/repo" }, + { name: "_login-tmp", status: "stopped" }, + ]); + expect(parseLsJson(json)).toEqual([ + { name: "t-C0-1.2", status: "running", agent: "codex", workspace: "/repo" }, + { name: "_login-tmp", status: "stopped", agent: undefined, workspace: undefined }, + ]); + }); + + it("parses a { sandboxes: [...] } wrapper and skips entries without a name", () => { + const json = JSON.stringify({ sandboxes: [{ name: "box" }, { status: "running" }] }); + expect(parseLsJson(json)).toEqual([ + { name: "box", status: undefined, agent: undefined, workspace: undefined }, + ]); + }); + + it("returns [] for empty output", () => { + expect(parseLsJson("")).toEqual([]); + expect(parseLsJson(" \n")).toEqual([]); + }); +}); + +describe("cp / stop / rm", () => { + it("addresses sandbox paths and builds cp argv", () => { + expect(remotePath("box", "/home/u/.agent-state/session")).toBe( + "box:/home/u/.agent-state/session", + ); + expect(cpArgv("box:/a", "./a")).toEqual(["cp", "box:/a", "./a"]); + }); + + it("stops one or many, removes with --force for non-interactive use", () => { + expect(stopArgv("box")).toEqual(["stop", "box"]); + expect(stopArgv("a", "b")).toEqual(["stop", "a", "b"]); + expect(rmArgv(["box"], { force: true })).toEqual(["rm", "--force", "box"]); + expect(rmArgv(["box"])).toEqual(["rm", "box"]); + }); +}); + +describe("secret (onboarding/auth)", () => { + it("lists, sets (global oauth / global stdin), and removes", () => { + expect(secretLsArgv()).toEqual(["secret", "ls"]); + expect(secretSetArgv("openai", { oauth: true })).toEqual([ + "secret", + "set", + "-g", + "--oauth", + "openai", + ]); + expect(secretSetArgv("openai")).toEqual(["secret", "set", "-g", "openai"]); + expect(secretSetArgv("openai", { global: false })).toEqual(["secret", "set", "openai"]); + expect(secretRmArgv("openai", { global: true })).toEqual(["secret", "rm", "-g", "openai"]); + }); +}); diff --git a/src/sandbox/sbx-argv.ts b/src/sandbox/sbx-argv.ts new file mode 100644 index 0000000..e49ff44 --- /dev/null +++ b/src/sandbox/sbx-argv.ts @@ -0,0 +1,221 @@ +/** + * Pure argv builders for the `sbx` CLI (spec §4.4/§4.5). + * + * The SandboxProvider I/O shell (sbx-provider.ts) shells out `sbx `; this + * file owns the argv construction so it's testable without the daemon. Verified + * against sbx v0.31.1 — re-check on every upgrade (§9, substrate risk). + * + * Two load-bearing rules baked in here: + * - `--clone` on create: the agent works on a private in-VM clone, never the + * live host working tree (§4.4). + * - `exec` is wrapped in `bash -c`: `sbx exec` runs with no shell and does not + * source the sandbox's persistent environment, so the toolchain/agent PATH + * may be missing otherwise (§4.5). We also never pass `-i` (the provider + * closes stdin so codex can't hang — §9.15). + */ + +// --------------------------------------------------------------------------- +// Shell quoting (the bug-prone part — a wrong quote makes "works interactively +// / fails headless" bugs). POSIX single-quoting so `bash -c ` +// reparses to exactly `argv`. +// --------------------------------------------------------------------------- + +/** Chars safe to leave unquoted in a POSIX shell word. */ +const BARE_WORD = /^[A-Za-z0-9_/.:=@%+,-]+$/; + +export function shellQuote(arg: string): string { + if (arg === "") { + return "''"; + } + if (BARE_WORD.test(arg)) { + return arg; + } + // Close the quote, emit an escaped ', reopen: ' -> '\'' + return `'${arg.replace(/'/g, "'\\''")}'`; +} + +/** Quote an argv into a single shell command string. */ +export function shellJoin(argv: string[]): string { + return argv.map(shellQuote).join(" "); +} + +/** + * Wrap a command so the child gets a pseudo-TTY, mitigating codex's headless + * empty-output regression (§9.14). util-linux `script` syntax (Linux/in-VM); + * confirm live which mitigation we keep. + */ +export function ptyCommand(command: string): string { + return `script -qec ${shellQuote(command)} /dev/null`; +} + +// --------------------------------------------------------------------------- +// create / exec +// --------------------------------------------------------------------------- + +export interface CreateOptions { + /** Agent kind sbx wires auth for. Default "codex" (§4.5). */ + agent?: string; + /** Private in-VM clone vs the rw bind-mount. Default true — never edit the host tree (§4.4). */ + clone?: boolean; + template?: string; + kits?: string[]; +} + +export function createArgv(name: string, repoRef: string, opts: CreateOptions = {}): string[] { + const { agent = "codex", clone = true, template, kits = [] } = opts; + const argv = ["create"]; + if (clone) { + argv.push("--clone"); + } + argv.push("--name", name); + if (template !== undefined) { + argv.push("--template", template); + } + for (const kit of kits) { + argv.push("--kit", kit); + } + argv.push(agent, repoRef); + return argv; +} + +export interface ExecOptions { + env?: Record; + workdir?: string; + /** Login shell (`bash -lc`) to source profile-level env if `-c` isn't enough (§4.5). */ + login?: boolean; + /** Wrap the command in a PTY (§9.14). */ + pty?: boolean; +} + +/** + * `sbx exec -- bash -c ''`. `command` is a shell string (build + * it from an agent argv with shellJoin). No `-i`: stdin stays closed (§9.15). + * The `--` stops sbx flag parsing so the command's own flags pass through. + */ +export function execArgv(name: string, command: string, opts: ExecOptions = {}): string[] { + const argv = ["exec"]; + for (const [key, value] of Object.entries(opts.env ?? {})) { + argv.push("-e", `${key}=${value}`); + } + if (opts.workdir !== undefined) { + argv.push("-w", opts.workdir); + } + const inner = opts.pty === true ? ptyCommand(command) : command; + argv.push(name, "--", "bash", opts.login === true ? "-lc" : "-c", inner); + return argv; +} + +/** Build an `exec` argv directly from an agent argv (quote + wrap). */ +export function execAgentArgv(name: string, agentArgv: string[], opts: ExecOptions = {}): string[] { + return execArgv(name, shellJoin(agentArgv), opts); +} + +// --------------------------------------------------------------------------- +// ls (the registry, §4.1) +// --------------------------------------------------------------------------- + +export function lsArgv(): string[] { + return ["ls", "--json"]; +} + +export function lsNamesArgv(): string[] { + return ["ls", "-q"]; +} + +export interface SbxListEntry { + name: string; + status?: string; + agent?: string; + workspace?: string; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function strOrUndef(value: unknown): string | undefined { + return typeof value === "string" ? value : undefined; +} + +/** + * Parse `sbx ls --json`. The exact field names are confirmed live (§9); this is + * tolerant of both a top-level array and a `{ sandboxes: [...] }` wrapper, and + * skips entries without a string `name`. + */ +export function parseLsJson(stdout: string): SbxListEntry[] { + const trimmed = stdout.trim(); + if (trimmed === "") { + return []; + } + const data: unknown = JSON.parse(trimmed); + const items: unknown[] = Array.isArray(data) + ? data + : isRecord(data) && Array.isArray(data.sandboxes) + ? data.sandboxes + : []; + const entries: SbxListEntry[] = []; + for (const item of items) { + if (isRecord(item) && typeof item.name === "string") { + entries.push({ + name: item.name, + status: strOrUndef(item.status), + agent: strOrUndef(item.agent), + workspace: strOrUndef(item.workspace), + }); + } + } + return entries; +} + +// --------------------------------------------------------------------------- +// cp (getFile / putFile, §4.6) / stop / rm +// --------------------------------------------------------------------------- + +/** `SANDBOX:PATH` addressing for `sbx cp`. */ +export function remotePath(name: string, path: string): string { + return `${name}:${path}`; +} + +export function cpArgv(src: string, dst: string): string[] { + return ["cp", src, dst]; +} + +export function stopArgv(...names: string[]): string[] { + return ["stop", ...names]; +} + +export function rmArgv(names: string[], opts: { force?: boolean } = {}): string[] { + return ["rm", ...(opts.force === true ? ["--force"] : []), ...names]; +} + +// --------------------------------------------------------------------------- +// secret (onboarding/auth detection, §4.7/§4.8) +// --------------------------------------------------------------------------- + +export function secretLsArgv(): string[] { + return ["secret", "ls"]; +} + +export interface SecretSetOptions { + /** Host-global (shared by every future sandbox). Default true (§4.8). */ + global?: boolean; + /** OAuth/browser flow (e.g. `openai --oauth`); else the value is piped via stdin. */ + oauth?: boolean; +} + +export function secretSetArgv(service: string, opts: SecretSetOptions = {}): string[] { + const { global = true, oauth = false } = opts; + const argv = ["secret", "set"]; + if (global) { + argv.push("-g"); + } + if (oauth) { + argv.push("--oauth"); + } + argv.push(service); + return argv; +} + +export function secretRmArgv(service: string, opts: { global?: boolean } = {}): string[] { + return ["secret", "rm", ...(opts.global === true ? ["-g"] : []), service]; +} From 20b1c954cf8d09c2c12f02e3d64ea3b7220400d8 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:09:28 -0400 Subject: [PATCH 09/47] =?UTF-8?q?C8:=20Codex=20backend=20=E2=80=94=20the?= =?UTF-8?q?=20sole=20quarantine=20of=20agent=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/backend/codex.ts — turnArgs/parseResult/parseSessionId/captureSessionId for codex 0.135.0 (spec §4.5/§4.9). All pure off captured bytes except captureSessionId, whose one shell call is injected. - turnArgs sets sandbox_mode + approval_policy via `-c` (uniform across `exec` and `exec resume`, since resume lacks `-s`); no --ephemeral, no -i; `--` guards the prompt. - parseResult tolerates several --json event shapes + non-JSON lines, and treats empty output as a failed turn (the §9.14 headless regression). - parseSessionId reads the id straight from the turn-1 stream; captureSessionId is the rollout-file fallback (newest by mtime, no shell pipeline; $HOME expands in the raw command). Extends CodingBackend with optional parseSessionId. Fixtures are best-guess shapes, replaced with real bytes in the live pass (C8.5). 32 tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/codex.test.ts | 176 +++++++++++++ src/backend/codex.ts | 231 ++++++++++++++++++ src/backend/fixtures/README.md | 15 ++ src/backend/fixtures/codex-empty.stdout.txt | 0 src/backend/fixtures/codex-error.jsonl | 2 + src/backend/fixtures/codex-find-output.txt | 2 + src/backend/fixtures/codex-json-stream.jsonl | 4 + .../fixtures/codex-rollout-filename.txt | 1 + src/backend/index.ts | 8 + 9 files changed, 439 insertions(+) create mode 100644 src/backend/codex.test.ts create mode 100644 src/backend/codex.ts create mode 100644 src/backend/fixtures/README.md create mode 100644 src/backend/fixtures/codex-empty.stdout.txt create mode 100644 src/backend/fixtures/codex-error.jsonl create mode 100644 src/backend/fixtures/codex-find-output.txt create mode 100644 src/backend/fixtures/codex-json-stream.jsonl create mode 100644 src/backend/fixtures/codex-rollout-filename.txt diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts new file mode 100644 index 0000000..4cd9725 --- /dev/null +++ b/src/backend/codex.test.ts @@ -0,0 +1,176 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; +import { + CODEX_NONBLOCKING_FLAGS, + CodexBackend, + codexEvents, + codexTurnArgs, + newestRolloutPath, + parseCodexResult, + parseRolloutId, + parseSessionId, + type SandboxShellExecutor, +} from "./codex.js"; + +const fixture = (name: string): string => + readFileSync(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8"); + +const stream = fixture("codex-json-stream.jsonl"); +const errorStream = fixture("codex-error.jsonl"); +const emptyStream = fixture("codex-empty.stdout.txt"); +const findOutput = fixture("codex-find-output.txt"); + +describe("codexTurnArgs", () => { + it("turn 1: exec --json + non-blocking flags + `--` guard, no --ephemeral/-i", () => { + const argv = codexTurnArgs("fix the bug"); + expect(argv).toEqual([ + "codex", + "exec", + "--json", + ...CODEX_NONBLOCKING_FLAGS, + "--", + "fix the bug", + ]); + expect(argv).not.toContain("--ephemeral"); + expect(argv).not.toContain("-i"); + }); + + it("keeps the native sandbox on and approvals non-blocking (§4.5)", () => { + const argv = codexTurnArgs("x"); + expect(argv.join(" ")).toContain("sandbox_mode=workspace-write"); + expect(argv.join(" ")).toContain("approval_policy=never"); + }); + + it("follow-up: exec resume with the same flags", () => { + expect(codexTurnArgs("more", "sess-abc")).toEqual([ + "codex", + "exec", + "resume", + "--json", + ...CODEX_NONBLOCKING_FLAGS, + "--", + "sess-abc", + "more", + ]); + }); + + it("treats null/undefined sessionId as a fresh turn", () => { + expect(codexTurnArgs("x", null)).toContain("exec"); + expect(codexTurnArgs("x", null)).not.toContain("resume"); + }); +}); + +describe("parseCodexResult", () => { + it("extracts the final assistant message and marks ok (success fixture)", () => { + expect(parseCodexResult(stream)).toEqual({ + finalText: "Fixed the login timeout in auth.go and added a regression test.", + ok: true, + }); + }); + + it("flags empty output as a failed turn (§9.14 headless regression)", () => { + expect(parseCodexResult(emptyStream)).toEqual({ finalText: "", ok: false }); + expect(parseCodexResult("")).toEqual({ finalText: "", ok: false }); + }); + + it("returns ok=false when the stream errors before a final message", () => { + expect(parseCodexResult(errorStream)).toEqual({ finalText: "", ok: false }); + }); + + it("tolerates interleaved non-JSON progress lines", () => { + const noisy = `progress: thinking...\n${stream}`; + expect(parseCodexResult(noisy).ok).toBe(true); + }); + + it("handles the older msg-wrapped agent_message shape", () => { + const old = '{"id":"1","msg":{"type":"agent_message","message":"done via msg shape"}}'; + expect(parseCodexResult(old)).toEqual({ finalText: "done via msg shape", ok: true }); + }); + + it("handles a task_complete last_agent_message", () => { + const done = '{"type":"turn.completed","last_agent_message":"shipped"}'; + expect(parseCodexResult(done)).toEqual({ finalText: "shipped", ok: true }); + }); +}); + +describe("parseSessionId", () => { + it("reads the session/thread id from the turn-1 stream", () => { + expect(parseSessionId(stream)).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + }); + + it("finds the id even on an errored turn (session was still created)", () => { + expect(parseSessionId(errorStream)).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + }); + + it("returns null when no id is present", () => { + expect( + parseSessionId('{"type":"item.completed","item":{"type":"assistant_message","text":"hi"}}'), + ).toBeNull(); + expect(parseSessionId("")).toBeNull(); + }); +}); + +describe("parseRolloutId / newestRolloutPath", () => { + it("extracts the UUID from a rollout filename", () => { + expect(parseRolloutId(fixture("codex-rollout-filename.txt").trim())).toBe( + "7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f", + ); + }); + + it("returns null for a non-rollout filename", () => { + expect(parseRolloutId("config.toml")).toBeNull(); + }); + + it("picks the path with the greatest mtime", () => { + expect(newestRolloutPath(findOutput)).toBe( + "/root/.codex/sessions/2026/05/30/rollout-2026-05-30T12-30-00-7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f.jsonl", + ); + expect(newestRolloutPath("")).toBeNull(); + }); +}); + +describe("codexEvents", () => { + it("parses every JSON line and degrades gracefully on garbage", () => { + expect(codexEvents(stream)).toHaveLength(4); + expect(codexEvents("not json\n{bad}\n")).toEqual([]); + }); +}); + +describe("CodexBackend.captureSessionId (I/O via injected executor)", () => { + const handle: SandboxHandle = { name: "t-C0-1.2" }; + + const fakeExecutor = (stdout: string): SandboxShellExecutor & { lastCommand: string } => { + const calls = { lastCommand: "" }; + return { + get lastCommand() { + return calls.lastCommand; + }, + async execShell(_handle: SandboxHandle, command: string): Promise { + calls.lastCommand = command; + return { stdout, stderr: "", exitCode: 0 }; + }, + }; + }; + + it("finds the newest rollout and parses its id; $HOME stays unquoted", async () => { + const exec = fakeExecutor(findOutput); + const backend = new CodexBackend(exec); + expect(await backend.captureSessionId(handle)).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + expect(exec.lastCommand).toContain("$HOME/.codex/sessions"); + expect(exec.lastCommand).toContain("rollout-*.jsonl"); + }); + + it("throws when no rollout file exists", async () => { + const backend = new CodexBackend(fakeExecutor("")); + await expect(backend.captureSessionId(handle)).rejects.toThrow(/no rollout file/); + }); + + it("exposes the pure helpers through the interface", () => { + const backend = new CodexBackend(fakeExecutor("")); + expect(backend.configHome()).toBe("~/.codex"); + expect(backend.parseSessionId(stream)).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + expect(backend.parseResult(emptyStream).ok).toBe(false); + expect(backend.events(stream)).toHaveLength(4); + }); +}); diff --git a/src/backend/codex.ts b/src/backend/codex.ts new file mode 100644 index 0000000..d80dffc --- /dev/null +++ b/src/backend/codex.ts @@ -0,0 +1,231 @@ +import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; +import type { CodingBackend, TurnResult } from "./index.js"; + +/** + * Codex backend — the ONLY place Codex's CLI/output format lives (spec §4.5/§4.9). + * Everything here is pure (off captured bytes) except captureSessionId, whose + * one shell call is injected so the rest stays unit-testable. + * + * Verified against codex 0.135.0: + * - `codex exec [--json] [-c k=v]... [-- PROMPT]` + * - `codex exec resume [--json] [-c k=v]... [-- SESSION_ID PROMPT]` + * - `resume` has `-c` and `--json` but NOT `-s/--sandbox`, so we set the sandbox + * mode + approval policy via `-c` uniformly (works on both subcommands). + * - NEVER `--ephemeral` (breaks resume, §9.15); no `-i` (provider closes stdin). + */ + +export const CODEX_HOME = "~/.codex"; +/** Shell-expanded ($HOME) inside execShell, not single-quoted. */ +export const CODEX_SESSIONS_PATH = "$HOME/.codex/sessions"; + +/** + * Non-blocking defense-in-depth (§4.5/§9.8): keep Codex's native sandbox ON + * (workspace-write) but never block headless on an approval prompt. Set via `-c` + * because `resume` lacks `-s`. Exact keys confirmed live (§9.8). + */ +export const CODEX_NONBLOCKING_FLAGS = [ + "-c", + "sandbox_mode=workspace-write", + "-c", + "approval_policy=never", +]; + +/** Minimal sandbox capability captureSessionId needs (one shell command). */ +export interface SandboxShellExecutor { + execShell(handle: SandboxHandle, command: string): Promise; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function asString(value: unknown): string | null { + return typeof value === "string" ? value : null; +} + +/** + * Build the turn invocation. `--` guards a prompt/id that might start with `-`. + * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). + */ +export function codexTurnArgs(message: string, sessionId?: string | null): string[] { + const common = ["--json", ...CODEX_NONBLOCKING_FLAGS]; + if (sessionId === undefined || sessionId === null) { + return ["codex", "exec", ...common, "--", message]; + } + return ["codex", "exec", "resume", ...common, "--", sessionId, message]; +} + +function extractAssistantText(event: Record): string | null { + // Most authoritative: a completion event carrying the last message verbatim. + const last = + asString(event.last_agent_message) ?? + (isRecord(event.msg) ? asString(event.msg.last_agent_message) : null); + if (last !== null) { + return last; + } + // `item.*` thread events: { item: { type: "assistant_message", text } }. + const item = isRecord(event.item) ? event.item : null; + if (item !== null) { + const itemType = asString(item.type) ?? asString(item.item_type); + if (itemType === "assistant_message" || itemType === "agent_message") { + return asString(item.text) ?? asString(item.message); + } + } + // `msg`-wrapped events: { msg: { type: "agent_message", message } }. + const msg = isRecord(event.msg) ? event.msg : null; + if (msg !== null && asString(msg.type) === "agent_message") { + return asString(msg.message) ?? asString(msg.text); + } + // Flat events: { type: "assistant_message", text }. + const type = asString(event.type); + if (type === "assistant_message" || type === "agent_message") { + return asString(event.text) ?? asString(event.message); + } + return null; +} + +/** + * Interpret the captured `--json` stream into the result to relay. Tolerant of + * several event shapes (the schema moves between versions) and of non-JSON + * progress lines. Empty output -> {"", false}: the §9.14 headless regression + * (argv -> 0 bytes, exit 0) surfaces as a failed turn, not a silent empty reply. + */ +export function parseCodexResult(captured: string): TurnResult { + if (captured.trim() === "") { + return { finalText: "", ok: false }; + } + let finalText = ""; + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + let event: unknown; + try { + event = JSON.parse(line); + } catch { + continue; + } + if (!isRecord(event)) { + continue; + } + const text = extractAssistantText(event); + if (text !== null && text !== "") { + finalText = text; + } + } + return { finalText, ok: finalText.trim() !== "" }; +} + +function findSessionId(record: Record): string | null { + return ( + asString(record.thread_id) ?? asString(record.session_id) ?? asString(record.conversation_id) + ); +} + +/** Extract the session id from a turn-1 `--json` stream (no filesystem). */ +export function parseSessionId(captured: string): string | null { + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + let event: unknown; + try { + event = JSON.parse(line); + } catch { + continue; + } + if (!isRecord(event)) { + continue; + } + const id = findSessionId(event) ?? (isRecord(event.msg) ? findSessionId(event.msg) : null); + if (id !== null) { + return id; + } + } + return null; +} + +const ROLLOUT_UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i; + +/** The session UUID embedded in a `rollout-…-.jsonl` filename (§4.5). */ +export function parseRolloutId(filename: string): string | null { + const match = ROLLOUT_UUID_RE.exec(filename); + return match !== null && match[1] !== undefined ? match[1] : null; +} + +/** From `find … -printf '%T@\t%p\n'` output, the path with the greatest mtime. */ +export function newestRolloutPath(findOutput: string): string | null { + let bestPath: string | null = null; + let bestMtime = Number.NEGATIVE_INFINITY; + for (const line of findOutput.split("\n")) { + if (line.trim() === "") { + continue; + } + const tab = line.indexOf("\t"); + if (tab < 0) { + continue; + } + const mtime = Number.parseFloat(line.slice(0, tab)); + if (Number.isFinite(mtime) && mtime > bestMtime) { + bestMtime = mtime; + bestPath = line.slice(tab + 1); + } + } + return bestPath; +} + +/** Parse the `--json` stream into raw events for log enrichment (degrade to []). */ +export function codexEvents(captured: string): unknown[] { + const events: unknown[] = []; + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + try { + events.push(JSON.parse(line)); + } catch { + // skip non-JSON progress lines + } + } + return events; +} + +export class CodexBackend implements CodingBackend { + constructor(private readonly executor: SandboxShellExecutor) {} + + configHome(): string { + return CODEX_HOME; + } + + turnArgs(message: string, sessionId?: string): string[] { + return codexTurnArgs(message, sessionId); + } + + parseResult(captured: string): TurnResult { + return parseCodexResult(captured); + } + + parseSessionId(captured: string): string | null { + return parseSessionId(captured); + } + + events(captured: string): unknown[] { + return codexEvents(captured); + } + + /** Fallback when the stream had no id: newest rollout file under CODEX_HOME. */ + async captureSessionId(handle: SandboxHandle): Promise { + const command = `find ${CODEX_SESSIONS_PATH} -name 'rollout-*.jsonl' -printf '%T@\\t%p\\n' 2>/dev/null`; + const { stdout } = await this.executor.execShell(handle, command); + const newest = newestRolloutPath(stdout); + if (newest === null) { + throw new Error("codex: no rollout file found to capture the session id (§4.5)"); + } + const filename = newest.slice(newest.lastIndexOf("/") + 1); + const id = parseRolloutId(filename); + if (id === null) { + throw new Error(`codex: could not parse the session id from rollout file "${filename}"`); + } + return id; + } +} diff --git a/src/backend/fixtures/README.md b/src/backend/fixtures/README.md new file mode 100644 index 0000000..de307d0 --- /dev/null +++ b/src/backend/fixtures/README.md @@ -0,0 +1,15 @@ +# Codex fixtures + +Sample `codex exec --json` bytes that `codex.ts` parsing is verified against. + +> **Status: best-guess placeholders.** These approximate the codex 0.135.0 +> `--json` event shapes. They are **replaced with real captured bytes** during +> the live verification pass (plan C8.5 / spec §9), at which point the parser is +> reconciled to the exact schema. The parser is intentionally tolerant of +> several plausible event shapes so it survives the swap. + +- `codex-json-stream.jsonl` — a successful fresh turn (session-id event + final assistant message). +- `codex-error.jsonl` — a turn that errored before producing a final message. +- `codex-empty.stdout.txt` — the headless empty-output regression: 0 bytes, exit 0 (§9.14). +- `codex-rollout-filename.txt` — a sample `~/.codex/sessions/.../rollout-*.jsonl` filename. +- `codex-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-rollout selection. diff --git a/src/backend/fixtures/codex-empty.stdout.txt b/src/backend/fixtures/codex-empty.stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/fixtures/codex-error.jsonl b/src/backend/fixtures/codex-error.jsonl new file mode 100644 index 0000000..3267848 --- /dev/null +++ b/src/backend/fixtures/codex-error.jsonl @@ -0,0 +1,2 @@ +{"type":"thread.started","thread_id":"7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"} +{"type":"error","message":"stream disconnected before the model finished"} diff --git a/src/backend/fixtures/codex-find-output.txt b/src/backend/fixtures/codex-find-output.txt new file mode 100644 index 0000000..6de5aa9 --- /dev/null +++ b/src/backend/fixtures/codex-find-output.txt @@ -0,0 +1,2 @@ +1748600000.1234 /root/.codex/sessions/2026/05/30/rollout-2026-05-30T12-00-00-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl +1748600100.5678 /root/.codex/sessions/2026/05/30/rollout-2026-05-30T12-30-00-7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f.jsonl diff --git a/src/backend/fixtures/codex-json-stream.jsonl b/src/backend/fixtures/codex-json-stream.jsonl new file mode 100644 index 0000000..222221e --- /dev/null +++ b/src/backend/fixtures/codex-json-stream.jsonl @@ -0,0 +1,4 @@ +{"type":"thread.started","thread_id":"7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"} +{"type":"item.started","item":{"type":"assistant_message"}} +{"type":"item.completed","item":{"type":"assistant_message","text":"Fixed the login timeout in auth.go and added a regression test."}} +{"type":"turn.completed","usage":{"input_tokens":1200,"output_tokens":64}} diff --git a/src/backend/fixtures/codex-rollout-filename.txt b/src/backend/fixtures/codex-rollout-filename.txt new file mode 100644 index 0000000..f033d49 --- /dev/null +++ b/src/backend/fixtures/codex-rollout-filename.txt @@ -0,0 +1 @@ +rollout-2026-05-30T12-34-56-7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f.jsonl diff --git a/src/backend/index.ts b/src/backend/index.ts index 5fee270..2d03123 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -27,6 +27,14 @@ export interface CodingBackend { /** After a fresh turn, read the new session's id (Codex: newest rollout filename) (§4.5). */ captureSessionId(handle: SandboxHandle): Promise; + /** + * OPTIONAL: extract the new session's id straight from the captured turn-1 + * stream (e.g. Codex prints it as a thread/session event) — pure and cheaper + * than {@link captureSessionId}. The dispatcher tries this first and falls + * back to captureSessionId (§4.5). + */ + parseSessionId?(captured: string): string | null; + /** OPTIONAL: normalized events to enrich logs; router degrades to raw bytes if absent. */ events?(captured: string): unknown[]; } From 733d4f82d0e39c6c7dcdb2b48a233a9c2ffc33c0 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:16:11 -0400 Subject: [PATCH 10/47] =?UTF-8?q?C9:=20AgentStateStore=20=E2=80=94=20per-t?= =?UTF-8?q?hread=20durable=20state=20I/O?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/agent-state-store.ts — reads/writes the ~/.agent-state ledger, session id, and thread reverse-map inside each thread's sandbox (spec §4.6). The router keeps no state of its own. Depends on a narrow SandboxFsLike (homeDir/readFile/writeFile), not the full provider, so it fakes trivially; appendDelta is write-after-success read-modify-write with a single writer (§4.1/§4.2). 9 tests over an in-memory fake fs. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/agent-state-store.test.ts | 83 ++++++++++++++++++++++++++++ src/router/agent-state-store.ts | 83 ++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 src/router/agent-state-store.test.ts create mode 100644 src/router/agent-state-store.ts diff --git a/src/router/agent-state-store.test.ts b/src/router/agent-state-store.test.ts new file mode 100644 index 0000000..0696755 --- /dev/null +++ b/src/router/agent-state-store.test.ts @@ -0,0 +1,83 @@ +import type { SandboxHandle } from "../sandbox/index.js"; +import type { ThreadMessage } from "../types.js"; +import { AgentStateStore, type SandboxFsLike } from "./agent-state-store.js"; + +const handle: SandboxHandle = { name: "t-C0ABCDEF-1748600000.123456" }; +const msg = (ts: string, user: string, text: string): ThreadMessage => ({ ts, user, text }); + +class FakeSandboxFs implements SandboxFsLike { + readonly files = new Map(); + writes = 0; + constructor(private readonly home = "/home/agent") {} + async homeDir(): Promise { + return this.home; + } + async readFile(_h: SandboxHandle, absPath: string): Promise { + return this.files.get(absPath) ?? null; + } + async writeFile(_h: SandboxHandle, absPath: string, content: string): Promise { + this.writes += 1; + this.files.set(absPath, content); + } +} + +describe("AgentStateStore — transcript", () => { + it("reads an empty ledger as [] (turn-1 signal)", async () => { + const store = new AgentStateStore(new FakeSandboxFs(), handle); + expect(await store.readTranscript()).toEqual([]); + }); + + it("appends a delta and reads it back under $HOME/.agent-state", async () => { + const fs = new FakeSandboxFs(); + const store = new AgentStateStore(fs, handle); + const delta = [msg("1.1", "U1", "hello"), msg("1.2", "U2", "world")]; + await store.appendDelta(delta); + expect(await store.readTranscript()).toEqual(delta); + expect(fs.files.has("/home/agent/.agent-state/transcript.jsonl")).toBe(true); + }); + + it("appends incrementally, preserving prior lines (read-modify-write)", async () => { + const store = new AgentStateStore(new FakeSandboxFs(), handle); + await store.appendDelta([msg("1.1", "U1", "a")]); + await store.appendDelta([msg("1.2", "U2", "b")]); + expect(await store.readTranscript()).toEqual([msg("1.1", "U1", "a"), msg("1.2", "U2", "b")]); + }); + + it("does not write when the delta is empty (no spurious file)", async () => { + const fs = new FakeSandboxFs(); + await new AgentStateStore(fs, handle).appendDelta([]); + expect(fs.writes).toBe(0); + }); +}); + +describe("AgentStateStore — session id", () => { + it("is null until written, then round-trips (trimmed)", async () => { + const fs = new FakeSandboxFs(); + const store = new AgentStateStore(fs, handle); + expect(await store.readSessionId()).toBeNull(); + await store.writeSessionId("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + expect(await store.readSessionId()).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + // stored with a trailing newline at the canonical path + expect(fs.files.get("/home/agent/.agent-state/session")).toBe( + "7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f\n", + ); + }); +}); + +describe("AgentStateStore — thread reverse map", () => { + it("round-trips the original channel+thread_ts (hash-name fallback, §4.1)", async () => { + const store = new AgentStateStore(new FakeSandboxFs(), handle); + expect(await store.readThread()).toBeNull(); + await store.writeThread("C0ABCDEF 1748600000.123456"); + expect(await store.readThread()).toBe("C0ABCDEF 1748600000.123456"); + }); +}); + +describe("AgentStateStore — home resolution", () => { + it("uses the sandbox's reported $HOME for absolute paths", async () => { + const fs = new FakeSandboxFs("/root"); + const store = new AgentStateStore(fs, handle); + await store.writeSessionId("abc"); + expect(fs.files.has("/root/.agent-state/session")).toBe(true); + }); +}); diff --git a/src/router/agent-state-store.ts b/src/router/agent-state-store.ts new file mode 100644 index 0000000..f26f236 --- /dev/null +++ b/src/router/agent-state-store.ts @@ -0,0 +1,83 @@ +import type { SandboxHandle } from "../sandbox/index.js"; +import type { ThreadMessage } from "../types.js"; +import { + parseTranscript, + SESSION_PATH, + serializeTranscript, + THREAD_PATH, + TRANSCRIPT_PATH, +} from "./agent-state.js"; + +/** + * I/O layer of the per-thread durable state (spec §4.6). Reads/writes the tiny + * `~/.agent-state/` dir that lives inside each thread's sandbox — the router + * keeps no state of its own. A single writer per file is guaranteed by + * per-thread serialization (§4.1/§4.2). + * + * Depends on a narrow sandbox-fs capability (not the full provider) so it fakes + * trivially in tests; the real SbxProvider implements it via `sbx exec`/`cp`. + */ +export interface SandboxFsLike { + /** The sandbox user's `$HOME` (paths under it persist with the VM, §4.6). */ + homeDir(handle: SandboxHandle): Promise; + /** File contents, or null if the file does not exist. */ + readFile(handle: SandboxHandle, absPath: string): Promise; + /** Write contents, creating parent directories as needed. */ + writeFile(handle: SandboxHandle, absPath: string, content: string): Promise; +} + +export class AgentStateStore { + constructor( + private readonly fs: SandboxFsLike, + private readonly handle: SandboxHandle, + ) {} + + private async abs(relPath: string): Promise { + return `${await this.fs.homeDir(this.handle)}/${relPath}`; + } + + /** The seen-message ledger (empty on turn 1, §4.2). */ + async readTranscript(): Promise { + const text = await this.fs.readFile(this.handle, await this.abs(TRANSCRIPT_PATH)); + return text === null ? [] : parseTranscript(text); + } + + /** + * Append the delta after a successful turn (write-after-success, §4.6) so an + * abandoned turn re-feeds cleanly. Read-modify-write of the whole file — the + * ledger is small and has a single writer. + */ + async appendDelta(delta: ThreadMessage[]): Promise { + if (delta.length === 0) { + return; + } + const existing = await this.readTranscript(); + await this.fs.writeFile( + this.handle, + await this.abs(TRANSCRIPT_PATH), + serializeTranscript([...existing, ...delta]), + ); + } + + /** The coding-agent session id captured on turn 1 (§4.5), or null if unset. */ + async readSessionId(): Promise { + const text = await this.fs.readFile(this.handle, await this.abs(SESSION_PATH)); + const trimmed = text?.trim() ?? ""; + return trimmed === "" ? null : trimmed; + } + + async writeSessionId(id: string): Promise { + await this.fs.writeFile(this.handle, await this.abs(SESSION_PATH), `${id}\n`); + } + + /** Original channel+thread_ts, for reverse lookup of a hash-named sandbox (§4.1). */ + async readThread(): Promise { + const text = await this.fs.readFile(this.handle, await this.abs(THREAD_PATH)); + const trimmed = text?.trim() ?? ""; + return trimmed === "" ? null : trimmed; + } + + async writeThread(value: string): Promise { + await this.fs.writeFile(this.handle, await this.abs(THREAD_PATH), `${value}\n`); + } +} From c83fa8807d362d646af1c1d6a011a5ace5edcc9a Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:20:47 -0400 Subject: [PATCH 11/47] =?UTF-8?q?C10:=20SbxProvider=20=E2=80=94=20the=20sb?= =?UTF-8?q?x=20I/O=20shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/sandbox/sbx-provider.ts — spawns `sbx ` and drains; all argv lives in sbx-argv.ts. exec drains stdout+stderr to exit, closes stdin (§9.15), and streams chunks to an onChunk callback for boundary logging (§4.9). Adds execShell (raw shell), list (parses the {sandboxes:[…]} wrapper confirmed live), getFile/putFile via cp+host-tempfile, and homeDir/readFile/writeFile that structurally satisfy SandboxFsLike (no upward imports). Extends the SandboxProvider interface with execShell + per-call streaming options. 14 tests with an injected fake spawn, incl. a virtual sandbox FS that round-trips get/putFile through real temp files. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/sandbox/index.ts | 19 ++- src/sandbox/sbx-provider.test.ts | 214 +++++++++++++++++++++++++++++++ src/sandbox/sbx-provider.ts | 181 ++++++++++++++++++++++++++ 3 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 src/sandbox/sbx-provider.test.ts create mode 100644 src/sandbox/sbx-provider.ts diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index 03cedb7..98a7f7d 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -19,11 +19,26 @@ export interface ExecResult { exitCode: number; } +/** Streaming options for a single exec call (§4.1/§4.9). */ +export interface ExecCallOptions { + /** Receive raw stdout/stderr chunks as they arrive (the router logs them, §4.9). */ + onChunk?: (stream: "stdout" | "stderr", chunk: string) => void; + /** Optional input to write before closing stdin. Stdin is always closed (§9.15). */ + stdin?: string; +} + export interface SandboxProvider { create(name: string, repoRef: string): Promise; - /** Drive the agent CLI per turn; the router streams and logs this raw output (§4.9). */ - exec(handle: SandboxHandle, argv: string[]): Promise; + /** + * Drive the agent CLI per turn (argv wrapped in `bash -c`); the router streams + * and logs this raw output (§4.9). The reader drains stdout+stderr to exit and + * closes stdin (§9.15). + */ + exec(handle: SandboxHandle, argv: string[], opts?: ExecCallOptions): Promise; + + /** Run a raw shell command (utility ops: file reads, mkdir, $HOME, find). */ + execShell(handle: SandboxHandle, command: string, opts?: ExecCallOptions): Promise; getFile(handle: SandboxHandle, path: string): Promise; putFile(handle: SandboxHandle, path: string, contents: Uint8Array): Promise; diff --git a/src/sandbox/sbx-provider.test.ts b/src/sandbox/sbx-provider.test.ts new file mode 100644 index 0000000..cca443d --- /dev/null +++ b/src/sandbox/sbx-provider.test.ts @@ -0,0 +1,214 @@ +import { EventEmitter } from "node:events"; +import { readFileSync, writeFileSync } from "node:fs"; +import type { SandboxHandle } from "./index.js"; +import { type ChildLike, SbxProvider, type SpawnFn } from "./sbx-provider.js"; + +class FakeStdin { + written = ""; + ended = false; + write(chunk: string): boolean { + this.written += chunk; + return true; + } + end(): void { + this.ended = true; + } +} + +class FakeChild extends EventEmitter implements ChildLike { + stdout = new EventEmitter(); + stderr = new EventEmitter(); + stdin = new FakeStdin(); +} + +type Resp = { stdout?: string; stderr?: string; code?: number; error?: Error }; + +function fakeSpawn(responder: (args: string[]) => Resp): { + spawnFn: SpawnFn; + calls: { args: string[]; stdin: FakeStdin }[]; +} { + const calls: { args: string[]; stdin: FakeStdin }[] = []; + const spawnFn: SpawnFn = (_command, args) => { + const child = new FakeChild(); + calls.push({ args, stdin: child.stdin }); + const r = responder(args); + setImmediate(() => { + if (r.error) { + child.emit("error", r.error); + return; + } + if (r.stdout) { + child.stdout.emit("data", Buffer.from(r.stdout)); + } + if (r.stderr) { + child.stderr.emit("data", Buffer.from(r.stderr)); + } + child.emit("close", r.code ?? 0); + }); + return child; + }; + return { spawnFn, calls }; +} + +const handle: SandboxHandle = { name: "t-C0ABCDEF-1748600000.123456" }; + +describe("exec — drain, stdin close, streaming (§4.9/§9.15)", () => { + it("wraps argv in bash -c (no -i), drains stdout+stderr, closes stdin, streams chunks", async () => { + let recorded: string[] = []; + let stdin: FakeStdin | undefined; + const spawnFn: SpawnFn = (_c, args) => { + recorded = args; + const child = new FakeChild(); + stdin = child.stdin; + setImmediate(() => { + child.stdout.emit("data", Buffer.from("Fixed ")); + child.stdout.emit("data", Buffer.from("the bug")); + child.stderr.emit("data", Buffer.from("progress\n")); + child.emit("close", 0); + }); + return child; + }; + const provider = new SbxProvider({ spawnFn }); + const chunks: string[] = []; + const result = await provider.exec(handle, ["codex", "exec", "fix the bug"], { + onChunk: (stream, chunk) => chunks.push(`${stream}:${chunk}`), + }); + + expect(result).toEqual({ stdout: "Fixed the bug", stderr: "progress\n", exitCode: 0 }); + expect(recorded).toEqual(["exec", handle.name, "--", "bash", "-c", "codex exec 'fix the bug'"]); + expect(recorded).not.toContain("-i"); + expect(stdin?.ended).toBe(true); + expect(chunks).toEqual(["stdout:Fixed ", "stdout:the bug", "stderr:progress\n"]); + }); + + it("propagates a non-zero exit code without throwing (agent failures interpreted upstream)", async () => { + const { spawnFn } = fakeSpawn(() => ({ stdout: "", code: 3 })); + const provider = new SbxProvider({ spawnFn }); + expect((await provider.exec(handle, ["codex", "exec", "x"])).exitCode).toBe(3); + }); + + it("rejects when the process emits an error (e.g. sbx not found)", async () => { + const { spawnFn } = fakeSpawn(() => ({ error: new Error("ENOENT sbx") })); + const provider = new SbxProvider({ spawnFn }); + await expect(provider.exec(handle, ["codex"])).rejects.toThrow(/ENOENT/); + }); +}); + +describe("create / stop / destroy / list", () => { + it("create uses --clone + --name and returns a handle on success", async () => { + const { spawnFn, calls } = fakeSpawn(() => ({ code: 0 })); + const provider = new SbxProvider({ spawnFn }); + expect(await provider.create("t-C0-1.2", "/repo")).toEqual({ name: "t-C0-1.2" }); + expect(calls[0]?.args).toEqual(["create", "--clone", "--name", "t-C0-1.2", "codex", "/repo"]); + }); + + it("create throws on non-zero exit, surfacing stderr", async () => { + const { spawnFn } = fakeSpawn(() => ({ stderr: "name already exists", code: 1 })); + const provider = new SbxProvider({ spawnFn }); + await expect(provider.create("box", "/repo")).rejects.toThrow(/name already exists/); + }); + + it("list parses the { sandboxes: [...] } wrapper into handles", async () => { + const { spawnFn, calls } = fakeSpawn(() => ({ + stdout: JSON.stringify({ sandboxes: [{ name: "a" }, { name: "b" }] }), + })); + const provider = new SbxProvider({ spawnFn }); + expect(await provider.list()).toEqual([{ name: "a" }, { name: "b" }]); + expect(calls[0]?.args).toEqual(["ls", "--json"]); + }); + + it("stop and destroy build the right argv (rm --force for non-interactive)", async () => { + const { spawnFn, calls } = fakeSpawn(() => ({ code: 0 })); + const provider = new SbxProvider({ spawnFn }); + await provider.stop(handle); + await provider.destroy(handle); + expect(calls[0]?.args).toEqual(["stop", handle.name]); + expect(calls[1]?.args).toEqual(["rm", "--force", handle.name]); + }); +}); + +describe("execShell + SandboxFsLike", () => { + it("execShell runs `bash -c ` verbatim (no shellJoin)", async () => { + const { spawnFn, calls } = fakeSpawn(() => ({ stdout: "" })); + const provider = new SbxProvider({ spawnFn }); + await provider.execShell(handle, "find $HOME -name '*.jsonl'"); + expect(calls[0]?.args).toEqual([ + "exec", + handle.name, + "--", + "bash", + "-c", + "find $HOME -name '*.jsonl'", + ]); + }); + + it("homeDir trims the reported $HOME", async () => { + const { spawnFn } = fakeSpawn(() => ({ stdout: "/root" })); + expect(await new SbxProvider({ spawnFn }).homeDir(handle)).toBe("/root"); + }); + + it("readFile returns content on exit 0 and null on non-zero (missing file)", async () => { + const present = new SbxProvider({ + spawnFn: fakeSpawn(() => ({ stdout: "data", code: 0 })).spawnFn, + }); + expect(await present.readFile(handle, "/home/u/.agent-state/session")).toBe("data"); + const missing = new SbxProvider({ + spawnFn: fakeSpawn(() => ({ stderr: "No such file", code: 1 })).spawnFn, + }); + expect(await missing.readFile(handle, "/home/u/.agent-state/session")).toBeNull(); + }); +}); + +describe("getFile / putFile — cp + host temp file round-trip", () => { + // A virtual sandbox FS; the fake cp moves bytes between host temp files and it. + function vfsSpawn(vfs: Map) { + return fakeSpawn((args) => { + if (args[0] !== "cp") { + return { code: 0 }; + } + const src = args[1] ?? ""; + const dst = args[2] ?? ""; + if (src.includes(":") && !dst.includes(":")) { + const content = vfs.get(src); + if (content === undefined) { + return { stderr: "no such file", code: 1 }; + } + writeFileSync(dst, content); + return { code: 0 }; + } + if (!src.includes(":") && dst.includes(":")) { + vfs.set(dst, readFileSync(src, "utf8")); + return { code: 0 }; + } + return { code: 1, stderr: "bad cp" }; + }); + } + + it("putFile writes then getFile reads the same bytes (via cp tempfiles)", async () => { + const vfs = new Map(); + const { spawnFn, calls } = vfsSpawn(vfs); + const provider = new SbxProvider({ spawnFn }); + const path = "/home/agent/.agent-state/session"; + + await provider.putFile(handle, path, new TextEncoder().encode("sess-123\n")); + const got = await provider.getFile(handle, path); + expect(new TextDecoder().decode(got)).toBe("sess-123\n"); + + // cp addressed the sandbox path as name:path on both legs + const cpCalls = calls.filter((c) => c.args[0] === "cp"); + expect(cpCalls.some((c) => c.args[2] === `${handle.name}:${path}`)).toBe(true); + expect(cpCalls.some((c) => c.args[1] === `${handle.name}:${path}`)).toBe(true); + }); + + it("writeFile mkdir -p's the parent dir before putting the file", async () => { + const vfs = new Map(); + const { spawnFn, calls } = vfsSpawn(vfs); + const provider = new SbxProvider({ spawnFn }); + await provider.writeFile(handle, "/home/agent/.agent-state/session", "abc\n"); + const shellCmds = calls + .filter((c) => c.args[0] === "exec") + .map((c) => c.args[c.args.length - 1]); + expect(shellCmds.some((cmd) => cmd?.startsWith("mkdir -p"))).toBe(true); + expect(vfs.get(`${handle.name}:/home/agent/.agent-state/session`)).toBe("abc\n"); + }); +}); diff --git a/src/sandbox/sbx-provider.ts b/src/sandbox/sbx-provider.ts new file mode 100644 index 0000000..2d4b939 --- /dev/null +++ b/src/sandbox/sbx-provider.ts @@ -0,0 +1,181 @@ +import { spawn as nodeSpawn } from "node:child_process"; +import { randomUUID } from "node:crypto"; +import { readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { ExecCallOptions, ExecResult, SandboxHandle, SandboxProvider } from "./index.js"; +import { + type CreateOptions, + cpArgv, + createArgv, + execAgentArgv, + execArgv, + lsArgv, + parseLsJson, + remotePath, + rmArgv, + shellQuote, + stopArgv, +} from "./sbx-argv.js"; + +/** + * SandboxProvider over the `sbx` CLI (spec §4.4) — the I/O shell. All argv + * construction lives in sbx-argv.ts; this file only spawns and drains. The + * spawn fn is injected so the streaming/stdin/exit handling is unit-testable + * without the daemon. + * + * Also provides homeDir/readFile/writeFile (structurally satisfying the router's + * SandboxFsLike) and execShell (the codex backend's SandboxShellExecutor) — no + * upward imports, just compatible method shapes. + */ + +/** Minimal child-process surface the drain loop needs (real ChildProcess fits). */ +export interface ChildLike { + stdout: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null; + stderr: { on(event: "data", listener: (chunk: unknown) => void): unknown } | null; + stdin: { write(chunk: string): unknown; end(): unknown } | null; + on(event: "close", listener: (code: number | null) => void): unknown; + on(event: "error", listener: (err: Error) => void): unknown; +} + +export type SpawnFn = (command: string, args: string[]) => ChildLike; + +const defaultSpawn: SpawnFn = (command, args) => + nodeSpawn(command, args, { stdio: ["pipe", "pipe", "pipe"] }); + +export interface SbxProviderConfig { + spawnFn?: SpawnFn; + bin?: string; + createOptions?: CreateOptions; + /** `bash -lc` to source profile-level env if `-c` isn't enough (§4.5/§9.9). */ + login?: boolean; + /** Wrap the agent command in a PTY (codex empty-output mitigation, §9.14). */ + pty?: boolean; +} + +export class SbxProvider implements SandboxProvider { + private readonly spawnFn: SpawnFn; + private readonly bin: string; + private readonly createOptions: CreateOptions; + private readonly login: boolean; + private readonly pty: boolean; + + constructor(config: SbxProviderConfig = {}) { + this.spawnFn = config.spawnFn ?? defaultSpawn; + this.bin = config.bin ?? "sbx"; + this.createOptions = config.createOptions ?? {}; + this.login = config.login ?? false; + this.pty = config.pty ?? false; + } + + /** Spawn `sbx `, drain stdout+stderr to exit, close stdin (§9.15). */ + private run(argv: string[], opts: ExecCallOptions = {}): Promise { + return new Promise((resolve, reject) => { + const child = this.spawnFn(this.bin, argv); + let stdout = ""; + let stderr = ""; + child.stdout?.on("data", (chunk) => { + const text = String(chunk); + stdout += text; + opts.onChunk?.("stdout", text); + }); + child.stderr?.on("data", (chunk) => { + const text = String(chunk); + stderr += text; + opts.onChunk?.("stderr", text); + }); + child.on("error", reject); + child.on("close", (code) => resolve({ stdout, stderr, exitCode: code ?? -1 })); + // Close stdin so a headless agent can't hang waiting on it (§9.15). + if (opts.stdin !== undefined) { + child.stdin?.write(opts.stdin); + } + child.stdin?.end(); + }); + } + + private async runOrThrow(argv: string[], what: string): Promise { + const result = await this.run(argv); + if (result.exitCode !== 0) { + throw new Error(`sbx ${what} failed (exit ${result.exitCode}): ${result.stderr.trim()}`); + } + return result; + } + + async create(name: string, repoRef: string): Promise { + await this.runOrThrow(createArgv(name, repoRef, this.createOptions), `create ${name}`); + return { name }; + } + + exec(handle: SandboxHandle, argv: string[], opts: ExecCallOptions = {}): Promise { + return this.run(execAgentArgv(handle.name, argv, { login: this.login, pty: this.pty }), opts); + } + + execShell( + handle: SandboxHandle, + command: string, + opts: ExecCallOptions = {}, + ): Promise { + return this.run(execArgv(handle.name, command, { login: this.login }), opts); + } + + async stop(handle: SandboxHandle): Promise { + await this.runOrThrow(stopArgv(handle.name), `stop ${handle.name}`); + } + + async destroy(handle: SandboxHandle): Promise { + await this.runOrThrow(rmArgv([handle.name], { force: true }), `rm ${handle.name}`); + } + + async list(): Promise { + const { stdout } = await this.runOrThrow(lsArgv(), "ls"); + return parseLsJson(stdout).map((entry) => ({ name: entry.name })); + } + + // --- SandboxFsLike (structural) --------------------------------------- + + async homeDir(handle: SandboxHandle): Promise { + const { stdout } = await this.execShell(handle, 'printf %s "$HOME"'); + return stdout.trim() || "/root"; + } + + /** File contents, or null if the file does not exist (non-zero `cat` exit). */ + async readFile(handle: SandboxHandle, absPath: string): Promise { + const { stdout, exitCode } = await this.execShell(handle, `cat ${shellQuote(absPath)}`); + return exitCode === 0 ? stdout : null; + } + + async writeFile(handle: SandboxHandle, absPath: string, content: string): Promise { + const dir = absPath.slice(0, absPath.lastIndexOf("/")); + if (dir !== "") { + await this.execShell(handle, `mkdir -p ${shellQuote(dir)}`); + } + await this.putFile(handle, absPath, new TextEncoder().encode(content)); + } + + // --- binary file transfer via `sbx cp` + a host temp file ------------- + + private tempPath(): string { + return join(tmpdir(), `sbx-${randomUUID()}`); + } + + async getFile(handle: SandboxHandle, path: string): Promise { + const tmp = this.tempPath(); + await this.runOrThrow(cpArgv(remotePath(handle.name, path), tmp), `cp (get) ${path}`); + try { + return await readFile(tmp); + } finally { + await rm(tmp, { force: true }); + } + } + + async putFile(handle: SandboxHandle, path: string, contents: Uint8Array): Promise { + const tmp = this.tempPath(); + await writeFile(tmp, contents); + try { + await this.runOrThrow(cpArgv(tmp, remotePath(handle.name, path)), `cp (put) ${path}`); + } finally { + await rm(tmp, { force: true }); + } + } +} From aaaa00bd3e83520f9d8449ea544d3f3d42b006b5 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:27:48 -0400 Subject: [PATCH 12/47] =?UTF-8?q?C11:=20dispatcher=20=E2=80=94=20the=20one?= =?UTF-8?q?-turn=20lifecycle=20(keystone)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/dispatcher.ts — ties the three seams together for a single turn (spec §4.1/§4.5/§4.9): resolve-or-create sandbox by deterministic name, turn-1-vs-resume by the session file (§4.5), whole-thread primer vs post-hwm delta (§4.2), exec with a stdout+stderr-draining reader that logs at the boundary (§4.9), parseResult, then write-after-success — session persisted BEFORE the reply, transcript appended AFTER it (§4.6) — and a 👀→✅/❌ swap. dispatchTurn never rejects so the router's coalescing always advances. This is the logical stand-in for the un-runnable E2E: 13 tests over fakes for all seams cover turn-1 ordering, sandbox reuse, session-id capture + fallback, follow-up resume + delta boundary, failure (no persist, re-feed), empty-delta skip, abandon-on-throw, and verbose chunk logging. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/dispatcher.test.ts | 306 ++++++++++++++++++++++++++++++++++ src/router/dispatcher.ts | 243 +++++++++++++++++++++++++++ 2 files changed, 549 insertions(+) create mode 100644 src/router/dispatcher.test.ts create mode 100644 src/router/dispatcher.ts diff --git a/src/router/dispatcher.test.ts b/src/router/dispatcher.test.ts new file mode 100644 index 0000000..9e369b8 --- /dev/null +++ b/src/router/dispatcher.test.ts @@ -0,0 +1,306 @@ +import type { CodingBackend, TurnResult } from "../backend/index.js"; +import type { ExecCallOptions, ExecResult, SandboxHandle } from "../sandbox/index.js"; +import type { Mention, ThreadId, ThreadMessage } from "../types.js"; +import { serializeTranscript } from "./agent-state.js"; +import { Dispatcher, formatPrompt } from "./dispatcher.js"; +import { createRecordingSink } from "./log.js"; + +const msg = (ts: string, user: string, text: string): ThreadMessage => ({ ts, user, text }); +const trigger: Mention = { + thread: { channel: "C0ABCDEF", threadTs: "1748600000.100000" }, + ts: "1748600000.500000", + user: "U1", + text: "@bot fix the bug", +}; +const SANDBOX = "t-C0ABCDEF-1748600000.100000"; +const HOME = "/home/agent"; + +class FakePlatform { + replies: string[] = []; + constructor( + private readonly trace: string[], + private readonly whole: ThreadMessage[], + private readonly delta: ThreadMessage[] = [], + ) {} + async fetchThread(_thread: ThreadId, sinceTs?: string): Promise { + this.trace.push(`fetch:${sinceTs ?? "all"}`); + return sinceTs === undefined ? this.whole : this.delta; + } + async postReply(_thread: ThreadId, text: string): Promise { + this.trace.push("reply"); + this.replies.push(text); + } + async addReaction(_c: string, _ts: string, emoji: string): Promise { + this.trace.push(`+${emoji}`); + } + async removeReaction(_c: string, _ts: string, emoji: string): Promise { + this.trace.push(`-${emoji}`); + } + async uploadFile(): Promise {} +} + +class FakeSandbox { + readonly files = new Map(); + readonly existing: SandboxHandle[]; + execArgs: string[][] = []; + constructor( + private readonly trace: string[], + private readonly execResult: ExecResult, + existing: string[] = [], + ) { + this.existing = existing.map((name) => ({ name })); + } + async list(): Promise { + return this.existing; + } + async create(name: string, _repoRef: string): Promise { + this.trace.push(`create:${name}`); + this.existing.push({ name }); + return { name }; + } + async exec(_h: SandboxHandle, argv: string[], opts?: ExecCallOptions): Promise { + this.trace.push("exec"); + this.execArgs.push(argv); + opts?.onChunk?.("stdout", this.execResult.stdout); + opts?.onChunk?.("stderr", this.execResult.stderr); + return this.execResult; + } + async execShell(_h: SandboxHandle, command: string): Promise { + this.trace.push(`shell:${command.split(" ")[0]}`); + return { stdout: "", stderr: "", exitCode: 0 }; + } + async homeDir(): Promise { + return HOME; + } + async readFile(_h: SandboxHandle, absPath: string): Promise { + return this.files.get(absPath) ?? null; + } + async writeFile(_h: SandboxHandle, absPath: string, content: string): Promise { + this.trace.push(`write:${absPath.split("/").pop()}`); + this.files.set(absPath, content); + } + async getFile(): Promise { + return new Uint8Array(); + } + async putFile(): Promise {} + async stop(): Promise {} + async destroy(): Promise {} +} + +class FakeBackend implements CodingBackend { + turnArgsCalls: (string | undefined)[] = []; + constructor( + private readonly trace: string[], + private readonly result: TurnResult, + private readonly streamId: string | null = "sess-new", + ) {} + configHome(): string { + return "~/.codex"; + } + turnArgs(message: string, sessionId?: string): string[] { + this.turnArgsCalls.push(sessionId); + return ["codex", "exec", ...(sessionId ? ["resume", sessionId] : []), message]; + } + parseResult(): TurnResult { + return this.result; + } + parseSessionId(): string | null { + return this.streamId; + } + async captureSessionId(): Promise { + this.trace.push("captureSessionId"); + return "captured-id"; + } + events(): unknown[] { + return []; + } +} + +interface Built { + dispatcher: Dispatcher; + trace: string[]; + platform: FakePlatform; + sandbox: FakeSandbox; + backend: FakeBackend; + sink: ReturnType; +} + +function build(opts: { + whole?: ThreadMessage[]; + delta?: ThreadMessage[]; + exec?: ExecResult; + result?: TurnResult; + existing?: string[]; + streamId?: string | null; + seedFiles?: Record; + level?: "summary" | "verbose"; +}): Built { + const trace: string[] = []; + const platform = new FakePlatform( + trace, + opts.whole ?? [msg("1748600000.100000", "U1", "start"), msg("1748600000.500000", "U1", "fix")], + opts.delta ?? [], + ); + const sandbox = new FakeSandbox( + trace, + opts.exec ?? { stdout: '{"type":"item.completed"}', stderr: "", exitCode: 0 }, + opts.existing ?? [], + ); + for (const [path, content] of Object.entries(opts.seedFiles ?? {})) { + sandbox.files.set(path, content); + } + const backend = new FakeBackend( + trace, + opts.result ?? { finalText: "Fixed it.", ok: true }, + opts.streamId === undefined ? "sess-new" : opts.streamId, + ); + const sink = createRecordingSink(); + const dispatcher = new Dispatcher({ + platform, + sandbox, + backend, + repoRef: "/repo", + logSink: sink, + level: opts.level ?? "summary", + now: () => 1_700_000_000_000, + newTurnId: () => "turn-test", + }); + return { dispatcher, trace, platform, sandbox, backend, sink }; +} + +describe("formatPrompt", () => { + it("renders messages plainly, no editorializing (router is plumbing)", () => { + expect(formatPrompt([msg("1", "U1", "a"), msg("2", "U2", "b")])).toBe("U1: a\n\nU2: b"); + }); +}); + +describe("dispatchTurn — turn 1 happy path", () => { + it("creates the sandbox, primes, execs, persists, relays, and ✅s — in order", async () => { + const b = build({}); + const out = await b.dispatcher.dispatchTurn(trigger); + + expect(out).toEqual({ ok: true }); + expect(b.platform.replies).toEqual(["Fixed it."]); + expect(b.backend.turnArgsCalls).toEqual([undefined]); // fresh turn + + const t = b.trace; + const at = (label: string) => t.indexOf(label); + expect(at(`create:${SANDBOX}`)).toBeGreaterThanOrEqual(0); + expect(at("fetch:all")).toBeGreaterThan(at(`create:${SANDBOX}`)); // whole-thread primer + expect(at("exec")).toBeGreaterThan(at("fetch:all")); + // session persisted BEFORE the reply; transcript appended AFTER it (§4.6) + expect(at("write:session")).toBeGreaterThan(at("exec")); + expect(at("write:session")).toBeLessThan(at("reply")); + expect(at("write:transcript.jsonl")).toBeGreaterThan(at("reply")); + // 👀 -> ✅ swap is last + expect(t.slice(-2)).toEqual(["-eyes", "+white_check_mark"]); + }); + + it("reuses an existing sandbox instead of creating one", async () => { + const b = build({ existing: [SANDBOX] }); + await b.dispatcher.dispatchTurn(trigger); + expect(b.trace).not.toContain(`create:${SANDBOX}`); + expect(b.trace).toContain("exec"); + }); + + it("falls back to captureSessionId when the stream has no id", async () => { + const b = build({ streamId: null }); + await b.dispatcher.dispatchTurn(trigger); + expect(b.trace).toContain("captureSessionId"); + expect(b.sandbox.files.get(`${HOME}/.agent-state/session`)).toBe("captured-id\n"); + }); + + it("emits boundary logs: sandbox.create, turn.in, turn.exit, result.relayed", async () => { + const b = build({}); + await b.dispatcher.dispatchTurn(trigger); + const kinds = b.sink.records.map((r) => r.kind); + expect(kinds).toContain("sandbox.create"); + expect(kinds).toContain("turn.in"); + expect(kinds).toContain("turn.exit"); + expect(kinds).toContain("result.relayed"); + const turnIn = b.sink.records.find((r) => r.kind === "turn.in"); + expect(turnIn?.sessionId).toBeNull(); + expect(turnIn?.resume).toBe(false); + }); +}); + +describe("dispatchTurn — follow-up (resume)", () => { + it("resumes the session and feeds only the delta after the hwm", async () => { + const seeded = serializeTranscript([ + msg("1748600000.100000", "U1", "start"), + msg("1748600000.500000", "U1", "fix"), + ]); + const b = build({ + existing: [SANDBOX], + seedFiles: { + [`${HOME}/.agent-state/session`]: "sess-1\n", + [`${HOME}/.agent-state/transcript.jsonl`]: seeded, + }, + delta: [msg("1748600000.700000", "U2", "also add a test")], + }); + const out = await b.dispatcher.dispatchTurn(trigger); + + expect(out).toEqual({ ok: true }); + expect(b.backend.turnArgsCalls).toEqual(["sess-1"]); // resume + expect(b.trace).toContain("fetch:1748600000.500000"); // sinceTs = hwm + expect(b.trace).not.toContain("write:session"); // not turn 1 + expect(b.trace).toContain("write:transcript.jsonl"); // delta appended + }); +}); + +describe("dispatchTurn — failure & skip", () => { + it("on !ok: posts the failure message, ❌s, and does NOT persist (re-feed)", async () => { + const b = build({ result: { finalText: "", ok: false } }); + const out = await b.dispatcher.dispatchTurn(trigger); + + expect(out).toEqual({ ok: false }); + expect(b.platform.replies[0]).toMatch(/didn't return a result/); + expect(b.trace).not.toContain("write:session"); + expect(b.trace).not.toContain("write:transcript.jsonl"); + expect(b.trace.slice(-2)).toEqual(["-eyes", "+x"]); + expect(b.sink.records.map((r) => r.kind)).toContain("turn.abandoned"); + }); + + it("on empty delta: skips exec entirely and ✅s", async () => { + const b = build({ + existing: [SANDBOX], + seedFiles: { + [`${HOME}/.agent-state/session`]: "sess-1\n", + // a transcript sets the hwm so the follow-up fetch uses sinceTs (-> empty delta) + [`${HOME}/.agent-state/transcript.jsonl`]: serializeTranscript([ + msg("1748600000.500000", "U1", "fix"), + ]), + }, + delta: [], // nothing new since the hwm + }); + const out = await b.dispatcher.dispatchTurn(trigger); + expect(out).toEqual({ ok: true }); + expect(b.trace).not.toContain("exec"); + expect(b.trace.slice(-2)).toEqual(["-eyes", "+white_check_mark"]); + }); + + it("never rejects, even when a seam throws (turn abandoned, ❌)", async () => { + const b = build({}); + b.sandbox.list = async () => { + throw new Error("daemon down"); + }; + const out = await b.dispatcher.dispatchTurn(trigger); + expect(out).toEqual({ ok: false }); + expect(b.sink.records.map((r) => r.kind)).toContain("turn.abandoned"); + }); +}); + +describe("dispatchTurn — verbose logging", () => { + it("streams raw out chunks only at verbose level", async () => { + const verbose = build({ + level: "verbose", + exec: { stdout: "raw-bytes", stderr: "", exitCode: 0 }, + }); + await verbose.dispatcher.dispatchTurn(trigger); + expect(verbose.sink.records.some((r) => r.kind === "turn.out.chunk")).toBe(true); + + const summary = build({}); + await summary.dispatcher.dispatchTurn(trigger); + expect(summary.sink.records.some((r) => r.kind === "turn.out.chunk")).toBe(false); + }); +}); diff --git a/src/router/dispatcher.ts b/src/router/dispatcher.ts new file mode 100644 index 0000000..1f2907b --- /dev/null +++ b/src/router/dispatcher.ts @@ -0,0 +1,243 @@ +import { randomUUID } from "node:crypto"; +import type { CodingBackend } from "../backend/index.js"; +import type { PlatformAdapter } from "../platform/index.js"; +import type { SandboxHandle, SandboxProvider } from "../sandbox/index.js"; +import type { Mention, ThreadId, ThreadMessage } from "../types.js"; +import { computeDelta, highWaterMark } from "./agent-state.js"; +import { AgentStateStore, type SandboxFsLike } from "./agent-state-store.js"; +import { + buildOutChunk, + buildRouterEvent, + buildTurnExit, + buildTurnIn, + type LogContext, + type LogLevel, + type LogSink, + nullSink, + RouterKind, + safeWrite, +} from "./log.js"; +import { chooseSandboxName } from "./sandbox-name.js"; + +/** + * The one-turn lifecycle (spec §4.1/§4.5/§4.9) — the keystone that ties the + * three seams together for a single turn. Order matters: + * + * resolve-or-create sandbox (deterministic name) → turn-1-vs-resume (session + * file, §4.5) → fetch primer/delta (§4.2) → exec (reader drains stdout+stderr, + * stays attached, logs at the boundary §4.9) → parseResult → write-after-success + * (persist session BEFORE posting, append transcript AFTER the reply, §4.6) → + * relay → swap 👀→✅/❌. + * + * Reactions: the router adds 👀 on every mention; the dispatcher swaps the + * trigger's 👀 to ✅/❌ on completion. dispatchTurn never rejects — it resolves + * with {ok} so the router's coalescing always advances. + */ + +export interface DispatcherDeps { + platform: Pick; + /** Provider (create/exec/list) + file helpers for the agent-state store. */ + sandbox: SandboxProvider & SandboxFsLike; + backend: CodingBackend; + repoRef: string; + logSink?: LogSink; + /** The bot's own Slack user id; its posts never re-enter the delta (§4.2). */ + botUser?: string; + level?: LogLevel; + failureMessage?: string; + /** Optional in-VM bootstrap run once after create (branch off base, §4.3; confirm live). */ + provisionScript?: string; + now?: () => number; + newTurnId?: () => string; +} + +const DEFAULT_FAILURE = + "⚠️ The coding agent didn't return a result for that turn. Your request is still queued — mention me again to retry."; + +/** Render messages into a single prompt. Plumbing, not authorship — no editorializing. */ +export function formatPrompt(messages: ThreadMessage[]): string { + return messages.map((m) => `${m.user}: ${m.text}`).join("\n\n"); +} + +export class Dispatcher { + private readonly platform: DispatcherDeps["platform"]; + private readonly sandbox: SandboxProvider & SandboxFsLike; + private readonly backend: CodingBackend; + private readonly repoRef: string; + private readonly logSink: LogSink; + private readonly botUser: string | undefined; + private readonly level: LogLevel; + private readonly failureMessage: string; + private readonly provisionScript: string | undefined; + private readonly now: () => number; + private readonly newTurnId: () => string; + + constructor(deps: DispatcherDeps) { + this.platform = deps.platform; + this.sandbox = deps.sandbox; + this.backend = deps.backend; + this.repoRef = deps.repoRef; + this.logSink = deps.logSink ?? nullSink; + this.botUser = deps.botUser; + this.level = deps.level ?? "summary"; + this.failureMessage = deps.failureMessage ?? DEFAULT_FAILURE; + this.provisionScript = deps.provisionScript; + this.now = deps.now ?? Date.now; + this.newTurnId = deps.newTurnId ?? (() => randomUUID()); + } + + async dispatchTurn(trigger: Mention): Promise<{ ok: boolean }> { + const thread = trigger.thread; + const name = chooseSandboxName(thread); + const ctx: LogContext = { + threadId: name, + channel: thread.channel, + thread_ts: thread.threadTs, + sandbox: name, + turnId: this.newTurnId(), + }; + try { + const handle = await this.resolveSandbox(thread, name, ctx); + const store = new AgentStateStore(this.sandbox, handle); + + // Turn-1 vs resume is the session file's presence (§4.5). + const sessionId = await store.readSessionId(); + const delta = await this.assembleDelta(thread, store, sessionId); + if (delta.length === 0) { + this.router(ctx, "turn.skipped", { reason: "empty delta" }); + await this.swapReaction(trigger, true); + return { ok: true }; + } + + const prompt = formatPrompt(delta); + const argv = this.backend.turnArgs(prompt, sessionId ?? undefined); + this.write( + buildTurnIn(ctx, this.ts(), { + sessionId, + argv, + cwd: this.repoRef, + prompt, + level: this.level, + }), + ); + + const start = this.now(); + const { stdout, stderr, exitCode } = await this.sandbox.exec(handle, argv, { + onChunk: (stream, chunk) => { + if (this.level === "verbose") { + this.write(buildOutChunk(ctx, this.ts(), { stream, chunk })); + } + }, + }); + const result = this.backend.parseResult(stdout); + this.write( + buildTurnExit(ctx, this.ts(), { + exitCode, + durationMs: this.now() - start, + finalSnippet: result.finalText, + ok: result.ok, + }), + ); + + if (!result.ok) { + this.router(ctx, RouterKind.TurnAbandoned, { + reason: "agent returned no result", + exitCode, + stderrSnippet: stderr.slice(0, 500), + }); + await this.relay(thread, this.failureMessage); + await this.swapReaction(trigger, false); + return { ok: false }; + } + + // Write-after-success (§4.6): persist the session id BEFORE posting (so a + // crash still resumes), append the transcript AFTER the reply (so a failed + // post re-feeds — at-least-once). + if (sessionId === null) { + const id = + this.backend.parseSessionId?.(stdout) ?? (await this.backend.captureSessionId(handle)); + await store.writeSessionId(id); + } + await this.relay(thread, result.finalText); + await store.appendDelta(delta); + await this.swapReaction(trigger, true); + this.router(ctx, RouterKind.ResultRelayed, { ok: true, chars: result.finalText.length }); + return { ok: true }; + } catch (err) { + this.router(ctx, RouterKind.TurnAbandoned, { error: errorMessage(err) }); + await this.swapReaction(trigger, false); + return { ok: false }; + } + } + + /** Find the thread's sandbox by deterministic name, or create + seed it (§4.1/§4.3). */ + private async resolveSandbox( + thread: ThreadId, + name: string, + ctx: LogContext, + ): Promise { + const existing = (await this.sandbox.list()).find((h) => h.name === name); + if (existing !== undefined) { + return existing; + } + this.router(ctx, RouterKind.SandboxCreate, { name }); + const handle = await this.sandbox.create(name, this.repoRef); + // Reverse-map the full id so a hash-named sandbox is reversible (§4.1/§4.6). + await new AgentStateStore(this.sandbox, handle).writeThread( + `${thread.channel} ${thread.threadTs}`, + ); + if (this.provisionScript !== undefined) { + await this.sandbox.execShell(handle, this.provisionScript); + } + return handle; + } + + /** Turn 1 (no session): whole-thread primer. Follow-up: messages after the hwm. */ + private async assembleDelta( + thread: ThreadId, + store: AgentStateStore, + sessionId: string | null, + ): Promise { + if (sessionId === null) { + const fetched = await this.platform.fetchThread(thread); + return computeDelta(fetched, null, { botUser: this.botUser }); + } + const hwm = highWaterMark(await store.readTranscript()); + const fetched = await this.platform.fetchThread(thread, hwm ?? undefined); + return computeDelta(fetched, hwm, { botUser: this.botUser }); + } + + private async relay(thread: ThreadId, text: string): Promise { + await this.platform.postReply(thread, text); + } + + /** Best-effort 👀→✅/❌ swap on the trigger (reactions never fail a turn, §4.1). */ + private async swapReaction(trigger: Mention, ok: boolean): Promise { + try { + await this.platform.removeReaction(trigger.thread.channel, trigger.ts, "eyes"); + await this.platform.addReaction( + trigger.thread.channel, + trigger.ts, + ok ? "white_check_mark" : "x", + ); + } catch { + // best-effort + } + } + + private ts(): string { + return new Date(this.now()).toISOString(); + } + + private write(env: ReturnType): void { + safeWrite(this.logSink, env); + } + + private router(ctx: LogContext, kind: string, payload: Record): void { + safeWrite(this.logSink, buildRouterEvent(ctx, this.ts(), kind, payload)); + } +} + +function errorMessage(err: unknown): string { + return err instanceof Error ? err.message : String(err); +} From cb9a554e9a2ad2e80b3f3389e0cfbd6c819b2ade Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:30:58 -0400 Subject: [PATCH 13/47] =?UTF-8?q?C12:=20router=20glue=20=E2=80=94=20dedupe?= =?UTF-8?q?,=20FSM,=20per-thread=20mutex,=20coalescing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/router.ts — the plumbing (spec §4.1): ts-dedupe, the 👀 ack on every mention, the idle/running/pending FSM, and a per-thread mutex so the state read-modify-write is atomic. Dispatch is fire-and-forget so the turn's reader outlives it and the router is free for other threads; on completion the FSM advances, coalescing mid-turn mentions into one follow-up. All maps are memory-only (reset on restart by design). Replaces the skeleton with a barrel re-exporting Router + Dispatcher. 8 tests with a controllable fake dispatcher: dedupe, pending-no-dispatch, 👀-every-mention, coalescing to one follow-up, finish-boundary atomicity, restart. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/router/index.ts | 72 ++--------------- src/router/router.test.ts | 131 ++++++++++++++++++++++++++++++ src/router/router.ts | 164 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 301 insertions(+), 66 deletions(-) create mode 100644 src/router/router.test.ts create mode 100644 src/router/router.ts diff --git a/src/router/index.ts b/src/router/index.ts index 42971c4..8c9b25c 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,68 +1,8 @@ -import type { CodingBackend } from "../backend/index.js"; -import type { PlatformAdapter } from "../platform/index.js"; -import type { SandboxProvider } from "../sandbox/index.js"; -import type { Mention } from "../types.js"; - -/** Per-thread coordination state, held in router memory only. See spec §3, §4.1. */ -type TurnState = "idle" | "running"; - -interface ThreadState { - state: TurnState; - pending: boolean; -} - /** - * Router — plumbing, NOT an LLM (spec §6). It owns: immediate-ACK ingress (in - * the adapter), idempotent dispatch, the idle/running/pending state machine + - * coalescing (§4.1), boundary logging (§4.9), and turn dispatch. It interprets - * the agent's output only via CodingBackend.parseResult. - * - * This is a skeleton: the seam wiring is real; the full turn lifecycle - * (coalescing, boundary logging, session-id capture, eviction) is TODO. + * Router barrel — the plumbing that maps Slack threads to coding-agent turns + * (spec §3, §4.1). The router is NOT an LLM: no model calls, no intent + * detection. See router.ts for the glue and dispatcher.ts for the turn + * lifecycle. */ -export class Router { - /** Transient coordination state (§3, bucket 2) — lost on restart, by design. */ - private readonly threads = new Map(); - /** Idempotent dispatch on ts (§4.1). A real impl TTLs this to the retry window. */ - private readonly seenTs = new Set(); - - constructor( - private readonly platform: PlatformAdapter, - private readonly sandbox: SandboxProvider, - private readonly backend: CodingBackend, - ) {} - - start(): void { - this.platform.onMention((mention) => { - this.onMention(mention).catch((err) => console.error("[router] turn failed", err)); - }); - } - - private async onMention(mention: Mention): Promise { - // 1. Idempotent dispatch on ts — drop already-accepted triggers (§4.1). - if (this.seenTs.has(mention.ts)) { - return; - } - this.seenTs.add(mention.ts); - - // 2. idle/running/pending state machine + coalescing (§4.1). - // TODO: if running, set pending and return; coalesce on completion. - const key = `${mention.thread.channel}:${mention.thread.threadTs}`; - const thread = this.threads.get(key) ?? { state: "idle", pending: false }; - thread.state = "running"; - this.threads.set(key, thread); - - // 3. Dispatch (minimal happy path). A real impl resolves-or-creates the - // sandbox by deterministic name (§4.1, §4.3), drives the turn so its - // stream-reader/logger outlives the dispatch (§4.1, §4.9), persists the - // session id (§4.5), and acks with a reaction first (§4.1/§4.2). - const sandboxName = `t-${key.replace(/[^a-zA-Z0-9_-]/g, "_")}`; - const handle = await this.sandbox.create(sandboxName, ""); - const argv = this.backend.turnArgs(mention.text); - const result = await this.sandbox.exec(handle, argv); // raw stream — log at the boundary (§4.9) - const { finalText, ok } = this.backend.parseResult(result.stdout); - await this.platform.postReply(mention.thread, ok ? finalText : "Turn failed."); - - thread.state = "idle"; - } -} +export { Dispatcher, type DispatcherDeps, formatPrompt } from "./dispatcher.js"; +export { Router, type RouterOptions, type TurnDispatcher } from "./router.js"; diff --git a/src/router/router.test.ts b/src/router/router.test.ts new file mode 100644 index 0000000..e1de03e --- /dev/null +++ b/src/router/router.test.ts @@ -0,0 +1,131 @@ +import type { Mention } from "../types.js"; +import { Router, type TurnDispatcher } from "./router.js"; + +const thread = { channel: "C0ABCDEF", threadTs: "1748600000.100000" }; +const mention = (ts: string): Mention => ({ thread, ts, user: "U1", text: `@bot ${ts}` }); +const m1 = mention("1748600000.100001"); +const m2 = mention("1748600000.100002"); +const m3 = mention("1748600000.100003"); + +const flush = (): Promise => new Promise((resolve) => setImmediate(resolve)); + +/** A dispatcher whose turns stay in flight until finishOne() is called. */ +class FakeDispatcher implements TurnDispatcher { + calls: Mention[] = []; + private resolvers: (() => void)[] = []; + async dispatchTurn(trigger: Mention): Promise<{ ok: boolean }> { + this.calls.push(trigger); + await new Promise((resolve) => this.resolvers.push(resolve)); + return { ok: true }; + } + finishOne(): void { + this.resolvers.shift()?.(); + } + get inFlight(): number { + return this.resolvers.length; + } +} + +class FakePlatform { + reactions: string[] = []; + onMention(): void {} + async addReaction(channel: string, ts: string, emoji: string): Promise { + this.reactions.push(`${channel}:${ts}:${emoji}`); + } +} + +function build(): { router: Router; dispatcher: FakeDispatcher; platform: FakePlatform } { + const dispatcher = new FakeDispatcher(); + const platform = new FakePlatform(); + const router = new Router(platform, dispatcher); + return { router, dispatcher, platform }; +} + +describe("dedupe (§4.1)", () => { + it("drops a re-delivered ts (no second dispatch)", async () => { + const { router, dispatcher } = build(); + await router.onMention(m1); + await router.onMention({ ...m1 }); // same ts re-delivered + expect(dispatcher.calls).toHaveLength(1); + }); +}); + +describe("idle/running/pending (§4.1)", () => { + it("dispatches on the first mention and 👀s it", async () => { + const { router, dispatcher, platform } = build(); + await router.onMention(m1); + expect(dispatcher.calls).toEqual([m1]); + expect(platform.reactions).toContain(`${thread.channel}:${m1.ts}:eyes`); + }); + + it("a mention during a running turn sets pending + 👀s, but does NOT dispatch", async () => { + const { router, dispatcher, platform } = build(); + await router.onMention(m1); // dispatch #1 (stays in flight) + await router.onMention(m2); // running -> pending + expect(dispatcher.calls).toEqual([m1]); // no new dispatch + expect(platform.reactions).toContain(`${thread.channel}:${m2.ts}:eyes`); // still 👀'd + }); + + it("👀s every mention", async () => { + const { router, platform } = build(); + await router.onMention(m1); + await router.onMention(m2); + await router.onMention(m3); + expect(platform.reactions).toEqual([ + `${thread.channel}:${m1.ts}:eyes`, + `${thread.channel}:${m2.ts}:eyes`, + `${thread.channel}:${m3.ts}:eyes`, + ]); + }); +}); + +describe("coalescing (§4.1)", () => { + it("N mentions during a turn produce exactly ONE coalesced follow-up (latest trigger)", async () => { + const { router, dispatcher } = build(); + await router.onMention(m1); // dispatch #1 + await router.onMention(m2); // pending + await router.onMention(m3); // still pending (idempotent) + expect(dispatcher.calls).toHaveLength(1); + + dispatcher.finishOne(); // turn 1 done -> coalesced follow-up + await flush(); + expect(dispatcher.calls).toEqual([m1, m3]); // exactly one follow-up, latest mention as trigger + + dispatcher.finishOne(); // follow-up done -> idle + await flush(); + expect(dispatcher.calls).toHaveLength(2); // nothing more + }); + + it("a single mention with no mid-turn mentions yields exactly one dispatch", async () => { + const { router, dispatcher } = build(); + await router.onMention(m1); + dispatcher.finishOne(); + await flush(); + expect(dispatcher.calls).toEqual([m1]); + }); +}); + +describe("atomicity at the finish boundary (§4.1)", () => { + it("a mention landing as a turn finishes is never lost", async () => { + const { router, dispatcher } = build(); + await router.onMention(m1); // dispatch #1 + + // Finish the turn and deliver m2 without awaiting in between — the per-thread + // mutex must serialize the turnFinished reduce and the mention reduce so m2 + // can't slip between "go idle" and "set pending". + dispatcher.finishOne(); + await router.onMention(m2); + await flush(); + + expect(dispatcher.calls).toContain(m2); + expect(dispatcher.calls).toHaveLength(2); + }); +}); + +describe("restart (§4.1)", () => { + it("a fresh Router starts idle (no persisted state) and dispatches", async () => { + const { router, dispatcher } = build(); + await router.onMention(m1); + expect(dispatcher.calls).toEqual([m1]); + }); +}); diff --git a/src/router/router.ts b/src/router/router.ts new file mode 100644 index 0000000..04e8a7c --- /dev/null +++ b/src/router/router.ts @@ -0,0 +1,164 @@ +import type { PlatformAdapter } from "../platform/index.js"; +import type { Mention, ThreadId } from "../types.js"; +import { createTtlSet, type TtlSet } from "./dedupe.js"; +import { + buildRouterEvent, + type LogContext, + type LogSink, + nullSink, + RouterKind, + safeWrite, +} from "./log.js"; +import { chooseSandboxName } from "./sandbox-name.js"; +import { INITIAL_STATE, reduce, type ThreadFsmState } from "./state-machine.js"; + +/** + * Router glue (spec §4.1) — plumbing, not an LLM. Owns the transient + * per-thread coordination: the idle/running/pending FSM, ts-dedupe, the 👀 ack + * on every mention, and a per-thread mutex so the state read-modify-write is + * atomic. All durable state lives in the sandbox (§4.6); these maps are + * memory-only and reset on restart, which is fine — an in-flight turn is + * abandoned and re-driven idempotently on the next mention (§4.1). + * + * Dispatching is fire-and-forget: the turn (and its stream-reader) outlives the + * dispatch so the router is free to service other threads, while this thread's + * turn keeps draining (§4.1). Cross-thread turns run in parallel; turns within a + * thread serialize and coalesce. + */ + +/** What the router needs from the dispatcher (the real Dispatcher fits). */ +export interface TurnDispatcher { + dispatchTurn(trigger: Mention): Promise<{ ok: boolean }>; +} + +export interface RouterOptions { + dedupeTtlMs?: number; + now?: () => number; + logSink?: LogSink; +} + +function threadKey(thread: ThreadId): string { + return `${thread.channel}:${thread.threadTs}`; +} + +export class Router { + private readonly states = new Map(); + private readonly latestMention = new Map(); + private readonly locks = new Map>(); + private readonly dedupe: TtlSet; + private readonly logSink: LogSink; + + constructor( + private readonly platform: Pick, + private readonly dispatcher: TurnDispatcher, + options: RouterOptions = {}, + ) { + this.dedupe = createTtlSet(options.dedupeTtlMs, options.now); + this.logSink = options.logSink ?? nullSink; + } + + start(): void { + this.platform.onMention((mention) => { + void this.onMention(mention); + }); + } + + async onMention(mention: Mention): Promise { + const key = threadKey(mention.thread); + + // 1. Idempotent dispatch on ts — drop already-accepted triggers (§4.1). + if (this.dedupe.has(mention.ts)) { + this.logRouter(mention.thread, mention.ts, RouterKind.MentionDeduped, { ts: mention.ts }); + return; + } + this.dedupe.add(mention.ts); + this.logRouter(mention.thread, mention.ts, RouterKind.MentionReceived, { user: mention.user }); + this.latestMention.set(key, mention); + + // 2. Atomic state read-modify-write under the per-thread lock (§4.1). + const effects = await this.withLock(key, () => { + const { state, effects } = reduce(this.stateOf(key), { kind: "mention" }); + this.states.set(key, state); + return effects; + }); + + // 3. Effects run outside the lock (they don't touch FSM state). + for (const effect of effects) { + if (effect === "ackRunning" || effect === "ackPending") { + void this.ack(mention); + } else if (effect === "dispatchTurn") { + this.dispatch(key, mention); + } + } + } + + /** Fire-and-forget a turn; on completion, advance the FSM (coalescing, §4.1). */ + private dispatch(key: string, trigger: Mention): void { + void this.dispatcher.dispatchTurn(trigger).then( + () => this.onTurnFinished(key), + () => this.onTurnFinished(key), + ); + } + + private onTurnFinished(key: string): void { + void this.withLock(key, () => { + const { state, effects } = reduce(this.stateOf(key), { kind: "turnFinished" }); + this.states.set(key, state); + return effects; + }).then((effects) => { + for (const effect of effects) { + if (effect === "dispatchTurn") { + const trigger = this.latestMention.get(key); + if (trigger !== undefined) { + this.dispatch(key, trigger); + } + } + } + }); + } + + private stateOf(key: string): ThreadFsmState { + return this.states.get(key) ?? INITIAL_STATE; + } + + /** Best-effort 👀 ack on the mention — never blocks or fails the turn (§4.1). */ + private async ack(mention: Mention): Promise { + try { + await this.platform.addReaction(mention.thread.channel, mention.ts, "eyes"); + this.logRouter(mention.thread, mention.ts, RouterKind.AckReaction, { emoji: "eyes" }); + } catch { + // best-effort + } + } + + /** Serialize handler bodies per thread so the state RMW is atomic (§4.1). */ + private withLock(key: string, fn: () => T): Promise { + const prev = this.locks.get(key) ?? Promise.resolve(); + const result = prev.then(fn, fn); + this.locks.set( + key, + result.then( + () => {}, + () => {}, + ), + ); + return result; + } + + private logRouter( + thread: ThreadId, + turnId: string, + kind: string, + payload: Record, + ): void { + const name = chooseSandboxName(thread); + const ctx: LogContext = { + threadId: name, + channel: thread.channel, + thread_ts: thread.threadTs, + sandbox: name, + turnId, + }; + safeWrite(this.logSink, buildRouterEvent(ctx, new Date().toISOString(), kind, payload)); + } +} From ee30a60d052aa487d912c6ecb25fbfad9f4f0d7c Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:33:42 -0400 Subject: [PATCH 14/47] =?UTF-8?q?C13:=20Slack=20adapter=20=E2=80=94=20pure?= =?UTF-8?q?=20mapping=20+=20Socket=20Mode=20shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/platform/slack-map.ts (pure, tested) — event→Mention and message→ThreadMessage: a root mention uses its own ts as the thread id, a reply uses the parent thread_ts, and bot/own posts never trigger (§4.1). src/platform/slack.ts (I/O shell) — PlatformAdapter over @slack/socket-mode + @slack/web-api: ACK the envelope immediately on receipt and run the handler async, drop retries (retry_num>0) to avoid double-dispatch (§4.1); fetchThread paginates conversations.replies with oldest=sinceTs for the exact delta; reactions are idempotent (ignore already_reacted/no_reaction). 10 mapping tests; the shell is covered by typecheck + build and verified live (C17). Co-Authored-By: Claude Opus 4.8 (1M context) --- package-lock.json | 527 ++++++++++++++++++++++++++++++++- package.json | 4 + src/platform/slack-map.test.ts | 81 +++++ src/platform/slack-map.ts | 78 +++++ src/platform/slack.ts | 132 +++++++++ 5 files changed, 820 insertions(+), 2 deletions(-) create mode 100644 src/platform/slack-map.test.ts create mode 100644 src/platform/slack-map.ts create mode 100644 src/platform/slack.ts diff --git a/package-lock.json b/package-lock.json index 16e1c0f..65a432f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "slack-coding-agent", "version": "0.0.1", "license": "MIT", + "dependencies": { + "@slack/socket-mode": "^2.0.7", + "@slack/web-api": "^7.16.0" + }, "bin": { "slack-coding-agent": "dist/cli/index.js" }, @@ -1043,6 +1047,71 @@ "dev": true, "license": "MIT" }, + "node_modules/@slack/logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.1.tgz", + "integrity": "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ==", + "license": "MIT", + "dependencies": { + "@types/node": ">=18" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/socket-mode": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.7.tgz", + "integrity": "sha512-qYy07je71WnEHgRwmw12DlAnZLi5HXmdlI2WUzUK2LH/rYXQpP6uEg462S5CwfE8FoCKUdIigHtYnOOfzZH1lQ==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/web-api": "^7.15.0", + "@types/node": ">=18", + "@types/ws": "^8", + "eventemitter3": "^5", + "ws": "^8" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.21.1.tgz", + "integrity": "sha512-I8vmSjNYWsaxuWPx6dz4yeh0h7vRBWbgAMK14LEmblbZ404BtrPbXs6jDPx4cYgGf8msDGF4A9opLZBu21FViQ==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.16.0.tgz", + "integrity": "sha512-68SAV77uuGKuhyyaRytX8UijVnqSLsTSKslGXw17cjQYXn+jtNl7gbaEjHgC5x2rhCuFdahBrEC2VCLppbzReg==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.1", + "@slack/types": "^2.21.0", + "@types/node": ">=18", + "@types/retry": "0.12.0", + "axios": "^1.16.0", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.4", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1090,12 +1159,26 @@ "version": "22.19.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", @@ -1240,6 +1323,18 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1262,6 +1357,37 @@ "js-tokens": "^10.0.0" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -1272,6 +1398,18 @@ "node": ">=18" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1279,6 +1417,32 @@ "dev": true, "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1289,6 +1453,38 @@ "node": ">=8" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", @@ -1296,6 +1492,33 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", @@ -1348,6 +1571,12 @@ "@types/estree": "^1.0.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -1376,6 +1605,42 @@ } } }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1391,6 +1656,64 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1401,6 +1724,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -1408,6 +1770,37 @@ "dev": true, "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -1753,6 +2146,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", @@ -1783,6 +2212,62 @@ ], "license": "MIT" }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -1839,6 +2324,24 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/rolldown": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", @@ -2019,7 +2522,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/vite": { @@ -2206,6 +2708,27 @@ "engines": { "node": ">=8" } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index b2ec3be..b57bfe5 100644 --- a/package.json +++ b/package.json @@ -32,5 +32,9 @@ "tsx": "^4.19.0", "typescript": "^5.7.0", "vitest": "^4.1.7" + }, + "dependencies": { + "@slack/socket-mode": "^2.0.7", + "@slack/web-api": "^7.16.0" } } diff --git a/src/platform/slack-map.test.ts b/src/platform/slack-map.test.ts new file mode 100644 index 0000000..81c7992 --- /dev/null +++ b/src/platform/slack-map.test.ts @@ -0,0 +1,81 @@ +import { + mentionFromEvent, + type SlackAppMentionEvent, + threadMessageFromSlack, + threadMessagesFromReplies, +} from "./slack-map.js"; + +const base: SlackAppMentionEvent = { + type: "app_mention", + user: "U1", + text: "<@UBOT> fix the bug", + ts: "1748600000.500000", + channel: "C0ABCDEF", +}; + +describe("mentionFromEvent", () => { + it("a root mention (no thread_ts) uses its own ts as the thread id (§4.1)", () => { + expect(mentionFromEvent(base)).toEqual({ + thread: { channel: "C0ABCDEF", threadTs: "1748600000.500000" }, + ts: "1748600000.500000", + user: "U1", + text: "<@UBOT> fix the bug", + }); + }); + + it("a reply mention uses the parent thread_ts", () => { + const m = mentionFromEvent({ ...base, thread_ts: "1748600000.100000" }); + expect(m?.thread.threadTs).toBe("1748600000.100000"); + expect(m?.ts).toBe("1748600000.500000"); + }); + + it("drops the bot's own post", () => { + expect(mentionFromEvent({ ...base, user: "UBOT" }, "UBOT")).toBeNull(); + }); + + it("drops messages from other bots (bot_id present)", () => { + expect(mentionFromEvent({ ...base, bot_id: "B123" })).toBeNull(); + }); + + it("drops events missing ts / channel / user", () => { + expect(mentionFromEvent({ ...base, ts: undefined })).toBeNull(); + expect(mentionFromEvent({ ...base, channel: undefined })).toBeNull(); + expect(mentionFromEvent({ ...base, user: undefined })).toBeNull(); + }); + + it("defaults missing text to empty", () => { + expect(mentionFromEvent({ ...base, text: undefined })?.text).toBe(""); + }); +}); + +describe("threadMessageFromSlack / threadMessagesFromReplies", () => { + it("maps ts/user/text", () => { + expect(threadMessageFromSlack({ ts: "1.1", user: "U1", text: "hi" })).toEqual({ + ts: "1.1", + user: "U1", + text: "hi", + }); + }); + + it("falls back to bot_id then 'unknown' for the author", () => { + expect(threadMessageFromSlack({ ts: "1.1", bot_id: "B1", text: "x" })?.user).toBe("B1"); + expect(threadMessageFromSlack({ ts: "1.1", text: "x" })?.user).toBe("unknown"); + }); + + it("returns null for a message without a ts", () => { + expect(threadMessageFromSlack({ text: "no ts" })).toBeNull(); + }); + + it("filters unmappable messages from a replies page", () => { + expect( + threadMessagesFromReplies([ + { ts: "1.1", user: "U1", text: "a" }, + { text: "dropped (no ts)" }, + { ts: "1.2", user: "U2", text: "b" }, + ]), + ).toEqual([ + { ts: "1.1", user: "U1", text: "a" }, + { ts: "1.2", user: "U2", text: "b" }, + ]); + }); +}); diff --git a/src/platform/slack-map.ts b/src/platform/slack-map.ts new file mode 100644 index 0000000..b67fc86 --- /dev/null +++ b/src/platform/slack-map.ts @@ -0,0 +1,78 @@ +import type { Mention, ThreadMessage } from "../types.js"; + +/** + * Pure Slack-event mapping (spec §4.1) — the subtle bits that hold the bugs, + * isolated from the Socket Mode / Web API I/O so they're unit-testable: + * - a root mention uses its own `ts` as the thread id (it opens the thread); + * a reply uses the parent `thread_ts`; + * - the bot's own posts and other bots never trigger a turn. + */ + +/** The fields we read off a Slack `app_mention` event. */ +export interface SlackAppMentionEvent { + type?: string; + user?: string; + bot_id?: string; + text?: string; + ts?: string; + channel?: string; + thread_ts?: string; + subtype?: string; +} + +/** + * Normalize an `app_mention` into a Mention, or null if it shouldn't trigger a + * turn (missing fields, a bot's message, or the bot's own post). + */ +export function mentionFromEvent(event: SlackAppMentionEvent, botUserId?: string): Mention | null { + if (event.ts === undefined || event.channel === undefined || event.user === undefined) { + return null; + } + if (event.bot_id !== undefined) { + return null; // another bot — don't loop + } + if (botUserId !== undefined && event.user === botUserId) { + return null; // our own post + } + // A root mention has no thread_ts; its own ts opens the thread (§4.1). + const threadTs = event.thread_ts ?? event.ts; + return { + thread: { channel: event.channel, threadTs }, + ts: event.ts, + user: event.user, + text: event.text ?? "", + }; +} + +/** The fields we read off a message in `conversations.replies`. */ +export interface SlackMessage { + ts?: string; + user?: string; + bot_id?: string; + text?: string; + subtype?: string; +} + +/** Map a Slack thread message to a ThreadMessage, or null if it has no ts. */ +export function threadMessageFromSlack(message: SlackMessage): ThreadMessage | null { + if (message.ts === undefined) { + return null; + } + return { + ts: message.ts, + user: message.user ?? message.bot_id ?? "unknown", + text: message.text ?? "", + }; +} + +/** Map a `conversations.replies` page to ThreadMessages, dropping unmappable ones. */ +export function threadMessagesFromReplies(messages: SlackMessage[]): ThreadMessage[] { + const result: ThreadMessage[] = []; + for (const message of messages) { + const mapped = threadMessageFromSlack(message); + if (mapped !== null) { + result.push(mapped); + } + } + return result; +} diff --git a/src/platform/slack.ts b/src/platform/slack.ts new file mode 100644 index 0000000..a807818 --- /dev/null +++ b/src/platform/slack.ts @@ -0,0 +1,132 @@ +import { SocketModeClient } from "@slack/socket-mode"; +import { WebClient } from "@slack/web-api"; +import type { Mention, ThreadId, ThreadMessage } from "../types.js"; +import type { PlatformAdapter } from "./index.js"; +import { + mentionFromEvent, + type SlackAppMentionEvent, + type SlackMessage, + threadMessagesFromReplies, +} from "./slack-map.js"; + +/** + * Slack PlatformAdapter via Socket Mode + Web API (spec §4.1) — the I/O shell. + * The mapping logic lives in slack-map.ts; this only wires the SDKs. + * + * Discipline (§4.1): the Socket Mode envelope is ACKed immediately on receipt, + * BEFORE any work, and the handler runs async to the ACK; retries + * (retry_num > 0) are dropped so a slow turn can't trigger double-dispatch. + * Acks to the user are reactions, not replies (§4.1/§4.2). + */ + +export interface SlackAdapterConfig { + /** App-level token (xapp-…, connections:write) → Socket Mode. */ + appToken: string; + /** Bot token (xoxb-…). */ + botToken: string; + /** Bot user id; resolved via auth.test() at start() if omitted. */ + botUserId?: string; +} + +/** The arg shape SocketModeClient emits for an events_api event. */ +interface SocketModeEventArgs { + ack: () => Promise; + event: SlackAppMentionEvent; + retry_num?: number; +} + +function slackErrorCode(err: unknown): string | undefined { + const data = (err as { data?: { error?: string } }).data; + return data?.error; +} + +export class SlackAdapter implements PlatformAdapter { + private readonly socket: SocketModeClient; + private readonly web: WebClient; + private botUserId: string | undefined; + private handler: ((mention: Mention) => void) | null = null; + + constructor(config: SlackAdapterConfig) { + this.socket = new SocketModeClient({ appToken: config.appToken }); + this.web = new WebClient(config.botToken); + this.botUserId = config.botUserId; + } + + onMention(handler: (mention: Mention) => void): void { + this.handler = handler; + } + + /** Resolve the bot's identity, wire the listener, and open the socket. */ + async start(): Promise { + if (this.botUserId === undefined) { + const auth = await this.web.auth.test(); + this.botUserId = typeof auth.user_id === "string" ? auth.user_id : undefined; + } + this.socket.on("app_mention", (args: SocketModeEventArgs) => { + // ACK at the WebSocket layer on receipt — never block the ~3s deadline (§4.1). + void args.ack(); + if (typeof args.retry_num === "number" && args.retry_num > 0) { + return; // a retry of an event we already accepted (§4.1) + } + const mention = mentionFromEvent(args.event, this.botUserId); + if (mention !== null && this.handler !== null) { + this.handler(mention); // async to the ACK + } + }); + await this.socket.start(); + } + + async fetchThread(thread: ThreadId, sinceTs?: string): Promise { + const messages: SlackMessage[] = []; + let cursor: string | undefined; + do { + const res = await this.web.conversations.replies({ + channel: thread.channel, + ts: thread.threadTs, + oldest: sinceTs, + inclusive: false, + limit: 200, + cursor, + }); + messages.push(...((res.messages ?? []) as SlackMessage[])); + cursor = res.response_metadata?.next_cursor || undefined; + } while (cursor !== undefined); + return threadMessagesFromReplies(messages); + } + + async postReply(thread: ThreadId, text: string): Promise { + await this.web.chat.postMessage({ + channel: thread.channel, + thread_ts: thread.threadTs, + text, + }); + } + + async uploadFile(thread: ThreadId, path: string): Promise { + await this.web.filesUploadV2({ + channel_id: thread.channel, + thread_ts: thread.threadTs, + file: path, + }); + } + + async addReaction(channel: string, ts: string, emoji: string): Promise { + try { + await this.web.reactions.add({ channel, timestamp: ts, name: emoji }); + } catch (err) { + if (slackErrorCode(err) !== "already_reacted") { + throw err; // idempotent: re-adding our own 👀 is fine (§4.1) + } + } + } + + async removeReaction(channel: string, ts: string, emoji: string): Promise { + try { + await this.web.reactions.remove({ channel, timestamp: ts, name: emoji }); + } catch (err) { + if (slackErrorCode(err) !== "no_reaction") { + throw err; + } + } + } +} From 6c3078216e1f3f07c893bf1256d5fc3cea377b30 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:37:22 -0400 Subject: [PATCH 15/47] C14: CLI composition root + onboarding gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/cli/onboarding.ts — router-guided onboarding (§4.8): pure detectMissingCredential/credentialService/onboardingInstructions + ensureOnboarded over an injected `sbx secret ls`. Defaults Codex to the OAuth/browser path (the chosen flow). src/cli/args.ts — pure parseArgs + USAGE (env documented). src/cli/index.ts — wires SbxProvider + CodexBackend + SlackAdapter + Dispatcher + Router behind the onboarding gate, resolves the bot user id (whoAmI) before constructing the dispatcher so bot posts are filtered, and opens Socket Mode. Boundary logs go to stdout + a JSONL file in the data dir. Adds SbxProvider.secretLs. Verified live against real sbx: `-h` prints usage; with no credential the gate runs `sbx secret ls`, detects the missing openai secret, and prints the OAuth steps. 10 tests for args + onboarding. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli/args.test.ts | 22 +++++++ src/cli/args.ts | 44 ++++++++++++++ src/cli/index.ts | 115 ++++++++++++++++++++++++------------ src/cli/onboarding.test.ts | 60 +++++++++++++++++++ src/cli/onboarding.ts | 65 ++++++++++++++++++++ src/platform/slack.ts | 10 +++- src/sandbox/sbx-provider.ts | 7 +++ 7 files changed, 284 insertions(+), 39 deletions(-) create mode 100644 src/cli/args.test.ts create mode 100644 src/cli/args.ts create mode 100644 src/cli/onboarding.test.ts create mode 100644 src/cli/onboarding.ts diff --git a/src/cli/args.test.ts b/src/cli/args.test.ts new file mode 100644 index 0000000..c358b27 --- /dev/null +++ b/src/cli/args.test.ts @@ -0,0 +1,22 @@ +import { parseArgs } from "./args.js"; + +describe("parseArgs", () => { + it("parses --sandbox= and a data-dir positional", () => { + expect(parseArgs(["--sandbox=sbx", "./data"])).toEqual({ sandbox: "sbx", dataDir: "./data" }); + }); + + it("is order-independent", () => { + expect(parseArgs(["./data", "--sandbox=sbx"])).toEqual({ sandbox: "sbx", dataDir: "./data" }); + }); + + it("returns null when the sandbox or data-dir is missing", () => { + expect(parseArgs(["./data"])).toBeNull(); + expect(parseArgs(["--sandbox=sbx"])).toBeNull(); + expect(parseArgs([])).toBeNull(); + }); + + it("returns null for -h / --help", () => { + expect(parseArgs(["-h"])).toBeNull(); + expect(parseArgs(["--sandbox=sbx", "./data", "--help"])).toBeNull(); + }); +}); diff --git a/src/cli/args.ts b/src/cli/args.ts new file mode 100644 index 0000000..8ca8f86 --- /dev/null +++ b/src/cli/args.ts @@ -0,0 +1,44 @@ +/** + * CLI argument parsing (pure). Shape mirrors pi-mom's convention (spec §10): + * + * slack-coding-agent --sandbox= + * e.g. slack-coding-agent --sandbox=sbx ./data + */ + +export interface Args { + sandbox: string; + dataDir: string; +} + +/** Parse argv (without node/script), or null if invalid / help requested. */ +export function parseArgs(argv: string[]): Args | null { + let sandbox = ""; + const positional: string[] = []; + for (const arg of argv) { + if (arg.startsWith("--sandbox=")) { + sandbox = arg.slice("--sandbox=".length); + } else if (arg === "-h" || arg === "--help") { + return null; + } else { + positional.push(arg); + } + } + const dataDir = positional[0]; + if (sandbox === "" || dataDir === undefined) { + return null; + } + return { sandbox, dataDir }; +} + +export const USAGE = [ + "Usage: slack-coding-agent --sandbox= ", + " e.g. slack-coding-agent --sandbox=sbx ./data", + "", + "Environment:", + " APP_SLACK_APP_TOKEN xapp-… app-level token (Socket Mode)", + " APP_SLACK_BOT_TOKEN xoxb-… bot token", + " SCA_REPO path to the target git repo (the --clone seed)", + " SCA_AGENT codex | claude (default: codex)", + " SCA_LOG_LEVEL summary | verbose (default: summary)", + " SCA_BOT_USER bot user id (optional; resolved via auth.test otherwise)", +].join("\n"); diff --git a/src/cli/index.ts b/src/cli/index.ts index fd5b3b5..10e9076 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,56 +1,97 @@ #!/usr/bin/env node /** - * CLI entrypoint. Shape mirrors pi-mom's convention (spec §10): + * Composition root (spec §3, §4.8). Wires the three seams + Router behind the + * onboarding gate, then starts the Socket Mode ingress: * - * slack-coding-agent --sandbox= - * e.g. slack-coding-agent --sandbox=sbx ./data + * slack-coding-agent --sandbox=sbx ./data * - * Slack Socket Mode env (see spec §4.7, §4.8): - * APP_SLACK_APP_TOKEN xapp-... (app-level token → Socket Mode) - * APP_SLACK_BOT_TOKEN xoxb-... (bot token) - * plus the coding-agent / LLM credentials, injected proxy-side (§4.7). + * See args.ts USAGE for the environment. The router is plumbing — no LLM here. */ +import { createWriteStream, mkdirSync } from "node:fs"; +import { join } from "node:path"; +import { CodexBackend } from "../backend/codex.js"; +import { SlackAdapter } from "../platform/slack.js"; +import { Dispatcher, Router } from "../router/index.js"; +import { createJsonlSink, type LogLevel, type LogSink } from "../router/log.js"; +import { SbxProvider } from "../sandbox/sbx-provider.js"; +import { type Args, parseArgs, USAGE } from "./args.js"; +import { type Agent, ensureOnboarded } from "./onboarding.js"; -interface Args { - sandbox: string; - dataDir: string; +/** Boundary-log sink: the router's stdout + an append-only JSONL file (§4.9). */ +function fileLogSink(dataDir: string): LogSink { + mkdirSync(dataDir, { recursive: true }); + const stream = createWriteStream(join(dataDir, "router.log"), { flags: "a" }); + return createJsonlSink((line) => { + process.stdout.write(line); + stream.write(line); + }); } -function parseArgs(argv: string[]): Args | null { - let sandbox = ""; - const positional: string[] = []; - for (const arg of argv) { - if (arg.startsWith("--sandbox=")) { - sandbox = arg.slice("--sandbox=".length); - } else if (arg === "-h" || arg === "--help") { - return null; - } else { - positional.push(arg); - } - } - const dataDir = positional[0]; - if (!sandbox || !dataDir) { - return null; +function requireEnv(name: string): string { + const value = process.env[name]; + if (value === undefined || value === "") { + console.error(`Missing required environment variable: ${name}`); + process.exit(1); } - return { sandbox, dataDir }; + return value; } -function usage(): void { - console.log("Usage: slack-coding-agent --sandbox= "); - console.log(" e.g. slack-coding-agent --sandbox=sbx ./data"); -} +async function run(args: Args): Promise { + if (args.sandbox !== "sbx") { + console.error(`Unsupported --sandbox=${args.sandbox} (v0 supports: sbx)`); + process.exit(1); + } + const agent: Agent = process.env.SCA_AGENT === "claude" ? "claude" : "codex"; + const level: LogLevel = process.env.SCA_LOG_LEVEL === "verbose" ? "verbose" : "summary"; -function main(): void { - const args = parseArgs(process.argv.slice(2)); - if (!args) { - usage(); + const provider = new SbxProvider({ createOptions: { agent } }); + + // Onboarding gate (§4.8): require the agent's credential before serving. + const onboarded = await ensureOnboarded(agent, { + secretLs: () => provider.secretLs(), + log: (message) => console.error(message), + }); + if (!onboarded) { process.exit(1); } - console.log(`[slack-coding-agent] sandbox=${args.sandbox} dataDir=${args.dataDir}`); - console.log( - "Scaffold OK. Implement PlatformAdapter, SandboxProvider, CodingBackend, then wire Router.start() (see spec §3, §4).", + const appToken = requireEnv("APP_SLACK_APP_TOKEN"); + const botToken = requireEnv("APP_SLACK_BOT_TOKEN"); + const repoRef = requireEnv("SCA_REPO"); + + const logSink = fileLogSink(args.dataDir); + const platform = new SlackAdapter({ appToken, botToken, botUserId: process.env.SCA_BOT_USER }); + const botUser = await platform.whoAmI(); + const backend = new CodexBackend(provider); + const dispatcher = new Dispatcher({ + platform, + sandbox: provider, + backend, + repoRef, + logSink, + level, + botUser, + }); + const router = new Router(platform, dispatcher, { logSink }); + + router.start(); + await platform.start(); + console.error( + `[slack-coding-agent] listening — agent=${agent} repo=${repoRef} data=${args.dataDir}`, ); } +function main(): void { + const argv = process.argv.slice(2); + const args = parseArgs(argv); + if (args === null) { + console.log(USAGE); + process.exit(argv.includes("-h") || argv.includes("--help") ? 0 : 1); + } + run(args).catch((err: unknown) => { + console.error("[slack-coding-agent] fatal:", err); + process.exit(1); + }); +} + main(); diff --git a/src/cli/onboarding.test.ts b/src/cli/onboarding.test.ts new file mode 100644 index 0000000..b5b0fc4 --- /dev/null +++ b/src/cli/onboarding.test.ts @@ -0,0 +1,60 @@ +import { + credentialService, + detectMissingCredential, + ensureOnboarded, + onboardingInstructions, +} from "./onboarding.js"; + +describe("credentialService", () => { + it("maps agents to their sbx secret service (§4.7)", () => { + expect(credentialService("codex")).toBe("openai"); + expect(credentialService("claude")).toBe("anthropic"); + }); +}); + +describe("detectMissingCredential", () => { + it("treats the empty 'No secrets found' output as missing", () => { + const empty = "No secrets found. Run 'sbx secret set --help' to see available services."; + expect(detectMissingCredential(empty, "codex")).toBe(true); + expect(detectMissingCredential(empty, "claude")).toBe(true); + }); + + it("detects a configured credential for the agent's service", () => { + const ls = "openai (oauth configured)\n"; + expect(detectMissingCredential(ls, "codex")).toBe(false); + expect(detectMissingCredential(ls, "claude")).toBe(true); // anthropic still missing + }); +}); + +describe("onboardingInstructions", () => { + it("guides Codex through the OAuth path the user chose (§4.8)", () => { + const text = onboardingInstructions("codex"); + expect(text).toContain("sbx secret set -g openai --oauth"); + }); + + it("guides Claude through the disposable login sandbox", () => { + expect(onboardingInstructions("claude")).toContain("_login-tmp"); + }); +}); + +describe("ensureOnboarded", () => { + it("returns true and stays quiet when the credential is present", async () => { + const logs: string[] = []; + const ok = await ensureOnboarded("codex", { + secretLs: async () => "openai (oauth configured)", + log: (m) => logs.push(m), + }); + expect(ok).toBe(true); + expect(logs).toEqual([]); + }); + + it("returns false and prints setup instructions when missing", async () => { + const logs: string[] = []; + const ok = await ensureOnboarded("codex", { + secretLs: async () => "No secrets found.", + log: (m) => logs.push(m), + }); + expect(ok).toBe(false); + expect(logs[0]).toContain("sbx secret set -g openai --oauth"); + }); +}); diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts new file mode 100644 index 0000000..a885612 --- /dev/null +++ b/src/cli/onboarding.ts @@ -0,0 +1,65 @@ +/** + * Router-guided onboarding (spec §4.8). The router gates first use on the + * coding agent's credential being present in sbx, walks the human through the + * one-time setup, and then runs unattended forever after. + * + * The parsing/decision logic is pure; the guided flow needs only a `secretLs` + * capability (so it's injectable in tests). + */ + +export type Agent = "codex" | "claude"; + +/** The sbx secret service that backs each agent's auth (§4.7). */ +export function credentialService(agent: Agent): string { + return agent === "codex" ? "openai" : "anthropic"; +} + +/** True if `sbx secret ls` shows no credential for the agent's service. */ +export function detectMissingCredential(secretLsOutput: string, agent: Agent): boolean { + const service = credentialService(agent); + const present = new RegExp(`\\b${service}\\b`, "i").test(secretLsOutput); + return !present; +} + +/** One-time setup instructions for the human (the OAuth/browser step needs a person). */ +export function onboardingInstructions(agent: Agent): string { + const service = credentialService(agent); + if (agent === "codex") { + return [ + `No '${service}' credential is configured in sbx for Codex.`, + "", + ` 1. Run: sbx secret set -g ${service} --oauth`, + " 2. Finish the browser sign-in.", + ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, + "", + "(The credential is host-side and proxy-injected — it never enters a sandbox, §4.7.)", + ].join("\n"); + } + return [ + `No '${service}' credential is configured in sbx for Claude.`, + "", + " 1. sbx create --name _login-tmp claude ", + " 2. sbx run _login-tmp → type /login, finish the browser sign-in, then exit", + ` 3. Wait for 'sbx secret ls' to show '${service} (oauth configured)', then: sbx rm --force _login-tmp`, + ].join("\n"); +} + +export interface OnboardingDeps { + /** Runs `sbx secret ls` and returns its raw output. */ + secretLs: () => Promise; + /** Where to surface human-facing setup instructions (stderr in the CLI). */ + log: (message: string) => void; +} + +/** + * Returns true if the agent's credential is present (proceed), or false after + * printing setup instructions (the human must complete the one-time OAuth). + */ +export async function ensureOnboarded(agent: Agent, deps: OnboardingDeps): Promise { + const output = await deps.secretLs(); + if (detectMissingCredential(output, agent)) { + deps.log(onboardingInstructions(agent)); + return false; + } + return true; +} diff --git a/src/platform/slack.ts b/src/platform/slack.ts index a807818..d47bf7b 100644 --- a/src/platform/slack.ts +++ b/src/platform/slack.ts @@ -56,12 +56,18 @@ export class SlackAdapter implements PlatformAdapter { this.handler = handler; } - /** Resolve the bot's identity, wire the listener, and open the socket. */ - async start(): Promise { + /** Resolve (and cache) the bot's own user id via auth.test (§4.2 self-filtering). */ + async whoAmI(): Promise { if (this.botUserId === undefined) { const auth = await this.web.auth.test(); this.botUserId = typeof auth.user_id === "string" ? auth.user_id : undefined; } + return this.botUserId; + } + + /** Resolve the bot's identity, wire the listener, and open the socket. */ + async start(): Promise { + await this.whoAmI(); this.socket.on("app_mention", (args: SocketModeEventArgs) => { // ACK at the WebSocket layer on receipt — never block the ~3s deadline (§4.1). void args.ack(); diff --git a/src/sandbox/sbx-provider.ts b/src/sandbox/sbx-provider.ts index 2d4b939..812cd12 100644 --- a/src/sandbox/sbx-provider.ts +++ b/src/sandbox/sbx-provider.ts @@ -14,6 +14,7 @@ import { parseLsJson, remotePath, rmArgv, + secretLsArgv, shellQuote, stopArgv, } from "./sbx-argv.js"; @@ -132,6 +133,12 @@ export class SbxProvider implements SandboxProvider { return parseLsJson(stdout).map((entry) => ({ name: entry.name })); } + /** Raw `sbx secret ls` output — drives the onboarding credential check (§4.8). */ + async secretLs(): Promise { + const { stdout } = await this.runOrThrow(secretLsArgv(), "secret ls"); + return stdout; + } + // --- SandboxFsLike (structural) --------------------------------------- async homeDir(handle: SandboxHandle): Promise { From b1e53ee64e7288d278971e8421d4a0fd834f6139 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:39:04 -0400 Subject: [PATCH 16/47] C15: AGENTS.md (+ CLAUDE.md) + refreshed README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGENTS.md — the rules agents follow in this repo: router-is-plumbing (not an LLM), the build/check gate, ESM import rules, the three-seams invariants (transport≠interpretation, no central store, pure-core vs I/O-shell), the sandbox-naming no-underscore rule, boundary-logging discipline, the router lifecycle (immediate ACK, reactions-not-replies, coalescing, reader outlives dispatch, write-after-success), Codex/exec gotchas, test/commit conventions, and the §6 "don't relitigate" + §7 out-of-scope lists. CLAUDE.md points to AGENTS.md. README rewritten for the implemented system (architecture, prerequisites, run instructions, env, commands, layout). Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 1 + README.md | 108 ++++++++++++++++++++++++++++----------------- 3 files changed, 199 insertions(+), 39 deletions(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a0f2817 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,129 @@ +# AGENTS.md — rules for working in this repo + +A Slack-native coding agent: mention the bot in a Slack thread → it does the coding work in an +isolated `sbx` microVM running a coding-agent CLI (Codex) → it reports back **in the same thread**. +The design spec is `.context/attachments/2PRioL/slack-coding-agent-spec.md`; section refs (§) point +there. Read this file before changing code. + +## 0. What this is — and is NOT + +- The **router is plumbing, not an LLM** (§6). It has **no model calls**, makes **no decisions about + whether to delegate**, and does **no intent detection**. In particular it does **not** detect "open + a PR" — shipping is the agent's job (`git`/`gh` inside the sandbox, §2.6/§4.6). If you're tempted to + add reasoning, branching on message content, or an "outer bot" to the router: **stop** — that's the + prototype mistake this design removed. +- Every Slack thread maps 1:1 to one sandbox to one coding-agent session. + +## 1. Build / check / test + +- `npm install` once. **`npm run check` is the single gate**: Biome (lint + format + organize-imports, + `--error-on-warnings`) + `tsc --noEmit` + `vitest run`. Run it before every commit; **keep the repo + green at every commit**. +- `npm run check:fix` applies Biome fixes then runs the gate. `npm test` / `npm run test:watch` / + `npm run test:cov` run Vitest. `npm run build` emits `dist/` (excludes `*.test.ts`). `npm run dev` + runs the CLI under `tsx watch`. +- `npm run test:live` runs the `*.live.test.ts` lane — needs the sbx daemon + Docker + creds, so it's + **NOT** part of `check`/CI. Run it locally after any `sbx` upgrade (§9, substrate risk). + +## 2. Module rules (ESM, strict) + +- NodeNext + `verbatimModuleSyntax`: **every relative import ends in `.js`** (e.g. + `import { Router } from "./router/index.js"`); type-only imports use `import type`. No CommonJS. +- `strict`, `noUncheckedIndexedAccess`, `noUnusedLocals/Parameters` are on — index access is + `T | undefined`; handle it explicitly. + +## 3. Three seams + the stateless router (§3) + +``` +Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM + └ CodingBackend (Codex CLI) +``` + +Invariants — do not break these: + +- **Transport ≠ interpretation (§4.9).** `SandboxProvider.exec` is an **opaque byte stream** the router + logs at the boundary. The **only** place that knows an agent's output/CLI format is `CodingBackend` + (`src/backend/codex.ts`). **No Codex `item.*`/event-schema or rollout-file knowledge may live outside + `backend/codex.ts`.** +- **No central state store (§3/§4.6).** The router runs no DB and keeps no disk state of its own. + Discovery = `SandboxProvider.list()` (`sbx ls`) + deterministic naming. Per-thread durable state + (`transcript.jsonl`, `session`, `thread`) lives **in each sandbox** under `~/.agent-state/`. Router + memory holds only the transient `idle/running/pending` lock + ts-dedupe set — lost on restart by + design (an in-flight turn is re-driven idempotently on the next mention). +- **Pure core vs I/O shell.** Pure modules import only `../types.js`, seam interfaces, and pure stdlib + (`node:crypto`) — **never** `node:child_process`, `node:fs`, `@slack/*`. I/O shells import the pure + core, never the reverse. New logic ⇒ a pure module + a colocated `*.test.ts`. This is what keeps the + system testable offline (no daemon, tokens, or creds in CI). + +## 4. Sandbox naming (§4.1) — `src/router/sandbox-name.ts` + +- `name = "t-" + channel + "-" + thread_ts`. **sbx `--name` allows `[A-Za-z0-9.+-]` but NOT `_`** + (verified, v0.31.1). Periods are legal — **keep the `.`; never `thread_ts.replace(".", "_")`** (the + spec's §4.1 sample has this bug; it emits an illegal `_`). A test guards against reintroducing it. +- The name is injective + reversible (split once after `t-`; channel is `[A-Za-z0-9]`, thread_ts is + `\d+\.\d+`). Over-length names fall back to `t-`; the full id is stored in `~/.agent-state/thread`. + +## 5. Boundary logging (§4.9) — `src/router/log.ts` + +- Always-on **summaries** (argv + session id + prompt size/hash; exit code + duration + final-message + snippet + `ok`) and a **verbose** toggle for raw stdout/stderr + full prompt. `direction ∈ in | out | + router`; `turnId` correlates a turn's IN + streamed OUT + exit. +- **Best-effort, never blocking** — writes never `await` on the turn's critical path and never throw + (a full disk / slow shipper must not stall a turn). **Cap/hash** large prompts and tool-output bursts. +- **Never log credentials** — they're proxy-side and never enter argv/prompt (§4.7). A test asserts no + credential-shaped field is emitted. + +## 6. Router lifecycle (§4.1) — `src/router/router.ts`, `dispatcher.ts` + +- **ACK the Socket Mode envelope immediately (<3s) on receipt; run the handler async.** Drop + `retry_num > 0`. Dedupe on the trigger `ts` (TTL'd to the ~5-min retry window). +- **Acks are reactions, not replies.** 👀 on every mention; swap to ✅/❌ on completion. A *reply* ack + would move the catch-up high-water mark and corrupt the delta if a turn fails before posting (§4.2). +- **Coalescing.** Mid-turn mentions only flip one `pending` bit; one follow-up turn runs whose delta + covers them all — **Slack is the queue**, mentions are never buffered in the router. +- **The stream reader is part of the dispatched turn and must outlive the dispatch.** "Free the router" + means free it for *other* threads — never drop this turn's reader (truncates the log / trips the + broken-pipe panic, §9.15). +- **Write-after-success (§4.6).** Persist the session id BEFORE posting; append the transcript AFTER the + reply. An abandoned/crashed turn then re-feeds cleanly (at-least-once). +- Turn-1 vs resume is the **session file's presence** (§4.5); the transcript high-water mark is the + **delta boundary** (§4.2). + +## 7. Codex / exec gotchas (§4.5, §9.14, §9.15) — `src/sandbox/sbx-argv.ts`, `backend/codex.ts` + +- Wrap exec in `bash -c` (`sbx exec` sources no env). **Close stdin** (never pass `-i`) and **capture + stderr** too. **Never `--ephemeral`** (breaks resume). +- Keep Codex's native sandbox ON: `-c sandbox_mode=workspace-write -c approval_policy=never` (set via + `-c` because `resume` lacks `-s`). This is defense-in-depth against prompt injection — Slack content + is untrusted (§5). +- Headless `codex exec` can emit **empty stdout** without a TTY on long prompts — `parseResult` treats + empty output as a failed turn, and `sbx-argv` has a PTY wrap option (`script`) to mitigate. Resolve + PTY-vs-pin during live verification; the boundary log surfaces "argv → 0 bytes, exit 0". + +## 8. Test conventions + +- **Vitest** (`globals` on), colocated `*.test.ts`, run via `npm test`. **Inject collaborators** (the + spawn fn, a clock, fakes) — there is no live daemon, no tokens, and no mocking framework needed. +- New pure logic ⇒ a colocated transition/round-trip/table/fixture test. Backend parsing is verified + against committed `src/backend/fixtures/` (real bytes after the live pass), including the empty-output + regression. Live-only checks go in `*.live.test.ts` (gated out of CI). + +## 9. Commits + +- Small, logical, **green at every commit** (`npm run check` passes). Add a runtime dependency only at + the seam that first needs it. Work on a branch; don't commit to the default branch directly. End + commit messages with the project's `Co-Authored-By` trailer. + +## 10. Don't relitigate (§6) / out of scope for v0 (§7) + +Settled decisions: router-is-plumbing; transport≠interpretation boundary logging; **Codex-first** behind +`CodingBackend` (Claude/Pi later); agent runs as a **CLI inside the sandbox**; **sbx `--clone`** isolation +(host repo read-only; never edit the live working tree); host clone is a **seed, not a sync target**; +services run **in-VM**; eviction = `sbx stop` (auto) and `sbx rm` = terminal; **durability = the PR + the +Slack thread**, no state store; **no per-turn `[checkpoint]` commits**; reactions-not-replies; immediate +ACK + ts-dedupe; repo-scoped GitHub token; session id captured once and persisted; mentions coalesce; +**shipping (push/PR) is the agent's job** — the harness runs no `git`/`gh` and detects no PR intent. + +Out of scope (v0): multi-platform; cloud/multi-tenant sandbox providers; host-`localhost` services; host +user-config passthrough; a local HTTP artifact server; recovery after `sbx rm`; central/persistent router +state; catching up on mentions missed during downtime; **any LLM logic in the router**. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..de0e244 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +See [AGENTS.md](./AGENTS.md) for the rules and architecture invariants to follow when working in this repo. diff --git a/README.md b/README.md index 0302bde..3f175e8 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,86 @@ -# Slack Coding Agent — starter +# Slack Coding Agent -A **standalone** TypeScript scaffold for the router in the design spec: mention the bot in a -Slack thread → it works in an isolated sandbox → it reports back in-thread. Independent project; -conventions are **learned from pi-mom, but no code is shared or imported** (spec §10). +Mention the bot in a Slack thread → a coding-agent CLI (Codex) does the work in an isolated `sbx` +microVM with your repo + credentials → it reports back **in the same thread**. The router is pure +plumbing (no LLM); every thread maps 1:1 to a sandbox and a coding-agent session. Standalone TypeScript +project; conventions learned from pi-mom but **no code shared** (spec §10). + +Design spec: `.context/attachments/2PRioL/slack-coding-agent-spec.md`. Working in the repo? Read +[AGENTS.md](./AGENTS.md) first. + +## Architecture (three seams + a stateless router, spec §3) + +``` +Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM + └ CodingBackend (Codex CLI) +``` + +The router owns only transient in-memory coordination (the idle/running/pending lock + ts-dedupe). +Per-thread durable state (the seen-message ledger + session id) lives **in each sandbox** under +`~/.agent-state/`; discovery is `sbx ls` + deterministic naming. Durability is the **PR + the Slack +thread**, not a state store. ## Prerequisites -- Node **≥ 22** (the project is ESM / `NodeNext`). +- **Node ≥ 22** (ESM / NodeNext). +- **Docker Desktop** running + **`sbx` v0.31.1** (the daemon: `sbx daemon start`). +- A coding-agent credential in sbx (the bot gates on this and walks you through it): for Codex, + `sbx secret set -g openai --oauth`. +- A **Slack app with Socket Mode** enabled — bot scopes `app_mentions:read`, `chat:write`, + `reactions:write`, `channels:history` (+`groups:history` for private), `files:write`, `users:read`; + an app-level token (`xapp-…`, `connections:write`) and a bot token (`xoxb-…`). +- A **target git repo** to operate on (the `--clone` seed). -## Commands (all verified on Node 22) +## Run + +```bash +npm install +export APP_SLACK_APP_TOKEN=xapp-… # Socket Mode app-level token +export APP_SLACK_BOT_TOKEN=xoxb-… # bot token +export SCA_REPO=/path/to/your/repo # the --clone seed +# optional: SCA_AGENT=codex|claude SCA_LOG_LEVEL=summary|verbose SCA_BOT_USER=U… +npm run build && npm start -- --sandbox=sbx ./data +# or, for development: npm run dev -- --sandbox=sbx ./data +``` + +On first use the router checks `sbx secret ls`; if the agent's credential is missing it prints the +one-time setup steps and exits. Then it listens for `@bot` mentions, runs a turn per thread, and +replies in-thread. Watch a thread's boundary log: + +```bash +tail -f ./data/router.log | jq 'select(.threadId=="t-C0ABCDEF-1748600000.123456")' +``` + +## Commands | Command | What it does | | --- | --- | -| `npm install` | Install the dev toolchain. | -| `npm run dev` | Run the CLI under `tsx watch` (pass args: `npm run dev -- --sandbox=sbx ./data`). | -| `npm run build` | Type-check and emit `dist/` (`tsc -p tsconfig.build.json`). | -| `npm start` | Run the built CLI (`node dist/cli/index.js`). | -| `npm run check` | **CI verify:** Biome (lint + format + import-organize) + `tsc --noEmit`. | -| `npm run check:fix` | Apply Biome fixes, then type-check. | -| `npm run lint` / `format` / `typecheck` | The individual steps. | +| `npm run check` | **The gate:** Biome (lint+format+imports, `--error-on-warnings`) + `tsc --noEmit` + Vitest. | +| `npm run check:fix` | Apply Biome fixes, then run the gate. | +| `npm test` / `test:watch` / `test:cov` | Run Vitest (offline unit tests). | +| `npm run test:live` | The `*.live.test.ts` lane (needs sbx daemon + Docker + creds; **not** in CI). | +| `npm run build` | Emit `dist/` (`tsc -p tsconfig.build.json`, excludes tests). | +| `npm start` / `npm run dev` | Run the built CLI / run under `tsx watch`. | ## Layout (by seam — spec §3) ``` src/ - platform/ PlatformAdapter — Slack Socket Mode ingress/egress - sandbox/ SandboxProvider — sbx microVM lifecycle (exec = the logged transport) - backend/ CodingBackend — Codex / Claude Code / Pi (turnArgs + parseResult) - router/ Router — dedupe, idle/running/pending, dispatch, boundary logging - cli/ entrypoint — `--sandbox= ` (pi-mom-style) - types.ts shared types -.github/workflows/ ci.yml (check + build), audit.yml (weekly npm audit) -biome.json single lint + format config (Biome 2.x) -tsconfig.json base TS config (strict, ESM/NodeNext) -tsconfig.build.json build config (emits dist/, excludes tests) + platform/ slack-map.ts (pure event→Mention) + slack.ts (Socket Mode + Web API adapter) + sandbox/ sbx-argv.ts (pure argv builders) + sbx-provider.ts (spawn/drain I/O shell) + backend/ codex.ts (turnArgs/parseResult/sessionId — the ONLY agent-format knowledge) + fixtures/ + router/ state-machine, dedupe, sandbox-name, agent-state(+store), log, dispatcher, router + cli/ args.ts + onboarding.ts + index.ts (composition root) + types.ts shared types ``` -## Conventions (re-implemented from pi-mom's — spec §10) - -- **TypeScript**, `strict`, **ESM (`NodeNext`)** — relative imports use `.js` extensions (e.g. `import { Router } from "./router/index.js"`). -- **Biome** is the single lint + format + import-organize tool. `$schema` points at the **local** `node_modules` schema so it always matches the installed Biome version (avoids the "schema does not match CLI version" error). -- **tsconfig pair:** `tsconfig.json` (base — editor + `tsc --noEmit`) and `tsconfig.build.json` (emits `dist/`, excludes tests). -- **CI:** GitHub Actions on push/PR → `npm ci --ignore-scripts` → `check` → `build`; plus a weekly `npm audit --omit=dev`. - -## Implementation checklist (next steps) - -The seams are typed interfaces with skeleton wiring (`Router` shows the dispatch shape). Fill them in roughly this order — section refs are to the design spec: +Pure logic is separated from I/O so almost everything is unit-tested offline with injected fakes (the +dispatcher test is the logical end-to-end). Parts that touch live services — the Socket Mode connection +and the sbx/Codex execution path — are verified hands-on (see [AGENTS.md](./AGENTS.md) §8 and the live +lane). -1. **PlatformAdapter** (`src/platform`) — Slack Socket Mode. **ACK the envelope immediately (<3s), run the handler async**; user-facing acks are reactions, not replies (§3, §4.1). Add `@slack/socket-mode` + `@slack/web-api`. -2. **SandboxProvider** (`src/sandbox`) — `sbx` microVM: `create` (`--clone`), `exec` (**wrap in `bash -c`**), `getFile`/`putFile`, `stop`/`destroy`, `list` (`sbx ls`). (§4.3, §4.4, §4.5) -3. **CodingBackend** (`src/backend`) — Codex first: `turnArgs` + `parseResult` + `captureSessionId`. Drive with `--json`, **capture stderr too, keep reading until exit** (§4.5, §4.9, §9.14–§9.15). -4. **Router** (`src/router`) — idle/running/pending coalescing, `ts` dedupe, boundary logging, and per-thread `~/.agent-state` (`transcript.jsonl` + `session`) (§4.1, §4.2, §4.6, §4.9). -5. **Wire it** in `src/cli` — construct the three implementations + `Router`, then `router.start()`. +## Conventions -> Runtime Slack/agent dependencies are intentionally **not** included yet, so the scaffold installs and builds with the toolchain alone. Add them as you implement each seam. +- **TypeScript** `strict`, **ESM (NodeNext)** — relative imports use `.js`; type-only imports use + `import type`. **Biome** is the single lint/format/import tool. CI: GitHub Actions → + `npm ci --ignore-scripts` → `check` → `build`; plus a weekly `npm audit`. From 663b4cf6096ae7db3fac38a59bdf5e9256e2a2b6 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:41:06 -0400 Subject: [PATCH 17/47] C16: idle-sweep eviction + wire into the CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/router/idle-sweep.ts — periodic sweep (spec §4.6): sbx ls running sandboxes, reverse each name to its thread (deterministic name, else the ~/.agent-state/thread reverse-map), read last-message time from Slack, and sbx stop the idle ones. Pure selectIdle/tsToMs/parseThreadRef + best-effort I/O; unknown activity is never evicted and foreign sandboxes (_login-tmp) are never touched. The router never auto-rm's. Wired into the CLI to sweep hourly (24h idle window). CI already runs the full Vitest suite via `npm run check`, so no workflow change is needed. 7 tests incl. hash-name reverse lookup. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli/index.ts | 5 ++ src/router/idle-sweep.test.ts | 122 ++++++++++++++++++++++++++++ src/router/idle-sweep.ts | 144 ++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 src/router/idle-sweep.test.ts create mode 100644 src/router/idle-sweep.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index 10e9076..c6b1865 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -11,6 +11,7 @@ import { createWriteStream, mkdirSync } from "node:fs"; import { join } from "node:path"; import { CodexBackend } from "../backend/codex.js"; import { SlackAdapter } from "../platform/slack.js"; +import { IdleSweeper } from "../router/idle-sweep.js"; import { Dispatcher, Router } from "../router/index.js"; import { createJsonlSink, type LogLevel, type LogSink } from "../router/log.js"; import { SbxProvider } from "../sandbox/sbx-provider.js"; @@ -74,6 +75,10 @@ async function run(args: Args): Promise { }); const router = new Router(platform, dispatcher, { logSink }); + // Idle eviction: sbx stop (lossless) sandboxes whose threads have gone quiet + // (§4.6). Sweep hourly; the router never auto-`rm`s. + new IdleSweeper({ sandbox: provider, platform, logSink }).start(60 * 60 * 1000); + router.start(); await platform.start(); console.error( diff --git a/src/router/idle-sweep.test.ts b/src/router/idle-sweep.test.ts new file mode 100644 index 0000000..1e38e0f --- /dev/null +++ b/src/router/idle-sweep.test.ts @@ -0,0 +1,122 @@ +import type { SandboxHandle } from "../sandbox/index.js"; +import type { ThreadId, ThreadMessage } from "../types.js"; +import { IdleSweeper, parseThreadRef, selectIdle, tsToMs } from "./idle-sweep.js"; + +const msg = (ts: string): ThreadMessage => ({ ts, user: "U1", text: "x" }); + +describe("tsToMs", () => { + it("converts Slack ts to epoch ms", () => { + expect(tsToMs("1748600000.123456")).toBeCloseTo(1748600000123.456, 0); + }); +}); + +describe("parseThreadRef", () => { + it("parses a 'channel threadTs' reverse-map line", () => { + expect(parseThreadRef("C0ABCDEF 1748600000.123456\n")).toEqual({ + channel: "C0ABCDEF", + threadTs: "1748600000.123456", + }); + }); + it("returns null for malformed input", () => { + expect(parseThreadRef("nospace")).toBeNull(); + expect(parseThreadRef("")).toBeNull(); + }); +}); + +describe("selectIdle", () => { + const now = 1_000_000; + const idleMs = 1000; + it("selects only sandboxes idle for >= idleMs", () => { + expect( + selectIdle( + [ + { name: "old", lastActivityMs: now - 2000 }, + { name: "fresh", lastActivityMs: now - 500 }, + { name: "exactly", lastActivityMs: now - 1000 }, + ], + now, + idleMs, + ), + ).toEqual(["old", "exactly"]); + }); + it("never evicts sandboxes with unknown activity", () => { + expect(selectIdle([{ name: "unknown", lastActivityMs: null }], now, idleMs)).toEqual([]); + }); +}); + +class FakeSweepSandbox { + stopped: string[] = []; + readonly files = new Map(); + constructor(private readonly handles: SandboxHandle[]) {} + async list(): Promise { + return this.handles; + } + async stop(handle: SandboxHandle): Promise { + this.stopped.push(handle.name); + } + async homeDir(): Promise { + return "/home/agent"; + } + async readFile(_h: SandboxHandle, absPath: string): Promise { + return this.files.get(absPath) ?? null; + } + async writeFile(): Promise {} + // unused provider surface + async create(): Promise { + return { name: "x" }; + } + async exec(): Promise<{ stdout: string; stderr: string; exitCode: number }> { + return { stdout: "", stderr: "", exitCode: 0 }; + } + async execShell(): Promise<{ stdout: string; stderr: string; exitCode: number }> { + return { stdout: "", stderr: "", exitCode: 0 }; + } + async getFile(): Promise { + return new Uint8Array(); + } + async putFile(): Promise {} + async destroy(): Promise {} +} + +class FakeSweepPlatform { + constructor(private readonly byThread: Map) {} + async fetchThread(thread: ThreadId): Promise { + return this.byThread.get(`${thread.channel}:${thread.threadTs}`) ?? []; + } +} + +describe("IdleSweeper.sweepOnce", () => { + const NOW = tsToMs("1748600100.000000"); + + it("stops idle threads, keeps fresh ones, and ignores foreign sandboxes", async () => { + const handles: SandboxHandle[] = [ + { name: "t-C0ABCDEF-1748600000.000000" }, // idle (100s ago) + { name: "t-C0FRESH-1748600099.000000" }, // fresh (1s ago) + { name: "_login-tmp" }, // foreign — never touch + ]; + const sandbox = new FakeSweepSandbox(handles); + const platform = new FakeSweepPlatform( + new Map([ + ["C0ABCDEF:1748600000.000000", [msg("1748600000.000000")]], + ["C0FRESH:1748600099.000000", [msg("1748600099.000000")]], + ]), + ); + const sweeper = new IdleSweeper({ sandbox, platform, idleMs: 10_000, now: () => NOW }); + + const { stopped } = await sweeper.sweepOnce(); + expect(stopped).toEqual(["t-C0ABCDEF-1748600000.000000"]); + expect(sandbox.stopped).toEqual(["t-C0ABCDEF-1748600000.000000"]); + }); + + it("reverses a hash-named sandbox via ~/.agent-state/thread", async () => { + const hashName = "t-0123456789abcdef"; + const sandbox = new FakeSweepSandbox([{ name: hashName }]); + sandbox.files.set("/home/agent/.agent-state/thread", "C0HASH 1748600000.000000\n"); + const platform = new FakeSweepPlatform( + new Map([["C0HASH:1748600000.000000", [msg("1748600000.000000")]]]), + ); + const sweeper = new IdleSweeper({ sandbox, platform, idleMs: 10_000, now: () => NOW }); + + expect((await sweeper.sweepOnce()).stopped).toEqual([hashName]); + }); +}); diff --git a/src/router/idle-sweep.ts b/src/router/idle-sweep.ts new file mode 100644 index 0000000..1d03ddb --- /dev/null +++ b/src/router/idle-sweep.ts @@ -0,0 +1,144 @@ +import type { PlatformAdapter } from "../platform/index.js"; +import type { SandboxHandle, SandboxProvider } from "../sandbox/index.js"; +import type { ThreadId } from "../types.js"; +import { highWaterMark } from "./agent-state.js"; +import { AgentStateStore, type SandboxFsLike } from "./agent-state-store.js"; +import { + buildRouterEvent, + type LogContext, + type LogSink, + nullSink, + RouterKind, + safeWrite, +} from "./log.js"; +import { isHashName, parseSandboxName } from "./sandbox-name.js"; + +/** + * Idle eviction without stored timestamps (spec §4.6): periodically `sbx ls` the + * running sandboxes, reverse each name back to its thread (§4.1), read the last + * message time from Slack, and `sbx stop` the idle ones. `stop` is lossless and + * recoverable, so coarse timing is harmless; the router NEVER auto-`rm`s. + * + * One Slack call per running sandbox per sweep — fine at v0 scale. + */ + +/** sbx's documented idle-eviction window (§4.4). */ +export const DEFAULT_IDLE_MS = 24 * 60 * 60 * 1000; + +/** Slack ts ("secs.micros") → epoch ms. */ +export function tsToMs(ts: string): number { + return Number.parseFloat(ts) * 1000; +} + +/** Parse a `~/.agent-state/thread` reverse-map line ("channel threadTs"). */ +export function parseThreadRef(raw: string): ThreadId | null { + const trimmed = raw.trim(); + const space = trimmed.indexOf(" "); + if (space < 0) { + return null; + } + const channel = trimmed.slice(0, space); + const threadTs = trimmed.slice(space + 1).trim(); + return channel !== "" && threadTs !== "" ? { channel, threadTs } : null; +} + +export interface SandboxActivity { + name: string; + /** Epoch ms of the thread's last message, or null if unknown (never evicted). */ + lastActivityMs: number | null; +} + +/** Names whose last activity is at least `idleMs` ago. Unknown activity is kept. */ +export function selectIdle(activities: SandboxActivity[], nowMs: number, idleMs: number): string[] { + return activities + .filter((a) => a.lastActivityMs !== null && nowMs - a.lastActivityMs >= idleMs) + .map((a) => a.name); +} + +export interface IdleSweeperDeps { + sandbox: SandboxProvider & SandboxFsLike; + platform: Pick; + idleMs?: number; + now?: () => number; + logSink?: LogSink; +} + +export class IdleSweeper { + private readonly sandbox: SandboxProvider & SandboxFsLike; + private readonly platform: Pick; + private readonly idleMs: number; + private readonly now: () => number; + private readonly logSink: LogSink; + + constructor(deps: IdleSweeperDeps) { + this.sandbox = deps.sandbox; + this.platform = deps.platform; + this.idleMs = deps.idleMs ?? DEFAULT_IDLE_MS; + this.now = deps.now ?? Date.now; + this.logSink = deps.logSink ?? nullSink; + } + + /** Map a sandbox handle back to its thread (deterministic name, then the reverse-map file). */ + private async threadOf(handle: SandboxHandle): Promise { + const parsed = parseSandboxName(handle.name); + if (parsed !== null) { + return parsed; + } + if (!isHashName(handle.name)) { + return null; // foreign/utility sandbox (e.g. _login-tmp) + } + const raw = await new AgentStateStore(this.sandbox, handle).readThread(); + return raw === null ? null : parseThreadRef(raw); + } + + /** One pass: stop every idle sandbox. Best-effort per sandbox. */ + async sweepOnce(): Promise<{ stopped: string[] }> { + const handles = await this.sandbox.list(); + const activities: SandboxActivity[] = []; + for (const handle of handles) { + const thread = await this.threadOf(handle); + if (thread === null) { + continue; // not one of ours — never touch it + } + const lastTs = highWaterMark(await this.platform.fetchThread(thread)); + activities.push({ + name: handle.name, + lastActivityMs: lastTs === null ? null : tsToMs(lastTs), + }); + } + const stopped: string[] = []; + for (const name of selectIdle(activities, this.now(), this.idleMs)) { + try { + await this.sandbox.stop({ name }); + this.logStop(name); + stopped.push(name); + } catch { + // best-effort: a failed stop is retried next sweep + } + } + return { stopped }; + } + + /** Run sweepOnce on an interval; returns a stop function. */ + start(intervalMs: number): () => void { + const timer = setInterval(() => { + void this.sweepOnce().catch(() => {}); + }, intervalMs); + timer.unref?.(); + return () => clearInterval(timer); + } + + private logStop(name: string): void { + const ctx: LogContext = { + threadId: name, + channel: "", + thread_ts: "", + sandbox: name, + turnId: "idle-sweep", + }; + safeWrite( + this.logSink, + buildRouterEvent(ctx, new Date().toISOString(), RouterKind.SandboxStop, { reason: "idle" }), + ); + } +} From bd3c559ff03e9d5013175777b8749b6728d6398f Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 17:47:40 -0400 Subject: [PATCH 18/47] Live: verify the sbx I/O seam against a real microVM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/sandbox/sbx-provider.live.test.ts — SBX_LIVE-gated, excluded from CI; creates a credential-free `shell` sandbox and exercises the real SbxProvider: exec via agent-argv + bash -c (§9.9), homeDir ($HOME), writeFile/readFile (+ missing→null), getFile/putFile cp round-trip, and lossless stop→exec auto-restart (§4.6). 6/6 pass on sbx v0.31.1 (~25s). Also confirmed live: sbx ls --json shape is {"sandbox es":[…]} (parseLsJson already handles it); default network policy must be `balanced` (§9.3); in-VM $HOME is /home/agent. `npm run test:live` now sets SBX_LIVE=1. Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 2 +- src/sandbox/sbx-provider.live.test.ts | 80 +++++++++++++++++++++++++++ vitest.live.config.ts | 3 +- 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/sandbox/sbx-provider.live.test.ts diff --git a/package.json b/package.json index b57bfe5..5bffa5a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test": "vitest run", "test:watch": "vitest", "test:cov": "vitest run --coverage", - "test:live": "vitest run --config vitest.live.config.ts", + "test:live": "SBX_LIVE=1 vitest run --config vitest.live.config.ts", "check": "biome check --error-on-warnings . && tsc --noEmit && vitest run", "check:fix": "biome check --write --error-on-warnings . && tsc --noEmit && vitest run" }, diff --git a/src/sandbox/sbx-provider.live.test.ts b/src/sandbox/sbx-provider.live.test.ts new file mode 100644 index 0000000..826f199 --- /dev/null +++ b/src/sandbox/sbx-provider.live.test.ts @@ -0,0 +1,80 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import type { SandboxHandle } from "./index.js"; +import { SbxProvider } from "./sbx-provider.js"; + +/** + * Live verification of the sbx I/O seam against a real microVM (spec §9). Gated + * by SBX_LIVE=1 (set by `npm run test:live`) and excluded from the default suite + * + CI — it needs Docker + the sbx daemon. Uses a credential-free `shell` + * sandbox so it covers exec/cp/$HOME/stop-restart without any agent auth. + * + * Verified manually 2026-05-31 (sbx v0.31.1): bash -c env, $HOME=/home/agent, + * cp round-trip, and lossless stop→exec auto-restart all pass. + */ + +const LIVE = process.env.SBX_LIVE === "1"; +const NAME = "sca-live-test"; + +describe.skipIf(!LIVE)("SbxProvider — live shell sandbox", () => { + const provider = new SbxProvider({ createOptions: { agent: "shell", clone: false } }); + const handle: SandboxHandle = { name: NAME }; + let workspace = ""; + + beforeAll(async () => { + workspace = mkdtempSync(join(tmpdir(), "sca-live-")); + try { + await provider.destroy(handle); // clean up any leftover from a prior run + } catch { + // not present — fine + } + await provider.create(NAME, workspace); + }); + + afterAll(async () => { + try { + await provider.destroy(handle); + } catch { + // best-effort cleanup + } + if (workspace !== "") { + rmSync(workspace, { recursive: true, force: true }); + } + }); + + it("exec runs an agent argv via bash -c and drains stdout (§4.5/§9.9)", async () => { + const result = await provider.exec(handle, ["echo", "hi from agent argv"]); + expect(result.stdout.trim()).toBe("hi from agent argv"); + expect(result.exitCode).toBe(0); + }); + + it("homeDir resolves $HOME", async () => { + expect((await provider.homeDir(handle)).length).toBeGreaterThan(0); + }); + + it("writeFile/readFile round-trips; a missing file reads as null", async () => { + const home = await provider.homeDir(handle); + await provider.writeFile(handle, `${home}/.agent-state/session`, "sess-live\n"); + expect(await provider.readFile(handle, `${home}/.agent-state/session`)).toBe("sess-live\n"); + expect(await provider.readFile(handle, `${home}/.agent-state/missing`)).toBeNull(); + }); + + it("getFile/putFile round-trips bytes via cp + a host temp file", async () => { + const home = await provider.homeDir(handle); + await provider.putFile(handle, `${home}/.agent-state/bin`, new TextEncoder().encode("bytes\n")); + const got = await provider.getFile(handle, `${home}/.agent-state/bin`); + expect(new TextDecoder().decode(got)).toBe("bytes\n"); + }); + + it("stop then exec auto-restarts losslessly — in-VM state survives (§4.6)", async () => { + const home = await provider.homeDir(handle); + await provider.stop(handle); + const result = await provider.exec(handle, ["cat", `${home}/.agent-state/session`]); + expect(result.stdout).toContain("sess-live"); + }); + + it("list() includes the sandbox", async () => { + expect((await provider.list()).some((h) => h.name === NAME)).toBe(true); + }); +}); diff --git a/vitest.live.config.ts b/vitest.live.config.ts index cc5c11b..33aefde 100644 --- a/vitest.live.config.ts +++ b/vitest.live.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ include: ["src/**/*.live.test.ts"], passWithNoTests: true, testTimeout: 120_000, - hookTimeout: 120_000, + // sandbox create can pull an image on first run — give setup generous time. + hookTimeout: 300_000, }, }); From 4a10c864804365c5f2ef1f69fd8be661d7cda31d Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 18:04:38 -0400 Subject: [PATCH 19/47] Live (codex): fix headless stdin hang + relay real errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live verification against codex 0.130.0 in an sbx microVM surfaced two findings, now fixed and committed: 1. §9.15 stdin hang (would have hung EVERY turn): `sbx exec` keeps the VM process's stdin open even when the host closes its end, so `codex exec` blocks on "Reading additional input from stdin..." forever. Closing host stdin is necessary but not sufficient — execArgv now redirects the in-VM command's stdin from /dev/null (` < /dev/null`), default on, off under pty. This is exactly the failure boundary logging is meant to catch. 2. Real --json schema confirmed: thread.started carries thread_id (=session id; parseSessionId verified live), plus turn.started/error/turn.failed. parseResult now extracts the codex error message so the dispatcher relays it (e.g. a usage limit) instead of a generic failure. codex-error.jsonl is now REAL captured bytes. Also documented (AGENTS.md/codex.ts): bash -c PATH works (§9.9); the --clone workspace is empty under exec (repo is RO at /run/sandbox/source) so provisioning must clone it (§4.3); subscription hit its usage cap (§9.7), so the success-path fixture + session-resume capture await an API key / reset. 163 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 11 +++++++-- src/backend/codex.test.ts | 10 ++++---- src/backend/codex.ts | 33 +++++++++++++++++++++++--- src/backend/fixtures/README.md | 18 ++++++++------ src/backend/fixtures/codex-error.jsonl | 6 +++-- src/router/dispatcher.test.ts | 7 ++++++ src/router/dispatcher.ts | 6 ++++- src/sandbox/sbx-argv.test.ts | 17 ++++++++----- src/sandbox/sbx-argv.ts | 27 +++++++++++++++++---- src/sandbox/sbx-provider.test.ts | 11 +++++++-- 10 files changed, 115 insertions(+), 31 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a0f2817..5ea9024 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -91,8 +91,15 @@ Invariants — do not break these: ## 7. Codex / exec gotchas (§4.5, §9.14, §9.15) — `src/sandbox/sbx-argv.ts`, `backend/codex.ts` -- Wrap exec in `bash -c` (`sbx exec` sources no env). **Close stdin** (never pass `-i`) and **capture - stderr** too. **Never `--ephemeral`** (breaks resume). +- Wrap exec in `bash -c` (`sbx exec` sources no env). **Redirect the agent's stdin from `/dev/null` + IN the VM** (` < /dev/null`) — verified live: `sbx exec` keeps the VM process's stdin open + even when the host closes its end, so codex otherwise hangs on "Reading additional input from + stdin..." forever (§9.15). Never pass `-i`; **capture stderr** too. **Never `--ephemeral`** (breaks + resume). The `--json` stream's `thread.started` event carries the session id (`thread_id`). +- **Provisioning (§4.3):** with `--clone`, `/home/agent/workspace` is empty under `sbx exec`; the repo + is a read-only mount at `/run/sandbox/source` (a valid git repo) plus a git daemon. A provision step + (the dispatcher's `provisionScript`) must clone it into a writable dir, point `origin` at the real + remote, and seed a non-base branch before the agent works. - Keep Codex's native sandbox ON: `-c sandbox_mode=workspace-write -c approval_policy=never` (set via `-c` because `resume` lacks `-s`). This is defense-in-depth against prompt injection — Slack content is untrusted (§5). diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts index 4cd9725..e027ea1 100644 --- a/src/backend/codex.test.ts +++ b/src/backend/codex.test.ts @@ -74,8 +74,10 @@ describe("parseCodexResult", () => { expect(parseCodexResult("")).toEqual({ finalText: "", ok: false }); }); - it("returns ok=false when the stream errors before a final message", () => { - expect(parseCodexResult(errorStream)).toEqual({ finalText: "", ok: false }); + it("surfaces the codex error message on a failed turn (real fixture)", () => { + const result = parseCodexResult(errorStream); + expect(result.ok).toBe(false); + expect(result.finalText).toContain("usage limit"); // relayed to the user }); it("tolerates interleaved non-JSON progress lines", () => { @@ -99,8 +101,8 @@ describe("parseSessionId", () => { expect(parseSessionId(stream)).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); }); - it("finds the id even on an errored turn (session was still created)", () => { - expect(parseSessionId(errorStream)).toBe("7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"); + it("finds the id even on an errored turn (real thread.started, session still created)", () => { + expect(parseSessionId(errorStream)).toBe("019e800c-958e-79e0-b7c9-9a5502563f58"); }); it("returns null when no id is present", () => { diff --git a/src/backend/codex.ts b/src/backend/codex.ts index d80dffc..05fb2e2 100644 --- a/src/backend/codex.ts +++ b/src/backend/codex.ts @@ -6,12 +6,15 @@ import type { CodingBackend, TurnResult } from "./index.js"; * Everything here is pure (off captured bytes) except captureSessionId, whose * one shell call is injected so the rest stays unit-testable. * - * Verified against codex 0.135.0: + * Verified against codex 0.135.0 (host) and run live on 0.130.0 (in an sbx VM): * - `codex exec [--json] [-c k=v]... [-- PROMPT]` * - `codex exec resume [--json] [-c k=v]... [-- SESSION_ID PROMPT]` * - `resume` has `-c` and `--json` but NOT `-s/--sandbox`, so we set the sandbox * mode + approval policy via `-c` uniformly (works on both subcommands). - * - NEVER `--ephemeral` (breaks resume, §9.15); no `-i` (provider closes stdin). + * - NEVER `--ephemeral` (breaks resume). The `--json` stream's `thread.started` + * event carries `thread_id` (= the session id; parseSessionId reads it live). + * - codex blocks on stdin headlessly; the provider redirects its stdin from + * /dev/null IN the VM (§9.15) — verified live. */ export const CODEX_HOME = "~/.codex"; @@ -84,17 +87,34 @@ function extractAssistantText(event: Record): string | null { return null; } +/** Error text from an `error` / `turn.failed` event (real shapes, verified live). */ +function extractErrorMessage(event: Record): string | null { + const type = asString(event.type) ?? ""; + if ( + type !== "error" && + type !== "turn.failed" && + !type.includes("error") && + !type.includes("failed") + ) { + return null; + } + return asString(event.message) ?? (isRecord(event.error) ? asString(event.error.message) : null); +} + /** * Interpret the captured `--json` stream into the result to relay. Tolerant of * several event shapes (the schema moves between versions) and of non-JSON * progress lines. Empty output -> {"", false}: the §9.14 headless regression * (argv -> 0 bytes, exit 0) surfaces as a failed turn, not a silent empty reply. + * On a failed turn the codex error message is relayed (e.g. a usage limit), + * still with ok=false. */ export function parseCodexResult(captured: string): TurnResult { if (captured.trim() === "") { return { finalText: "", ok: false }; } let finalText = ""; + let errorText = ""; for (const line of captured.split("\n")) { if (line.trim() === "") { continue; @@ -112,8 +132,15 @@ export function parseCodexResult(captured: string): TurnResult { if (text !== null && text !== "") { finalText = text; } + const error = extractErrorMessage(event); + if (error !== null && error !== "") { + errorText = error; + } + } + if (finalText.trim() !== "") { + return { finalText, ok: true }; } - return { finalText, ok: finalText.trim() !== "" }; + return { finalText: errorText, ok: false }; } function findSessionId(record: Record): string | null { diff --git a/src/backend/fixtures/README.md b/src/backend/fixtures/README.md index de307d0..886decd 100644 --- a/src/backend/fixtures/README.md +++ b/src/backend/fixtures/README.md @@ -2,14 +2,18 @@ Sample `codex exec --json` bytes that `codex.ts` parsing is verified against. -> **Status: best-guess placeholders.** These approximate the codex 0.135.0 -> `--json` event shapes. They are **replaced with real captured bytes** during -> the live verification pass (plan C8.5 / spec §9), at which point the parser is -> reconciled to the exact schema. The parser is intentionally tolerant of -> several plausible event shapes so it survives the swap. +Captured live against codex 0.130.0 in an sbx microVM (2026-05-31). The real +`--json` event schema is `thread.started` (carries `thread_id` = the session id), +`turn.started`, `item.*`, `turn.completed`/`turn.failed`, and `error`. The parser +is tolerant of several shapes so it survives version drift. -- `codex-json-stream.jsonl` — a successful fresh turn (session-id event + final assistant message). -- `codex-error.jsonl` — a turn that errored before producing a final message. +- `codex-error.jsonl` — **REAL bytes**: a `thread.started` + `turn.started` + + `error` + `turn.failed` stream (the run hit the subscription usage limit, §9.7). + Confirms `parseSessionId` reads `thread_id` and the error message is relayed. +- `codex-json-stream.jsonl` — a **successful** fresh turn (assistant message). Still + a documented placeholder: the live success path was **quota-blocked** (§9.7); + recapture with an OpenAI API key or after the limit resets. The parser already + handles the `item.completed`/`agent_message`/`last_agent_message` shapes. - `codex-empty.stdout.txt` — the headless empty-output regression: 0 bytes, exit 0 (§9.14). - `codex-rollout-filename.txt` — a sample `~/.codex/sessions/.../rollout-*.jsonl` filename. - `codex-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-rollout selection. diff --git a/src/backend/fixtures/codex-error.jsonl b/src/backend/fixtures/codex-error.jsonl index 3267848..c1533cf 100644 --- a/src/backend/fixtures/codex-error.jsonl +++ b/src/backend/fixtures/codex-error.jsonl @@ -1,2 +1,4 @@ -{"type":"thread.started","thread_id":"7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f"} -{"type":"error","message":"stream disconnected before the model finished"} +{"type":"thread.started","thread_id":"019e800c-958e-79e0-b7c9-9a5502563f58"} +{"type":"turn.started"} +{"type":"error","message":"You've hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus), or try again at Jun 2nd, 2026 11:49 AM."} +{"type":"turn.failed","error":{"message":"You've hit your usage limit. Upgrade to Plus to continue using Codex (https://chatgpt.com/explore/plus), or try again at Jun 2nd, 2026 11:49 AM."}} diff --git a/src/router/dispatcher.test.ts b/src/router/dispatcher.test.ts index 9e369b8..70c2465 100644 --- a/src/router/dispatcher.test.ts +++ b/src/router/dispatcher.test.ts @@ -261,6 +261,13 @@ describe("dispatchTurn — failure & skip", () => { expect(b.sink.records.map((r) => r.kind)).toContain("turn.abandoned"); }); + it("on !ok WITH an error message, relays the agent's error (e.g. usage limit)", async () => { + const b = build({ result: { finalText: "You've hit your usage limit.", ok: false } }); + await b.dispatcher.dispatchTurn(trigger); + expect(b.platform.replies[0]).toBe("You've hit your usage limit."); + expect(b.trace).not.toContain("write:transcript.jsonl"); // still not persisted (re-feed) + }); + it("on empty delta: skips exec entirely and ✅s", async () => { const b = build({ existing: [SANDBOX], diff --git a/src/router/dispatcher.ts b/src/router/dispatcher.ts index 1f2907b..f46d3d1 100644 --- a/src/router/dispatcher.ts +++ b/src/router/dispatcher.ts @@ -145,7 +145,11 @@ export class Dispatcher { exitCode, stderrSnippet: stderr.slice(0, 500), }); - await this.relay(thread, this.failureMessage); + // Relay the agent's own error (e.g. a usage limit) when it gave one, + // else a generic message. Either way the transcript is NOT appended, so + // the request re-feeds on the next mention (§4.6). + const reply = result.finalText.trim() !== "" ? result.finalText : this.failureMessage; + await this.relay(thread, reply); await this.swapReaction(trigger, false); return { ok: false }; } diff --git a/src/sandbox/sbx-argv.test.ts b/src/sandbox/sbx-argv.test.ts index bd51f12..d323946 100644 --- a/src/sandbox/sbx-argv.test.ts +++ b/src/sandbox/sbx-argv.test.ts @@ -55,18 +55,22 @@ describe("createArgv", () => { }); describe("execArgv", () => { - it("wraps in `bash -c`, uses `--`, and never passes -i (§4.5/§9.15)", () => { + it("wraps in `bash -c`, uses `--`, redirects stdin from /dev/null, no -i (§4.5/§9.15)", () => { expect(execArgv("box", "codex exec --json")).toEqual([ "exec", "box", "--", "bash", "-c", - "codex exec --json", + "codex exec --json < /dev/null", ]); expect(execArgv("box", "x")).not.toContain("-i"); }); + it("stdinFromNull:false omits the /dev/null redirect", () => { + expect(execArgv("box", "cmd", { stdinFromNull: false }).at(-1)).toBe("cmd"); + }); + it("supports login shell, workdir, env, and the PTY mitigation (§9.14)", () => { expect(execArgv("box", "cmd", { login: true })).toContain("-lc"); expect(execArgv("box", "cmd", { workdir: "/repo" })).toEqual([ @@ -77,7 +81,7 @@ describe("execArgv", () => { "--", "bash", "-c", - "cmd", + "cmd < /dev/null", ]); expect(execArgv("box", "cmd", { env: { FOO: "bar" } })).toEqual([ "exec", @@ -87,20 +91,21 @@ describe("execArgv", () => { "--", "bash", "-c", - "cmd", + "cmd < /dev/null", ]); + // Under pty the TTY supplies stdin — no /dev/null redirect. const pty = execArgv("box", "codex exec", { pty: true }); expect(pty[pty.length - 1]).toBe(ptyCommand("codex exec")); }); - it("execAgentArgv quotes an agent argv into the bash -c string", () => { + it("execAgentArgv quotes an agent argv into the bash -c string (+ stdin redirect)", () => { expect(execAgentArgv("box", ["codex", "exec", "fix the bug"])).toEqual([ "exec", "box", "--", "bash", "-c", - "codex exec 'fix the bug'", + "codex exec 'fix the bug' < /dev/null", ]); }); }); diff --git a/src/sandbox/sbx-argv.ts b/src/sandbox/sbx-argv.ts index e49ff44..f85f385 100644 --- a/src/sandbox/sbx-argv.ts +++ b/src/sandbox/sbx-argv.ts @@ -85,12 +85,24 @@ export interface ExecOptions { login?: boolean; /** Wrap the command in a PTY (§9.14). */ pty?: boolean; + /** + * Redirect the in-VM command's stdin from /dev/null (default true). + * + * VERIFIED LIVE (§9.15): `sbx exec` keeps the VM process's stdin open even when + * the host closes its end, so a headless agent like `codex exec` blocks on + * "Reading additional input from stdin..." forever. Redirecting stdin from + * /dev/null IN the VM gives it an immediate EOF. Closing host stdin (the + * provider also does that) is necessary but NOT sufficient on its own. Ignored + * under `pty` (the TTY supplies stdin). + */ + stdinFromNull?: boolean; } /** - * `sbx exec -- bash -c ''`. `command` is a shell string (build - * it from an agent argv with shellJoin). No `-i`: stdin stays closed (§9.15). - * The `--` stops sbx flag parsing so the command's own flags pass through. + * `sbx exec -- bash -c ' < /dev/null'`. `command` is a shell + * string (build it from an agent argv with shellJoin). No `-i`, and stdin is + * redirected from /dev/null in-VM so headless agents can't hang (§9.15). The + * `--` stops sbx flag parsing so the command's own flags pass through. */ export function execArgv(name: string, command: string, opts: ExecOptions = {}): string[] { const argv = ["exec"]; @@ -100,7 +112,14 @@ export function execArgv(name: string, command: string, opts: ExecOptions = {}): if (opts.workdir !== undefined) { argv.push("-w", opts.workdir); } - const inner = opts.pty === true ? ptyCommand(command) : command; + let inner: string; + if (opts.pty === true) { + inner = ptyCommand(command); + } else if (opts.stdinFromNull === false) { + inner = command; + } else { + inner = `${command} < /dev/null`; + } argv.push(name, "--", "bash", opts.login === true ? "-lc" : "-c", inner); return argv; } diff --git a/src/sandbox/sbx-provider.test.ts b/src/sandbox/sbx-provider.test.ts index cca443d..8a8b494 100644 --- a/src/sandbox/sbx-provider.test.ts +++ b/src/sandbox/sbx-provider.test.ts @@ -75,7 +75,14 @@ describe("exec — drain, stdin close, streaming (§4.9/§9.15)", () => { }); expect(result).toEqual({ stdout: "Fixed the bug", stderr: "progress\n", exitCode: 0 }); - expect(recorded).toEqual(["exec", handle.name, "--", "bash", "-c", "codex exec 'fix the bug'"]); + expect(recorded).toEqual([ + "exec", + handle.name, + "--", + "bash", + "-c", + "codex exec 'fix the bug' < /dev/null", + ]); expect(recorded).not.toContain("-i"); expect(stdin?.ended).toBe(true); expect(chunks).toEqual(["stdout:Fixed ", "stdout:the bug", "stderr:progress\n"]); @@ -138,7 +145,7 @@ describe("execShell + SandboxFsLike", () => { "--", "bash", "-c", - "find $HOME -name '*.jsonl'", + "find $HOME -name '*.jsonl' < /dev/null", ]); }); From ad8f3f61c3042f18fe72b59bbc34348073528c7e Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 19:24:05 -0400 Subject: [PATCH 20/47] Live (E2E): tolerate sbx info lines in `sbx ls --json` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First real Slack mention drove the full pipeline (mention.received → 👀 → dispatch) but the turn was abandoned by a bug the boundary log caught: `sbx ls --json` prepends a human-readable "Starting sandboxd daemon..." line to stdout when it auto-starts the daemon, so JSON.parse threw "Unexpected token 'S'". parseLsJson now parses from the first {/[ and never throws (-> [] on no JSON). homeDir defensively takes the last stdout line too. 165 tests (+2). Verified live: mention reached the router and 👀'd. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/sandbox/sbx-argv.test.ts | 12 ++++++++++++ src/sandbox/sbx-argv.ts | 19 ++++++++++++++----- src/sandbox/sbx-provider.ts | 5 ++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/sandbox/sbx-argv.test.ts b/src/sandbox/sbx-argv.test.ts index d323946..a3d5d54 100644 --- a/src/sandbox/sbx-argv.test.ts +++ b/src/sandbox/sbx-argv.test.ts @@ -173,6 +173,18 @@ describe("ls + parseLsJson", () => { expect(parseLsJson("")).toEqual([]); expect(parseLsJson(" \n")).toEqual([]); }); + + it("tolerates sbx info lines prepended before the JSON (verified live)", () => { + const noisy = + 'Starting sandboxd daemon...\nDaemon started.\n{"sandboxes":[{"name":"t-C0-1.2"}]}'; + expect(parseLsJson(noisy)).toEqual([ + { name: "t-C0-1.2", status: undefined, agent: undefined, workspace: undefined }, + ]); + }); + + it("returns [] (never throws) on output with no JSON", () => { + expect(parseLsJson("Starting sandboxd daemon...\nno json here")).toEqual([]); + }); }); describe("cp / stop / rm", () => { diff --git a/src/sandbox/sbx-argv.ts b/src/sandbox/sbx-argv.ts index f85f385..aa1617f 100644 --- a/src/sandbox/sbx-argv.ts +++ b/src/sandbox/sbx-argv.ts @@ -157,16 +157,25 @@ function strOrUndef(value: unknown): string | undefined { } /** - * Parse `sbx ls --json`. The exact field names are confirmed live (§9); this is - * tolerant of both a top-level array and a `{ sandboxes: [...] }` wrapper, and - * skips entries without a string `name`. + * Parse `sbx ls --json`. Tolerant of both a top-level array and a + * `{ sandboxes: [...] }` wrapper, and skips entries without a string `name`. + * + * VERIFIED LIVE: sbx prepends human-readable lines (e.g. "Starting sandboxd + * daemon...") to stdout before the JSON when it auto-starts the daemon, so we + * parse from the first `{`/`[` and never throw — a malformed list yields []. */ export function parseLsJson(stdout: string): SbxListEntry[] { const trimmed = stdout.trim(); - if (trimmed === "") { + const start = trimmed.search(/[[{]/); + if (start < 0) { + return []; + } + let data: unknown; + try { + data = JSON.parse(trimmed.slice(start)); + } catch { return []; } - const data: unknown = JSON.parse(trimmed); const items: unknown[] = Array.isArray(data) ? data : isRecord(data) && Array.isArray(data.sandboxes) diff --git a/src/sandbox/sbx-provider.ts b/src/sandbox/sbx-provider.ts index 812cd12..03c3bd3 100644 --- a/src/sandbox/sbx-provider.ts +++ b/src/sandbox/sbx-provider.ts @@ -143,7 +143,10 @@ export class SbxProvider implements SandboxProvider { async homeDir(handle: SandboxHandle): Promise { const { stdout } = await this.execShell(handle, 'printf %s "$HOME"'); - return stdout.trim() || "/root"; + // Defensive: take the last line in case sbx ever prefixes a start-up info + // line on stdout (it normally routes those to stderr). + const lines = stdout.trim().split("\n"); + return (lines[lines.length - 1] ?? "").trim() || "/root"; } /** File contents, or null if the file does not exist (non-zero `cat` exit). */ From a56d1f0b7f042564bf1fca761210b4b0311441c0 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 19:29:46 -0400 Subject: [PATCH 21/47] Live (E2E): hostname-safe sandbox names (encode '.' as '-') MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Second mention got past parseLsJson to sandbox.create, which then failed: sbx uses the name as the container HOSTNAME, and "t-...-1780269886.030339" fails Docker's hostname validation ("value must be a valid hostname"). Isolated live: the DOT is rejected (the case is fine). So neither '.' (spec §4.1's intent) nor '_' (its sample) is usable. Encode thread_ts's '.' as '-': name = t---, charset [A-Za-z0-9-], still injective + reversible (channel has no '-'; ts is two numeric groups). isCharsetSafe now rejects both '_' and '.'. Verified live: `sbx create --name t-C0B77337UGM-1780269886-030339` succeeds. 165 tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 12 +++++++----- src/router/dispatcher.test.ts | 2 +- src/router/idle-sweep.test.ts | 8 ++++---- src/router/sandbox-name.test.ts | 22 ++++++++++++---------- src/router/sandbox-name.ts | Bin 3537 -> 3831 bytes 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5ea9024..dcb9162 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -57,11 +57,13 @@ Invariants — do not break these: ## 4. Sandbox naming (§4.1) — `src/router/sandbox-name.ts` -- `name = "t-" + channel + "-" + thread_ts`. **sbx `--name` allows `[A-Za-z0-9.+-]` but NOT `_`** - (verified, v0.31.1). Periods are legal — **keep the `.`; never `thread_ts.replace(".", "_")`** (the - spec's §4.1 sample has this bug; it emits an illegal `_`). A test guards against reintroducing it. -- The name is injective + reversible (split once after `t-`; channel is `[A-Za-z0-9]`, thread_ts is - `\d+\.\d+`). Over-length names fall back to `t-`; the full id is stored in `~/.agent-state/thread`. +- `name = "t-" + channel + "-" + thread_ts` with the `.` encoded as `-`. **The binding constraint + (verified LIVE) is the container HOSTNAME**, which rejects BOTH `_` (so the spec's §4.1 + `replace(".", "_")` is illegal for `--name`) AND `.` (accepted by `--name` but `sbx create` fails + "hostname: value must be a valid hostname"). So emit only `[A-Za-z0-9-]` — encode `thread_ts`'s `.` + as `-`. A test guards against ever emitting `_` or `.`. +- Reversible: `t---` (channel has no `-`; ts is two numeric groups). Over-length + names fall back to `t-`; the full id is stored in `~/.agent-state/thread`. ## 5. Boundary logging (§4.9) — `src/router/log.ts` diff --git a/src/router/dispatcher.test.ts b/src/router/dispatcher.test.ts index 70c2465..5f46f77 100644 --- a/src/router/dispatcher.test.ts +++ b/src/router/dispatcher.test.ts @@ -12,7 +12,7 @@ const trigger: Mention = { user: "U1", text: "@bot fix the bug", }; -const SANDBOX = "t-C0ABCDEF-1748600000.100000"; +const SANDBOX = "t-C0ABCDEF-1748600000-100000"; // chooseSandboxName encodes '.' as '-' const HOME = "/home/agent"; class FakePlatform { diff --git a/src/router/idle-sweep.test.ts b/src/router/idle-sweep.test.ts index 1e38e0f..37f70dc 100644 --- a/src/router/idle-sweep.test.ts +++ b/src/router/idle-sweep.test.ts @@ -90,8 +90,8 @@ describe("IdleSweeper.sweepOnce", () => { it("stops idle threads, keeps fresh ones, and ignores foreign sandboxes", async () => { const handles: SandboxHandle[] = [ - { name: "t-C0ABCDEF-1748600000.000000" }, // idle (100s ago) - { name: "t-C0FRESH-1748600099.000000" }, // fresh (1s ago) + { name: "t-C0ABCDEF-1748600000-000000" }, // idle (100s ago) + { name: "t-C0FRESH-1748600099-000000" }, // fresh (1s ago) { name: "_login-tmp" }, // foreign — never touch ]; const sandbox = new FakeSweepSandbox(handles); @@ -104,8 +104,8 @@ describe("IdleSweeper.sweepOnce", () => { const sweeper = new IdleSweeper({ sandbox, platform, idleMs: 10_000, now: () => NOW }); const { stopped } = await sweeper.sweepOnce(); - expect(stopped).toEqual(["t-C0ABCDEF-1748600000.000000"]); - expect(sandbox.stopped).toEqual(["t-C0ABCDEF-1748600000.000000"]); + expect(stopped).toEqual(["t-C0ABCDEF-1748600000-000000"]); + expect(sandbox.stopped).toEqual(["t-C0ABCDEF-1748600000-000000"]); }); it("reverses a hash-named sandbox via ~/.agent-state/thread", async () => { diff --git a/src/router/sandbox-name.test.ts b/src/router/sandbox-name.test.ts index ad5b591..c1ab0f2 100644 --- a/src/router/sandbox-name.test.ts +++ b/src/router/sandbox-name.test.ts @@ -17,21 +17,21 @@ const SAMPLES: ThreadId[] = [ ]; describe("sandboxName", () => { - it("encodes as t--, keeping the '.' (spec §4.1)", () => { + it("encodes as t---, with '.' -> '-' (hostname-safe, §4.1)", () => { expect(sandboxName({ channel: "C0ABCDEF", threadTs: "1748600000.123456" })).toBe( - "t-C0ABCDEF-1748600000.123456", + "t-C0ABCDEF-1748600000-123456", ); }); - it("NEVER substitutes '.' -> '_' (the spec's charset bug)", () => { + it("emits NEITHER '_' (illegal --name) NOR '.' (illegal hostname) — both verified live", () => { for (const thread of SAMPLES) { const name = sandboxName(thread); expect(name).not.toContain("_"); - expect(name).toContain("."); + expect(name).not.toContain("."); } }); - it("only ever emits sbx-legal characters [A-Za-z0-9.+-]", () => { + it("only ever emits hostname-safe characters [A-Za-z0-9-]", () => { for (const thread of SAMPLES) { expect(isCharsetSafe(sandboxName(thread))).toBe(true); } @@ -58,7 +58,8 @@ describe("parseSandboxName (round-trip + namespacing)", () => { "t-C0ABCDEF", // no thread_ts "t-C0ABCDEF-notats", // thread_ts not numeric "t-C0ABCDEF-1748600000", // missing fractional part - "C0ABCDEF-1748600000.1", // missing prefix + "t-C0ABCDEF-1748600000.123456", // old dotted form (no longer produced) + "C0ABCDEF-1748600000-1", // missing prefix ]) { expect(parseSandboxName(name)).toBeNull(); } @@ -116,12 +117,13 @@ describe("chooseSandboxName", () => { describe("isHashName / isCharsetSafe", () => { it("distinguishes hash names from plain names", () => { expect(isHashName("t-0123456789abcdef")).toBe(true); - expect(isHashName("t-C0ABCDEF-1748600000.123456")).toBe(false); + expect(isHashName("t-C0ABCDEF-1748600000-123456")).toBe(false); expect(isHashName("t-0123456789ABCDEF")).toBe(false); // uppercase != hex digest }); - it("flags underscores as charset-unsafe", () => { - expect(isCharsetSafe("t-C0ABCDEF-1748600000_123456")).toBe(false); - expect(isCharsetSafe("t-C0ABCDEF-1748600000.123456")).toBe(true); + it("flags BOTH underscores and periods as unsafe (hostname rules)", () => { + expect(isCharsetSafe("t-C0ABCDEF-1748600000_123456")).toBe(false); // illegal --name + expect(isCharsetSafe("t-C0ABCDEF-1748600000.123456")).toBe(false); // illegal hostname + expect(isCharsetSafe("t-C0ABCDEF-1748600000-123456")).toBe(true); }); }); diff --git a/src/router/sandbox-name.ts b/src/router/sandbox-name.ts index 449737099ad20ca695bc3812a5edd66a81ca2ae6..801e28f46ddb466cbaf0f35e5d3cfaedeec75e18 100644 GIT binary patch delta 1059 zcmYjQO-~d-5Dl_GvXO)kO(aNCSy6Y*&VZmME*}X75=;~lK@S?FdwObjT6$+D(>*K* zoAt)Qf9EbQM|v*o2MC@FeHpeu zM4>hbm!W~`F($Gp(T5k$-)z-DYA^vepuH1FHR>`vpaX=Ew-LC#MB8qt857vv-P_;U zc)3-FP9Q}934TCfG(6kg-v)NbU}I+!n9JrH76%-A*2pn*jN-nApI;xj4?**GtT6k0 z?amzh{`4j9lsPp3NTsmF6*QwH`!#Z-=MJD;g5pA8Y-oD#6qw`KDH(XF0cIBv335XR zG;|0}E|rFkG(ipkPzfS!Y)1o*xk?e*sW#xzn)YA%Fu1SelzWPcBVauf5UPJ@G_zu{ zRgFTgyQ^&}l1Q(!I_L;G2pEdU$9z{3z)Hs)W|^yL9AhH5CLseJhNvt~lk~Jje8^fW zJWEig%H&k5&_o%xu}$v zy(-O2#GKrcCQC~Tx3~LFI+{0gSrd&WccCq_AElXTTDQ8hYok~P`#ogYpVHmz*4X3g zxqnXKU-o)zakS=Cvx~8BOExM){|Y<(zK>ANgIgbNn-kZvsfnA}(Rg#(O)#doV71~_ z>QHeiwd`uVdQXOSbJE?5P6{m+{(DRV<-Si%td7_0Sg;0dZba~I>0qS{#j}MXXv3@J t?EPeAwy0iPgEaKfX32;1^P)7nnB1Ig0GmIvMO}CPtU%~8=&qEz<$vG_U0MJD delta 748 zcmZWn-D(p-7^T4^WedF!K|#br{OQJIC#hAzmV$+f2(=0=URb3&+5M6o+?@?Ovq>Y= z`Ut{!;R6VI;YGnW@XkwL!I$vXnQaxto8|ky`SzSM=giMb-!C2?&mF;^P$7nbV(<|c zQfa8c0VaL6vdos-s|Z~i6~r|m2qe!a1W(g^syy64F4GxWDQQiG>Oz*;pemBtI3Z~# zV~d{0u1HG-B}P(#%NUuEu3vS7mP7Kxy^Y`*4_@_yyQ~wu2;2Da=AC6OBVqYw*nW&s z#uh!w3u4$c>$UR>!%{|Cw;w;U&=+Iw6G2-ckiZF>I4{2n7kLi##qPI$78cp~`{MLYGbevv@tv zTs|{Ca>&-SB?UU*kwz+<456(2KVJKeM2#uJs3fIG;q^+ty@oq0ZqXe^d6rf8vX#^} z87g|4+dDzZ2b7*-IwLt09};33@-o#7PunifncbLB<+K)~M3aOVI$@G1wplxCwVA75 z_efZ_0#iOStloR%(hh&;TzP{i;e{ey{quj))~PPSj{5{5{C`q1AL`d?tYf~_Z_n$* zzM*(u8?Vu?J!fXU@%gG#@wesn0aa$(oZx@`c*{OOX6`MVGyUe!d}{jUZS8{Vzbwoz iHO`(O?kcP!<2p+AZkoNtR`Yah-Y;%8aJc3jdcOd75BNU- From 4cadf99cbb730951b7d9a315009cd312ff49739a Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 19:38:28 -0400 Subject: [PATCH 22/47] =?UTF-8?q?Live=20(E2E):=20provision=20the=20repo=20?= =?UTF-8?q?+=20run=20codex=20in=20it=20(=C2=A74.3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third live mention created the sandbox + ran codex, which exited "Not inside a trusted directory" — codex ran in the empty /home/agent/workspace (the --clone source is a read-only mount at /run/sandbox/source). Fix: the dispatcher now clones the source into $HOME/repo (idempotent), seeds a non-base branch (slack/work), and runs the agent there via a new exec cwd (-w). turnArgs adds --skip-git-repo-check so codex never blocks headlessly. The §9.15 stdin fix is confirmed working (codex no longer hangs). 167 tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/codex.test.ts | 2 ++ src/backend/codex.ts | 5 ++++- src/router/dispatcher.test.ts | 5 +++++ src/router/dispatcher.ts | 37 ++++++++++++++++++++++++++++++++++- src/sandbox/index.ts | 2 ++ src/sandbox/sbx-provider.ts | 9 ++++++++- 6 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts index e027ea1..ec05b48 100644 --- a/src/backend/codex.test.ts +++ b/src/backend/codex.test.ts @@ -28,6 +28,7 @@ describe("codexTurnArgs", () => { "codex", "exec", "--json", + "--skip-git-repo-check", ...CODEX_NONBLOCKING_FLAGS, "--", "fix the bug", @@ -48,6 +49,7 @@ describe("codexTurnArgs", () => { "exec", "resume", "--json", + "--skip-git-repo-check", ...CODEX_NONBLOCKING_FLAGS, "--", "sess-abc", diff --git a/src/backend/codex.ts b/src/backend/codex.ts index 05fb2e2..b959b4b 100644 --- a/src/backend/codex.ts +++ b/src/backend/codex.ts @@ -51,7 +51,10 @@ function asString(value: unknown): string | null { * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). */ export function codexTurnArgs(message: string, sessionId?: string | null): string[] { - const common = ["--json", ...CODEX_NONBLOCKING_FLAGS]; + // --skip-git-repo-check: never block headless on codex's trusted-directory + // check (the microVM is the boundary). Verified live: without it, codex exits + // "Not inside a trusted directory". + const common = ["--json", "--skip-git-repo-check", ...CODEX_NONBLOCKING_FLAGS]; if (sessionId === undefined || sessionId === null) { return ["codex", "exec", ...common, "--", message]; } diff --git a/src/router/dispatcher.test.ts b/src/router/dispatcher.test.ts index 5f46f77..9cebd11 100644 --- a/src/router/dispatcher.test.ts +++ b/src/router/dispatcher.test.ts @@ -58,9 +58,11 @@ class FakeSandbox { this.existing.push({ name }); return { name }; } + lastCwd: string | undefined; async exec(_h: SandboxHandle, argv: string[], opts?: ExecCallOptions): Promise { this.trace.push("exec"); this.execArgs.push(argv); + this.lastCwd = opts?.cwd; opts?.onChunk?.("stdout", this.execResult.stdout); opts?.onChunk?.("stderr", this.execResult.stderr); return this.execResult; @@ -194,6 +196,9 @@ describe("dispatchTurn — turn 1 happy path", () => { expect(at("write:transcript.jsonl")).toBeGreaterThan(at("reply")); // 👀 -> ✅ swap is last expect(t.slice(-2)).toEqual(["-eyes", "+white_check_mark"]); + // provisioned the repo and ran the agent IN it (§4.3) + expect(t).toContain("shell:test"); // the `test -d …/.git || git clone …` provision + expect(b.sandbox.lastCwd).toBe("/home/agent/repo"); }); it("reuses an existing sandbox instead of creating one", async () => { diff --git a/src/router/dispatcher.ts b/src/router/dispatcher.ts index f46d3d1..e344eca 100644 --- a/src/router/dispatcher.ts +++ b/src/router/dispatcher.ts @@ -54,6 +54,13 @@ export interface DispatcherDeps { const DEFAULT_FAILURE = "⚠️ The coding agent didn't return a result for that turn. Your request is still queued — mention me again to retry."; +/** sbx --clone exposes the read-only source repo here (verified live; §9.13). */ +const SBX_CLONE_SOURCE = "/run/sandbox/source"; +/** In-VM writable clone dir the agent works in (under $HOME, outside ~/.agent-state). */ +const IN_VM_REPO_DIR = "repo"; +/** Non-base branch the agent's work starts on (§4.3). */ +const WORK_BRANCH = "slack/work"; + /** Render messages into a single prompt. Plumbing, not authorship — no editorializing. */ export function formatPrompt(messages: ThreadMessage[]): string { return messages.map((m) => `${m.user}: ${m.text}`).join("\n\n"); @@ -111,11 +118,18 @@ export class Dispatcher { const prompt = formatPrompt(delta); const argv = this.backend.turnArgs(prompt, sessionId ?? undefined); + + // Provision a writable repo clone and run the agent IN it (§4.3) — verified + // live: without this codex exits "Not inside a trusted directory". + const home = await this.sandbox.homeDir(handle); + const repoPath = `${home}/${IN_VM_REPO_DIR}`; + await this.provisionRepo(handle, repoPath, ctx); + this.write( buildTurnIn(ctx, this.ts(), { sessionId, argv, - cwd: this.repoRef, + cwd: repoPath, prompt, level: this.level, }), @@ -123,6 +137,7 @@ export class Dispatcher { const start = this.now(); const { stdout, stderr, exitCode } = await this.sandbox.exec(handle, argv, { + cwd: repoPath, onChunk: (stream, chunk) => { if (this.level === "verbose") { this.write(buildOutChunk(ctx, this.ts(), { stream, chunk })); @@ -196,6 +211,26 @@ export class Dispatcher { return handle; } + /** + * Clone the read-only `--clone` source into a writable repo dir and seed a + * non-base branch (§4.3). Idempotent — runs cheaply each turn, clones once. + * The agent later points origin at the real remote + pushes when asked (§4.6). + */ + private async provisionRepo( + handle: SandboxHandle, + repoPath: string, + ctx: LogContext, + ): Promise { + const script = + `test -d ${repoPath}/.git || ` + + `{ git clone -q ${SBX_CLONE_SOURCE} ${repoPath} && ` + + `git -C ${repoPath} checkout -q -B ${WORK_BRANCH}; }`; + const result = await this.sandbox.execShell(handle, script); + if (result.exitCode !== 0) { + this.router(ctx, "provision.failed", { stderrSnippet: result.stderr.slice(0, 300) }); + } + } + /** Turn 1 (no session): whole-thread primer. Follow-up: messages after the hwm. */ private async assembleDelta( thread: ThreadId, diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index 98a7f7d..2957416 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -25,6 +25,8 @@ export interface ExecCallOptions { onChunk?: (stream: "stdout" | "stderr", chunk: string) => void; /** Optional input to write before closing stdin. Stdin is always closed (§9.15). */ stdin?: string; + /** Working directory inside the sandbox (e.g. the provisioned repo clone, §4.3). */ + cwd?: string; } export interface SandboxProvider { diff --git a/src/sandbox/sbx-provider.ts b/src/sandbox/sbx-provider.ts index 03c3bd3..f071195 100644 --- a/src/sandbox/sbx-provider.ts +++ b/src/sandbox/sbx-provider.ts @@ -109,7 +109,14 @@ export class SbxProvider implements SandboxProvider { } exec(handle: SandboxHandle, argv: string[], opts: ExecCallOptions = {}): Promise { - return this.run(execAgentArgv(handle.name, argv, { login: this.login, pty: this.pty }), opts); + return this.run( + execAgentArgv(handle.name, argv, { + login: this.login, + pty: this.pty, + workdir: opts.cwd, + }), + opts, + ); } execShell( From 398d1c2889370124f255bee497ff20be4e1a31e0 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 20:33:13 -0400 Subject: [PATCH 23/47] docs: rewrite README to lead with user-facing usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure around how to use the bot — what you can do (mention → code → reply in-thread, iterate, run in parallel, resume after idle, ship a PR, grab a file), what the 👀/✅/❌ reactions mean, and a step-by-step setup (sbx + credentials + Slack app + run). The development section (architecture, commands, layout, link to AGENTS.md) is kept below it. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 157 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 110 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 3f175e8..af0f14e 100644 --- a/README.md +++ b/README.md @@ -1,86 +1,149 @@ # Slack Coding Agent -Mention the bot in a Slack thread → a coding-agent CLI (Codex) does the work in an isolated `sbx` -microVM with your repo + credentials → it reports back **in the same thread**. The router is pure -plumbing (no LLM); every thread maps 1:1 to a sandbox and a coding-agent session. Standalone TypeScript -project; conventions learned from pi-mom but **no code shared** (spec §10). +**Mention a bot in a Slack thread — a real coding agent does the work in an isolated sandbox with +your repo and credentials, and replies in the same thread.** Follow-up mentions continue the same +session; ask it to open a PR and it ships the work itself. -Design spec: `.context/attachments/2PRioL/slack-coding-agent-spec.md`. Working in the repo? Read -[AGENTS.md](./AGENTS.md) first. +It's plumbing, not a chatbot: every thread maps 1:1 to its own microVM sandbox and coding-agent +session, and the router itself runs no LLM — it just relays your thread to a coding-agent CLI (Codex) +and relays the result back. -## Architecture (three seams + a stateless router, spec §3) +--- +## What you can do with it + +- **Fix an issue from a thread.** Describe a problem, `@mention` the bot, and it reads the thread, + makes the change in your repo, and replies in-thread. +- **Iterate conversationally.** Mention it again in the same thread to continue — it remembers prior + turns and any messages teammates added in between. +- **Run work in parallel.** Different threads run independent tasks at the same time without colliding. +- **Pick up after idle.** Mention it a day later and it resumes where it left off — code and + conversation both restored. +- **Ship it.** Ask it to push or open a PR and the agent runs `git`/`gh` itself from inside the sandbox. +- **Grab a file.** Ask for a file it produced and it uploads it to the thread. + +**Reading the bot's reactions:** 👀 means it picked up your mention and is working; it swaps to ✅ on +success or ❌ on failure when the turn finishes. The actual answer is posted as a reply in the thread. + +--- + +## Set it up + +You need **Docker Desktop**, the **`sbx` CLI** (Docker Sandboxes, v0.31.1), a **Slack app**, and a +**target git repo**. One-time setup: + +### 1. Sandboxes (`sbx`) + +```bash +# Docker Desktop must be running, then: +sbx login # sign in to Docker +sbx policy set-default balanced # allow OpenAI / GitHub / package registries + +# Give the coding agent its credential (must exist BEFORE the bot creates sandboxes): +sbx secret set -g openai --oauth # ChatGPT subscription (browser sign-in) +# …or, to avoid subscription rate limits, an API key: +# sbx secret set -g openai # paste an sk-… key +sbx secret ls # should show: openai (… configured) ``` -Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM - └ CodingBackend (Codex CLI) -``` -The router owns only transient in-memory coordination (the idle/running/pending lock + ts-dedupe). -Per-thread durable state (the seen-message ledger + session id) lives **in each sandbox** under -`~/.agent-state/`; discovery is `sbx ls` + deterministic naming. Durability is the **PR + the Slack -thread**, not a state store. +### 2. Slack app (Socket Mode) + +Create a Slack app with **Socket Mode enabled** and these **bot scopes**: `app_mentions:read`, +`chat:write`, `reactions:write`, `channels:history` (+ `groups:history` for private channels), +`files:write`, `users:read`. Install it to your workspace and **invite the bot to a channel**. -## Prerequisites +Put the two tokens in a `.env` file at the repo root: -- **Node ≥ 22** (ESM / NodeNext). -- **Docker Desktop** running + **`sbx` v0.31.1** (the daemon: `sbx daemon start`). -- A coding-agent credential in sbx (the bot gates on this and walks you through it): for Codex, - `sbx secret set -g openai --oauth`. -- A **Slack app with Socket Mode** enabled — bot scopes `app_mentions:read`, `chat:write`, - `reactions:write`, `channels:history` (+`groups:history` for private), `files:write`, `users:read`; - an app-level token (`xapp-…`, `connections:write`) and a bot token (`xoxb-…`). -- A **target git repo** to operate on (the `--clone` seed). +```bash +APP_SLACK_APP_TOKEN=xapp-… # app-level token (Socket Mode; scope connections:write) +APP_SLACK_BOT_TOKEN=xoxb-… # bot token +``` -## Run +### 3. Run ```bash npm install -export APP_SLACK_APP_TOKEN=xapp-… # Socket Mode app-level token -export APP_SLACK_BOT_TOKEN=xoxb-… # bot token -export SCA_REPO=/path/to/your/repo # the --clone seed -# optional: SCA_AGENT=codex|claude SCA_LOG_LEVEL=summary|verbose SCA_BOT_USER=U… -npm run build && npm start -- --sandbox=sbx ./data -# or, for development: npm run dev -- --sandbox=sbx ./data +npm run build +SCA_REPO=/path/to/your/repo \ + node --env-file=.env dist/cli/index.js --sandbox=sbx ./data ``` -On first use the router checks `sbx secret ls`; if the agent's credential is missing it prints the -one-time setup steps and exits. Then it listens for `@bot` mentions, runs a turn per thread, and -replies in-thread. Watch a thread's boundary log: +On first use the bot checks `sbx secret ls`; if the agent's credential is missing it prints the exact +setup steps and exits. Once it prints `listening …`, **`@mention` it in your channel** and it goes to +work. Optional env: `SCA_AGENT=codex|claude` (default codex), `SCA_LOG_LEVEL=summary|verbose`, +`SCA_BOT_USER=U…`. + +> To let the agent **push / open PRs**, also give it a repo-scoped GitHub token: +> `sbx secret set -g github` (prefer a fine-grained PAT scoped to the target repo). + +### Watch what it's doing + +Every turn is logged at the agent boundary to `./data/router.log` (JSONL). Tail one thread: ```bash -tail -f ./data/router.log | jq 'select(.threadId=="t-C0ABCDEF-1748600000.123456")' +tail -f ./data/router.log | jq 'select(.threadId=="t--")' +``` + +--- + +## How it works + +``` +Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM + └ CodingBackend (Codex CLI) ``` -## Commands +The router owns only transient in-memory coordination (an idle/running/pending lock + ts-dedupe). +Per-thread durable state — the seen-message ledger and the coding-agent session id — lives **inside +each sandbox** (`~/.agent-state/`); the router finds a thread's sandbox by deterministic naming + +`sbx ls`, with no database. Durability is the **PR + the Slack thread**, not a state store. Isolation +is a hypervisor-backed microVM per thread, with host-proxied credentials that never enter the VM. + +Three swappable seams (Slack · sbx · Codex today; Discord/e2b/Claude tomorrow) keep every +vendor-specific choice behind an interface. Standalone project; conventions learned from pi-mom but +**no code shared**. + +--- + +## Development + +Working in this repo? **Read [AGENTS.md](./AGENTS.md) first** — it has the architecture invariants +(router-is-plumbing, transport ≠ interpretation, pure-core vs I/O-shell, the naming/logging/lifecycle +rules) you must follow. | Command | What it does | | --- | --- | -| `npm run check` | **The gate:** Biome (lint+format+imports, `--error-on-warnings`) + `tsc --noEmit` + Vitest. | +| `npm run check` | **The gate:** Biome (lint + format + imports) + `tsc --noEmit` + Vitest. Keep it green. | | `npm run check:fix` | Apply Biome fixes, then run the gate. | | `npm test` / `test:watch` / `test:cov` | Run Vitest (offline unit tests). | -| `npm run test:live` | The `*.live.test.ts` lane (needs sbx daemon + Docker + creds; **not** in CI). | +| `npm run test:live` | `*.live.test.ts` against the real sbx daemon (needs Docker + sbx; **not** in CI). | | `npm run build` | Emit `dist/` (`tsc -p tsconfig.build.json`, excludes tests). | | `npm start` / `npm run dev` | Run the built CLI / run under `tsx watch`. | -## Layout (by seam — spec §3) +Pure logic is separated from I/O so almost everything is unit-tested offline with injected fakes (the +dispatcher test is the logical end-to-end); the live sbx/Codex and Socket Mode paths are verified +hands-on. **TypeScript** `strict`, **ESM (NodeNext)** — relative imports use `.js`, type-only imports +use `import type`. **Biome** is the single lint/format tool. CI runs `check` then `build`. + +### Layout (by seam) ``` src/ platform/ slack-map.ts (pure event→Mention) + slack.ts (Socket Mode + Web API adapter) sandbox/ sbx-argv.ts (pure argv builders) + sbx-provider.ts (spawn/drain I/O shell) backend/ codex.ts (turnArgs/parseResult/sessionId — the ONLY agent-format knowledge) + fixtures/ - router/ state-machine, dedupe, sandbox-name, agent-state(+store), log, dispatcher, router + router/ state-machine, dedupe, sandbox-name, agent-state(+store), log, dispatcher, idle-sweep, router cli/ args.ts + onboarding.ts + index.ts (composition root) types.ts shared types ``` -Pure logic is separated from I/O so almost everything is unit-tested offline with injected fakes (the -dispatcher test is the logical end-to-end). Parts that touch live services — the Socket Mode connection -and the sbx/Codex execution path — are verified hands-on (see [AGENTS.md](./AGENTS.md) §8 and the live -lane). +--- -## Conventions +## Status -- **TypeScript** `strict`, **ESM (NodeNext)** — relative imports use `.js`; type-only imports use - `import type`. **Biome** is the single lint/format/import tool. CI: GitHub Actions → - `npm ci --ignore-scripts` → `check` → `build`; plus a weekly `npm audit`. +**Phase 1 (local, single-user, macOS Apple silicon)** — verified end-to-end against real Slack + sbx + +Codex: mention → isolated sandbox → repo clone → headless codex → reply in-thread, with deterministic +sandbox naming, per-thread `~/.agent-state` ledger + session resume, idle/running/pending coalescing, +and always-on boundary logging. The design spec lives at +`.context/attachments/2PRioL/slack-coding-agent-spec.md`; the phased roadmap (ergonomics → teams → +cloud) is §8 there. From eaf66be8fc88b24aab055233fe4bf27f4ea3d0d6 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 20:41:28 -0400 Subject: [PATCH 24/47] =?UTF-8?q?docs:=20address=20README=20review=20?= =?UTF-8?q?=E2=80=94=20drop=20unimplemented=20"grab=20a=20file",=20expand?= =?UTF-8?q?=20Slack=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove the "Grab a file" bullet: artifact upload is Phase 2 (the dispatcher does not handle file requests yet), so it was overpromising. - Rewrite the Slack-app setup as numbered, followable steps with the in-app navigation, for people new to Slack apps. Keeps our env-var names (APP_SLACK_APP_TOKEN / APP_SLACK_BOT_TOKEN) and the scopes/events the bot actually uses — including reactions:write and only the app_mention event subscription (the adapter ignores other events). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index af0f14e..fa2bf72 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ and relays the result back. - **Pick up after idle.** Mention it a day later and it resumes where it left off — code and conversation both restored. - **Ship it.** Ask it to push or open a PR and the agent runs `git`/`gh` itself from inside the sandbox. -- **Grab a file.** Ask for a file it produced and it uploads it to the thread. **Reading the bot's reactions:** 👀 means it picked up your mention and is working; it swaps to ✅ on success or ❌ on failure when the turn finishes. The actual answer is posted as a reply in the thread. @@ -48,15 +47,32 @@ sbx secret ls # should show: openai (… configured) ### 2. Slack app (Socket Mode) -Create a Slack app with **Socket Mode enabled** and these **bot scopes**: `app_mentions:read`, -`chat:write`, `reactions:write`, `channels:history` (+ `groups:history` for private channels), -`files:write`, `users:read`. Install it to your workspace and **invite the bot to a channel**. +Never made a Slack app? Follow these steps: + +1. **Create the app** — go to → **Create New App** → **From scratch**, + name it, and pick your workspace. +2. **Enable Socket Mode** — *Settings → Socket Mode* → toggle **Enable Socket Mode** on. +3. **App-Level Token** — generate one (the Socket Mode toggle prompts for it, or *Settings → Basic + Information → App-Level Tokens*) with the **`connections:write`** scope. This `xapp-…` token is your + **`APP_SLACK_APP_TOKEN`**. +4. **Bot Token Scopes** — *Features → OAuth & Permissions → Scopes → Bot Token Scopes*, add: + - `app_mentions:read` — receive the `@mention` that triggers a turn + - `chat:write` — post replies in the thread + - `reactions:write` — the 👀 / ✅ / ❌ acks + - `channels:history` — read thread messages in public channels + - `groups:history` — read thread messages in private channels +5. **Subscribe to events** — *Features → Event Subscriptions* → toggle **Enable Events** on → under + **Subscribe to bot events**, add **`app_mention`** (the only event this bot needs). +6. **Install** — *Settings → Install App* → install to your workspace → copy the **Bot User OAuth + Token** (`xoxb-…`). This is your **`APP_SLACK_BOT_TOKEN`**. +7. **Invite the bot** to any channel you want it to work in: `/invite @YourBot` — it only sees + messages in channels it's been added to. Put the two tokens in a `.env` file at the repo root: ```bash -APP_SLACK_APP_TOKEN=xapp-… # app-level token (Socket Mode; scope connections:write) -APP_SLACK_BOT_TOKEN=xoxb-… # bot token +APP_SLACK_APP_TOKEN=xapp-… # step 3 — app-level token (Socket Mode) +APP_SLACK_BOT_TOKEN=xoxb-… # step 6 — bot user OAuth token ``` ### 3. Run From c9b3b737546cfcd6451c2cb66fa54a102d886dee Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 20:47:28 -0400 Subject: [PATCH 25/47] docs: sbx does not require Docker Desktop (verified live) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer flagged the Docker Desktop dependency. Verified empirically with Docker Desktop fully shut down (CLI unreachable, process gone): `sbx create` and `sbx exec` both work — sbx runs its own bundled container runtime (~/.sbx/run/d/docker.sock) and drives the microVM via the macOS hypervisor directly. Updated the README requirements + sbx setup, and the test:live notes in README/AGENTS.md, to drop the Docker Desktop dependency. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 5 +++-- README.md | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dcb9162..0ce562c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,8 +22,9 @@ there. Read this file before changing code. - `npm run check:fix` applies Biome fixes then runs the gate. `npm test` / `npm run test:watch` / `npm run test:cov` run Vitest. `npm run build` emits `dist/` (excludes `*.test.ts`). `npm run dev` runs the CLI under `tsx watch`. -- `npm run test:live` runs the `*.live.test.ts` lane — needs the sbx daemon + Docker + creds, so it's - **NOT** part of `check`/CI. Run it locally after any `sbx` upgrade (§9, substrate risk). +- `npm run test:live` runs the `*.live.test.ts` lane — needs the sbx daemon running (sbx bundles its + own runtime; no Docker Desktop), so it's **NOT** part of `check`/CI. Run it locally after any `sbx` + upgrade (§9, substrate risk). ## 2. Module rules (ESM, strict) diff --git a/README.md b/README.md index fa2bf72..cbf2dd7 100644 --- a/README.md +++ b/README.md @@ -28,14 +28,14 @@ success or ❌ on failure when the turn finishes. The actual answer is posted as ## Set it up -You need **Docker Desktop**, the **`sbx` CLI** (Docker Sandboxes, v0.31.1), a **Slack app**, and a -**target git repo**. One-time setup: +You need the **`sbx` CLI** (Docker Sandboxes, v0.31.1 — it bundles its own container runtime and +hypervisor, so **Docker Desktop is not required**), a **Slack app**, and a **target git repo**. +One-time setup: ### 1. Sandboxes (`sbx`) ```bash -# Docker Desktop must be running, then: -sbx login # sign in to Docker +sbx login # sign in to Docker (to pull the sandbox images) sbx policy set-default balanced # allow OpenAI / GitHub / package registries # Give the coding agent its credential (must exist BEFORE the bot creates sandboxes): @@ -132,7 +132,7 @@ rules) you must follow. | `npm run check` | **The gate:** Biome (lint + format + imports) + `tsc --noEmit` + Vitest. Keep it green. | | `npm run check:fix` | Apply Biome fixes, then run the gate. | | `npm test` / `test:watch` / `test:cov` | Run Vitest (offline unit tests). | -| `npm run test:live` | `*.live.test.ts` against the real sbx daemon (needs Docker + sbx; **not** in CI). | +| `npm run test:live` | `*.live.test.ts` against the real sbx daemon (needs `sbx` running; **not** in CI). | | `npm run build` | Emit `dist/` (`tsc -p tsconfig.build.json`, excludes tests). | | `npm start` / `npm run dev` | Run the built CLI / run under `tsx watch`. | From bcc0912d07e90542a7c94e1c60fb24c4d1407cf6 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 21:23:06 -0400 Subject: [PATCH 26/47] refactor(backend): share SandboxShellExecutor via the backend barrel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lift the SandboxShellExecutor interface out of codex.ts into backend/index.ts so a second backend (Claude Code, next) can depend on it without importing codex.ts — keeping the two backend modules decoupled. Pure move: no behavior change. codex.ts drops the now-unused ExecResult import; codex.test.ts imports the type from ./index.js. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/codex.test.ts | 2 +- src/backend/codex.ts | 9 ++------- src/backend/index.ts | 7 ++++++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts index ec05b48..79051eb 100644 --- a/src/backend/codex.test.ts +++ b/src/backend/codex.test.ts @@ -10,8 +10,8 @@ import { parseCodexResult, parseRolloutId, parseSessionId, - type SandboxShellExecutor, } from "./codex.js"; +import type { SandboxShellExecutor } from "./index.js"; const fixture = (name: string): string => readFileSync(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8"); diff --git a/src/backend/codex.ts b/src/backend/codex.ts index b959b4b..f34887f 100644 --- a/src/backend/codex.ts +++ b/src/backend/codex.ts @@ -1,5 +1,5 @@ -import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; -import type { CodingBackend, TurnResult } from "./index.js"; +import type { SandboxHandle } from "../sandbox/index.js"; +import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js"; /** * Codex backend — the ONLY place Codex's CLI/output format lives (spec §4.5/§4.9). @@ -33,11 +33,6 @@ export const CODEX_NONBLOCKING_FLAGS = [ "approval_policy=never", ]; -/** Minimal sandbox capability captureSessionId needs (one shell command). */ -export interface SandboxShellExecutor { - execShell(handle: SandboxHandle, command: string): Promise; -} - function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } diff --git a/src/backend/index.ts b/src/backend/index.ts index 2d03123..bc972ac 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,4 +1,4 @@ -import type { SandboxHandle } from "../sandbox/index.js"; +import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; /** * CodingBackend — per-agent knowledge: the ONLY place agent-format details @@ -38,3 +38,8 @@ export interface CodingBackend { /** OPTIONAL: normalized events to enrich logs; router degrades to raw bytes if absent. */ events?(captured: string): unknown[]; } + +/** Minimal sandbox capability captureSessionId needs (one shell command). */ +export interface SandboxShellExecutor { + execShell(handle: SandboxHandle, command: string): Promise; +} From 72e0a0bda5244e36fdcd03b3987399e45314711a Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 21:26:00 -0400 Subject: [PATCH 27/47] test(backend): add Claude Code stream-json fixtures (placeholders) Hand-authored sample bytes for the upcoming Claude backend parser, mirroring the codex fixtures: a successful stream-json turn, an errored turn, the 0-byte empty-output case, and sample session-file find/filename output. Documented placeholders to be recaptured live against a real sbx `claude` sandbox. Inert for now (no parser references them yet); the fixtures README gains a per-backend structure. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/fixtures/README.md | 28 ++++++++++++++++++- src/backend/fixtures/claude-empty.stdout.txt | 0 src/backend/fixtures/claude-error.jsonl | 2 ++ src/backend/fixtures/claude-find-output.txt | 2 ++ .../fixtures/claude-session-filename.txt | 1 + src/backend/fixtures/claude-stream.jsonl | 5 ++++ 6 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/backend/fixtures/claude-empty.stdout.txt create mode 100644 src/backend/fixtures/claude-error.jsonl create mode 100644 src/backend/fixtures/claude-find-output.txt create mode 100644 src/backend/fixtures/claude-session-filename.txt create mode 100644 src/backend/fixtures/claude-stream.jsonl diff --git a/src/backend/fixtures/README.md b/src/backend/fixtures/README.md index 886decd..7ae03bb 100644 --- a/src/backend/fixtures/README.md +++ b/src/backend/fixtures/README.md @@ -1,4 +1,8 @@ -# Codex fixtures +# Backend fixtures + +Sample agent-CLI output bytes that the backend parsers are verified against. + +## Codex Sample `codex exec --json` bytes that `codex.ts` parsing is verified against. @@ -17,3 +21,25 @@ is tolerant of several shapes so it survives version drift. - `codex-empty.stdout.txt` — the headless empty-output regression: 0 bytes, exit 0 (§9.14). - `codex-rollout-filename.txt` — a sample `~/.codex/sessions/.../rollout-*.jsonl` filename. - `codex-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-rollout selection. + +## Claude Code + +Sample `claude -p --output-format stream-json --verbose` bytes that `claude.ts` parsing is verified +against. + +**Documented placeholders** (hand-authored 2026-05-31), to be recaptured live against a real sbx +`claude` sandbox — the same convention the codex fixtures started with. The real stream is JSONL: a +`{"type":"system","subtype":"init",…}` first line (carries `session_id`), `{"type":"assistant",…}` / +`{"type":"user",…}` turns, and a final `{"type":"result",…}` (carries `result` + `is_error` + +`session_id`). Session transcripts live at `~/.claude/projects//.jsonl` (the +filename is the session id). + +- `claude-stream.jsonl` — a **successful** fresh turn (init → assistant `tool_use` → `tool_result` → + final assistant text → `result`). Confirms `parseClaudeResult` reads the `result` text, + `parseClaudeSessionId` reads the init `session_id`, and `claudeEvents` parses every line. +- `claude-error.jsonl` — an **errored** turn (`is_error:true`, `subtype:"error_during_execution"`). + The session is still created (the init line), so `parseClaudeSessionId` still works and the error + message is relayed to the user. +- `claude-empty.stdout.txt` — the headless empty-output failure mode: 0 bytes (parity with codex §9.14). +- `claude-session-filename.txt` — a sample `~/.claude/projects/.../.jsonl` basename. +- `claude-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-session selection. diff --git a/src/backend/fixtures/claude-empty.stdout.txt b/src/backend/fixtures/claude-empty.stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/fixtures/claude-error.jsonl b/src/backend/fixtures/claude-error.jsonl new file mode 100644 index 0000000..b47bf1f --- /dev/null +++ b/src/backend/fixtures/claude-error.jsonl @@ -0,0 +1,2 @@ +{"type":"system","subtype":"init","cwd":"/root/repo","session_id":"019e900d-aa11-4b22-9c33-445566778899","tools":["Bash","Read","Edit"],"model":"claude-opus-4-20250514","permissionMode":"bypassPermissions","apiKeySource":"none"} +{"type":"result","subtype":"error_during_execution","is_error":true,"duration_ms":800,"num_turns":1,"result":"Credit balance is too low to continue. Visit the Anthropic console to add credits.","session_id":"019e900d-aa11-4b22-9c33-445566778899"} diff --git a/src/backend/fixtures/claude-find-output.txt b/src/backend/fixtures/claude-find-output.txt new file mode 100644 index 0000000..290871b --- /dev/null +++ b/src/backend/fixtures/claude-find-output.txt @@ -0,0 +1,2 @@ +1748600000.1234 /root/.claude/projects/-root-repo/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl +1748600100.5678 /root/.claude/projects/-root-repo/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.jsonl diff --git a/src/backend/fixtures/claude-session-filename.txt b/src/backend/fixtures/claude-session-filename.txt new file mode 100644 index 0000000..bc52528 --- /dev/null +++ b/src/backend/fixtures/claude-session-filename.txt @@ -0,0 +1 @@ +a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.jsonl diff --git a/src/backend/fixtures/claude-stream.jsonl b/src/backend/fixtures/claude-stream.jsonl new file mode 100644 index 0000000..973abd0 --- /dev/null +++ b/src/backend/fixtures/claude-stream.jsonl @@ -0,0 +1,5 @@ +{"type":"system","subtype":"init","cwd":"/root/repo","session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","tools":["Bash","Read","Edit"],"model":"claude-opus-4-20250514","permissionMode":"bypassPermissions","apiKeySource":"none"} +{"type":"assistant","message":{"id":"msg_01","type":"message","role":"assistant","model":"claude-opus-4-20250514","content":[{"type":"tool_use","id":"toolu_01","name":"Edit","input":{"file_path":"auth.go"}}],"stop_reason":"tool_use","stop_sequence":null},"parent_tool_use_id":null,"session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01","content":"File updated"}]},"parent_tool_use_id":null,"session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"} +{"type":"assistant","message":{"id":"msg_02","type":"message","role":"assistant","model":"claude-opus-4-20250514","content":[{"type":"text","text":"Fixed the login timeout in auth.go and added a regression test."}],"stop_reason":"end_turn","stop_sequence":null},"parent_tool_use_id":null,"session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"} +{"type":"result","subtype":"success","is_error":false,"duration_ms":12000,"duration_api_ms":11000,"num_turns":3,"result":"Fixed the login timeout in auth.go and added a regression test.","session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","total_cost_usd":0.012} From 0fc2440412dfd80b8111d0079d2d4978bcd82836 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 21:29:07 -0400 Subject: [PATCH 28/47] feat(backend): add the Claude Code backend (turnArgs/parseResult/sessionId) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements CodingBackend for Anthropic's Claude Code CLI, mirroring codex.ts: pure parsers off the captured `claude -p --output-format stream-json --verbose` bytes plus one injected shell call for the session-id fallback. Not wired into the CLI yet (next commit), so the app still runs codex-only. - claudeTurnArgs: `claude -p --output-format stream-json --verbose --dangerously-skip-permissions [--resume ID] -- PROMPT`. --verbose is required with stream-json in print mode; the prompt is a positional (stdin is /dev/null'd, §9.15) guarded by `--`; --dangerously-skip-permissions is the non-blocking analog of codex's approval_policy=never (the microVM is the isolation boundary). - parseClaudeResult: the authoritative `{"type":"result",result,is_error}` event, falling back to the last assistant text; empty output -> failed turn (codex §9.14 parity); is_error relays the error text/subtype with ok=false. - parseClaudeSessionId: the session_id every stream line carries (init is first). - captureSessionId fallback: newest ~/.claude/projects/**/.jsonl. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/claude.test.ts | 185 +++++++++++++++++++++++++++++ src/backend/claude.ts | 234 +++++++++++++++++++++++++++++++++++++ 2 files changed, 419 insertions(+) create mode 100644 src/backend/claude.test.ts create mode 100644 src/backend/claude.ts diff --git a/src/backend/claude.test.ts b/src/backend/claude.test.ts new file mode 100644 index 0000000..378352d --- /dev/null +++ b/src/backend/claude.test.ts @@ -0,0 +1,185 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; +import { + CLAUDE_NONBLOCKING_FLAGS, + ClaudeBackend, + claudeEvents, + claudeTurnArgs, + newestSessionPath, + parseClaudeResult, + parseClaudeSessionFilename, + parseClaudeSessionId, +} from "./claude.js"; +import type { SandboxShellExecutor } from "./index.js"; + +const fixture = (name: string): string => + readFileSync(fileURLToPath(new URL(`./fixtures/${name}`, import.meta.url)), "utf8"); + +const stream = fixture("claude-stream.jsonl"); +const errorStream = fixture("claude-error.jsonl"); +const emptyStream = fixture("claude-empty.stdout.txt"); +const findOutput = fixture("claude-find-output.txt"); + +const SESSION_ID = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"; + +describe("claudeTurnArgs", () => { + it("turn 1: -p stream-json + --verbose + non-blocking flags + `--` guard, no --resume", () => { + const argv = claudeTurnArgs("fix the bug"); + expect(argv).toEqual([ + "claude", + "-p", + "--output-format", + "stream-json", + "--verbose", + ...CLAUDE_NONBLOCKING_FLAGS, + "--", + "fix the bug", + ]); + expect(argv).not.toContain("--resume"); + }); + + it("requires --verbose with stream-json and skips permission prompts headlessly", () => { + const argv = claudeTurnArgs("x"); + expect(argv).toContain("--verbose"); + expect(argv.join(" ")).toContain("--output-format stream-json"); + expect(argv).toContain("--dangerously-skip-permissions"); + }); + + it("follow-up: --resume -- with the same flags", () => { + expect(claudeTurnArgs("more", "sess-abc")).toEqual([ + "claude", + "-p", + "--output-format", + "stream-json", + "--verbose", + ...CLAUDE_NONBLOCKING_FLAGS, + "--resume", + "sess-abc", + "--", + "more", + ]); + }); + + it("treats null/undefined sessionId as a fresh turn", () => { + expect(claudeTurnArgs("x", null)).not.toContain("--resume"); + expect(claudeTurnArgs("x", undefined)).not.toContain("--resume"); + }); +}); + +describe("parseClaudeResult", () => { + it("extracts the final result text and marks ok (success fixture)", () => { + expect(parseClaudeResult(stream)).toEqual({ + finalText: "Fixed the login timeout in auth.go and added a regression test.", + ok: true, + }); + }); + + it("flags empty output as a failed turn (headless empty-output regression)", () => { + expect(parseClaudeResult(emptyStream)).toEqual({ finalText: "", ok: false }); + expect(parseClaudeResult("")).toEqual({ finalText: "", ok: false }); + }); + + it("surfaces the claude error message on a failed turn (real fixture)", () => { + const result = parseClaudeResult(errorStream); + expect(result.ok).toBe(false); + expect(result.finalText).toContain("Credit balance"); // relayed to the user + }); + + it("tolerates interleaved non-JSON progress lines", () => { + const noisy = `warming up...\n${stream}`; + expect(parseClaudeResult(noisy).ok).toBe(true); + }); + + it("falls back to the last assistant text when no result event is present", () => { + const aborted = + '{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"partial answer"}]},"session_id":"x"}'; + expect(parseClaudeResult(aborted)).toEqual({ finalText: "partial answer", ok: true }); + }); + + it("synthesizes an error from the subtype when an errored result carries no text", () => { + const maxTurns = + '{"type":"result","subtype":"error_max_turns","is_error":true,"session_id":"x"}'; + expect(parseClaudeResult(maxTurns)).toEqual({ finalText: "error_max_turns", ok: false }); + }); +}); + +describe("parseClaudeSessionId", () => { + it("reads the session id from the init line of the stream", () => { + expect(parseClaudeSessionId(stream)).toBe(SESSION_ID); + }); + + it("finds the id even on an errored turn (session still created)", () => { + expect(parseClaudeSessionId(errorStream)).toBe("019e900d-aa11-4b22-9c33-445566778899"); + }); + + it("returns null when no id is present", () => { + expect(parseClaudeSessionId('{"type":"assistant","message":{"content":[]}}')).toBeNull(); + expect(parseClaudeSessionId("")).toBeNull(); + }); +}); + +describe("parseClaudeSessionFilename / newestSessionPath", () => { + it("extracts the UUID from a transcript filename", () => { + expect(parseClaudeSessionFilename(fixture("claude-session-filename.txt").trim())).toBe( + SESSION_ID, + ); + }); + + it("returns null for a non-session filename", () => { + expect(parseClaudeSessionFilename("config.json")).toBeNull(); + expect(parseClaudeSessionFilename("notes.jsonl")).toBeNull(); + }); + + it("picks the path with the greatest mtime", () => { + expect(newestSessionPath(findOutput)).toBe( + `/root/.claude/projects/-root-repo/${SESSION_ID}.jsonl`, + ); + expect(newestSessionPath("")).toBeNull(); + }); +}); + +describe("claudeEvents", () => { + it("parses every JSON line and degrades gracefully on garbage", () => { + expect(claudeEvents(stream)).toHaveLength(5); + expect(claudeEvents("not json\n{bad}\n")).toEqual([]); + }); +}); + +describe("ClaudeBackend.captureSessionId (I/O via injected executor)", () => { + const handle: SandboxHandle = { name: "t-C0-1.2" }; + + const fakeExecutor = (stdout: string): SandboxShellExecutor & { lastCommand: string } => { + const calls = { lastCommand: "" }; + return { + get lastCommand() { + return calls.lastCommand; + }, + async execShell(_handle: SandboxHandle, command: string): Promise { + calls.lastCommand = command; + return { stdout, stderr: "", exitCode: 0 }; + }, + }; + }; + + it("finds the newest transcript and parses its id; $HOME stays unquoted", async () => { + const exec = fakeExecutor(findOutput); + const backend = new ClaudeBackend(exec); + expect(await backend.captureSessionId(handle)).toBe(SESSION_ID); + expect(exec.lastCommand).toContain("$HOME/.claude/projects"); + expect(exec.lastCommand).toContain("*.jsonl"); + }); + + it("throws when no transcript file exists", async () => { + const backend = new ClaudeBackend(fakeExecutor("")); + await expect(backend.captureSessionId(handle)).rejects.toThrow(/no session transcript/); + }); + + it("exposes the pure helpers through the interface", () => { + const backend = new ClaudeBackend(fakeExecutor("")); + expect(backend.configHome()).toBe("~/.claude"); + expect(backend.parseSessionId(stream)).toBe(SESSION_ID); + expect(backend.parseResult(emptyStream).ok).toBe(false); + expect(backend.events(stream)).toHaveLength(5); + }); +}); diff --git a/src/backend/claude.ts b/src/backend/claude.ts new file mode 100644 index 0000000..32472d1 --- /dev/null +++ b/src/backend/claude.ts @@ -0,0 +1,234 @@ +import type { SandboxHandle } from "../sandbox/index.js"; +import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js"; + +/** + * Claude Code backend — the ONLY place Claude Code's CLI/output format lives + * (spec §4.5/§4.9). Everything here is pure (off captured bytes) except + * captureSessionId, whose one shell call is injected so the rest stays + * unit-testable. + * + * Verified (spec §4.5/§9): `claude -p --resume ` continues the SAME session + * — id stable across turns, history replayed, surviving sbx stop/restart — so + * the router captures the id once on turn 1 and resumes with it, exactly like + * codex. + * - `claude -p --output-format stream-json --verbose [--resume ID] -- PROMPT` + * - `--output-format stream-json` REQUIRES `--verbose` in print mode. + * - The prompt is a positional (not stdin) so the provider can close stdin + * (§9.15); `--` guards a prompt that might start with `-`. + * - The stream's first `{"type":"system","subtype":"init",…}` line carries + * `session_id` (parseSessionId reads it live); the final `{"type":"result",…}` + * line carries the answer + `is_error`. + */ + +export const CLAUDE_HOME = "~/.claude"; +/** Shell-expanded ($HOME) inside execShell, not single-quoted. */ +export const CLAUDE_PROJECTS_PATH = "$HOME/.claude/projects"; + +/** + * Non-blocking headless — the analog of codex's `approval_policy=never`: never + * block on a permission prompt. The sbx microVM is the isolation boundary + * (Slack content is untrusted, §5/§7), so defense-in-depth is the hypervisor, + * not Claude's in-process permission gate. Isolated here so the choice is one + * edit (`--permission-mode bypassPermissions` is an equivalent newer spelling). + */ +export const CLAUDE_NONBLOCKING_FLAGS = ["--dangerously-skip-permissions"]; + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +function asString(value: unknown): string | null { + return typeof value === "string" ? value : null; +} + +/** + * Build the turn invocation. `--` guards a prompt that might start with `-`. + * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). + * `--verbose` is mandatory with `--output-format stream-json` in print mode. + */ +export function claudeTurnArgs(message: string, sessionId?: string | null): string[] { + const common = ["-p", "--output-format", "stream-json", "--verbose", ...CLAUDE_NONBLOCKING_FLAGS]; + if (sessionId === undefined || sessionId === null) { + return ["claude", ...common, "--", message]; + } + return ["claude", ...common, "--resume", sessionId, "--", message]; +} + +/** Concatenated text blocks of an `{type:"assistant", message:{content:[…]}}` event. */ +function extractAssistantText(event: Record): string | null { + if (asString(event.type) !== "assistant") { + return null; + } + const message = isRecord(event.message) ? event.message : null; + if (message === null || !Array.isArray(message.content)) { + return null; + } + let text = ""; + for (const block of message.content) { + if (isRecord(block) && asString(block.type) === "text") { + text += asString(block.text) ?? ""; + } + } + return text === "" ? null : text; +} + +/** + * Interpret the captured stream-json into the result to relay. The authoritative + * final answer is the `{"type":"result", result, is_error, …}` event; the last + * assistant text is a fallback if a run ends without one. Empty output -> + * {"", false}: the headless empty-output failure mode surfaces as a failed turn, + * not a silent empty reply (parity with codex §9.14). On `is_error` the error + * text (or the `subtype`, e.g. "error_max_turns") is relayed, still with ok=false. + */ +export function parseClaudeResult(captured: string): TurnResult { + if (captured.trim() === "") { + return { finalText: "", ok: false }; + } + let finalText = ""; // authoritative: the result event's text + let assistantText = ""; // fallback: the last assistant message's text + let errorText = ""; // an is_error result's text (or its subtype) + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + let event: unknown; + try { + event = JSON.parse(line); + } catch { + continue; // tolerate non-JSON progress lines + } + if (!isRecord(event)) { + continue; + } + if (asString(event.type) === "result") { + const text = asString(event.result); + if (event.is_error === true) { + errorText = text ?? asString(event.subtype) ?? ""; + } else if (text !== null) { + finalText = text; + } + continue; + } + const text = extractAssistantText(event); + if (text !== null) { + assistantText = text; + } + } + if (finalText.trim() !== "") { + return { finalText, ok: true }; + } + if (errorText.trim() !== "") { + return { finalText: errorText, ok: false }; // relay the agent's own error + } + if (assistantText.trim() !== "") { + return { finalText: assistantText, ok: true }; // no result event, but it answered + } + return { finalText: "", ok: false }; +} + +/** Extract the session id from a stream-json turn (every line carries it; init is first). */ +export function parseClaudeSessionId(captured: string): string | null { + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + let event: unknown; + try { + event = JSON.parse(line); + } catch { + continue; + } + if (isRecord(event)) { + const id = asString(event.session_id); + if (id !== null) { + return id; + } + } + } + return null; +} + +const SESSION_UUID_RE = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i; + +/** The session UUID from a `.jsonl` transcript filename (§4.5). */ +export function parseClaudeSessionFilename(filename: string): string | null { + const match = SESSION_UUID_RE.exec(filename); + return match !== null && match[1] !== undefined ? match[1] : null; +} + +/** From `find … -printf '%T@\t%p\n'` output, the path with the greatest mtime. */ +export function newestSessionPath(findOutput: string): string | null { + let bestPath: string | null = null; + let bestMtime = Number.NEGATIVE_INFINITY; + for (const line of findOutput.split("\n")) { + if (line.trim() === "") { + continue; + } + const tab = line.indexOf("\t"); + if (tab < 0) { + continue; + } + const mtime = Number.parseFloat(line.slice(0, tab)); + if (Number.isFinite(mtime) && mtime > bestMtime) { + bestMtime = mtime; + bestPath = line.slice(tab + 1); + } + } + return bestPath; +} + +/** Parse the stream-json into raw events for log enrichment (degrade to []). */ +export function claudeEvents(captured: string): unknown[] { + const events: unknown[] = []; + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + try { + events.push(JSON.parse(line)); + } catch { + // skip non-JSON progress lines + } + } + return events; +} + +export class ClaudeBackend implements CodingBackend { + constructor(private readonly executor: SandboxShellExecutor) {} + + configHome(): string { + return CLAUDE_HOME; + } + + turnArgs(message: string, sessionId?: string): string[] { + return claudeTurnArgs(message, sessionId); + } + + parseResult(captured: string): TurnResult { + return parseClaudeResult(captured); + } + + parseSessionId(captured: string): string | null { + return parseClaudeSessionId(captured); + } + + events(captured: string): unknown[] { + return claudeEvents(captured); + } + + /** Fallback when the stream had no id: newest transcript under CLAUDE_PROJECTS_PATH. */ + async captureSessionId(handle: SandboxHandle): Promise { + const command = `find ${CLAUDE_PROJECTS_PATH} -name '*.jsonl' -printf '%T@\\t%p\\n' 2>/dev/null`; + const { stdout } = await this.executor.execShell(handle, command); + const newest = newestSessionPath(stdout); + if (newest === null) { + throw new Error("claude: no session transcript found to capture the session id (§4.5)"); + } + const filename = newest.slice(newest.lastIndexOf("/") + 1); + const id = parseClaudeSessionFilename(filename); + if (id === null) { + throw new Error(`claude: could not parse the session id from transcript file "${filename}"`); + } + return id; + } +} From efd40111789674c976beeca2e86dbe110682f93b Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 21:29:32 -0400 Subject: [PATCH 29/47] feat(cli): select the Claude backend when SCA_AGENT=claude The composition root already computes `agent` from SCA_AGENT and threads it into sbx create + the onboarding gate; the only remaining hardcode was CodexBackend. Construct ClaudeBackend vs CodexBackend off `agent`. The provider structurally satisfies SandboxShellExecutor for both. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cli/index.ts b/src/cli/index.ts index c6b1865..172a0da 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -9,7 +9,9 @@ */ import { createWriteStream, mkdirSync } from "node:fs"; import { join } from "node:path"; +import { ClaudeBackend } from "../backend/claude.js"; import { CodexBackend } from "../backend/codex.js"; +import type { CodingBackend } from "../backend/index.js"; import { SlackAdapter } from "../platform/slack.js"; import { IdleSweeper } from "../router/idle-sweep.js"; import { Dispatcher, Router } from "../router/index.js"; @@ -63,7 +65,8 @@ async function run(args: Args): Promise { const logSink = fileLogSink(args.dataDir); const platform = new SlackAdapter({ appToken, botToken, botUserId: process.env.SCA_BOT_USER }); const botUser = await platform.whoAmI(); - const backend = new CodexBackend(provider); + const backend: CodingBackend = + agent === "claude" ? new ClaudeBackend(provider) : new CodexBackend(provider); const dispatcher = new Dispatcher({ platform, sandbox: provider, From 68feb873785c20db5e22f354609e5646a56c9ccd Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 21:36:59 -0400 Subject: [PATCH 30/47] test(backend): live-verify Claude backend; real error fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verified against Claude Code 2.1.141 in an sbx `claude` microVM (sbx v0.31.1, image docker/sandbox-templates:claude-code-docker): - the backend argv parses (`-- PROMPT` is the prompt; the run reaches the API); `--output-format stream-json` errors without `--verbose`; - `--resume` reuses the session id unless `--fork-session` (capture-once holds); - `claude` resolves on the non-login `bash -c` PATH the provider uses; - `is_error:true` can ship with `subtype:"success"` — parseClaudeResult keys on is_error, confirmed against the real bytes; - captureSessionId's find command returns the real ~/.claude/projects//.jsonl transcript. claude-error.jsonl is now REAL captured bytes (an auth-failure turn). The error test asserts the relayed message + the session id from those bytes. The success fixture stays a placeholder pending an `anthropic` credential to capture live. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/claude.test.ts | 6 ++-- src/backend/fixtures/README.md | 43 ++++++++++++++++--------- src/backend/fixtures/claude-error.jsonl | 5 +-- 3 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/backend/claude.test.ts b/src/backend/claude.test.ts index 378352d..ee1fb0d 100644 --- a/src/backend/claude.test.ts +++ b/src/backend/claude.test.ts @@ -81,9 +81,11 @@ describe("parseClaudeResult", () => { }); it("surfaces the claude error message on a failed turn (real fixture)", () => { + // Real capture: an is_error:true result whose subtype is nonetheless "success" + // (so we key on is_error, never subtype). The result text is relayed. const result = parseClaudeResult(errorStream); expect(result.ok).toBe(false); - expect(result.finalText).toContain("Credit balance"); // relayed to the user + expect(result.finalText).toContain("Not logged in"); // relayed to the user }); it("tolerates interleaved non-JSON progress lines", () => { @@ -110,7 +112,7 @@ describe("parseClaudeSessionId", () => { }); it("finds the id even on an errored turn (session still created)", () => { - expect(parseClaudeSessionId(errorStream)).toBe("019e900d-aa11-4b22-9c33-445566778899"); + expect(parseClaudeSessionId(errorStream)).toBe("baa1bd5f-51eb-4ee1-8b52-4d2872998728"); }); it("returns null when no id is present", () => { diff --git a/src/backend/fixtures/README.md b/src/backend/fixtures/README.md index 7ae03bb..9f55897 100644 --- a/src/backend/fixtures/README.md +++ b/src/backend/fixtures/README.md @@ -25,21 +25,34 @@ is tolerant of several shapes so it survives version drift. ## Claude Code Sample `claude -p --output-format stream-json --verbose` bytes that `claude.ts` parsing is verified -against. - -**Documented placeholders** (hand-authored 2026-05-31), to be recaptured live against a real sbx -`claude` sandbox — the same convention the codex fixtures started with. The real stream is JSONL: a -`{"type":"system","subtype":"init",…}` first line (carries `session_id`), `{"type":"assistant",…}` / -`{"type":"user",…}` turns, and a final `{"type":"result",…}` (carries `result` + `is_error` + -`session_id`). Session transcripts live at `~/.claude/projects//.jsonl` (the -filename is the session id). - -- `claude-stream.jsonl` — a **successful** fresh turn (init → assistant `tool_use` → `tool_result` → - final assistant text → `result`). Confirms `parseClaudeResult` reads the `result` text, - `parseClaudeSessionId` reads the init `session_id`, and `claudeEvents` parses every line. -- `claude-error.jsonl` — an **errored** turn (`is_error:true`, `subtype:"error_during_execution"`). - The session is still created (the init line), so `parseClaudeSessionId` still works and the error - message is relayed to the user. +against. The stream is JSONL: a `{"type":"system","subtype":"init",…}` first line (carries +`session_id`), `{"type":"assistant",…}` / `{"type":"user",…}` turns, and a final +`{"type":"result",…}` (carries `result` + `is_error` + `session_id`). Session transcripts live at +`~/.claude/projects//.jsonl` (the filename is the session id). + +**Live-validated 2026-05-31** against Claude Code **2.1.141** in an sbx `claude` microVM (image +`docker/sandbox-templates:claude-code-docker`, sbx v0.31.1): + +- the argv `claude -p --output-format stream-json --verbose --dangerously-skip-permissions -- PROMPT` + parses (the `--` operand is the prompt; the run reaches the API), and `--output-format stream-json` + errors without `--verbose` (`"When using --print, --output-format=stream-json requires --verbose"`); +- `--resume` **reuses** the session id (a stable id across turns) unless `--fork-session` is passed — + the basis for capture-once + resume; +- `is_error:true` can ship with `subtype:"success"` — so `parseClaudeResult` keys on `is_error`, + never `subtype`; +- `captureSessionId`'s `find $HOME/.claude/projects -name '*.jsonl' -printf '%T@\t%p\n'` returns the + real transcript (`…/-home-agent-workspace/.jsonl`); `parseClaudeSessionFilename` reads + the id from that basename. + +Fixtures: + +- `claude-error.jsonl` — **REAL bytes**: a fresh turn that reached the API and failed auth + (`is_error:true`, `result:"Not logged in · Please run /login"`, `subtype:"success"`). The session + is still created (the init line), so `parseClaudeSessionId` still works and the message is relayed. +- `claude-stream.jsonl` — a **successful** turn (init → assistant `tool_use` → `tool_result` → final + assistant text → `result`). Still a **documented placeholder**: the success path needs an + `anthropic` credential to capture live (recapture after `sbx secret set -g anthropic` / `/login`). + The parser already handles the real `result`/`assistant` shapes (verified against `claude-error`). - `claude-empty.stdout.txt` — the headless empty-output failure mode: 0 bytes (parity with codex §9.14). - `claude-session-filename.txt` — a sample `~/.claude/projects/.../.jsonl` basename. - `claude-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-session selection. diff --git a/src/backend/fixtures/claude-error.jsonl b/src/backend/fixtures/claude-error.jsonl index b47bf1f..5b5a39a 100644 --- a/src/backend/fixtures/claude-error.jsonl +++ b/src/backend/fixtures/claude-error.jsonl @@ -1,2 +1,3 @@ -{"type":"system","subtype":"init","cwd":"/root/repo","session_id":"019e900d-aa11-4b22-9c33-445566778899","tools":["Bash","Read","Edit"],"model":"claude-opus-4-20250514","permissionMode":"bypassPermissions","apiKeySource":"none"} -{"type":"result","subtype":"error_during_execution","is_error":true,"duration_ms":800,"num_turns":1,"result":"Credit balance is too low to continue. Visit the Anthropic console to add credits.","session_id":"019e900d-aa11-4b22-9c33-445566778899"} +{"type":"system","subtype":"init","cwd":"/home/agent/workspace","session_id":"baa1bd5f-51eb-4ee1-8b52-4d2872998728","tools":["Task","AskUserQuestion","Bash","CronCreate","CronDelete","CronList","Edit","EnterPlanMode","EnterWorktree","ExitPlanMode","ExitWorktree","Glob","Grep","NotebookEdit","Read","ScheduleWakeup","Skill","TaskOutput","TaskStop","TodoWrite","ToolSearch","WebFetch","WebSearch","Write"],"mcp_servers":[],"model":"claude-opus-4-7[1m]","permissionMode":"bypassPermissions","slash_commands":["update-config","debug","simplify","batch","fewer-permission-prompts","loop","claude-api","clear","compact","context","heapdump","init","review","security-review","usage","insights","goal","team-onboarding"],"apiKeySource":"none","claude_code_version":"2.1.141","output_style":"default","agents":["claude","Explore","general-purpose","Plan","statusline-setup"],"skills":["update-config","debug","simplify","batch","fewer-permission-prompts","loop","claude-api"],"plugins":[],"analytics_disabled":false,"uuid":"98f961e8-6ed8-4565-af25-5df4e904a6f6","memory_paths":{"auto":"/home/agent/.claude/projects/-home-agent-workspace/memory/"},"fast_mode_state":"off"} +{"type":"assistant","message":{"id":"973a1fd7-b152-4b67-8b03-4c22f8e5f8b0","container":null,"model":"","role":"assistant","stop_details":null,"stop_reason":"stop_sequence","stop_sequence":"","type":"message","usage":{"input_tokens":0,"output_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":null,"cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":null,"iterations":null,"speed":null},"content":[{"type":"text","text":"Not logged in · Please run /login"}],"context_management":null},"parent_tool_use_id":null,"session_id":"baa1bd5f-51eb-4ee1-8b52-4d2872998728","uuid":"49bac3b6-2d0a-4362-8eaa-32c19d6a3ba1","error":"authentication_failed"} +{"type":"result","subtype":"success","is_error":true,"api_error_status":null,"duration_ms":46,"duration_api_ms":0,"num_turns":1,"result":"Not logged in · Please run /login","stop_reason":"stop_sequence","session_id":"baa1bd5f-51eb-4ee1-8b52-4d2872998728","total_cost_usd":0,"usage":{"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"2d8bd5de-01b8-4e2a-8561-038a62e521d4"} From e5e1c4e538da12f5671a8ce354221f70f0f347f1 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 22:10:18 -0400 Subject: [PATCH 31/47] feat(onboarding): simplify Claude login to one command (sbx run claude) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bot already prints a setup guide and exits when the agent's credential is missing; this makes the Claude guide a single command. `sbx run claude` auto-creates a login sandbox and attaches (verified: `sbx run` "creates the sandbox if it does not already exist") — drop the separate `sbx create _login-tmp` + `sbx run` + `sbx rm` dance. The credential is captured host-globally, so the throwaway sandbox can be removed afterward. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli/onboarding.test.ts | 6 ++++-- src/cli/onboarding.ts | 9 ++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cli/onboarding.test.ts b/src/cli/onboarding.test.ts index b5b0fc4..bb7c13b 100644 --- a/src/cli/onboarding.test.ts +++ b/src/cli/onboarding.test.ts @@ -32,8 +32,10 @@ describe("onboardingInstructions", () => { expect(text).toContain("sbx secret set -g openai --oauth"); }); - it("guides Claude through the disposable login sandbox", () => { - expect(onboardingInstructions("claude")).toContain("_login-tmp"); + it("guides Claude through the one-command login sandbox (/login)", () => { + const text = onboardingInstructions("claude"); + expect(text).toContain("sbx run claude"); + expect(text).toContain("/login"); }); }); diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts index a885612..50c46e2 100644 --- a/src/cli/onboarding.ts +++ b/src/cli/onboarding.ts @@ -38,9 +38,12 @@ export function onboardingInstructions(agent: Agent): string { return [ `No '${service}' credential is configured in sbx for Claude.`, "", - " 1. sbx create --name _login-tmp claude ", - " 2. sbx run _login-tmp → type /login, finish the browser sign-in, then exit", - ` 3. Wait for 'sbx secret ls' to show '${service} (oauth configured)', then: sbx rm --force _login-tmp`, + " 1. Run: sbx run claude", + " 2. In the session type /login, finish the browser sign-in, then exit.", + ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, + "", + "(The credential is host-side and proxy-injected — it never enters a sandbox, §4.7.", + " There's no 'anthropic --oauth'; the login sandbox is throwaway — 'sbx rm --force claude' clears it.)", ].join("\n"); } From 5ea077aa8d67bb3c2c5f602c0f70dccd65435cfd Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 22:23:37 -0400 Subject: [PATCH 32/47] test(backend): real success fixture; live-verify Claude resume end-to-end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit End-to-end against Claude Code 2.1.141 in a --clone sbx claude sandbox with a provisioned repo (the dispatcher's exact flow): - turn 1 (real backend argv) created a file via the Write tool with --dangerously-skip-permissions and NO approval prompt; result is_error:false, result:"done", non-empty stdout headless; - turn 2 `claude -p --resume ` recalled a planted codeword from session history — the SAME session id across both turns (no fork), confirming the capture-once/resume design live. claude-stream.jsonl is now REAL captured bytes (init → rate_limit_event → assistant thinking → tool_use → tool_result → assistant text → result), so the parser is verified against a real success AND error stream (the success stream exercises the thinking/tool_use skip). Aligned the session-file fixtures to the captured id; tests updated. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/backend/claude.test.ts | 17 ++++++------ src/backend/fixtures/README.md | 27 +++++++++++-------- src/backend/fixtures/claude-find-output.txt | 4 +-- .../fixtures/claude-session-filename.txt | 2 +- src/backend/fixtures/claude-stream.jsonl | 12 +++++---- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/backend/claude.test.ts b/src/backend/claude.test.ts index ee1fb0d..a71ed82 100644 --- a/src/backend/claude.test.ts +++ b/src/backend/claude.test.ts @@ -21,7 +21,7 @@ const errorStream = fixture("claude-error.jsonl"); const emptyStream = fixture("claude-empty.stdout.txt"); const findOutput = fixture("claude-find-output.txt"); -const SESSION_ID = "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"; +const SESSION_ID = "c4beb659-a473-49be-a56c-51ddc0c0ce81"; describe("claudeTurnArgs", () => { it("turn 1: -p stream-json + --verbose + non-blocking flags + `--` guard, no --resume", () => { @@ -68,11 +68,10 @@ describe("claudeTurnArgs", () => { }); describe("parseClaudeResult", () => { - it("extracts the final result text and marks ok (success fixture)", () => { - expect(parseClaudeResult(stream)).toEqual({ - finalText: "Fixed the login timeout in auth.go and added a regression test.", - ok: true, - }); + it("extracts the final result text and marks ok (real success fixture)", () => { + // Real capture: init → rate_limit_event → assistant thinking → tool_use → + // tool_result → assistant text → result. The result event is authoritative. + expect(parseClaudeResult(stream)).toEqual({ finalText: "done", ok: true }); }); it("flags empty output as a failed turn (headless empty-output regression)", () => { @@ -135,7 +134,7 @@ describe("parseClaudeSessionFilename / newestSessionPath", () => { it("picks the path with the greatest mtime", () => { expect(newestSessionPath(findOutput)).toBe( - `/root/.claude/projects/-root-repo/${SESSION_ID}.jsonl`, + `/home/agent/.claude/projects/-home-agent-repo/${SESSION_ID}.jsonl`, ); expect(newestSessionPath("")).toBeNull(); }); @@ -143,7 +142,7 @@ describe("parseClaudeSessionFilename / newestSessionPath", () => { describe("claudeEvents", () => { it("parses every JSON line and degrades gracefully on garbage", () => { - expect(claudeEvents(stream)).toHaveLength(5); + expect(claudeEvents(stream)).toHaveLength(7); expect(claudeEvents("not json\n{bad}\n")).toEqual([]); }); }); @@ -182,6 +181,6 @@ describe("ClaudeBackend.captureSessionId (I/O via injected executor)", () => { expect(backend.configHome()).toBe("~/.claude"); expect(backend.parseSessionId(stream)).toBe(SESSION_ID); expect(backend.parseResult(emptyStream).ok).toBe(false); - expect(backend.events(stream)).toHaveLength(5); + expect(backend.events(stream)).toHaveLength(7); }); }); diff --git a/src/backend/fixtures/README.md b/src/backend/fixtures/README.md index 9f55897..aa426bb 100644 --- a/src/backend/fixtures/README.md +++ b/src/backend/fixtures/README.md @@ -41,18 +41,23 @@ against. The stream is JSONL: a `{"type":"system","subtype":"init",…}` first l - `is_error:true` can ship with `subtype:"success"` — so `parseClaudeResult` keys on `is_error`, never `subtype`; - `captureSessionId`'s `find $HOME/.claude/projects -name '*.jsonl' -printf '%T@\t%p\n'` returns the - real transcript (`…/-home-agent-workspace/.jsonl`); `parseClaudeSessionFilename` reads - the id from that basename. - -Fixtures: - + real transcript (`…//.jsonl`); `parseClaudeSessionFilename` reads the id from + that basename; +- **end-to-end**: a `--clone` sandbox with a provisioned repo ran turn 1 (created a file via the + `Write` tool, `is_error:false`, no approval prompt) then turn 2 `claude -p --resume ` recalled a + planted codeword from session history — the **same session id across both turns** (no fork). + +Fixtures (all REAL captured bytes unless noted): + +- `claude-stream.jsonl` — **REAL bytes**: a successful turn that created a file (init → + `rate_limit_event` → assistant `thinking` → assistant `tool_use` (Write) → `tool_result` → + assistant text → `result`, `is_error:false`, `result:"done"`). Exercises the thinking/tool_use skip + and the authoritative `result` text; captured with `--dangerously-skip-permissions` (the Write ran + with no approval prompt). - `claude-error.jsonl` — **REAL bytes**: a fresh turn that reached the API and failed auth (`is_error:true`, `result:"Not logged in · Please run /login"`, `subtype:"success"`). The session is still created (the init line), so `parseClaudeSessionId` still works and the message is relayed. -- `claude-stream.jsonl` — a **successful** turn (init → assistant `tool_use` → `tool_result` → final - assistant text → `result`). Still a **documented placeholder**: the success path needs an - `anthropic` credential to capture live (recapture after `sbx secret set -g anthropic` / `/login`). - The parser already handles the real `result`/`assistant` shapes (verified against `claude-error`). - `claude-empty.stdout.txt` — the headless empty-output failure mode: 0 bytes (parity with codex §9.14). -- `claude-session-filename.txt` — a sample `~/.claude/projects/.../.jsonl` basename. -- `claude-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-session selection. +- `claude-session-filename.txt` — a sample `.jsonl` transcript basename. +- `claude-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-session selection + (real format/paths; two lines to exercise the mtime tiebreak). diff --git a/src/backend/fixtures/claude-find-output.txt b/src/backend/fixtures/claude-find-output.txt index 290871b..484834a 100644 --- a/src/backend/fixtures/claude-find-output.txt +++ b/src/backend/fixtures/claude-find-output.txt @@ -1,2 +1,2 @@ -1748600000.1234 /root/.claude/projects/-root-repo/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl -1748600100.5678 /root/.claude/projects/-root-repo/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.jsonl +1780000000.0000000000 /home/agent/.claude/projects/-home-agent-repo/00000000-0000-4000-8000-000000000000.jsonl +1780277999.9999999999 /home/agent/.claude/projects/-home-agent-repo/c4beb659-a473-49be-a56c-51ddc0c0ce81.jsonl diff --git a/src/backend/fixtures/claude-session-filename.txt b/src/backend/fixtures/claude-session-filename.txt index bc52528..df01ad9 100644 --- a/src/backend/fixtures/claude-session-filename.txt +++ b/src/backend/fixtures/claude-session-filename.txt @@ -1 +1 @@ -a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d.jsonl +c4beb659-a473-49be-a56c-51ddc0c0ce81.jsonl diff --git a/src/backend/fixtures/claude-stream.jsonl b/src/backend/fixtures/claude-stream.jsonl index 973abd0..1f1f74e 100644 --- a/src/backend/fixtures/claude-stream.jsonl +++ b/src/backend/fixtures/claude-stream.jsonl @@ -1,5 +1,7 @@ -{"type":"system","subtype":"init","cwd":"/root/repo","session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","tools":["Bash","Read","Edit"],"model":"claude-opus-4-20250514","permissionMode":"bypassPermissions","apiKeySource":"none"} -{"type":"assistant","message":{"id":"msg_01","type":"message","role":"assistant","model":"claude-opus-4-20250514","content":[{"type":"tool_use","id":"toolu_01","name":"Edit","input":{"file_path":"auth.go"}}],"stop_reason":"tool_use","stop_sequence":null},"parent_tool_use_id":null,"session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"} -{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu_01","content":"File updated"}]},"parent_tool_use_id":null,"session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"} -{"type":"assistant","message":{"id":"msg_02","type":"message","role":"assistant","model":"claude-opus-4-20250514","content":[{"type":"text","text":"Fixed the login timeout in auth.go and added a regression test."}],"stop_reason":"end_turn","stop_sequence":null},"parent_tool_use_id":null,"session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"} -{"type":"result","subtype":"success","is_error":false,"duration_ms":12000,"duration_api_ms":11000,"num_turns":3,"result":"Fixed the login timeout in auth.go and added a regression test.","session_id":"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d","total_cost_usd":0.012} +{"type":"system","subtype":"init","cwd":"/home/agent/repo","session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81","tools":["Task","AskUserQuestion","Bash","CronCreate","CronDelete","CronList","Edit","EnterPlanMode","EnterWorktree","ExitPlanMode","ExitWorktree","Glob","Grep","Monitor","NotebookEdit","PushNotification","Read","RemoteTrigger","ScheduleWakeup","Skill","TaskOutput","TaskStop","TodoWrite","ToolSearch","WebFetch","WebSearch","Write"],"mcp_servers":[],"model":"claude-sonnet-4-6","permissionMode":"bypassPermissions","slash_commands":["update-config","debug","simplify","batch","fewer-permission-prompts","loop","claude-api","clear","compact","context","heapdump","init","review","security-review","usage","insights","goal","team-onboarding"],"apiKeySource":"none","claude_code_version":"2.1.141","output_style":"default","agents":["claude","Explore","general-purpose","Plan","statusline-setup"],"skills":["update-config","debug","simplify","batch","fewer-permission-prompts","loop","schedule","claude-api"],"plugins":[],"analytics_disabled":false,"uuid":"51be01ac-e12c-43af-8070-015b8c589a32","memory_paths":{"auto":"/home/agent/.claude/projects/-home-agent-repo/memory/"},"fast_mode_state":"off"} +{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1780291800,"rateLimitType":"five_hour","overageStatus":"rejected","overageDisabledReason":"org_level_disabled","isUsingOverage":false},"uuid":"b716668b-a6bb-462b-8eb2-9b47b2e22258","session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81"} +{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_01G3SguQf9J4BAyRdkbKY4F7","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"The user wants me to create a file named CLAUDE_E2E.md with exactly the line \"codeword MANGO\".","signature":"EqYCCmUIDhgCKkA1AIQA6Z0BRZJlz6j6IFl3KLzkKFbNSbvdhllriovWAbBbgzWMD3XQ7CcFUquXp/+jtg2c9LqzI1ig+KorqZAVMhFjbGF1ZGUtc29ubmV0LTQtNjgAQgh0aGlua2luZxIMUjsKvLq5EZdLnpgtGgzzW8Elcrf5CpRfEXciMH3jaghAm/VtrPSqF2NloCp1mwTm+fywYZzy26J+OouoYIljBRoOK+asHtQc4G47sSpvMWp8Eo7Frs2olrcwiDIfQbY9IB628YfmxkUTIqpGz0cL2jEOLZH/W8RDqZXEC+Y5M3ANgycVoQP39Npz+YO0IPW06+3ruIaP3gTYgMcV9VeWytHHGYOeG4XwOeoCJlYL7SFn7w28BbhxVeWQzVYIGAE="}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":5248,"cache_read_input_tokens":12964,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":5248},"output_tokens":8,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81","uuid":"9b231fa6-c3d8-41e2-8352-740b37c4369b"} +{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_01G3SguQf9J4BAyRdkbKY4F7","type":"message","role":"assistant","content":[{"type":"tool_use","id":"toolu_01MD1VFmB29EJqEjkbqCPadT","name":"Write","input":{"file_path":"/home/agent/repo/CLAUDE_E2E.md","content":"codeword MANGO\n"},"caller":{"type":"direct"}}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":5248,"cache_read_input_tokens":12964,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":5248},"output_tokens":8,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81","uuid":"29b33a17-dd98-42f5-853d-934fa0305f25"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_01MD1VFmB29EJqEjkbqCPadT","type":"tool_result","content":"File created successfully at: /home/agent/repo/CLAUDE_E2E.md (file state is current in your context — no need to Read it back)"}]},"parent_tool_use_id":null,"session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81","uuid":"f97d8bce-a5e5-4be8-85df-d2ec7fe729c4","timestamp":"2026-06-01T02:19:43.458Z","tool_use_result":{"type":"create","filePath":"/home/agent/repo/CLAUDE_E2E.md","content":"codeword MANGO\n","structuredPatch":[],"originalFile":null,"userModified":false}} +{"type":"assistant","message":{"model":"claude-sonnet-4-6","id":"msg_01MLXj9gm46QkBjSreLbKAPr","type":"message","role":"assistant","content":[{"type":"text","text":"done"}],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":183,"cache_read_input_tokens":18212,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":183},"output_tokens":1,"service_tier":"standard","inference_geo":"not_available"},"diagnostics":null,"context_management":null},"parent_tool_use_id":null,"session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81","uuid":"55d94e6f-bc75-4655-b5e6-a9cf7ba689e9"} +{"type":"result","subtype":"success","is_error":false,"api_error_status":null,"duration_ms":3614,"duration_api_ms":4939,"num_turns":2,"result":"done","stop_reason":"end_turn","session_id":"c4beb659-a473-49be-a56c-51ddc0c0ce81","total_cost_usd":0.03228605,"usage":{"input_tokens":4,"cache_creation_input_tokens":5431,"cache_read_input_tokens":31176,"output_tokens":138,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":5431,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[{"input_tokens":1,"output_tokens":4,"cache_read_input_tokens":18212,"cache_creation_input_tokens":183,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":183},"type":"message"}],"speed":"standard"},"modelUsage":{"claude-haiku-4-5-20251001":{"inputTokens":375,"outputTokens":22,"cacheReadInputTokens":0,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.00048499999999999997,"contextWindow":200000,"maxOutputTokens":32000},"claude-sonnet-4-6":{"inputTokens":4,"outputTokens":138,"cacheReadInputTokens":31176,"cacheCreationInputTokens":5431,"webSearchRequests":0,"costUSD":0.03180105,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"4a60d92e-a857-4c72-98be-d75b88cd6830"} From 31aa9bbc5eccf9a5c03b4b1513f7f85e0dcfaca4 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 22:44:28 -0400 Subject: [PATCH 33/47] refactor: rename the project to Mishu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebrand from "Slack Coding Agent" to Mishu — a coding agent that lives in your team's chat (Slack today). Renames the package + bin name, the CLI usage banner, the [mishu] log prefix, and the SCA_* env vars → MISHU_* (SCA literally stood for "Slack Coding Agent"). The README/AGENTS intros are reframed to the platform-agnostic positioning and now mention "Codex or Claude Code". Genuinely Slack-specific names (SlackAdapter, the APP_SLACK_* tokens, the Slack app setup) and the design-spec attachment path are intentionally unchanged. BREAKING: runtime env vars are now MISHU_REPO / MISHU_AGENT / MISHU_LOG_LEVEL / MISHU_BOT_USER (were SCA_*). Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 5 +++-- README.md | 21 +++++++++++---------- package-lock.json | 6 +++--- package.json | 6 +++--- src/cli/args.ts | 16 ++++++++-------- src/cli/index.ts | 16 +++++++--------- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0ce562c..f8b714f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,8 @@ # AGENTS.md — rules for working in this repo -A Slack-native coding agent: mention the bot in a Slack thread → it does the coding work in an -isolated `sbx` microVM running a coding-agent CLI (Codex) → it reports back **in the same thread**. +Mishu — a coding agent that lives in your team's chat (Slack today): mention it in a Slack thread → it +does the coding work in an isolated `sbx` microVM running a coding-agent CLI (Codex or Claude Code) → +it reports back **in the same thread**. The design spec is `.context/attachments/2PRioL/slack-coding-agent-spec.md`; section refs (§) point there. Read this file before changing code. diff --git a/README.md b/README.md index cbf2dd7..da0fff4 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ -# Slack Coding Agent +# Mishu -**Mention a bot in a Slack thread — a real coding agent does the work in an isolated sandbox with -your repo and credentials, and replies in the same thread.** Follow-up mentions continue the same -session; ask it to open a PR and it ships the work itself. +**Mishu is a coding agent that lives in your team's chat.** Mention it in a thread — a real coding +agent does the work in an isolated sandbox with your repo and credentials, and replies in the same +thread. Follow-up mentions continue the same session; ask it to open a PR and it ships the work itself. -It's plumbing, not a chatbot: every thread maps 1:1 to its own microVM sandbox and coding-agent -session, and the router itself runs no LLM — it just relays your thread to a coding-agent CLI (Codex) -and relays the result back. +It works in your team's communication platform — **Slack today**, more later. It's plumbing, not a +chatbot: every thread maps 1:1 to its own microVM sandbox and coding-agent session, and the router +itself runs no LLM — it just relays your thread to a coding-agent CLI (Codex or Claude Code) and +relays the result back. --- @@ -80,14 +81,14 @@ APP_SLACK_BOT_TOKEN=xoxb-… # step 6 — bot user OAuth token ```bash npm install npm run build -SCA_REPO=/path/to/your/repo \ +MISHU_REPO=/path/to/your/repo \ node --env-file=.env dist/cli/index.js --sandbox=sbx ./data ``` On first use the bot checks `sbx secret ls`; if the agent's credential is missing it prints the exact setup steps and exits. Once it prints `listening …`, **`@mention` it in your channel** and it goes to -work. Optional env: `SCA_AGENT=codex|claude` (default codex), `SCA_LOG_LEVEL=summary|verbose`, -`SCA_BOT_USER=U…`. +work. Optional env: `MISHU_AGENT=codex|claude` (default codex), `MISHU_LOG_LEVEL=summary|verbose`, +`MISHU_BOT_USER=U…`. > To let the agent **push / open PRs**, also give it a repo-scoped GitHub token: > `sbx secret set -g github` (prefer a fine-grained PAT scoped to the target repo). diff --git a/package-lock.json b/package-lock.json index 65a432f..d02be85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "slack-coding-agent", + "name": "mishu", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "slack-coding-agent", + "name": "mishu", "version": "0.0.1", "license": "MIT", "dependencies": { @@ -13,7 +13,7 @@ "@slack/web-api": "^7.16.0" }, "bin": { - "slack-coding-agent": "dist/cli/index.js" + "mishu": "dist/cli/index.js" }, "devDependencies": { "@biomejs/biome": "^2.4.0", diff --git a/package.json b/package.json index 5bffa5a..891b4a7 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "slack-coding-agent", + "name": "mishu", "version": "0.0.1", "private": true, "type": "module", - "description": "Slack-native coding agent — a router that delegates Slack threads to a coding-agent CLI in an isolated sandbox.", + "description": "Mishu — a coding agent that lives in your team's chat (Slack today): mention it in a thread and it does the work in an isolated sandbox, then replies in-thread.", "license": "MIT", "bin": { - "slack-coding-agent": "dist/cli/index.js" + "mishu": "dist/cli/index.js" }, "engines": { "node": ">=22" diff --git a/src/cli/args.ts b/src/cli/args.ts index 8ca8f86..5782a1c 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -1,8 +1,8 @@ /** * CLI argument parsing (pure). Shape mirrors pi-mom's convention (spec §10): * - * slack-coding-agent --sandbox= - * e.g. slack-coding-agent --sandbox=sbx ./data + * mishu --sandbox= + * e.g. mishu --sandbox=sbx ./data */ export interface Args { @@ -31,14 +31,14 @@ export function parseArgs(argv: string[]): Args | null { } export const USAGE = [ - "Usage: slack-coding-agent --sandbox= ", - " e.g. slack-coding-agent --sandbox=sbx ./data", + "Usage: mishu --sandbox= ", + " e.g. mishu --sandbox=sbx ./data", "", "Environment:", " APP_SLACK_APP_TOKEN xapp-… app-level token (Socket Mode)", " APP_SLACK_BOT_TOKEN xoxb-… bot token", - " SCA_REPO path to the target git repo (the --clone seed)", - " SCA_AGENT codex | claude (default: codex)", - " SCA_LOG_LEVEL summary | verbose (default: summary)", - " SCA_BOT_USER bot user id (optional; resolved via auth.test otherwise)", + " MISHU_REPO path to the target git repo (the --clone seed)", + " MISHU_AGENT codex | claude (default: codex)", + " MISHU_LOG_LEVEL summary | verbose (default: summary)", + " MISHU_BOT_USER bot user id (optional; resolved via auth.test otherwise)", ].join("\n"); diff --git a/src/cli/index.ts b/src/cli/index.ts index 172a0da..8fa2664 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -3,7 +3,7 @@ * Composition root (spec §3, §4.8). Wires the three seams + Router behind the * onboarding gate, then starts the Socket Mode ingress: * - * slack-coding-agent --sandbox=sbx ./data + * mishu --sandbox=sbx ./data * * See args.ts USAGE for the environment. The router is plumbing — no LLM here. */ @@ -44,8 +44,8 @@ async function run(args: Args): Promise { console.error(`Unsupported --sandbox=${args.sandbox} (v0 supports: sbx)`); process.exit(1); } - const agent: Agent = process.env.SCA_AGENT === "claude" ? "claude" : "codex"; - const level: LogLevel = process.env.SCA_LOG_LEVEL === "verbose" ? "verbose" : "summary"; + const agent: Agent = process.env.MISHU_AGENT === "claude" ? "claude" : "codex"; + const level: LogLevel = process.env.MISHU_LOG_LEVEL === "verbose" ? "verbose" : "summary"; const provider = new SbxProvider({ createOptions: { agent } }); @@ -60,10 +60,10 @@ async function run(args: Args): Promise { const appToken = requireEnv("APP_SLACK_APP_TOKEN"); const botToken = requireEnv("APP_SLACK_BOT_TOKEN"); - const repoRef = requireEnv("SCA_REPO"); + const repoRef = requireEnv("MISHU_REPO"); const logSink = fileLogSink(args.dataDir); - const platform = new SlackAdapter({ appToken, botToken, botUserId: process.env.SCA_BOT_USER }); + const platform = new SlackAdapter({ appToken, botToken, botUserId: process.env.MISHU_BOT_USER }); const botUser = await platform.whoAmI(); const backend: CodingBackend = agent === "claude" ? new ClaudeBackend(provider) : new CodexBackend(provider); @@ -84,9 +84,7 @@ async function run(args: Args): Promise { router.start(); await platform.start(); - console.error( - `[slack-coding-agent] listening — agent=${agent} repo=${repoRef} data=${args.dataDir}`, - ); + console.error(`[mishu] listening — agent=${agent} repo=${repoRef} data=${args.dataDir}`); } function main(): void { @@ -97,7 +95,7 @@ function main(): void { process.exit(argv.includes("-h") || argv.includes("--help") ? 0 : 1); } run(args).catch((err: unknown) => { - console.error("[slack-coding-agent] fatal:", err); + console.error("[mishu] fatal:", err); process.exit(1); }); } From ba2f14b70650255df05d137a3a1d5e8686c1f71b Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 22:48:30 -0400 Subject: [PATCH 34/47] docs: drop the design-spec attachment path references MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the `.context/attachments/.../slack-coding-agent-spec.md` path from README and AGENTS (a gitignored attachment carrying the old name). AGENTS keeps a one-line note that § refs point to the design spec; README's Status section keeps the roadmap without the path. Co-Authored-By: Claude Opus 4.8 (1M context) --- AGENTS.md | 4 +--- README.md | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f8b714f..5d296c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,9 +2,7 @@ Mishu — a coding agent that lives in your team's chat (Slack today): mention it in a Slack thread → it does the coding work in an isolated `sbx` microVM running a coding-agent CLI (Codex or Claude Code) → -it reports back **in the same thread**. -The design spec is `.context/attachments/2PRioL/slack-coding-agent-spec.md`; section refs (§) point -there. Read this file before changing code. +it reports back **in the same thread**. Section refs (§) point to the design spec. ## 0. What this is — and is NOT diff --git a/README.md b/README.md index da0fff4..e52b431 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,4 @@ src/ **Phase 1 (local, single-user, macOS Apple silicon)** — verified end-to-end against real Slack + sbx + Codex: mention → isolated sandbox → repo clone → headless codex → reply in-thread, with deterministic sandbox naming, per-thread `~/.agent-state` ledger + session resume, idle/running/pending coalescing, -and always-on boundary logging. The design spec lives at -`.context/attachments/2PRioL/slack-coding-agent-spec.md`; the phased roadmap (ergonomics → teams → -cloud) is §8 there. +and always-on boundary logging. The phased roadmap runs ergonomics → teams → cloud. From ebcc870d7aa5c4616be5ae1b098f01cdfe57d973 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 23:01:15 -0400 Subject: [PATCH 35/47] feat(cli): add `npm run setup` guided onboarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One command checks the selected agent's sbx credential and, if it's missing, runs the interactive auth flow with the terminal attached — browser OAuth for Codex (`sbx secret set -g openai --oauth`), in-sandbox `/login` for Claude (`sbx run claude`) — then confirms. Idempotent. The pure decision lives in onboarding.ts (`setupCommand`); src/cli/setup.ts is a thin I/O shell reusing SbxProvider.secretLs + detectMissingCredential. The bot's onboarding gate and the README now point to `npm run setup`. Runs via tsx, so no build needed. Verified live: the idempotent "already configured" path for both codex and claude. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 4 ++ package.json | 1 + src/cli/onboarding.test.ts | 14 +++++++ src/cli/onboarding.ts | 18 +++++++++ src/cli/setup.ts | 82 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+) create mode 100644 src/cli/setup.ts diff --git a/README.md b/README.md index e52b431..f843e99 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ sbx secret set -g openai --oauth # ChatGPT subscription (browser sign-in) sbx secret ls # should show: openai (… configured) ``` +> **Prefer a guided flow?** After `npm install` (below), `npm run setup` honors `MISHU_AGENT` +> (`codex`|`claude`), checks the credential, and walks you through the browser OAuth (Codex) or +> in-sandbox `/login` (Claude). Mishu prints the same one-liner if it starts without one. + ### 2. Slack app (Socket Mode) Never made a Slack app? Follow these steps: diff --git a/package.json b/package.json index 891b4a7..c32c10d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "scripts": { "build": "tsc -p tsconfig.build.json", + "setup": "tsx src/cli/setup.ts", "dev": "tsx watch src/cli/index.ts", "start": "node dist/cli/index.js", "typecheck": "tsc --noEmit", diff --git a/src/cli/onboarding.test.ts b/src/cli/onboarding.test.ts index bb7c13b..0d0504c 100644 --- a/src/cli/onboarding.test.ts +++ b/src/cli/onboarding.test.ts @@ -3,6 +3,7 @@ import { detectMissingCredential, ensureOnboarded, onboardingInstructions, + setupCommand, } from "./onboarding.js"; describe("credentialService", () => { @@ -29,16 +30,29 @@ describe("detectMissingCredential", () => { describe("onboardingInstructions", () => { it("guides Codex through the OAuth path the user chose (§4.8)", () => { const text = onboardingInstructions("codex"); + expect(text).toContain("npm run setup"); expect(text).toContain("sbx secret set -g openai --oauth"); }); it("guides Claude through the one-command login sandbox (/login)", () => { const text = onboardingInstructions("claude"); + expect(text).toContain("npm run setup"); expect(text).toContain("sbx run claude"); expect(text).toContain("/login"); }); }); +describe("setupCommand", () => { + it("codex: a single sbx OAuth command", () => { + expect(setupCommand("codex")).toEqual(["secret", "set", "-g", "openai", "--oauth"]); + }); + + it("claude: in-sandbox login, not an (unsupported) anthropic --oauth", () => { + expect(setupCommand("claude")).toEqual(["run", "claude"]); + expect(setupCommand("claude")).not.toContain("--oauth"); + }); +}); + describe("ensureOnboarded", () => { it("returns true and stays quiet when the credential is present", async () => { const logs: string[] = []; diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts index 50c46e2..6fb8a42 100644 --- a/src/cli/onboarding.ts +++ b/src/cli/onboarding.ts @@ -14,6 +14,18 @@ export function credentialService(agent: Agent): string { return agent === "codex" ? "openai" : "anthropic"; } +/** + * The interactive `sbx` command (argv after the `sbx` bin) that establishes the + * agent's credential — what `npm run setup` runs (§4.8). Codex has a one-shot + * OAuth command; Claude has no `anthropic --oauth`, so it logs in inside a + * sandbox (`sbx run claude` → /login) and sbx captures the credential host-side. + */ +export function setupCommand(agent: Agent): string[] { + return agent === "codex" + ? ["secret", "set", "-g", credentialService(agent), "--oauth"] + : ["run", "claude"]; +} + /** True if `sbx secret ls` shows no credential for the agent's service. */ export function detectMissingCredential(secretLsOutput: string, agent: Agent): boolean { const service = credentialService(agent); @@ -28,6 +40,9 @@ export function onboardingInstructions(agent: Agent): string { return [ `No '${service}' credential is configured in sbx for Codex.`, "", + " Easiest: npm run setup (walks you through it)", + "", + " …or do it manually:", ` 1. Run: sbx secret set -g ${service} --oauth`, " 2. Finish the browser sign-in.", ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, @@ -38,6 +53,9 @@ export function onboardingInstructions(agent: Agent): string { return [ `No '${service}' credential is configured in sbx for Claude.`, "", + " Easiest: npm run setup (walks you through it)", + "", + " …or do it manually:", " 1. Run: sbx run claude", " 2. In the session type /login, finish the browser sign-in, then exit.", ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, diff --git a/src/cli/setup.ts b/src/cli/setup.ts new file mode 100644 index 0000000..37f1af2 --- /dev/null +++ b/src/cli/setup.ts @@ -0,0 +1,82 @@ +#!/usr/bin/env node +/** + * `npm run setup` — one-command onboarding (spec §4.8). Checks the selected + * agent's sbx credential and, if it's missing, runs the interactive auth flow + * (browser OAuth for Codex; `sbx run claude` → /login for Claude) with the + * terminal attached, then confirms. Idempotent: re-running once configured just + * reports ready. The pure decisions (which service, which command) live in + * onboarding.ts; this file is the thin I/O shell. + */ +import { spawnSync } from "node:child_process"; +import { SbxProvider } from "../sandbox/sbx-provider.js"; +import { + type Agent, + credentialService, + detectMissingCredential, + setupCommand, +} from "./onboarding.js"; + +const SBX_BIN = "sbx"; + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** Is the agent's credential present? Polls (sbx may set it a beat after the flow exits). */ +async function credentialPresent( + provider: SbxProvider, + agent: Agent, + tries: number, +): Promise { + for (let attempt = 0; attempt < tries; attempt++) { + if (!detectMissingCredential(await provider.secretLs(), agent)) { + return true; + } + if (attempt < tries - 1) { + await sleep(2000); + } + } + return false; +} + +async function main(): Promise { + const agent: Agent = process.env.MISHU_AGENT === "claude" ? "claude" : "codex"; + const service = credentialService(agent); + const provider = new SbxProvider({ createOptions: { agent } }); + + // Already set up? (idempotent) + if (await credentialPresent(provider, agent, 1)) { + console.log(`✓ '${service}' is already configured in sbx — Mishu is ready (agent=${agent}).`); + return; + } + + // Run the interactive auth flow with the terminal attached. + console.log(`Setting up the '${service}' credential for ${agent}…\n`); + if (agent === "claude") { + console.log( + "A Claude session will open — type /login, finish the browser sign-in, then exit.\n", + ); + } + const argv = setupCommand(agent); + const result = spawnSync(SBX_BIN, argv, { stdio: "inherit" }); + if (result.error !== undefined) { + console.error(`\nCouldn't run '${SBX_BIN} ${argv.join(" ")}': ${result.error.message}`); + console.error("Is the sbx CLI installed and on your PATH?"); + process.exit(1); + } + + // Confirm. + if (!(await credentialPresent(provider, agent, 3))) { + console.error(`\n✗ '${service}' still isn't configured. Re-run 'npm run setup' to try again.`); + process.exit(1); + } + console.log(`\n✓ '${service}' configured — Mishu is ready (agent=${agent}).`); + console.log( + "Start it with: MISHU_REPO=/path/to/repo node --env-file=.env dist/cli/index.js --sandbox=sbx ./data", + ); +} + +main().catch((err: unknown) => { + console.error("[mishu setup] fatal:", err instanceof Error ? err.message : String(err)); + process.exit(1); +}); From edc0ac5db79e3f3540b21a2bd459bc97e450d155 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 23:15:39 -0400 Subject: [PATCH 36/47] feat(cli): setup prompts for the agent + guards headless runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm run setup` now resolves the agent per spec §4.8: MISHU_AGENT if set, else an interactive [codex/claude] prompt on a TTY, else codex (so piped/CI runs never block on a prompt). Adds a pure parseAgent() helper, shared with the composition root (replacing its inline ternary) + a unit test. Also guards the interactive auth flow: when the credential is missing and stdin isn't a TTY, print a clear "run it in a terminal" message and exit instead of launching the browser / `sbx run claude` flow headlessly (which hangs). Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 6 +++--- src/cli/index.ts | 4 ++-- src/cli/onboarding.test.ts | 11 +++++++++++ src/cli/onboarding.ts | 5 +++++ src/cli/setup.ts | 32 +++++++++++++++++++++++++++++++- 5 files changed, 52 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f843e99..56aa3f5 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ sbx secret set -g openai --oauth # ChatGPT subscription (browser sign-in) sbx secret ls # should show: openai (… configured) ``` -> **Prefer a guided flow?** After `npm install` (below), `npm run setup` honors `MISHU_AGENT` -> (`codex`|`claude`), checks the credential, and walks you through the browser OAuth (Codex) or -> in-sandbox `/login` (Claude). Mishu prints the same one-liner if it starts without one. +> **Prefer a guided flow?** After `npm install` (below), `npm run setup` asks which agent (or honors +> `MISHU_AGENT=codex|claude`), checks the credential, and walks you through the browser OAuth (Codex) +> or in-sandbox `/login` (Claude). Mishu prints the same one-liner if it starts without one. ### 2. Slack app (Socket Mode) diff --git a/src/cli/index.ts b/src/cli/index.ts index 8fa2664..cd4b4d9 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -18,7 +18,7 @@ import { Dispatcher, Router } from "../router/index.js"; import { createJsonlSink, type LogLevel, type LogSink } from "../router/log.js"; import { SbxProvider } from "../sandbox/sbx-provider.js"; import { type Args, parseArgs, USAGE } from "./args.js"; -import { type Agent, ensureOnboarded } from "./onboarding.js"; +import { type Agent, ensureOnboarded, parseAgent } from "./onboarding.js"; /** Boundary-log sink: the router's stdout + an append-only JSONL file (§4.9). */ function fileLogSink(dataDir: string): LogSink { @@ -44,7 +44,7 @@ async function run(args: Args): Promise { console.error(`Unsupported --sandbox=${args.sandbox} (v0 supports: sbx)`); process.exit(1); } - const agent: Agent = process.env.MISHU_AGENT === "claude" ? "claude" : "codex"; + const agent: Agent = parseAgent(process.env.MISHU_AGENT) ?? "codex"; const level: LogLevel = process.env.MISHU_LOG_LEVEL === "verbose" ? "verbose" : "summary"; const provider = new SbxProvider({ createOptions: { agent } }); diff --git a/src/cli/onboarding.test.ts b/src/cli/onboarding.test.ts index 0d0504c..8f185d9 100644 --- a/src/cli/onboarding.test.ts +++ b/src/cli/onboarding.test.ts @@ -3,6 +3,7 @@ import { detectMissingCredential, ensureOnboarded, onboardingInstructions, + parseAgent, setupCommand, } from "./onboarding.js"; @@ -13,6 +14,16 @@ describe("credentialService", () => { }); }); +describe("parseAgent", () => { + it("accepts codex/claude and rejects anything else", () => { + expect(parseAgent("codex")).toBe("codex"); + expect(parseAgent("claude")).toBe("claude"); + expect(parseAgent("")).toBeNull(); + expect(parseAgent(undefined)).toBeNull(); + expect(parseAgent("gpt")).toBeNull(); + }); +}); + describe("detectMissingCredential", () => { it("treats the empty 'No secrets found' output as missing", () => { const empty = "No secrets found. Run 'sbx secret set --help' to see available services."; diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts index 6fb8a42..dffcb75 100644 --- a/src/cli/onboarding.ts +++ b/src/cli/onboarding.ts @@ -9,6 +9,11 @@ export type Agent = "codex" | "claude"; +/** Parse an agent name (from an env var or a prompt answer); null if unrecognized. */ +export function parseAgent(value: string | null | undefined): Agent | null { + return value === "codex" || value === "claude" ? value : null; +} + /** The sbx secret service that backs each agent's auth (§4.7). */ export function credentialService(agent: Agent): string { return agent === "codex" ? "openai" : "anthropic"; diff --git a/src/cli/setup.ts b/src/cli/setup.ts index 37f1af2..5e66a83 100644 --- a/src/cli/setup.ts +++ b/src/cli/setup.ts @@ -8,11 +8,13 @@ * onboarding.ts; this file is the thin I/O shell. */ import { spawnSync } from "node:child_process"; +import { createInterface } from "node:readline/promises"; import { SbxProvider } from "../sandbox/sbx-provider.js"; import { type Agent, credentialService, detectMissingCredential, + parseAgent, setupCommand, } from "./onboarding.js"; @@ -39,8 +41,26 @@ async function credentialPresent( return false; } +/** MISHU_AGENT if set; else prompt on a TTY; else default codex (non-interactive). */ +async function resolveAgent(): Promise { + const fromEnv = parseAgent(process.env.MISHU_AGENT); + if (fromEnv !== null) { + return fromEnv; + } + if (process.stdin.isTTY !== true) { + return "codex"; + } + const rl = createInterface({ input: process.stdin, output: process.stdout }); + try { + const answer = await rl.question("Which agent? [codex/claude] (default: codex): "); + return parseAgent(answer.trim().toLowerCase()) ?? "codex"; + } finally { + rl.close(); + } +} + async function main(): Promise { - const agent: Agent = process.env.MISHU_AGENT === "claude" ? "claude" : "codex"; + const agent = await resolveAgent(); const service = credentialService(agent); const provider = new SbxProvider({ createOptions: { agent } }); @@ -50,6 +70,16 @@ async function main(): Promise { return; } + // The auth flow is interactive (browser sign-in / `/login`); don't launch it + // headlessly (it would hang waiting on a terminal that isn't there). + if (process.stdin.isTTY !== true) { + console.error( + `'${service}' isn't configured for ${agent}, and setup is interactive — ` + + "run 'npm run setup' directly in a terminal.", + ); + process.exit(1); + } + // Run the interactive auth flow with the terminal attached. console.log(`Setting up the '${service}' credential for ${agent}…\n`); if (agent === "claude") { From b4ad94965481816e45868290fb2886b07121a7d6 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Sun, 31 May 2026 23:22:46 -0400 Subject: [PATCH 37/47] feat(cli): setup removes the Claude login sandbox afterward MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude auth logs in via a sandbox; name it deterministically (`sbx run --name mishu-login claude`) so setup can `sbx rm --force` it once the credential is captured — no more stray login sandbox. Best-effort: a failed cleanup just prints the manual command, never fails setup. The manual onboarding instructions use the same named sandbox + an explicit cleanup step. Adds loginSandbox() + tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli/onboarding.test.ts | 17 +++++++++++++---- src/cli/onboarding.ts | 20 +++++++++++++++----- src/cli/setup.ts | 12 ++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/cli/onboarding.test.ts b/src/cli/onboarding.test.ts index 8f185d9..84d2e4c 100644 --- a/src/cli/onboarding.test.ts +++ b/src/cli/onboarding.test.ts @@ -1,7 +1,9 @@ import { + CLAUDE_LOGIN_SANDBOX, credentialService, detectMissingCredential, ensureOnboarded, + loginSandbox, onboardingInstructions, parseAgent, setupCommand, @@ -45,10 +47,10 @@ describe("onboardingInstructions", () => { expect(text).toContain("sbx secret set -g openai --oauth"); }); - it("guides Claude through the one-command login sandbox (/login)", () => { + it("guides Claude through the named login sandbox (/login)", () => { const text = onboardingInstructions("claude"); expect(text).toContain("npm run setup"); - expect(text).toContain("sbx run claude"); + expect(text).toContain(CLAUDE_LOGIN_SANDBOX); expect(text).toContain("/login"); }); }); @@ -58,12 +60,19 @@ describe("setupCommand", () => { expect(setupCommand("codex")).toEqual(["secret", "set", "-g", "openai", "--oauth"]); }); - it("claude: in-sandbox login, not an (unsupported) anthropic --oauth", () => { - expect(setupCommand("claude")).toEqual(["run", "claude"]); + it("claude: a named throwaway sandbox, not an (unsupported) anthropic --oauth", () => { + expect(setupCommand("claude")).toEqual(["run", "--name", CLAUDE_LOGIN_SANDBOX, "claude"]); expect(setupCommand("claude")).not.toContain("--oauth"); }); }); +describe("loginSandbox", () => { + it("names the throwaway sandbox for Claude, none for Codex", () => { + expect(loginSandbox("claude")).toBe(CLAUDE_LOGIN_SANDBOX); + expect(loginSandbox("codex")).toBeNull(); + }); +}); + describe("ensureOnboarded", () => { it("returns true and stays quiet when the credential is present", async () => { const logs: string[] = []; diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts index dffcb75..47e2b6e 100644 --- a/src/cli/onboarding.ts +++ b/src/cli/onboarding.ts @@ -19,16 +19,25 @@ export function credentialService(agent: Agent): string { return agent === "codex" ? "openai" : "anthropic"; } +/** The throwaway sandbox `npm run setup` creates for Claude's `/login`, then removes. */ +export const CLAUDE_LOGIN_SANDBOX = "mishu-login"; + /** * The interactive `sbx` command (argv after the `sbx` bin) that establishes the * agent's credential — what `npm run setup` runs (§4.8). Codex has a one-shot - * OAuth command; Claude has no `anthropic --oauth`, so it logs in inside a - * sandbox (`sbx run claude` → /login) and sbx captures the credential host-side. + * OAuth command; Claude has no `anthropic --oauth`, so it logs in inside a named + * throwaway sandbox (`sbx run --name mishu-login claude` → /login) and sbx + * captures the credential host-side; setup removes the sandbox afterward. */ export function setupCommand(agent: Agent): string[] { return agent === "codex" ? ["secret", "set", "-g", credentialService(agent), "--oauth"] - : ["run", "claude"]; + : ["run", "--name", CLAUDE_LOGIN_SANDBOX, "claude"]; +} + +/** The throwaway login sandbox `setupCommand` creates (to remove after), or null (Codex). */ +export function loginSandbox(agent: Agent): string | null { + return agent === "claude" ? CLAUDE_LOGIN_SANDBOX : null; } /** True if `sbx secret ls` shows no credential for the agent's service. */ @@ -61,12 +70,13 @@ export function onboardingInstructions(agent: Agent): string { " Easiest: npm run setup (walks you through it)", "", " …or do it manually:", - " 1. Run: sbx run claude", + ` 1. Run: sbx run --name ${CLAUDE_LOGIN_SANDBOX} claude`, " 2. In the session type /login, finish the browser sign-in, then exit.", ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, + ` 4. (optional) sbx rm --force ${CLAUDE_LOGIN_SANDBOX} # the login sandbox is throwaway`, "", "(The credential is host-side and proxy-injected — it never enters a sandbox, §4.7.", - " There's no 'anthropic --oauth'; the login sandbox is throwaway — 'sbx rm --force claude' clears it.)", + " There's no 'anthropic --oauth' — login happens inside the sandbox above.)", ].join("\n"); } diff --git a/src/cli/setup.ts b/src/cli/setup.ts index 5e66a83..e1f49bf 100644 --- a/src/cli/setup.ts +++ b/src/cli/setup.ts @@ -14,6 +14,7 @@ import { type Agent, credentialService, detectMissingCredential, + loginSandbox, parseAgent, setupCommand, } from "./onboarding.js"; @@ -100,6 +101,17 @@ async function main(): Promise { console.error(`\n✗ '${service}' still isn't configured. Re-run 'npm run setup' to try again.`); process.exit(1); } + + // Tidy up the throwaway login sandbox (Claude only) — best-effort; never fail over it. + const login = loginSandbox(agent); + if (login !== null) { + try { + await provider.destroy({ name: login }); + } catch { + console.log(`(Leftover login sandbox '${login}' — remove it with: sbx rm --force ${login})`); + } + } + console.log(`\n✓ '${service}' configured — Mishu is ready (agent=${agent}).`); console.log( "Start it with: MISHU_REPO=/path/to/repo node --env-file=.env dist/cli/index.js --sandbox=sbx ./data", From 9e16d7c8d130ed79345a04b1ac4657d000ef5f3a Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Mon, 1 Jun 2026 09:45:29 -0400 Subject: [PATCH 38/47] refactor(backend): share agent-agnostic parsing helpers across backends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Claude backend duplicated, byte-for-byte, four generic helpers that already lived in codex.ts: the `isRecord`/`asString` JSON type guards, the JSONL-stream event parser, and the "newest path by mtime from `find -printf` output" selector. None carry agent-format knowledge, so hoist them into a new pure `src/backend/parsing.ts` (with colocated tests) and have both backends import them — finishing the sharing the diff already began for `SandboxShellExecutor`. Each backend keeps all of its agent-specific format knowledge (argv shapes, result/event interpretation, the session-id field + transcript/rollout filename regexes, and the `find` path+glob constants), preserving the §3 invariant that agent CLI/output format lives only in the backend file. Also tighten both filename-regex guards to `match?.[1] ?? null`. Net −130 lines across the two backends + their tests, replaced by one shared 54-line module. `npm run check` green (190 tests). Co-Authored-By: Claude Opus 4.8 --- src/backend/claude.test.ts | 18 +------------ src/backend/claude.ts | 52 +++-------------------------------- src/backend/codex.test.ts | 18 +------------ src/backend/codex.ts | 52 +++-------------------------------- src/backend/parsing.test.ts | 43 +++++++++++++++++++++++++++++ src/backend/parsing.ts | 54 +++++++++++++++++++++++++++++++++++++ 6 files changed, 107 insertions(+), 130 deletions(-) create mode 100644 src/backend/parsing.test.ts create mode 100644 src/backend/parsing.ts diff --git a/src/backend/claude.test.ts b/src/backend/claude.test.ts index a71ed82..d1cb8f7 100644 --- a/src/backend/claude.test.ts +++ b/src/backend/claude.test.ts @@ -4,9 +4,7 @@ import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; import { CLAUDE_NONBLOCKING_FLAGS, ClaudeBackend, - claudeEvents, claudeTurnArgs, - newestSessionPath, parseClaudeResult, parseClaudeSessionFilename, parseClaudeSessionId, @@ -120,7 +118,7 @@ describe("parseClaudeSessionId", () => { }); }); -describe("parseClaudeSessionFilename / newestSessionPath", () => { +describe("parseClaudeSessionFilename", () => { it("extracts the UUID from a transcript filename", () => { expect(parseClaudeSessionFilename(fixture("claude-session-filename.txt").trim())).toBe( SESSION_ID, @@ -131,20 +129,6 @@ describe("parseClaudeSessionFilename / newestSessionPath", () => { expect(parseClaudeSessionFilename("config.json")).toBeNull(); expect(parseClaudeSessionFilename("notes.jsonl")).toBeNull(); }); - - it("picks the path with the greatest mtime", () => { - expect(newestSessionPath(findOutput)).toBe( - `/home/agent/.claude/projects/-home-agent-repo/${SESSION_ID}.jsonl`, - ); - expect(newestSessionPath("")).toBeNull(); - }); -}); - -describe("claudeEvents", () => { - it("parses every JSON line and degrades gracefully on garbage", () => { - expect(claudeEvents(stream)).toHaveLength(7); - expect(claudeEvents("not json\n{bad}\n")).toEqual([]); - }); }); describe("ClaudeBackend.captureSessionId (I/O via injected executor)", () => { diff --git a/src/backend/claude.ts b/src/backend/claude.ts index 32472d1..df286ca 100644 --- a/src/backend/claude.ts +++ b/src/backend/claude.ts @@ -1,5 +1,6 @@ import type { SandboxHandle } from "../sandbox/index.js"; import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js"; +import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; /** * Claude Code backend — the ONLY place Claude Code's CLI/output format lives @@ -33,14 +34,6 @@ export const CLAUDE_PROJECTS_PATH = "$HOME/.claude/projects"; */ export const CLAUDE_NONBLOCKING_FLAGS = ["--dangerously-skip-permissions"]; -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; -} - -function asString(value: unknown): string | null { - return typeof value === "string" ? value : null; -} - /** * Build the turn invocation. `--` guards a prompt that might start with `-`. * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). @@ -153,44 +146,7 @@ const SESSION_UUID_RE = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a /** The session UUID from a `.jsonl` transcript filename (§4.5). */ export function parseClaudeSessionFilename(filename: string): string | null { const match = SESSION_UUID_RE.exec(filename); - return match !== null && match[1] !== undefined ? match[1] : null; -} - -/** From `find … -printf '%T@\t%p\n'` output, the path with the greatest mtime. */ -export function newestSessionPath(findOutput: string): string | null { - let bestPath: string | null = null; - let bestMtime = Number.NEGATIVE_INFINITY; - for (const line of findOutput.split("\n")) { - if (line.trim() === "") { - continue; - } - const tab = line.indexOf("\t"); - if (tab < 0) { - continue; - } - const mtime = Number.parseFloat(line.slice(0, tab)); - if (Number.isFinite(mtime) && mtime > bestMtime) { - bestMtime = mtime; - bestPath = line.slice(tab + 1); - } - } - return bestPath; -} - -/** Parse the stream-json into raw events for log enrichment (degrade to []). */ -export function claudeEvents(captured: string): unknown[] { - const events: unknown[] = []; - for (const line of captured.split("\n")) { - if (line.trim() === "") { - continue; - } - try { - events.push(JSON.parse(line)); - } catch { - // skip non-JSON progress lines - } - } - return events; + return match?.[1] ?? null; } export class ClaudeBackend implements CodingBackend { @@ -213,14 +169,14 @@ export class ClaudeBackend implements CodingBackend { } events(captured: string): unknown[] { - return claudeEvents(captured); + return parseJsonlEvents(captured); } /** Fallback when the stream had no id: newest transcript under CLAUDE_PROJECTS_PATH. */ async captureSessionId(handle: SandboxHandle): Promise { const command = `find ${CLAUDE_PROJECTS_PATH} -name '*.jsonl' -printf '%T@\\t%p\\n' 2>/dev/null`; const { stdout } = await this.executor.execShell(handle, command); - const newest = newestSessionPath(stdout); + const newest = newestByMtime(stdout); if (newest === null) { throw new Error("claude: no session transcript found to capture the session id (§4.5)"); } diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts index 79051eb..2e28b5e 100644 --- a/src/backend/codex.test.ts +++ b/src/backend/codex.test.ts @@ -4,9 +4,7 @@ import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; import { CODEX_NONBLOCKING_FLAGS, CodexBackend, - codexEvents, codexTurnArgs, - newestRolloutPath, parseCodexResult, parseRolloutId, parseSessionId, @@ -115,7 +113,7 @@ describe("parseSessionId", () => { }); }); -describe("parseRolloutId / newestRolloutPath", () => { +describe("parseRolloutId", () => { it("extracts the UUID from a rollout filename", () => { expect(parseRolloutId(fixture("codex-rollout-filename.txt").trim())).toBe( "7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f", @@ -125,20 +123,6 @@ describe("parseRolloutId / newestRolloutPath", () => { it("returns null for a non-rollout filename", () => { expect(parseRolloutId("config.toml")).toBeNull(); }); - - it("picks the path with the greatest mtime", () => { - expect(newestRolloutPath(findOutput)).toBe( - "/root/.codex/sessions/2026/05/30/rollout-2026-05-30T12-30-00-7f3e9c21-4b6a-4c2d-9e1f-2a3b4c5d6e7f.jsonl", - ); - expect(newestRolloutPath("")).toBeNull(); - }); -}); - -describe("codexEvents", () => { - it("parses every JSON line and degrades gracefully on garbage", () => { - expect(codexEvents(stream)).toHaveLength(4); - expect(codexEvents("not json\n{bad}\n")).toEqual([]); - }); }); describe("CodexBackend.captureSessionId (I/O via injected executor)", () => { diff --git a/src/backend/codex.ts b/src/backend/codex.ts index f34887f..dd088e1 100644 --- a/src/backend/codex.ts +++ b/src/backend/codex.ts @@ -1,5 +1,6 @@ import type { SandboxHandle } from "../sandbox/index.js"; import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js"; +import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; /** * Codex backend — the ONLY place Codex's CLI/output format lives (spec §4.5/§4.9). @@ -33,14 +34,6 @@ export const CODEX_NONBLOCKING_FLAGS = [ "approval_policy=never", ]; -function isRecord(value: unknown): value is Record { - return typeof value === "object" && value !== null; -} - -function asString(value: unknown): string | null { - return typeof value === "string" ? value : null; -} - /** * Build the turn invocation. `--` guards a prompt/id that might start with `-`. * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). @@ -175,44 +168,7 @@ const ROLLOUT_UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a- /** The session UUID embedded in a `rollout-…-.jsonl` filename (§4.5). */ export function parseRolloutId(filename: string): string | null { const match = ROLLOUT_UUID_RE.exec(filename); - return match !== null && match[1] !== undefined ? match[1] : null; -} - -/** From `find … -printf '%T@\t%p\n'` output, the path with the greatest mtime. */ -export function newestRolloutPath(findOutput: string): string | null { - let bestPath: string | null = null; - let bestMtime = Number.NEGATIVE_INFINITY; - for (const line of findOutput.split("\n")) { - if (line.trim() === "") { - continue; - } - const tab = line.indexOf("\t"); - if (tab < 0) { - continue; - } - const mtime = Number.parseFloat(line.slice(0, tab)); - if (Number.isFinite(mtime) && mtime > bestMtime) { - bestMtime = mtime; - bestPath = line.slice(tab + 1); - } - } - return bestPath; -} - -/** Parse the `--json` stream into raw events for log enrichment (degrade to []). */ -export function codexEvents(captured: string): unknown[] { - const events: unknown[] = []; - for (const line of captured.split("\n")) { - if (line.trim() === "") { - continue; - } - try { - events.push(JSON.parse(line)); - } catch { - // skip non-JSON progress lines - } - } - return events; + return match?.[1] ?? null; } export class CodexBackend implements CodingBackend { @@ -235,14 +191,14 @@ export class CodexBackend implements CodingBackend { } events(captured: string): unknown[] { - return codexEvents(captured); + return parseJsonlEvents(captured); } /** Fallback when the stream had no id: newest rollout file under CODEX_HOME. */ async captureSessionId(handle: SandboxHandle): Promise { const command = `find ${CODEX_SESSIONS_PATH} -name 'rollout-*.jsonl' -printf '%T@\\t%p\\n' 2>/dev/null`; const { stdout } = await this.executor.execShell(handle, command); - const newest = newestRolloutPath(stdout); + const newest = newestByMtime(stdout); if (newest === null) { throw new Error("codex: no rollout file found to capture the session id (§4.5)"); } diff --git a/src/backend/parsing.test.ts b/src/backend/parsing.test.ts new file mode 100644 index 0000000..699d456 --- /dev/null +++ b/src/backend/parsing.test.ts @@ -0,0 +1,43 @@ +import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; + +describe("isRecord", () => { + it("accepts non-null objects and rejects everything else", () => { + expect(isRecord({ a: 1 })).toBe(true); + expect(isRecord([])).toBe(true); // arrays are objects; callers guard array-ness separately + expect(isRecord(null)).toBe(false); + expect(isRecord("x")).toBe(false); + expect(isRecord(7)).toBe(false); + expect(isRecord(undefined)).toBe(false); + }); +}); + +describe("asString", () => { + it("returns strings as-is and null otherwise", () => { + expect(asString("hi")).toBe("hi"); + expect(asString("")).toBe(""); + expect(asString(3)).toBeNull(); + expect(asString(null)).toBeNull(); + expect(asString(undefined)).toBeNull(); + }); +}); + +describe("parseJsonlEvents", () => { + it("parses every JSON line and degrades gracefully on garbage", () => { + expect(parseJsonlEvents('{"a":1}\n{"b":2}\n')).toEqual([{ a: 1 }, { b: 2 }]); + expect(parseJsonlEvents("not json\n{bad}\n")).toEqual([]); + expect(parseJsonlEvents("")).toEqual([]); + }); +}); + +describe("newestByMtime", () => { + it("picks the path with the greatest mtime from find -printf output", () => { + const out = "1780000000.0\t/a/old.jsonl\n1780277999.9\t/a/new.jsonl\n"; + expect(newestByMtime(out)).toBe("/a/new.jsonl"); + }); + + it("ignores blank and tab-less lines and returns null when empty", () => { + expect(newestByMtime("")).toBeNull(); + expect(newestByMtime("no-tab-here\n")).toBeNull(); + expect(newestByMtime("\n\n")).toBeNull(); + }); +}); diff --git a/src/backend/parsing.ts b/src/backend/parsing.ts new file mode 100644 index 0000000..de8be26 --- /dev/null +++ b/src/backend/parsing.ts @@ -0,0 +1,54 @@ +/** + * Shared parsing primitives for coding backends — the agent-AGNOSTIC scaffolding + * every backend needs to read its CLI's captured bytes. These carry NO agent + * format knowledge (no event schema, no rollout/transcript naming): that stays + * in each backend (§3/§4.9). A backend layers its agent-specific interpretation + * on top (which JSON fields mean what, which glob/regex names a session file). + */ + +/** True for any non-null object (the precondition for safely indexing parsed JSON). */ +export function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null; +} + +/** A value as a string, or null if it isn't one. */ +export function asString(value: unknown): string | null { + return typeof value === "string" ? value : null; +} + +/** Parse a JSONL stream into raw events for log enrichment (skip non-JSON lines). */ +export function parseJsonlEvents(captured: string): unknown[] { + const events: unknown[] = []; + for (const line of captured.split("\n")) { + if (line.trim() === "") { + continue; + } + try { + events.push(JSON.parse(line)); + } catch { + // skip non-JSON progress lines + } + } + return events; +} + +/** From `find … -printf '%T@\t%p\n'` output, the path with the greatest mtime. */ +export function newestByMtime(findOutput: string): string | null { + let bestPath: string | null = null; + let bestMtime = Number.NEGATIVE_INFINITY; + for (const line of findOutput.split("\n")) { + if (line.trim() === "") { + continue; + } + const tab = line.indexOf("\t"); + if (tab < 0) { + continue; + } + const mtime = Number.parseFloat(line.slice(0, tab)); + if (Number.isFinite(mtime) && mtime > bestMtime) { + bestMtime = mtime; + bestPath = line.slice(tab + 1); + } + } + return bestPath; +} From 43a9c47bfd72d0984ac76c32dc5ef3a0bd4f4f0e Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Mon, 1 Jun 2026 10:32:59 -0400 Subject: [PATCH 39/47] fix(router): drop a stray NUL byte from the sandbox-name hash input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hashSandboxName` separated the channel from the thread_ts with a literal NUL (0x00) instead of a space — invisible in editors, and enough to make git classify the whole `.ts` as binary (no line diffs, blame, or textual merges). Replace it with a real space, matching the `${channel} ${threadTs}` separator the idle-sweep/dispatcher reverse-map already uses. No behavior tests pin a hash value; determinism, charset-safety, and the cross-boundary collision guard hold for any separator outside the channel charset, so the suite is unchanged (190 tests green). Co-Authored-By: Claude Opus 4.8 --- src/router/sandbox-name.ts | Bin 3831 -> 3831 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/router/sandbox-name.ts b/src/router/sandbox-name.ts index 801e28f46ddb466cbaf0f35e5d3cfaedeec75e18..00ada672de41781cd194ad32cf072cbe95bafd03 100644 GIT binary patch delta 14 Vcmew^`(1WJI2WVB<_NC&EC4Mb1rq=O delta 14 Vcmew^`(1WJI2R+s<_NC&EC4KF1oHp@ From f0c155bab5dd3faa372de3b8b8a8344f2a8631a0 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Mon, 1 Jun 2026 10:33:07 -0400 Subject: [PATCH 40/47] perf(sandbox): cache $HOME per sandbox to cut ~5 sbx execs per turn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit $HOME is invariant for a sandbox's lifetime, but `AgentStateStore.abs()` re-resolved it via a full `sbx exec` round-trip on every state read/write — about five per turn (readSessionId, the dispatcher's direct call, writeSessionId, and appendDelta's two). Memoize it per sandbox in the provider, the single place that pays the exec, so every caller benefits with no signature or behavior change; the first lookup pays the round-trip and the rest are Map hits. Co-Authored-By: Claude Opus 4.8 --- src/sandbox/sbx-provider.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/sandbox/sbx-provider.ts b/src/sandbox/sbx-provider.ts index f071195..8cacaa5 100644 --- a/src/sandbox/sbx-provider.ts +++ b/src/sandbox/sbx-provider.ts @@ -60,6 +60,8 @@ export class SbxProvider implements SandboxProvider { private readonly createOptions: CreateOptions; private readonly login: boolean; private readonly pty: boolean; + /** Per-sandbox `$HOME` cache — invariant for a sandbox's life (see homeDir). */ + private readonly homeDirCache = new Map(); constructor(config: SbxProviderConfig = {}) { this.spawnFn = config.spawnFn ?? defaultSpawn; @@ -149,11 +151,20 @@ export class SbxProvider implements SandboxProvider { // --- SandboxFsLike (structural) --------------------------------------- async homeDir(handle: SandboxHandle): Promise { + // $HOME is invariant for a sandbox's lifetime, yet each lookup is a full + // `sbx exec` round-trip — and the per-thread state store resolves it on + // every read/write (~5×/turn). Cache it per sandbox so we pay that once. + const cached = this.homeDirCache.get(handle.name); + if (cached !== undefined) { + return cached; + } const { stdout } = await this.execShell(handle, 'printf %s "$HOME"'); // Defensive: take the last line in case sbx ever prefixes a start-up info // line on stdout (it normally routes those to stderr). const lines = stdout.trim().split("\n"); - return (lines[lines.length - 1] ?? "").trim() || "/root"; + const home = (lines[lines.length - 1] ?? "").trim() || "/root"; + this.homeDirCache.set(handle.name, home); + return home; } /** File contents, or null if the file does not exist (non-zero `cat` exit). */ From a121679511aea88257df7a2652d19b7c067c4c14 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 12:59:48 -0400 Subject: [PATCH 41/47] chore(githooks): run the gate on pre-commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a version-controlled pre-commit hook wired via core.hooksPath (set by the `prepare` script on `npm install`, so no extra dependency). It runs the single gate — Biome + tsc + Vitest — before every commit; bypass with `--no-verify`. Co-Authored-By: Claude Opus 4.8 --- .githooks/pre-commit | 5 +++++ package.json | 1 + 2 files changed, 6 insertions(+) create mode 100755 .githooks/pre-commit diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..773830f --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh +# Enforce the single gate — keep the repo green at every commit. +# Runs Biome (lint+format+organize-imports) + tsc --noEmit + Vitest — all offline. +# In a genuine pinch, bypass with `git commit --no-verify`. +exec npm run check diff --git a/package.json b/package.json index c32c10d..bddcc28 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "node": ">=22" }, "scripts": { + "prepare": "git config core.hooksPath .githooks 2>/dev/null || true", "build": "tsc -p tsconfig.build.json", "setup": "tsx src/cli/setup.ts", "dev": "tsx watch src/cli/index.ts", From 82f875e2787205bbc1f37cd4d13bbece79514468 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 12:59:51 -0400 Subject: [PATCH 42/47] docs(agents): trim spec refs and stale sections, document the hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Soften the "outer bot" guidance to out-of-scope-by-default, open to a strong use case (was framed as a prototype mistake the design removed). - Remove the design-spec § section references (the spec isn't committed). - Drop the "Don't relitigate / out of scope for v0" section. - Document the new pre-commit hook under Build / check / test. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 66 +++++++++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 39 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5d296c7..244be30 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,28 +2,30 @@ Mishu — a coding agent that lives in your team's chat (Slack today): mention it in a Slack thread → it does the coding work in an isolated `sbx` microVM running a coding-agent CLI (Codex or Claude Code) → -it reports back **in the same thread**. Section refs (§) point to the design spec. +it reports back **in the same thread**. ## 0. What this is — and is NOT -- The **router is plumbing, not an LLM** (§6). It has **no model calls**, makes **no decisions about +- The **router is plumbing, not an LLM**. It has **no model calls**, makes **no decisions about whether to delegate**, and does **no intent detection**. In particular it does **not** detect "open - a PR" — shipping is the agent's job (`git`/`gh` inside the sandbox, §2.6/§4.6). If you're tempted to - add reasoning, branching on message content, or an "outer bot" to the router: **stop** — that's the - prototype mistake this design removed. + a PR" — shipping is the agent's job (`git`/`gh` inside the sandbox). Adding reasoning, + branching on message content, or an "outer bot" to the router is **out of scope by default** — the + router stays pure plumbing unless a strong, explicit use case justifies otherwise. - Every Slack thread maps 1:1 to one sandbox to one coding-agent session. ## 1. Build / check / test - `npm install` once. **`npm run check` is the single gate**: Biome (lint + format + organize-imports, `--error-on-warnings`) + `tsc --noEmit` + `vitest run`. Run it before every commit; **keep the repo - green at every commit**. + green at every commit**. `npm install` also wires a **pre-commit hook** (`.githooks/pre-commit`, via + the `prepare` script setting `core.hooksPath`) that runs the gate automatically; bypass in a pinch + with `git commit --no-verify`. - `npm run check:fix` applies Biome fixes then runs the gate. `npm test` / `npm run test:watch` / `npm run test:cov` run Vitest. `npm run build` emits `dist/` (excludes `*.test.ts`). `npm run dev` runs the CLI under `tsx watch`. - `npm run test:live` runs the `*.live.test.ts` lane — needs the sbx daemon running (sbx bundles its own runtime; no Docker Desktop), so it's **NOT** part of `check`/CI. Run it locally after any `sbx` - upgrade (§9, substrate risk). + upgrade (substrate risk). ## 2. Module rules (ESM, strict) @@ -32,7 +34,7 @@ it reports back **in the same thread**. Section refs (§) point to the design sp - `strict`, `noUncheckedIndexedAccess`, `noUnusedLocals/Parameters` are on — index access is `T | undefined`; handle it explicitly. -## 3. Three seams + the stateless router (§3) +## 3. Three seams + the stateless router ``` Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM @@ -41,11 +43,11 @@ Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ Sandbo Invariants — do not break these: -- **Transport ≠ interpretation (§4.9).** `SandboxProvider.exec` is an **opaque byte stream** the router +- **Transport ≠ interpretation.** `SandboxProvider.exec` is an **opaque byte stream** the router logs at the boundary. The **only** place that knows an agent's output/CLI format is `CodingBackend` (`src/backend/codex.ts`). **No Codex `item.*`/event-schema or rollout-file knowledge may live outside `backend/codex.ts`.** -- **No central state store (§3/§4.6).** The router runs no DB and keeps no disk state of its own. +- **No central state store.** The router runs no DB and keeps no disk state of its own. Discovery = `SandboxProvider.list()` (`sbx ls`) + deterministic naming. Per-thread durable state (`transcript.jsonl`, `session`, `thread`) lives **in each sandbox** under `~/.agent-state/`. Router memory holds only the transient `idle/running/pending` lock + ts-dedupe set — lost on restart by @@ -55,56 +57,56 @@ Invariants — do not break these: core, never the reverse. New logic ⇒ a pure module + a colocated `*.test.ts`. This is what keeps the system testable offline (no daemon, tokens, or creds in CI). -## 4. Sandbox naming (§4.1) — `src/router/sandbox-name.ts` +## 4. Sandbox naming — `src/router/sandbox-name.ts` - `name = "t-" + channel + "-" + thread_ts` with the `.` encoded as `-`. **The binding constraint - (verified LIVE) is the container HOSTNAME**, which rejects BOTH `_` (so the spec's §4.1 - `replace(".", "_")` is illegal for `--name`) AND `.` (accepted by `--name` but `sbx create` fails + (verified LIVE) is the container HOSTNAME**, which rejects BOTH `_` (so an earlier + `replace(".", "_")` sample is illegal for `--name`) AND `.` (accepted by `--name` but `sbx create` fails "hostname: value must be a valid hostname"). So emit only `[A-Za-z0-9-]` — encode `thread_ts`'s `.` as `-`. A test guards against ever emitting `_` or `.`. - Reversible: `t---` (channel has no `-`; ts is two numeric groups). Over-length names fall back to `t-`; the full id is stored in `~/.agent-state/thread`. -## 5. Boundary logging (§4.9) — `src/router/log.ts` +## 5. Boundary logging — `src/router/log.ts` - Always-on **summaries** (argv + session id + prompt size/hash; exit code + duration + final-message snippet + `ok`) and a **verbose** toggle for raw stdout/stderr + full prompt. `direction ∈ in | out | router`; `turnId` correlates a turn's IN + streamed OUT + exit. - **Best-effort, never blocking** — writes never `await` on the turn's critical path and never throw (a full disk / slow shipper must not stall a turn). **Cap/hash** large prompts and tool-output bursts. -- **Never log credentials** — they're proxy-side and never enter argv/prompt (§4.7). A test asserts no +- **Never log credentials** — they're proxy-side and never enter argv/prompt. A test asserts no credential-shaped field is emitted. -## 6. Router lifecycle (§4.1) — `src/router/router.ts`, `dispatcher.ts` +## 6. Router lifecycle — `src/router/router.ts`, `dispatcher.ts` - **ACK the Socket Mode envelope immediately (<3s) on receipt; run the handler async.** Drop `retry_num > 0`. Dedupe on the trigger `ts` (TTL'd to the ~5-min retry window). - **Acks are reactions, not replies.** 👀 on every mention; swap to ✅/❌ on completion. A *reply* ack - would move the catch-up high-water mark and corrupt the delta if a turn fails before posting (§4.2). + would move the catch-up high-water mark and corrupt the delta if a turn fails before posting. - **Coalescing.** Mid-turn mentions only flip one `pending` bit; one follow-up turn runs whose delta covers them all — **Slack is the queue**, mentions are never buffered in the router. - **The stream reader is part of the dispatched turn and must outlive the dispatch.** "Free the router" means free it for *other* threads — never drop this turn's reader (truncates the log / trips the - broken-pipe panic, §9.15). -- **Write-after-success (§4.6).** Persist the session id BEFORE posting; append the transcript AFTER the + broken-pipe panic). +- **Write-after-success.** Persist the session id BEFORE posting; append the transcript AFTER the reply. An abandoned/crashed turn then re-feeds cleanly (at-least-once). -- Turn-1 vs resume is the **session file's presence** (§4.5); the transcript high-water mark is the - **delta boundary** (§4.2). +- Turn-1 vs resume is the **session file's presence**; the transcript high-water mark is the + **delta boundary**. -## 7. Codex / exec gotchas (§4.5, §9.14, §9.15) — `src/sandbox/sbx-argv.ts`, `backend/codex.ts` +## 7. Codex / exec gotchas — `src/sandbox/sbx-argv.ts`, `backend/codex.ts` - Wrap exec in `bash -c` (`sbx exec` sources no env). **Redirect the agent's stdin from `/dev/null` IN the VM** (` < /dev/null`) — verified live: `sbx exec` keeps the VM process's stdin open even when the host closes its end, so codex otherwise hangs on "Reading additional input from - stdin..." forever (§9.15). Never pass `-i`; **capture stderr** too. **Never `--ephemeral`** (breaks + stdin..." forever. Never pass `-i`; **capture stderr** too. **Never `--ephemeral`** (breaks resume). The `--json` stream's `thread.started` event carries the session id (`thread_id`). -- **Provisioning (§4.3):** with `--clone`, `/home/agent/workspace` is empty under `sbx exec`; the repo +- **Provisioning:** with `--clone`, `/home/agent/workspace` is empty under `sbx exec`; the repo is a read-only mount at `/run/sandbox/source` (a valid git repo) plus a git daemon. A provision step (the dispatcher's `provisionScript`) must clone it into a writable dir, point `origin` at the real remote, and seed a non-base branch before the agent works. - Keep Codex's native sandbox ON: `-c sandbox_mode=workspace-write -c approval_policy=never` (set via `-c` because `resume` lacks `-s`). This is defense-in-depth against prompt injection — Slack content - is untrusted (§5). + is untrusted. - Headless `codex exec` can emit **empty stdout** without a TTY on long prompts — `parseResult` treats empty output as a failed turn, and `sbx-argv` has a PTY wrap option (`script`) to mitigate. Resolve PTY-vs-pin during live verification; the boundary log surfaces "argv → 0 bytes, exit 0". @@ -122,17 +124,3 @@ Invariants — do not break these: - Small, logical, **green at every commit** (`npm run check` passes). Add a runtime dependency only at the seam that first needs it. Work on a branch; don't commit to the default branch directly. End commit messages with the project's `Co-Authored-By` trailer. - -## 10. Don't relitigate (§6) / out of scope for v0 (§7) - -Settled decisions: router-is-plumbing; transport≠interpretation boundary logging; **Codex-first** behind -`CodingBackend` (Claude/Pi later); agent runs as a **CLI inside the sandbox**; **sbx `--clone`** isolation -(host repo read-only; never edit the live working tree); host clone is a **seed, not a sync target**; -services run **in-VM**; eviction = `sbx stop` (auto) and `sbx rm` = terminal; **durability = the PR + the -Slack thread**, no state store; **no per-turn `[checkpoint]` commits**; reactions-not-replies; immediate -ACK + ts-dedupe; repo-scoped GitHub token; session id captured once and persisted; mentions coalesce; -**shipping (push/PR) is the agent's job** — the harness runs no `git`/`gh` and detects no PR intent. - -Out of scope (v0): multi-platform; cloud/multi-tenant sandbox providers; host-`localhost` services; host -user-config passthrough; a local HTTP artifact server; recovery after `sbx rm`; central/persistent router -state; catching up on mentions missed during downtime; **any LLM logic in the router**. From df550ae587130ca5fe7411419dec00e2fb7a16a2 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 12:59:52 -0400 Subject: [PATCH 43/47] =?UTF-8?q?docs:=20remove=20design-spec=20(=C2=A7)?= =?UTF-8?q?=20references=20from=20code=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The design spec won't be committed, so strip its § section citations from comments and test names across the backend, router, sandbox, platform, and cli modules. Comment/string-only — no logic change; `npm run check` stays green. Co-Authored-By: Claude Opus 4.8 --- src/backend/claude.ts | 20 ++++++++-------- src/backend/codex.test.ts | 4 ++-- src/backend/codex.ts | 16 ++++++------- src/backend/fixtures/README.md | 8 +++---- src/backend/index.ts | 12 +++++----- src/backend/parsing.ts | 2 +- src/cli/args.ts | 2 +- src/cli/index.ts | 10 ++++---- src/cli/onboarding.test.ts | 4 ++-- src/cli/onboarding.ts | 10 ++++---- src/cli/setup.ts | 2 +- src/platform/index.ts | 8 +++---- src/platform/slack-map.test.ts | 2 +- src/platform/slack-map.ts | 4 ++-- src/platform/slack.ts | 14 +++++------ src/router/agent-state-store.test.ts | 2 +- src/router/agent-state-store.ts | 14 +++++------ src/router/agent-state.ts | 14 +++++------ src/router/dedupe.ts | 4 ++-- src/router/dispatcher.test.ts | 4 ++-- src/router/dispatcher.ts | 34 +++++++++++++-------------- src/router/idle-sweep.ts | 6 ++--- src/router/index.ts | 4 ++-- src/router/log.test.ts | 4 ++-- src/router/log.ts | 14 +++++------ src/router/router.test.ts | 10 ++++---- src/router/router.ts | 18 +++++++------- src/router/sandbox-name.test.ts | 2 +- src/router/sandbox-name.ts | 15 ++++++------ src/router/state-machine.test.ts | 6 ++--- src/router/state-machine.ts | 14 +++++------ src/sandbox/index.ts | 22 ++++++++--------- src/sandbox/sbx-argv.test.ts | 6 ++--- src/sandbox/sbx-argv.ts | 32 ++++++++++++------------- src/sandbox/sbx-provider.live.test.ts | 6 ++--- src/sandbox/sbx-provider.test.ts | 2 +- src/sandbox/sbx-provider.ts | 12 +++++----- src/types.ts | 8 +++---- vitest.config.ts | 2 +- vitest.live.config.ts | 2 +- 40 files changed, 187 insertions(+), 188 deletions(-) diff --git a/src/backend/claude.ts b/src/backend/claude.ts index df286ca..ae08cf9 100644 --- a/src/backend/claude.ts +++ b/src/backend/claude.ts @@ -3,19 +3,19 @@ import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; /** - * Claude Code backend — the ONLY place Claude Code's CLI/output format lives - * (spec §4.5/§4.9). Everything here is pure (off captured bytes) except + * Claude Code backend — the ONLY place Claude Code's CLI/output format lives. + * Everything here is pure (off captured bytes) except * captureSessionId, whose one shell call is injected so the rest stays * unit-testable. * - * Verified (spec §4.5/§9): `claude -p --resume ` continues the SAME session + * Verified: `claude -p --resume ` continues the SAME session * — id stable across turns, history replayed, surviving sbx stop/restart — so * the router captures the id once on turn 1 and resumes with it, exactly like * codex. * - `claude -p --output-format stream-json --verbose [--resume ID] -- PROMPT` * - `--output-format stream-json` REQUIRES `--verbose` in print mode. - * - The prompt is a positional (not stdin) so the provider can close stdin - * (§9.15); `--` guards a prompt that might start with `-`. + * - The prompt is a positional (not stdin) so the provider can close stdin; + * `--` guards a prompt that might start with `-`. * - The stream's first `{"type":"system","subtype":"init",…}` line carries * `session_id` (parseSessionId reads it live); the final `{"type":"result",…}` * line carries the answer + `is_error`. @@ -28,7 +28,7 @@ export const CLAUDE_PROJECTS_PATH = "$HOME/.claude/projects"; /** * Non-blocking headless — the analog of codex's `approval_policy=never`: never * block on a permission prompt. The sbx microVM is the isolation boundary - * (Slack content is untrusted, §5/§7), so defense-in-depth is the hypervisor, + * (Slack content is untrusted), so defense-in-depth is the hypervisor, * not Claude's in-process permission gate. Isolated here so the choice is one * edit (`--permission-mode bypassPermissions` is an equivalent newer spelling). */ @@ -36,7 +36,7 @@ export const CLAUDE_NONBLOCKING_FLAGS = ["--dangerously-skip-permissions"]; /** * Build the turn invocation. `--` guards a prompt that might start with `-`. - * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). + * Prompt is a positional (not stdin) so the provider can close stdin. * `--verbose` is mandatory with `--output-format stream-json` in print mode. */ export function claudeTurnArgs(message: string, sessionId?: string | null): string[] { @@ -70,7 +70,7 @@ function extractAssistantText(event: Record): string | null { * final answer is the `{"type":"result", result, is_error, …}` event; the last * assistant text is a fallback if a run ends without one. Empty output -> * {"", false}: the headless empty-output failure mode surfaces as a failed turn, - * not a silent empty reply (parity with codex §9.14). On `is_error` the error + * not a silent empty reply (parity with codex). On `is_error` the error * text (or the `subtype`, e.g. "error_max_turns") is relayed, still with ok=false. */ export function parseClaudeResult(captured: string): TurnResult { @@ -143,7 +143,7 @@ export function parseClaudeSessionId(captured: string): string | null { const SESSION_UUID_RE = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i; -/** The session UUID from a `.jsonl` transcript filename (§4.5). */ +/** The session UUID from a `.jsonl` transcript filename. */ export function parseClaudeSessionFilename(filename: string): string | null { const match = SESSION_UUID_RE.exec(filename); return match?.[1] ?? null; @@ -178,7 +178,7 @@ export class ClaudeBackend implements CodingBackend { const { stdout } = await this.executor.execShell(handle, command); const newest = newestByMtime(stdout); if (newest === null) { - throw new Error("claude: no session transcript found to capture the session id (§4.5)"); + throw new Error("claude: no session transcript found to capture the session id"); } const filename = newest.slice(newest.lastIndexOf("/") + 1); const id = parseClaudeSessionFilename(filename); diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts index 2e28b5e..2c4558f 100644 --- a/src/backend/codex.test.ts +++ b/src/backend/codex.test.ts @@ -35,7 +35,7 @@ describe("codexTurnArgs", () => { expect(argv).not.toContain("-i"); }); - it("keeps the native sandbox on and approvals non-blocking (§4.5)", () => { + it("keeps the native sandbox on and approvals non-blocking", () => { const argv = codexTurnArgs("x"); expect(argv.join(" ")).toContain("sandbox_mode=workspace-write"); expect(argv.join(" ")).toContain("approval_policy=never"); @@ -69,7 +69,7 @@ describe("parseCodexResult", () => { }); }); - it("flags empty output as a failed turn (§9.14 headless regression)", () => { + it("flags empty output as a failed turn (headless regression)", () => { expect(parseCodexResult(emptyStream)).toEqual({ finalText: "", ok: false }); expect(parseCodexResult("")).toEqual({ finalText: "", ok: false }); }); diff --git a/src/backend/codex.ts b/src/backend/codex.ts index dd088e1..af68e37 100644 --- a/src/backend/codex.ts +++ b/src/backend/codex.ts @@ -3,7 +3,7 @@ import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; /** - * Codex backend — the ONLY place Codex's CLI/output format lives (spec §4.5/§4.9). + * Codex backend — the ONLY place Codex's CLI/output format lives. * Everything here is pure (off captured bytes) except captureSessionId, whose * one shell call is injected so the rest stays unit-testable. * @@ -15,7 +15,7 @@ import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.j * - NEVER `--ephemeral` (breaks resume). The `--json` stream's `thread.started` * event carries `thread_id` (= the session id; parseSessionId reads it live). * - codex blocks on stdin headlessly; the provider redirects its stdin from - * /dev/null IN the VM (§9.15) — verified live. + * /dev/null IN the VM — verified live. */ export const CODEX_HOME = "~/.codex"; @@ -23,9 +23,9 @@ export const CODEX_HOME = "~/.codex"; export const CODEX_SESSIONS_PATH = "$HOME/.codex/sessions"; /** - * Non-blocking defense-in-depth (§4.5/§9.8): keep Codex's native sandbox ON + * Non-blocking defense-in-depth: keep Codex's native sandbox ON * (workspace-write) but never block headless on an approval prompt. Set via `-c` - * because `resume` lacks `-s`. Exact keys confirmed live (§9.8). + * because `resume` lacks `-s`. Exact keys confirmed live. */ export const CODEX_NONBLOCKING_FLAGS = [ "-c", @@ -36,7 +36,7 @@ export const CODEX_NONBLOCKING_FLAGS = [ /** * Build the turn invocation. `--` guards a prompt/id that might start with `-`. - * Prompt is a positional (not stdin) so the provider can close stdin (§9.15). + * Prompt is a positional (not stdin) so the provider can close stdin. */ export function codexTurnArgs(message: string, sessionId?: string | null): string[] { // --skip-git-repo-check: never block headless on codex's trusted-directory @@ -95,7 +95,7 @@ function extractErrorMessage(event: Record): string | null { /** * Interpret the captured `--json` stream into the result to relay. Tolerant of * several event shapes (the schema moves between versions) and of non-JSON - * progress lines. Empty output -> {"", false}: the §9.14 headless regression + * progress lines. Empty output -> {"", false}: the headless regression * (argv -> 0 bytes, exit 0) surfaces as a failed turn, not a silent empty reply. * On a failed turn the codex error message is relayed (e.g. a usage limit), * still with ok=false. @@ -165,7 +165,7 @@ export function parseSessionId(captured: string): string | null { const ROLLOUT_UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i; -/** The session UUID embedded in a `rollout-…-.jsonl` filename (§4.5). */ +/** The session UUID embedded in a `rollout-…-.jsonl` filename. */ export function parseRolloutId(filename: string): string | null { const match = ROLLOUT_UUID_RE.exec(filename); return match?.[1] ?? null; @@ -200,7 +200,7 @@ export class CodexBackend implements CodingBackend { const { stdout } = await this.executor.execShell(handle, command); const newest = newestByMtime(stdout); if (newest === null) { - throw new Error("codex: no rollout file found to capture the session id (§4.5)"); + throw new Error("codex: no rollout file found to capture the session id"); } const filename = newest.slice(newest.lastIndexOf("/") + 1); const id = parseRolloutId(filename); diff --git a/src/backend/fixtures/README.md b/src/backend/fixtures/README.md index aa426bb..66b49af 100644 --- a/src/backend/fixtures/README.md +++ b/src/backend/fixtures/README.md @@ -12,13 +12,13 @@ Captured live against codex 0.130.0 in an sbx microVM (2026-05-31). The real is tolerant of several shapes so it survives version drift. - `codex-error.jsonl` — **REAL bytes**: a `thread.started` + `turn.started` + - `error` + `turn.failed` stream (the run hit the subscription usage limit, §9.7). + `error` + `turn.failed` stream (the run hit the subscription usage limit). Confirms `parseSessionId` reads `thread_id` and the error message is relayed. - `codex-json-stream.jsonl` — a **successful** fresh turn (assistant message). Still - a documented placeholder: the live success path was **quota-blocked** (§9.7); + a documented placeholder: the live success path was **quota-blocked**; recapture with an OpenAI API key or after the limit resets. The parser already handles the `item.completed`/`agent_message`/`last_agent_message` shapes. -- `codex-empty.stdout.txt` — the headless empty-output regression: 0 bytes, exit 0 (§9.14). +- `codex-empty.stdout.txt` — the headless empty-output regression: 0 bytes, exit 0. - `codex-rollout-filename.txt` — a sample `~/.codex/sessions/.../rollout-*.jsonl` filename. - `codex-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-rollout selection. @@ -57,7 +57,7 @@ Fixtures (all REAL captured bytes unless noted): - `claude-error.jsonl` — **REAL bytes**: a fresh turn that reached the API and failed auth (`is_error:true`, `result:"Not logged in · Please run /login"`, `subtype:"success"`). The session is still created (the init line), so `parseClaudeSessionId` still works and the message is relayed. -- `claude-empty.stdout.txt` — the headless empty-output failure mode: 0 bytes (parity with codex §9.14). +- `claude-empty.stdout.txt` — the headless empty-output failure mode: 0 bytes (parity with codex). - `claude-session-filename.txt` — a sample `.jsonl` transcript basename. - `claude-find-output.txt` — sample `find … -printf '%T@\t%p\n'` output for newest-session selection (real format/paths; two lines to exercise the mtime tiebreak). diff --git a/src/backend/index.ts b/src/backend/index.ts index bc972ac..08a1262 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -2,12 +2,12 @@ import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; /** * CodingBackend — per-agent knowledge: the ONLY place agent-format details - * live. See spec §3, §4.9. Transport (SandboxProvider.exec) is router-owned + * live. Transport (SandboxProvider.exec) is router-owned * and format-agnostic; only turnArgs / parseResult / events know the agent's * format. * * v0: Codex. Then Claude Code, then Pi (the `pi` CLI — a third-party binary - * run inside the sandbox, not a code dependency; see spec §10). + * run inside the sandbox, not a code dependency). */ export interface TurnResult { finalText: string; @@ -18,20 +18,20 @@ export interface CodingBackend { /** e.g. ~/.codex, ~/.claude, ~/.pi/agent */ configHome(): string; - /** Build the CLI invocation. sessionId omitted → fresh session, else resume (§4.5). */ + /** Build the CLI invocation. sessionId omitted → fresh session, else resume. */ turnArgs(message: string, sessionId?: string): string[]; - /** Interpret the raw captured stream into the result to relay (agent-specific, §4.9). */ + /** Interpret the raw captured stream into the result to relay (agent-specific). */ parseResult(captured: string): TurnResult; - /** After a fresh turn, read the new session's id (Codex: newest rollout filename) (§4.5). */ + /** After a fresh turn, read the new session's id (Codex: newest rollout filename). */ captureSessionId(handle: SandboxHandle): Promise; /** * OPTIONAL: extract the new session's id straight from the captured turn-1 * stream (e.g. Codex prints it as a thread/session event) — pure and cheaper * than {@link captureSessionId}. The dispatcher tries this first and falls - * back to captureSessionId (§4.5). + * back to captureSessionId. */ parseSessionId?(captured: string): string | null; diff --git a/src/backend/parsing.ts b/src/backend/parsing.ts index de8be26..fb58b35 100644 --- a/src/backend/parsing.ts +++ b/src/backend/parsing.ts @@ -2,7 +2,7 @@ * Shared parsing primitives for coding backends — the agent-AGNOSTIC scaffolding * every backend needs to read its CLI's captured bytes. These carry NO agent * format knowledge (no event schema, no rollout/transcript naming): that stays - * in each backend (§3/§4.9). A backend layers its agent-specific interpretation + * in each backend. A backend layers its agent-specific interpretation * on top (which JSON fields mean what, which glob/regex names a session file). */ diff --git a/src/cli/args.ts b/src/cli/args.ts index 5782a1c..bcab01d 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -1,5 +1,5 @@ /** - * CLI argument parsing (pure). Shape mirrors pi-mom's convention (spec §10): + * CLI argument parsing (pure). Shape mirrors pi-mom's convention: * * mishu --sandbox= * e.g. mishu --sandbox=sbx ./data diff --git a/src/cli/index.ts b/src/cli/index.ts index cd4b4d9..4cb79a9 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * Composition root (spec §3, §4.8). Wires the three seams + Router behind the + * Composition root. Wires the three seams + Router behind the * onboarding gate, then starts the Socket Mode ingress: * * mishu --sandbox=sbx ./data @@ -20,7 +20,7 @@ import { SbxProvider } from "../sandbox/sbx-provider.js"; import { type Args, parseArgs, USAGE } from "./args.js"; import { type Agent, ensureOnboarded, parseAgent } from "./onboarding.js"; -/** Boundary-log sink: the router's stdout + an append-only JSONL file (§4.9). */ +/** Boundary-log sink: the router's stdout + an append-only JSONL file. */ function fileLogSink(dataDir: string): LogSink { mkdirSync(dataDir, { recursive: true }); const stream = createWriteStream(join(dataDir, "router.log"), { flags: "a" }); @@ -49,7 +49,7 @@ async function run(args: Args): Promise { const provider = new SbxProvider({ createOptions: { agent } }); - // Onboarding gate (§4.8): require the agent's credential before serving. + // Onboarding gate: require the agent's credential before serving. const onboarded = await ensureOnboarded(agent, { secretLs: () => provider.secretLs(), log: (message) => console.error(message), @@ -78,8 +78,8 @@ async function run(args: Args): Promise { }); const router = new Router(platform, dispatcher, { logSink }); - // Idle eviction: sbx stop (lossless) sandboxes whose threads have gone quiet - // (§4.6). Sweep hourly; the router never auto-`rm`s. + // Idle eviction: sbx stop (lossless) sandboxes whose threads have gone quiet. + // Sweep hourly; the router never auto-`rm`s. new IdleSweeper({ sandbox: provider, platform, logSink }).start(60 * 60 * 1000); router.start(); diff --git a/src/cli/onboarding.test.ts b/src/cli/onboarding.test.ts index 84d2e4c..1281048 100644 --- a/src/cli/onboarding.test.ts +++ b/src/cli/onboarding.test.ts @@ -10,7 +10,7 @@ import { } from "./onboarding.js"; describe("credentialService", () => { - it("maps agents to their sbx secret service (§4.7)", () => { + it("maps agents to their sbx secret service", () => { expect(credentialService("codex")).toBe("openai"); expect(credentialService("claude")).toBe("anthropic"); }); @@ -41,7 +41,7 @@ describe("detectMissingCredential", () => { }); describe("onboardingInstructions", () => { - it("guides Codex through the OAuth path the user chose (§4.8)", () => { + it("guides Codex through the OAuth path the user chose", () => { const text = onboardingInstructions("codex"); expect(text).toContain("npm run setup"); expect(text).toContain("sbx secret set -g openai --oauth"); diff --git a/src/cli/onboarding.ts b/src/cli/onboarding.ts index 47e2b6e..1d7497d 100644 --- a/src/cli/onboarding.ts +++ b/src/cli/onboarding.ts @@ -1,5 +1,5 @@ /** - * Router-guided onboarding (spec §4.8). The router gates first use on the + * Router-guided onboarding. The router gates first use on the * coding agent's credential being present in sbx, walks the human through the * one-time setup, and then runs unattended forever after. * @@ -14,7 +14,7 @@ export function parseAgent(value: string | null | undefined): Agent | null { return value === "codex" || value === "claude" ? value : null; } -/** The sbx secret service that backs each agent's auth (§4.7). */ +/** The sbx secret service that backs each agent's auth. */ export function credentialService(agent: Agent): string { return agent === "codex" ? "openai" : "anthropic"; } @@ -24,7 +24,7 @@ export const CLAUDE_LOGIN_SANDBOX = "mishu-login"; /** * The interactive `sbx` command (argv after the `sbx` bin) that establishes the - * agent's credential — what `npm run setup` runs (§4.8). Codex has a one-shot + * agent's credential — what `npm run setup` runs. Codex has a one-shot * OAuth command; Claude has no `anthropic --oauth`, so it logs in inside a named * throwaway sandbox (`sbx run --name mishu-login claude` → /login) and sbx * captures the credential host-side; setup removes the sandbox afterward. @@ -61,7 +61,7 @@ export function onboardingInstructions(agent: Agent): string { " 2. Finish the browser sign-in.", ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, "", - "(The credential is host-side and proxy-injected — it never enters a sandbox, §4.7.)", + "(The credential is host-side and proxy-injected — it never enters a sandbox)", ].join("\n"); } return [ @@ -75,7 +75,7 @@ export function onboardingInstructions(agent: Agent): string { ` 3. Re-run once 'sbx secret ls' shows '${service} (oauth configured)'.`, ` 4. (optional) sbx rm --force ${CLAUDE_LOGIN_SANDBOX} # the login sandbox is throwaway`, "", - "(The credential is host-side and proxy-injected — it never enters a sandbox, §4.7.", + "(The credential is host-side and proxy-injected — it never enters a sandbox.", " There's no 'anthropic --oauth' — login happens inside the sandbox above.)", ].join("\n"); } diff --git a/src/cli/setup.ts b/src/cli/setup.ts index e1f49bf..58e8a2a 100644 --- a/src/cli/setup.ts +++ b/src/cli/setup.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * `npm run setup` — one-command onboarding (spec §4.8). Checks the selected + * `npm run setup` — one-command onboarding. Checks the selected * agent's sbx credential and, if it's missing, runs the interactive auth flow * (browser OAuth for Codex; `sbx run claude` → /login for Claude) with the * terminal attached, then confirms. Idempotent: re-running once configured just diff --git a/src/platform/index.ts b/src/platform/index.ts index b560f74..e1d0c83 100644 --- a/src/platform/index.ts +++ b/src/platform/index.ts @@ -1,12 +1,12 @@ import type { Mention, ThreadId, ThreadMessage } from "../types.js"; /** - * PlatformAdapter — ingress/egress of human conversation. See spec §3, §4.1. + * PlatformAdapter — ingress/egress of human conversation. * * v0: Slack via Socket Mode. The implementation MUST acknowledge the Socket * Mode envelope immediately (<3s) and invoke the handler asynchronously — it - * must never block the ACK on sandbox/turn work (spec §4.1). Acks to the user - * are reactions, not replies (§4.1/§4.2). + * must never block the ACK on sandbox/turn work. Acks to the user + * are reactions, not replies. * * Later platforms (Discord, Teams, Linear comments) implement the same shape. */ @@ -14,7 +14,7 @@ export interface PlatformAdapter { /** Register the mention handler. The adapter ACKs the transport, then calls this. */ onMention(handler: (mention: Mention) => void): void; - /** Whole thread (sinceTs omitted) or only messages after sinceTs — the delta (§4.2). */ + /** Whole thread (sinceTs omitted) or only messages after sinceTs — the delta. */ fetchThread(thread: ThreadId, sinceTs?: string): Promise; postReply(thread: ThreadId, text: string): Promise; diff --git a/src/platform/slack-map.test.ts b/src/platform/slack-map.test.ts index 81c7992..7227fd9 100644 --- a/src/platform/slack-map.test.ts +++ b/src/platform/slack-map.test.ts @@ -14,7 +14,7 @@ const base: SlackAppMentionEvent = { }; describe("mentionFromEvent", () => { - it("a root mention (no thread_ts) uses its own ts as the thread id (§4.1)", () => { + it("a root mention (no thread_ts) uses its own ts as the thread id", () => { expect(mentionFromEvent(base)).toEqual({ thread: { channel: "C0ABCDEF", threadTs: "1748600000.500000" }, ts: "1748600000.500000", diff --git a/src/platform/slack-map.ts b/src/platform/slack-map.ts index b67fc86..d97c33f 100644 --- a/src/platform/slack-map.ts +++ b/src/platform/slack-map.ts @@ -1,7 +1,7 @@ import type { Mention, ThreadMessage } from "../types.js"; /** - * Pure Slack-event mapping (spec §4.1) — the subtle bits that hold the bugs, + * Pure Slack-event mapping — the subtle bits that hold the bugs, * isolated from the Socket Mode / Web API I/O so they're unit-testable: * - a root mention uses its own `ts` as the thread id (it opens the thread); * a reply uses the parent `thread_ts`; @@ -34,7 +34,7 @@ export function mentionFromEvent(event: SlackAppMentionEvent, botUserId?: string if (botUserId !== undefined && event.user === botUserId) { return null; // our own post } - // A root mention has no thread_ts; its own ts opens the thread (§4.1). + // A root mention has no thread_ts; its own ts opens the thread. const threadTs = event.thread_ts ?? event.ts; return { thread: { channel: event.channel, threadTs }, diff --git a/src/platform/slack.ts b/src/platform/slack.ts index d47bf7b..fa56cdc 100644 --- a/src/platform/slack.ts +++ b/src/platform/slack.ts @@ -10,13 +10,13 @@ import { } from "./slack-map.js"; /** - * Slack PlatformAdapter via Socket Mode + Web API (spec §4.1) — the I/O shell. + * Slack PlatformAdapter via Socket Mode + Web API — the I/O shell. * The mapping logic lives in slack-map.ts; this only wires the SDKs. * - * Discipline (§4.1): the Socket Mode envelope is ACKed immediately on receipt, + * Discipline: the Socket Mode envelope is ACKed immediately on receipt, * BEFORE any work, and the handler runs async to the ACK; retries * (retry_num > 0) are dropped so a slow turn can't trigger double-dispatch. - * Acks to the user are reactions, not replies (§4.1/§4.2). + * Acks to the user are reactions, not replies. */ export interface SlackAdapterConfig { @@ -56,7 +56,7 @@ export class SlackAdapter implements PlatformAdapter { this.handler = handler; } - /** Resolve (and cache) the bot's own user id via auth.test (§4.2 self-filtering). */ + /** Resolve (and cache) the bot's own user id via auth.test (self-filtering). */ async whoAmI(): Promise { if (this.botUserId === undefined) { const auth = await this.web.auth.test(); @@ -69,10 +69,10 @@ export class SlackAdapter implements PlatformAdapter { async start(): Promise { await this.whoAmI(); this.socket.on("app_mention", (args: SocketModeEventArgs) => { - // ACK at the WebSocket layer on receipt — never block the ~3s deadline (§4.1). + // ACK at the WebSocket layer on receipt — never block the ~3s deadline. void args.ack(); if (typeof args.retry_num === "number" && args.retry_num > 0) { - return; // a retry of an event we already accepted (§4.1) + return; // a retry of an event we already accepted } const mention = mentionFromEvent(args.event, this.botUserId); if (mention !== null && this.handler !== null) { @@ -121,7 +121,7 @@ export class SlackAdapter implements PlatformAdapter { await this.web.reactions.add({ channel, timestamp: ts, name: emoji }); } catch (err) { if (slackErrorCode(err) !== "already_reacted") { - throw err; // idempotent: re-adding our own 👀 is fine (§4.1) + throw err; // idempotent: re-adding our own 👀 is fine } } } diff --git a/src/router/agent-state-store.test.ts b/src/router/agent-state-store.test.ts index 0696755..8ca33dc 100644 --- a/src/router/agent-state-store.test.ts +++ b/src/router/agent-state-store.test.ts @@ -65,7 +65,7 @@ describe("AgentStateStore — session id", () => { }); describe("AgentStateStore — thread reverse map", () => { - it("round-trips the original channel+thread_ts (hash-name fallback, §4.1)", async () => { + it("round-trips the original channel+thread_ts (hash-name fallback)", async () => { const store = new AgentStateStore(new FakeSandboxFs(), handle); expect(await store.readThread()).toBeNull(); await store.writeThread("C0ABCDEF 1748600000.123456"); diff --git a/src/router/agent-state-store.ts b/src/router/agent-state-store.ts index f26f236..72531f2 100644 --- a/src/router/agent-state-store.ts +++ b/src/router/agent-state-store.ts @@ -9,16 +9,16 @@ import { } from "./agent-state.js"; /** - * I/O layer of the per-thread durable state (spec §4.6). Reads/writes the tiny + * I/O layer of the per-thread durable state. Reads/writes the tiny * `~/.agent-state/` dir that lives inside each thread's sandbox — the router * keeps no state of its own. A single writer per file is guaranteed by - * per-thread serialization (§4.1/§4.2). + * per-thread serialization. * * Depends on a narrow sandbox-fs capability (not the full provider) so it fakes * trivially in tests; the real SbxProvider implements it via `sbx exec`/`cp`. */ export interface SandboxFsLike { - /** The sandbox user's `$HOME` (paths under it persist with the VM, §4.6). */ + /** The sandbox user's `$HOME` (paths under it persist with the VM). */ homeDir(handle: SandboxHandle): Promise; /** File contents, or null if the file does not exist. */ readFile(handle: SandboxHandle, absPath: string): Promise; @@ -36,14 +36,14 @@ export class AgentStateStore { return `${await this.fs.homeDir(this.handle)}/${relPath}`; } - /** The seen-message ledger (empty on turn 1, §4.2). */ + /** The seen-message ledger (empty on turn 1). */ async readTranscript(): Promise { const text = await this.fs.readFile(this.handle, await this.abs(TRANSCRIPT_PATH)); return text === null ? [] : parseTranscript(text); } /** - * Append the delta after a successful turn (write-after-success, §4.6) so an + * Append the delta after a successful turn (write-after-success) so an * abandoned turn re-feeds cleanly. Read-modify-write of the whole file — the * ledger is small and has a single writer. */ @@ -59,7 +59,7 @@ export class AgentStateStore { ); } - /** The coding-agent session id captured on turn 1 (§4.5), or null if unset. */ + /** The coding-agent session id captured on turn 1, or null if unset. */ async readSessionId(): Promise { const text = await this.fs.readFile(this.handle, await this.abs(SESSION_PATH)); const trimmed = text?.trim() ?? ""; @@ -70,7 +70,7 @@ export class AgentStateStore { await this.fs.writeFile(this.handle, await this.abs(SESSION_PATH), `${id}\n`); } - /** Original channel+thread_ts, for reverse lookup of a hash-named sandbox (§4.1). */ + /** Original channel+thread_ts, for reverse lookup of a hash-named sandbox. */ async readThread(): Promise { const text = await this.fs.readFile(this.handle, await this.abs(THREAD_PATH)); const trimmed = text?.trim() ?? ""; diff --git a/src/router/agent-state.ts b/src/router/agent-state.ts index 7db64c8..aeca089 100644 --- a/src/router/agent-state.ts +++ b/src/router/agent-state.ts @@ -1,13 +1,13 @@ import type { ThreadMessage } from "../types.js"; /** - * Per-thread durable state — pure layer (spec §4.2/§4.6). + * Per-thread durable state — pure layer. * * The thread's seen-message ledger is `~/.agent-state/transcript.jsonl` in the * sandbox: one JSON line per message the agent has been fed, `{ts, user, text}`. * It is both the **high-water mark** (largest `ts`) and a durable audit of * exactly what the agent saw. This file holds only the *pure* logic over message - * arrays; the sbx-backed read/write store lives in agent-state.store.ts (§4.6). + * arrays; the sbx-backed read/write store lives in agent-state.store.ts. * * Paths are relative to the sandbox user's `$HOME` (resolved by the store). */ @@ -18,7 +18,7 @@ export const THREAD_PATH = `${AGENT_STATE_DIR}/thread`; /** * Largest `ts` in the ledger, or `null` when empty (which is how the router - * knows it's turn 1 — §4.2). + * knows it's turn 1). * * Slack `ts` is `"<10-digit secs>.<6-digit micros>"`, zero-padded, so plain * string comparison is chronological (verified by test). This holds until the @@ -41,15 +41,15 @@ export interface DeltaOptions { /** * The messages to feed this turn: everything after the high-water mark, minus - * the bot's own posts (§4.2). + * the bot's own posts. * * - Turn 1 (`hwm === null`): the whole thread minus bot posts — the primer. * - Follow-up: messages with `ts > hwm` minus bot posts — the exact delta. * - * Note on §4.2's "minus the trigger": the *previous* turn's trigger is already + * On "minus the trigger": the *previous* turn's trigger is already * excluded by `ts > hwm` (it's in the ledger). The *current* trigger carries the - * user's new request and MUST be included — §4.1 says "the delta covers all of - * them [the mentions]". So the only author we drop is the bot itself. + * user's new request and MUST be included — the delta covers all of + * them [the mentions]. So the only author we drop is the bot itself. */ export function computeDelta( fetched: ThreadMessage[], diff --git a/src/router/dedupe.ts b/src/router/dedupe.ts index 09d9068..9231e7c 100644 --- a/src/router/dedupe.ts +++ b/src/router/dedupe.ts @@ -1,12 +1,12 @@ /** - * Idempotent-dispatch dedupe (spec §4.1). + * Idempotent-dispatch dedupe. * * Immediate Socket Mode ACK stops the retry storm but duplicates still arrive (a * lost ACK inside the retry window, or a reconnect race). The router dedupes on * the mention's Slack `ts`: a `ts` already accepted is dropped. * * This is a TTL set keyed by `ts`, sized to cover Slack's ~5-min retry window. - * It lives in router memory (transient bucket, §3) and is lost on restart, which + * It lives in router memory (transient bucket) and is lost on restart, which * is fine — retries don't outlive that window. The clock is injected so the TTL * is testable without sleeping. */ diff --git a/src/router/dispatcher.test.ts b/src/router/dispatcher.test.ts index 9cebd11..9d6a336 100644 --- a/src/router/dispatcher.test.ts +++ b/src/router/dispatcher.test.ts @@ -190,13 +190,13 @@ describe("dispatchTurn — turn 1 happy path", () => { expect(at(`create:${SANDBOX}`)).toBeGreaterThanOrEqual(0); expect(at("fetch:all")).toBeGreaterThan(at(`create:${SANDBOX}`)); // whole-thread primer expect(at("exec")).toBeGreaterThan(at("fetch:all")); - // session persisted BEFORE the reply; transcript appended AFTER it (§4.6) + // session persisted BEFORE the reply; transcript appended AFTER it expect(at("write:session")).toBeGreaterThan(at("exec")); expect(at("write:session")).toBeLessThan(at("reply")); expect(at("write:transcript.jsonl")).toBeGreaterThan(at("reply")); // 👀 -> ✅ swap is last expect(t.slice(-2)).toEqual(["-eyes", "+white_check_mark"]); - // provisioned the repo and ran the agent IN it (§4.3) + // provisioned the repo and ran the agent IN it expect(t).toContain("shell:test"); // the `test -d …/.git || git clone …` provision expect(b.sandbox.lastCwd).toBe("/home/agent/repo"); }); diff --git a/src/router/dispatcher.ts b/src/router/dispatcher.ts index e344eca..13af8ae 100644 --- a/src/router/dispatcher.ts +++ b/src/router/dispatcher.ts @@ -20,13 +20,13 @@ import { import { chooseSandboxName } from "./sandbox-name.js"; /** - * The one-turn lifecycle (spec §4.1/§4.5/§4.9) — the keystone that ties the + * The one-turn lifecycle — the keystone that ties the * three seams together for a single turn. Order matters: * * resolve-or-create sandbox (deterministic name) → turn-1-vs-resume (session - * file, §4.5) → fetch primer/delta (§4.2) → exec (reader drains stdout+stderr, - * stays attached, logs at the boundary §4.9) → parseResult → write-after-success - * (persist session BEFORE posting, append transcript AFTER the reply, §4.6) → + * file) → fetch primer/delta → exec (reader drains stdout+stderr, + * stays attached, logs at the boundary) → parseResult → write-after-success + * (persist session BEFORE posting, append transcript AFTER the reply) → * relay → swap 👀→✅/❌. * * Reactions: the router adds 👀 on every mention; the dispatcher swaps the @@ -41,11 +41,11 @@ export interface DispatcherDeps { backend: CodingBackend; repoRef: string; logSink?: LogSink; - /** The bot's own Slack user id; its posts never re-enter the delta (§4.2). */ + /** The bot's own Slack user id; its posts never re-enter the delta. */ botUser?: string; level?: LogLevel; failureMessage?: string; - /** Optional in-VM bootstrap run once after create (branch off base, §4.3; confirm live). */ + /** Optional in-VM bootstrap run once after create (branch off base; confirm live). */ provisionScript?: string; now?: () => number; newTurnId?: () => string; @@ -54,11 +54,11 @@ export interface DispatcherDeps { const DEFAULT_FAILURE = "⚠️ The coding agent didn't return a result for that turn. Your request is still queued — mention me again to retry."; -/** sbx --clone exposes the read-only source repo here (verified live; §9.13). */ +/** sbx --clone exposes the read-only source repo here (verified live). */ const SBX_CLONE_SOURCE = "/run/sandbox/source"; /** In-VM writable clone dir the agent works in (under $HOME, outside ~/.agent-state). */ const IN_VM_REPO_DIR = "repo"; -/** Non-base branch the agent's work starts on (§4.3). */ +/** Non-base branch the agent's work starts on. */ const WORK_BRANCH = "slack/work"; /** Render messages into a single prompt. Plumbing, not authorship — no editorializing. */ @@ -107,7 +107,7 @@ export class Dispatcher { const handle = await this.resolveSandbox(thread, name, ctx); const store = new AgentStateStore(this.sandbox, handle); - // Turn-1 vs resume is the session file's presence (§4.5). + // Turn-1 vs resume is the session file's presence. const sessionId = await store.readSessionId(); const delta = await this.assembleDelta(thread, store, sessionId); if (delta.length === 0) { @@ -119,7 +119,7 @@ export class Dispatcher { const prompt = formatPrompt(delta); const argv = this.backend.turnArgs(prompt, sessionId ?? undefined); - // Provision a writable repo clone and run the agent IN it (§4.3) — verified + // Provision a writable repo clone and run the agent IN it — verified // live: without this codex exits "Not inside a trusted directory". const home = await this.sandbox.homeDir(handle); const repoPath = `${home}/${IN_VM_REPO_DIR}`; @@ -162,14 +162,14 @@ export class Dispatcher { }); // Relay the agent's own error (e.g. a usage limit) when it gave one, // else a generic message. Either way the transcript is NOT appended, so - // the request re-feeds on the next mention (§4.6). + // the request re-feeds on the next mention. const reply = result.finalText.trim() !== "" ? result.finalText : this.failureMessage; await this.relay(thread, reply); await this.swapReaction(trigger, false); return { ok: false }; } - // Write-after-success (§4.6): persist the session id BEFORE posting (so a + // Write-after-success: persist the session id BEFORE posting (so a // crash still resumes), append the transcript AFTER the reply (so a failed // post re-feeds — at-least-once). if (sessionId === null) { @@ -189,7 +189,7 @@ export class Dispatcher { } } - /** Find the thread's sandbox by deterministic name, or create + seed it (§4.1/§4.3). */ + /** Find the thread's sandbox by deterministic name, or create + seed it. */ private async resolveSandbox( thread: ThreadId, name: string, @@ -201,7 +201,7 @@ export class Dispatcher { } this.router(ctx, RouterKind.SandboxCreate, { name }); const handle = await this.sandbox.create(name, this.repoRef); - // Reverse-map the full id so a hash-named sandbox is reversible (§4.1/§4.6). + // Reverse-map the full id so a hash-named sandbox is reversible. await new AgentStateStore(this.sandbox, handle).writeThread( `${thread.channel} ${thread.threadTs}`, ); @@ -213,8 +213,8 @@ export class Dispatcher { /** * Clone the read-only `--clone` source into a writable repo dir and seed a - * non-base branch (§4.3). Idempotent — runs cheaply each turn, clones once. - * The agent later points origin at the real remote + pushes when asked (§4.6). + * non-base branch. Idempotent — runs cheaply each turn, clones once. + * The agent later points origin at the real remote + pushes when asked. */ private async provisionRepo( handle: SandboxHandle, @@ -250,7 +250,7 @@ export class Dispatcher { await this.platform.postReply(thread, text); } - /** Best-effort 👀→✅/❌ swap on the trigger (reactions never fail a turn, §4.1). */ + /** Best-effort 👀→✅/❌ swap on the trigger (reactions never fail a turn). */ private async swapReaction(trigger: Mention, ok: boolean): Promise { try { await this.platform.removeReaction(trigger.thread.channel, trigger.ts, "eyes"); diff --git a/src/router/idle-sweep.ts b/src/router/idle-sweep.ts index 1d03ddb..3884cf5 100644 --- a/src/router/idle-sweep.ts +++ b/src/router/idle-sweep.ts @@ -14,15 +14,15 @@ import { import { isHashName, parseSandboxName } from "./sandbox-name.js"; /** - * Idle eviction without stored timestamps (spec §4.6): periodically `sbx ls` the - * running sandboxes, reverse each name back to its thread (§4.1), read the last + * Idle eviction without stored timestamps: periodically `sbx ls` the + * running sandboxes, reverse each name back to its thread, read the last * message time from Slack, and `sbx stop` the idle ones. `stop` is lossless and * recoverable, so coarse timing is harmless; the router NEVER auto-`rm`s. * * One Slack call per running sandbox per sweep — fine at v0 scale. */ -/** sbx's documented idle-eviction window (§4.4). */ +/** sbx's documented idle-eviction window. */ export const DEFAULT_IDLE_MS = 24 * 60 * 60 * 1000; /** Slack ts ("secs.micros") → epoch ms. */ diff --git a/src/router/index.ts b/src/router/index.ts index 8c9b25c..b184ebb 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,6 @@ /** - * Router barrel — the plumbing that maps Slack threads to coding-agent turns - * (spec §3, §4.1). The router is NOT an LLM: no model calls, no intent + * Router barrel — the plumbing that maps Slack threads to coding-agent turns. + * The router is NOT an LLM: no model calls, no intent * detection. See router.ts for the glue and dispatcher.ts for the turn * lifecycle. */ diff --git a/src/router/log.test.ts b/src/router/log.test.ts index 163f918..870f1bf 100644 --- a/src/router/log.test.ts +++ b/src/router/log.test.ts @@ -26,7 +26,7 @@ const ctx: LogContext = { const TS = "2026-05-31T00:00:00.000Z"; describe("envelope shape & correlation fields", () => { - it("every builder carries the common envelope (§4.9)", () => { + it("every builder carries the common envelope", () => { const env = buildRouterEvent(ctx, TS, RouterKind.MentionReceived, { user: "U1" }); expect(env).toMatchObject({ ts: TS, @@ -137,7 +137,7 @@ describe("safeguards", () => { expect(capArgv(["short", "z".repeat(500)], 10)[1]).toContain("…"); }); - it("never emits credential-shaped fields (§4.7)", () => { + it("never emits credential-shaped fields", () => { const env = buildTurnIn(ctx, TS, { sessionId: null, argv: ["codex"], diff --git a/src/router/log.ts b/src/router/log.ts index 8fb3a02..071b646 100644 --- a/src/router/log.ts +++ b/src/router/log.ts @@ -1,11 +1,11 @@ import { createHash } from "node:crypto"; /** - * Boundary logging (spec §4.9). + * Boundary logging. * * The router logs both edges of every turn at the agent boundary, treating the * agent's output as opaque bytes — format-agnostic, so it survives Codex schema - * changes and catches the agent misbehaving (e.g. silent empty output, §9.14). + * changes and catches the agent misbehaving (e.g. silent empty output). * * - `direction: "in"` — router -> agent (argv, target session, prompt) * - `direction: "out"` — agent -> router (raw stdout/stderr chunks; exit summary) @@ -15,7 +15,7 @@ import { createHash } from "node:crypto"; * **verbose** toggle for full raw bytes. Logs are a sink, never read back for * correctness, and writes are best-effort — they must never stall or fail a turn. * Credentials are proxy-side and never enter argv/prompt, so they are never - * logged (§4.7). + * logged. */ export type Direction = "in" | "out" | "router"; @@ -36,10 +36,10 @@ interface EnvelopeBase extends LogContext { kind: string; } -/** Common envelope + kind-specific payload spread at the top level (§4.9). */ +/** Common envelope + kind-specific payload spread at the top level. */ export type LogEnvelope = EnvelopeBase & Record; -/** Router-direction record kinds — the gaps raw agent I/O can't explain (§4.9). */ +/** Router-direction record kinds — the gaps raw agent I/O can't explain. */ export const RouterKind = { MentionReceived: "mention.received", MentionDeduped: "mention.deduped", @@ -109,7 +109,7 @@ function base(ctx: LogContext, ts: string, direction: Direction, kind: string): } export interface TurnInInput { - /** Target session id: null => fresh turn, else resume (§4.5). */ + /** Target session id: null => fresh turn, else resume. */ sessionId: string | null; argv: string[]; cwd: string; @@ -198,7 +198,7 @@ export function createRecordingSink(): LogSink & { records: LogEnvelope[] } { /** * Serialize each record to a JSONL line and hand it to `writeLine` (which does * the actual stdout/file I/O, injected by the composition root). Swallows all - * errors — a full disk or slow shipper must never stall a turn (§4.9). + * errors — a full disk or slow shipper must never stall a turn. */ export function createJsonlSink(writeLine: (line: string) => void): LogSink { return { diff --git a/src/router/router.test.ts b/src/router/router.test.ts index e1de03e..4980bca 100644 --- a/src/router/router.test.ts +++ b/src/router/router.test.ts @@ -41,7 +41,7 @@ function build(): { router: Router; dispatcher: FakeDispatcher; platform: FakePl return { router, dispatcher, platform }; } -describe("dedupe (§4.1)", () => { +describe("dedupe", () => { it("drops a re-delivered ts (no second dispatch)", async () => { const { router, dispatcher } = build(); await router.onMention(m1); @@ -50,7 +50,7 @@ describe("dedupe (§4.1)", () => { }); }); -describe("idle/running/pending (§4.1)", () => { +describe("idle/running/pending", () => { it("dispatches on the first mention and 👀s it", async () => { const { router, dispatcher, platform } = build(); await router.onMention(m1); @@ -79,7 +79,7 @@ describe("idle/running/pending (§4.1)", () => { }); }); -describe("coalescing (§4.1)", () => { +describe("coalescing", () => { it("N mentions during a turn produce exactly ONE coalesced follow-up (latest trigger)", async () => { const { router, dispatcher } = build(); await router.onMention(m1); // dispatch #1 @@ -105,7 +105,7 @@ describe("coalescing (§4.1)", () => { }); }); -describe("atomicity at the finish boundary (§4.1)", () => { +describe("atomicity at the finish boundary", () => { it("a mention landing as a turn finishes is never lost", async () => { const { router, dispatcher } = build(); await router.onMention(m1); // dispatch #1 @@ -122,7 +122,7 @@ describe("atomicity at the finish boundary (§4.1)", () => { }); }); -describe("restart (§4.1)", () => { +describe("restart", () => { it("a fresh Router starts idle (no persisted state) and dispatches", async () => { const { router, dispatcher } = build(); await router.onMention(m1); diff --git a/src/router/router.ts b/src/router/router.ts index 04e8a7c..3657a5f 100644 --- a/src/router/router.ts +++ b/src/router/router.ts @@ -13,16 +13,16 @@ import { chooseSandboxName } from "./sandbox-name.js"; import { INITIAL_STATE, reduce, type ThreadFsmState } from "./state-machine.js"; /** - * Router glue (spec §4.1) — plumbing, not an LLM. Owns the transient + * Router glue — plumbing, not an LLM. Owns the transient * per-thread coordination: the idle/running/pending FSM, ts-dedupe, the 👀 ack * on every mention, and a per-thread mutex so the state read-modify-write is - * atomic. All durable state lives in the sandbox (§4.6); these maps are + * atomic. All durable state lives in the sandbox; these maps are * memory-only and reset on restart, which is fine — an in-flight turn is - * abandoned and re-driven idempotently on the next mention (§4.1). + * abandoned and re-driven idempotently on the next mention. * * Dispatching is fire-and-forget: the turn (and its stream-reader) outlives the * dispatch so the router is free to service other threads, while this thread's - * turn keeps draining (§4.1). Cross-thread turns run in parallel; turns within a + * turn keeps draining. Cross-thread turns run in parallel; turns within a * thread serialize and coalesce. */ @@ -66,7 +66,7 @@ export class Router { async onMention(mention: Mention): Promise { const key = threadKey(mention.thread); - // 1. Idempotent dispatch on ts — drop already-accepted triggers (§4.1). + // 1. Idempotent dispatch on ts — drop already-accepted triggers. if (this.dedupe.has(mention.ts)) { this.logRouter(mention.thread, mention.ts, RouterKind.MentionDeduped, { ts: mention.ts }); return; @@ -75,7 +75,7 @@ export class Router { this.logRouter(mention.thread, mention.ts, RouterKind.MentionReceived, { user: mention.user }); this.latestMention.set(key, mention); - // 2. Atomic state read-modify-write under the per-thread lock (§4.1). + // 2. Atomic state read-modify-write under the per-thread lock. const effects = await this.withLock(key, () => { const { state, effects } = reduce(this.stateOf(key), { kind: "mention" }); this.states.set(key, state); @@ -92,7 +92,7 @@ export class Router { } } - /** Fire-and-forget a turn; on completion, advance the FSM (coalescing, §4.1). */ + /** Fire-and-forget a turn; on completion, advance the FSM (coalescing). */ private dispatch(key: string, trigger: Mention): void { void this.dispatcher.dispatchTurn(trigger).then( () => this.onTurnFinished(key), @@ -121,7 +121,7 @@ export class Router { return this.states.get(key) ?? INITIAL_STATE; } - /** Best-effort 👀 ack on the mention — never blocks or fails the turn (§4.1). */ + /** Best-effort 👀 ack on the mention — never blocks or fails the turn. */ private async ack(mention: Mention): Promise { try { await this.platform.addReaction(mention.thread.channel, mention.ts, "eyes"); @@ -131,7 +131,7 @@ export class Router { } } - /** Serialize handler bodies per thread so the state RMW is atomic (§4.1). */ + /** Serialize handler bodies per thread so the state RMW is atomic. */ private withLock(key: string, fn: () => T): Promise { const prev = this.locks.get(key) ?? Promise.resolve(); const result = prev.then(fn, fn); diff --git a/src/router/sandbox-name.test.ts b/src/router/sandbox-name.test.ts index c1ab0f2..fac873e 100644 --- a/src/router/sandbox-name.test.ts +++ b/src/router/sandbox-name.test.ts @@ -17,7 +17,7 @@ const SAMPLES: ThreadId[] = [ ]; describe("sandboxName", () => { - it("encodes as t---, with '.' -> '-' (hostname-safe, §4.1)", () => { + it("encodes as t---, with '.' -> '-' (hostname-safe)", () => { expect(sandboxName({ channel: "C0ABCDEF", threadTs: "1748600000.123456" })).toBe( "t-C0ABCDEF-1748600000-123456", ); diff --git a/src/router/sandbox-name.ts b/src/router/sandbox-name.ts index 00ada67..ebdeeda 100644 --- a/src/router/sandbox-name.ts +++ b/src/router/sandbox-name.ts @@ -2,16 +2,15 @@ import { createHash } from "node:crypto"; import type { ThreadId } from "../types.js"; /** - * Deterministic, reversible sandbox naming (spec §4.1). + * Deterministic, reversible sandbox naming. * * The sandbox name is a pure function of the thread id, so the router can find a * thread's sandbox via `sbx ls` + this function without any persisted mapping, - * and reverse a running sandbox's name back to its thread for the idle sweep - * (§4.6). + * and reverse a running sandbox's name back to its thread for the idle sweep. * * IMPORTANT — charset. The binding constraint (verified LIVE) is that sbx uses * the name as the container HOSTNAME, which rejects BOTH `_` AND `.`: - * - `_`: the spec's §4.1 sample does `thread_ts.replace(".", "_")` — `_` is + * - `_`: an earlier sample did `thread_ts.replace(".", "_")` — `_` is * illegal for `sbx create --name`. * - `.`: accepted by `--name` but `sbx create` then fails with * "hostname: value must be a valid hostname". @@ -24,7 +23,7 @@ import type { ThreadId } from "../types.js"; export const SANDBOX_PREFIX = "t-"; /** - * Max sandbox-name length before we fall back to a hash name (§4.1). 63 is the + * Max sandbox-name length before we fall back to a hash name. 63 is the * single-label hostname limit; our dot-less name is a single label, so this is * the right ceiling. The hash fallback keeps naming correct past it. */ @@ -39,7 +38,7 @@ const PLAIN_RE = /^t-([A-Za-z0-9]+)-(\d+)-(\d+)$/; /** A hash-fallback name: `t-<16 hex>` (not reversible from the name alone). */ const HASH_RE = /^t-[0-9a-f]{16}$/; -/** `name = "t-" + channel + "-" + thread_ts` with the `.` encoded as `-` (§4.1, hostname-safe). */ +/** `name = "t-" + channel + "-" + thread_ts` with the `.` encoded as `-` (hostname-safe). */ export function sandboxName(thread: ThreadId): string { return `${SANDBOX_PREFIX}${thread.channel}-${thread.threadTs.replace(".", "-")}`; } @@ -57,7 +56,7 @@ export function isHashName(name: string): boolean { /** * Length/charset fallback: a charset-safe, fixed-length name derived from the * thread id. Not reversible from the name — the full id is stored in the - * sandbox's `~/.agent-state/thread` for reverse lookup (§4.1/§4.6). + * sandbox's `~/.agent-state/thread` for reverse lookup. */ export function hashSandboxName(thread: ThreadId): string { const digest = createHash("sha256").update(`${thread.channel} ${thread.threadTs}`).digest("hex"); @@ -80,7 +79,7 @@ export function chooseSandboxName(thread: ThreadId, maxLen: number = SBX_NAME_MA * Reverse a plain name back to its thread id, or `null` if the name isn't one of * ours in reversible form. Returns `null` for hash names (caller should read * `~/.agent-state/thread`) and for foreign/utility names like `_login-tmp` — - * the `t-` prefix namespaces our sandboxes away from those (§4.1). + * the `t-` prefix namespaces our sandboxes away from those. */ export function parseSandboxName(name: string): ThreadId | null { const match = PLAIN_RE.exec(name); diff --git a/src/router/state-machine.test.ts b/src/router/state-machine.test.ts index ac48065..2c36c5a 100644 --- a/src/router/state-machine.test.ts +++ b/src/router/state-machine.test.ts @@ -24,7 +24,7 @@ function run( return { state, effects }; } -describe("reduce — every state x event cell (§4.1)", () => { +describe("reduce — every state x event cell", () => { it("idle + mention -> running [ackRunning, dispatchTurn]", () => { expect(reduce("idle", mention)).toEqual({ state: "running", @@ -62,7 +62,7 @@ describe("reduce — every state x event cell (§4.1)", () => { }); }); -describe("coalescing (§4.1)", () => { +describe("coalescing", () => { it("dispatches exactly once per turn regardless of mentions arriving mid-turn", () => { // 1 initial mention + 5 mid-turn mentions, then the turn finishes, then the // coalesced follow-up finishes. Expect exactly 2 dispatches total. @@ -87,7 +87,7 @@ describe("coalescing (§4.1)", () => { expect(state).toBe("idle"); }); - it("acks every mention but only dispatches when idle (👀 on each, §4.1)", () => { + it("acks every mention but only dispatches when idle (👀 on each)", () => { const { effects } = run(INITIAL_STATE, [mention, mention, mention]); const acks = effects.filter((e) => e === "ackRunning" || e === "ackPending"); expect(acks).toHaveLength(3); // one 👀 per mention diff --git a/src/router/state-machine.ts b/src/router/state-machine.ts index 0394b4e..387bce0 100644 --- a/src/router/state-machine.ts +++ b/src/router/state-machine.ts @@ -1,16 +1,16 @@ /** - * Per-thread coordination state machine (spec §4.1). + * Per-thread coordination state machine. * * Pure, total reducer `(state, event) -> {state, effects}`. No timers, no I/O, - * no message payload — "Slack is the queue" (§4.1), so the FSM only tracks + * no message payload — "Slack is the queue", so the FSM only tracks * whether a turn is running and whether one more is pending; the dispatcher * recomputes the delta covering every coalesced mention. * - * Held in router memory only (transient bucket, §3): lost on restart, which is + * Held in router memory only (transient bucket): lost on restart, which is * fine — an in-flight turn is abandoned and re-driven idempotently on the next * mention. * - * State table (§4.1): + * State table: * idle + mention -> running [ackRunning, dispatchTurn] * running + mention -> runningPending [ackPending] (set pending, no dispatch) * runningPending + mention -> runningPending [ackPending] (idempotent) @@ -19,15 +19,15 @@ * idle + turnFinished -> idle [] (defensive no-op) */ -/** `runningPending` is the spec's "running + pending". */ +/** `runningPending` means "running + pending". */ export type ThreadFsmState = "idle" | "running" | "runningPending"; export type FsmEvent = { kind: "mention" } | { kind: "turnFinished" }; /** * An instruction for the router to execute. `ackRunning`/`ackPending` both add - * the 👀 reaction (§4.1) but are distinct so the router can log the dispatch - * path vs the coalesce path differently (§4.9). + * the 👀 reaction but are distinct so the router can log the dispatch + * path vs the coalesce path differently. */ export type FsmEffect = "ackRunning" | "ackPending" | "dispatchTurn" | "goIdle"; diff --git a/src/sandbox/index.ts b/src/sandbox/index.ts index 2957416..4827db7 100644 --- a/src/sandbox/index.ts +++ b/src/sandbox/index.ts @@ -1,15 +1,15 @@ /** - * SandboxProvider — isolated execution-environment lifecycle. See spec §3, §4.4. + * SandboxProvider — isolated execution-environment lifecycle. * * v0: Docker Sandboxes (sbx) microVM. `exec` is the transport the router logs - * at the agent boundary (§4.9); commands should be wrapped in `bash -c` so the - * sandbox's persistent environment is loaded (§4.5). + * at the agent boundary; commands should be wrapped in `bash -c` so the + * sandbox's persistent environment is loaded. * * Fallback: plain Docker + iptables egress. Cloud/teams later: e2b / Daytona / * self-hosted Firecracker. */ export interface SandboxHandle { - /** Deterministic, reversible name = f(threadId). See spec §4.1. */ + /** Deterministic, reversible name = f(threadId). */ name: string; } @@ -19,13 +19,13 @@ export interface ExecResult { exitCode: number; } -/** Streaming options for a single exec call (§4.1/§4.9). */ +/** Streaming options for a single exec call. */ export interface ExecCallOptions { - /** Receive raw stdout/stderr chunks as they arrive (the router logs them, §4.9). */ + /** Receive raw stdout/stderr chunks as they arrive (the router logs them). */ onChunk?: (stream: "stdout" | "stderr", chunk: string) => void; - /** Optional input to write before closing stdin. Stdin is always closed (§9.15). */ + /** Optional input to write before closing stdin. Stdin is always closed. */ stdin?: string; - /** Working directory inside the sandbox (e.g. the provisioned repo clone, §4.3). */ + /** Working directory inside the sandbox (e.g. the provisioned repo clone). */ cwd?: string; } @@ -34,8 +34,8 @@ export interface SandboxProvider { /** * Drive the agent CLI per turn (argv wrapped in `bash -c`); the router streams - * and logs this raw output (§4.9). The reader drains stdout+stderr to exit and - * closes stdin (§9.15). + * and logs this raw output. The reader drains stdout+stderr to exit and + * closes stdin. */ exec(handle: SandboxHandle, argv: string[], opts?: ExecCallOptions): Promise; @@ -48,6 +48,6 @@ export interface SandboxProvider { stop(handle: SandboxHandle): Promise; destroy(handle: SandboxHandle): Promise; - /** The registry — `sbx ls`. See spec §4.1, §4.6. */ + /** The registry — `sbx ls`. */ list(): Promise; } diff --git a/src/sandbox/sbx-argv.test.ts b/src/sandbox/sbx-argv.test.ts index a3d5d54..0640af5 100644 --- a/src/sandbox/sbx-argv.test.ts +++ b/src/sandbox/sbx-argv.test.ts @@ -19,7 +19,7 @@ import { } from "./sbx-argv.js"; describe("createArgv", () => { - it("always uses --clone and the codex agent by default (§4.4)", () => { + it("always uses --clone and the codex agent by default", () => { expect(createArgv("t-C0-1.2", "/repo")).toEqual([ "create", "--clone", @@ -55,7 +55,7 @@ describe("createArgv", () => { }); describe("execArgv", () => { - it("wraps in `bash -c`, uses `--`, redirects stdin from /dev/null, no -i (§4.5/§9.15)", () => { + it("wraps in `bash -c`, uses `--`, redirects stdin from /dev/null, no -i", () => { expect(execArgv("box", "codex exec --json")).toEqual([ "exec", "box", @@ -71,7 +71,7 @@ describe("execArgv", () => { expect(execArgv("box", "cmd", { stdinFromNull: false }).at(-1)).toBe("cmd"); }); - it("supports login shell, workdir, env, and the PTY mitigation (§9.14)", () => { + it("supports login shell, workdir, env, and the PTY mitigation", () => { expect(execArgv("box", "cmd", { login: true })).toContain("-lc"); expect(execArgv("box", "cmd", { workdir: "/repo" })).toEqual([ "exec", diff --git a/src/sandbox/sbx-argv.ts b/src/sandbox/sbx-argv.ts index aa1617f..31fc175 100644 --- a/src/sandbox/sbx-argv.ts +++ b/src/sandbox/sbx-argv.ts @@ -1,17 +1,17 @@ /** - * Pure argv builders for the `sbx` CLI (spec §4.4/§4.5). + * Pure argv builders for the `sbx` CLI. * * The SandboxProvider I/O shell (sbx-provider.ts) shells out `sbx `; this * file owns the argv construction so it's testable without the daemon. Verified - * against sbx v0.31.1 — re-check on every upgrade (§9, substrate risk). + * against sbx v0.31.1 — re-check on every upgrade (substrate risk). * * Two load-bearing rules baked in here: * - `--clone` on create: the agent works on a private in-VM clone, never the - * live host working tree (§4.4). + * live host working tree. * - `exec` is wrapped in `bash -c`: `sbx exec` runs with no shell and does not * source the sandbox's persistent environment, so the toolchain/agent PATH - * may be missing otherwise (§4.5). We also never pass `-i` (the provider - * closes stdin so codex can't hang — §9.15). + * may be missing otherwise. We also never pass `-i` (the provider + * closes stdin so codex can't hang). */ // --------------------------------------------------------------------------- @@ -41,7 +41,7 @@ export function shellJoin(argv: string[]): string { /** * Wrap a command so the child gets a pseudo-TTY, mitigating codex's headless - * empty-output regression (§9.14). util-linux `script` syntax (Linux/in-VM); + * empty-output regression. util-linux `script` syntax (Linux/in-VM); * confirm live which mitigation we keep. */ export function ptyCommand(command: string): string { @@ -53,9 +53,9 @@ export function ptyCommand(command: string): string { // --------------------------------------------------------------------------- export interface CreateOptions { - /** Agent kind sbx wires auth for. Default "codex" (§4.5). */ + /** Agent kind sbx wires auth for. Default "codex". */ agent?: string; - /** Private in-VM clone vs the rw bind-mount. Default true — never edit the host tree (§4.4). */ + /** Private in-VM clone vs the rw bind-mount. Default true — never edit the host tree. */ clone?: boolean; template?: string; kits?: string[]; @@ -81,14 +81,14 @@ export function createArgv(name: string, repoRef: string, opts: CreateOptions = export interface ExecOptions { env?: Record; workdir?: string; - /** Login shell (`bash -lc`) to source profile-level env if `-c` isn't enough (§4.5). */ + /** Login shell (`bash -lc`) to source profile-level env if `-c` isn't enough. */ login?: boolean; - /** Wrap the command in a PTY (§9.14). */ + /** Wrap the command in a PTY. */ pty?: boolean; /** * Redirect the in-VM command's stdin from /dev/null (default true). * - * VERIFIED LIVE (§9.15): `sbx exec` keeps the VM process's stdin open even when + * VERIFIED LIVE: `sbx exec` keeps the VM process's stdin open even when * the host closes its end, so a headless agent like `codex exec` blocks on * "Reading additional input from stdin..." forever. Redirecting stdin from * /dev/null IN the VM gives it an immediate EOF. Closing host stdin (the @@ -101,7 +101,7 @@ export interface ExecOptions { /** * `sbx exec -- bash -c ' < /dev/null'`. `command` is a shell * string (build it from an agent argv with shellJoin). No `-i`, and stdin is - * redirected from /dev/null in-VM so headless agents can't hang (§9.15). The + * redirected from /dev/null in-VM so headless agents can't hang. The * `--` stops sbx flag parsing so the command's own flags pass through. */ export function execArgv(name: string, command: string, opts: ExecOptions = {}): string[] { @@ -130,7 +130,7 @@ export function execAgentArgv(name: string, agentArgv: string[], opts: ExecOptio } // --------------------------------------------------------------------------- -// ls (the registry, §4.1) +// ls (the registry) // --------------------------------------------------------------------------- export function lsArgv(): string[] { @@ -196,7 +196,7 @@ export function parseLsJson(stdout: string): SbxListEntry[] { } // --------------------------------------------------------------------------- -// cp (getFile / putFile, §4.6) / stop / rm +// cp (getFile / putFile) / stop / rm // --------------------------------------------------------------------------- /** `SANDBOX:PATH` addressing for `sbx cp`. */ @@ -217,7 +217,7 @@ export function rmArgv(names: string[], opts: { force?: boolean } = {}): string[ } // --------------------------------------------------------------------------- -// secret (onboarding/auth detection, §4.7/§4.8) +// secret (onboarding/auth detection) // --------------------------------------------------------------------------- export function secretLsArgv(): string[] { @@ -225,7 +225,7 @@ export function secretLsArgv(): string[] { } export interface SecretSetOptions { - /** Host-global (shared by every future sandbox). Default true (§4.8). */ + /** Host-global (shared by every future sandbox). Default true. */ global?: boolean; /** OAuth/browser flow (e.g. `openai --oauth`); else the value is piped via stdin. */ oauth?: boolean; diff --git a/src/sandbox/sbx-provider.live.test.ts b/src/sandbox/sbx-provider.live.test.ts index 826f199..fb055ba 100644 --- a/src/sandbox/sbx-provider.live.test.ts +++ b/src/sandbox/sbx-provider.live.test.ts @@ -5,7 +5,7 @@ import type { SandboxHandle } from "./index.js"; import { SbxProvider } from "./sbx-provider.js"; /** - * Live verification of the sbx I/O seam against a real microVM (spec §9). Gated + * Live verification of the sbx I/O seam against a real microVM. Gated * by SBX_LIVE=1 (set by `npm run test:live`) and excluded from the default suite * + CI — it needs Docker + the sbx daemon. Uses a credential-free `shell` * sandbox so it covers exec/cp/$HOME/stop-restart without any agent auth. @@ -43,7 +43,7 @@ describe.skipIf(!LIVE)("SbxProvider — live shell sandbox", () => { } }); - it("exec runs an agent argv via bash -c and drains stdout (§4.5/§9.9)", async () => { + it("exec runs an agent argv via bash -c and drains stdout", async () => { const result = await provider.exec(handle, ["echo", "hi from agent argv"]); expect(result.stdout.trim()).toBe("hi from agent argv"); expect(result.exitCode).toBe(0); @@ -67,7 +67,7 @@ describe.skipIf(!LIVE)("SbxProvider — live shell sandbox", () => { expect(new TextDecoder().decode(got)).toBe("bytes\n"); }); - it("stop then exec auto-restarts losslessly — in-VM state survives (§4.6)", async () => { + it("stop then exec auto-restarts losslessly — in-VM state survives", async () => { const home = await provider.homeDir(handle); await provider.stop(handle); const result = await provider.exec(handle, ["cat", `${home}/.agent-state/session`]); diff --git a/src/sandbox/sbx-provider.test.ts b/src/sandbox/sbx-provider.test.ts index 8a8b494..ebb6d1a 100644 --- a/src/sandbox/sbx-provider.test.ts +++ b/src/sandbox/sbx-provider.test.ts @@ -52,7 +52,7 @@ function fakeSpawn(responder: (args: string[]) => Resp): { const handle: SandboxHandle = { name: "t-C0ABCDEF-1748600000.123456" }; -describe("exec — drain, stdin close, streaming (§4.9/§9.15)", () => { +describe("exec — drain, stdin close, streaming", () => { it("wraps argv in bash -c (no -i), drains stdout+stderr, closes stdin, streams chunks", async () => { let recorded: string[] = []; let stdin: FakeStdin | undefined; diff --git a/src/sandbox/sbx-provider.ts b/src/sandbox/sbx-provider.ts index 8cacaa5..691b50b 100644 --- a/src/sandbox/sbx-provider.ts +++ b/src/sandbox/sbx-provider.ts @@ -20,7 +20,7 @@ import { } from "./sbx-argv.js"; /** - * SandboxProvider over the `sbx` CLI (spec §4.4) — the I/O shell. All argv + * SandboxProvider over the `sbx` CLI — the I/O shell. All argv * construction lives in sbx-argv.ts; this file only spawns and drains. The * spawn fn is injected so the streaming/stdin/exit handling is unit-testable * without the daemon. @@ -48,9 +48,9 @@ export interface SbxProviderConfig { spawnFn?: SpawnFn; bin?: string; createOptions?: CreateOptions; - /** `bash -lc` to source profile-level env if `-c` isn't enough (§4.5/§9.9). */ + /** `bash -lc` to source profile-level env if `-c` isn't enough. */ login?: boolean; - /** Wrap the agent command in a PTY (codex empty-output mitigation, §9.14). */ + /** Wrap the agent command in a PTY (codex empty-output mitigation). */ pty?: boolean; } @@ -71,7 +71,7 @@ export class SbxProvider implements SandboxProvider { this.pty = config.pty ?? false; } - /** Spawn `sbx `, drain stdout+stderr to exit, close stdin (§9.15). */ + /** Spawn `sbx `, drain stdout+stderr to exit, close stdin. */ private run(argv: string[], opts: ExecCallOptions = {}): Promise { return new Promise((resolve, reject) => { const child = this.spawnFn(this.bin, argv); @@ -89,7 +89,7 @@ export class SbxProvider implements SandboxProvider { }); child.on("error", reject); child.on("close", (code) => resolve({ stdout, stderr, exitCode: code ?? -1 })); - // Close stdin so a headless agent can't hang waiting on it (§9.15). + // Close stdin so a headless agent can't hang waiting on it. if (opts.stdin !== undefined) { child.stdin?.write(opts.stdin); } @@ -142,7 +142,7 @@ export class SbxProvider implements SandboxProvider { return parseLsJson(stdout).map((entry) => ({ name: entry.name })); } - /** Raw `sbx secret ls` output — drives the onboarding credential check (§4.8). */ + /** Raw `sbx secret ls` output — drives the onboarding credential check. */ async secretLs(): Promise { const { stdout } = await this.runOrThrow(secretLsArgv(), "secret ls"); return stdout; diff --git a/src/types.ts b/src/types.ts index 9569aca..fd212cc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,4 @@ -/** Shared types for the seams. See spec §3. */ +/** Shared types for the seams. */ /** A Slack thread identity: channel + root-message ts. */ export interface ThreadId { @@ -6,16 +6,16 @@ export interface ThreadId { threadTs: string; } -/** A normalized inbound mention handed to the router. See spec §4.1. */ +/** A normalized inbound mention handed to the router. */ export interface Mention { thread: ThreadId; - /** ts of the triggering message — used for idempotent dispatch (§4.1). */ + /** ts of the triggering message — used for idempotent dispatch. */ ts: string; user: string; text: string; } -/** One message in a thread's transcript. See spec §4.2. */ +/** One message in a thread's transcript. */ export interface ThreadMessage { ts: string; user: string; diff --git a/vitest.config.ts b/vitest.config.ts index daa834d..5689165 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config"; /** * Default test run: offline unit tests only. Live tests (`*.live.test.ts`, - * which need the sbx daemon / Docker / creds — spec §9) are excluded here and + * which need the sbx daemon / Docker / creds) are excluded here and * run via `npm run test:live` (see vitest.live.config.ts). */ export default defineConfig({ diff --git a/vitest.live.config.ts b/vitest.live.config.ts index 33aefde..7550219 100644 --- a/vitest.live.config.ts +++ b/vitest.live.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from "vitest/config"; /** * Live lane — only `*.live.test.ts`, which exercise the real sbx daemon / Docker - * / Codex (spec §9). NOT part of `npm run check` or CI; run on demand with + * / Codex. NOT part of `npm run check` or CI; run on demand with * `npm run test:live` once the environment is up (Tier 1, see the plan). Passes * with no test files so the script is safe before any live tests exist. */ From 83db081338894a67a9f4ab4747168f04475b88d7 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 12:59:54 -0400 Subject: [PATCH 44/47] docs(readme): add "work in the open" philosophy, trim Status section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a driving-philosophy note (inspired by pi-mom and Shopify's River; cites Tobi Lütke's post on working in the open) and remove the trailing Status section. Co-Authored-By: Claude Opus 4.8 --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 56aa3f5..b9b174f 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,15 @@ chatbot: every thread maps 1:1 to its own microVM sandbox and coding-agent sessi itself runs no LLM — it just relays your thread to a coding-agent CLI (Codex or Claude Code) and relays the result back. +**Driving philosophy — work in the open.** People collaborate best in the open, and so should a coding +agent. A private window only teaches the person at the keyboard; a shared thread teaches everyone +watching. When the work happens where the whole team can see it, every session is searchable, teachable, +and compounding — the next person with the same question doesn't have to ask it. + +Inspired by [pi-mom](https://www.npmjs.com/package/@mariozechner/pi-mom) and Shopify's +[River](https://shopify.engineering/under-the-river) — see Tobi Lütke on +[working in the open](https://x.com/tobi/status/2053121182044451016). + --- ## What you can do with it @@ -157,12 +166,3 @@ src/ cli/ args.ts + onboarding.ts + index.ts (composition root) types.ts shared types ``` - ---- - -## Status - -**Phase 1 (local, single-user, macOS Apple silicon)** — verified end-to-end against real Slack + sbx + -Codex: mention → isolated sandbox → repo clone → headless codex → reply in-thread, with deterministic -sandbox naming, per-thread `~/.agent-state` ledger + session resume, idle/running/pending coalescing, -and always-on boundary logging. The phased roadmap runs ergonomics → teams → cloud. From 9e72d19b6e768310a4e4b6a92203c6012e3d8895 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 22:46:41 -0400 Subject: [PATCH 45/47] chore: prepare branch for open source Tighten router and sandbox failure handling, simplify docs and comments, add OSS metadata, and clean up the test suite with more meaningful coverage. Co-Authored-By: Claude Opus 4.8 --- .env.example | 8 ++ AGENTS.md | 178 ++++++++++---------------- LICENSE | 21 +++ README.md | 213 +++++++++++++------------------ package.json | 7 +- src/backend/claude.test.ts | 7 + src/backend/claude.ts | 50 +------- src/backend/codex.test.ts | 5 + src/backend/codex.ts | 45 ------- src/backend/index.ts | 21 --- src/cli/args.test.ts | 5 + src/cli/args.ts | 15 +-- src/platform/slack.ts | 36 ++---- src/router/agent-state.test.ts | 4 + src/router/agent-state.ts | 56 +++----- src/router/dedupe.test.ts | 7 - src/router/dedupe.ts | 16 --- src/router/dispatcher.test.ts | 52 +++++++- src/router/dispatcher.ts | 84 ++++++------ src/router/idle-sweep.test.ts | 41 +++++- src/router/index.ts | 6 - src/router/log.test.ts | 18 --- src/router/router.test.ts | 11 ++ src/router/router.ts | 30 +---- src/router/sandbox-name.test.ts | 2 +- src/router/sandbox-name.ts | 48 +------ src/router/state-machine.ts | 27 ---- src/sandbox/sbx-argv.test.ts | 2 +- src/sandbox/sbx-argv.ts | 77 ----------- src/sandbox/sbx-provider.test.ts | 11 ++ src/types.test.ts | 16 --- 31 files changed, 406 insertions(+), 713 deletions(-) create mode 100644 .env.example create mode 100644 LICENSE delete mode 100644 src/types.test.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6945ace --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +APP_SLACK_APP_TOKEN=xapp-... +APP_SLACK_BOT_TOKEN=xoxb-... +MISHU_REPO=/path/to/target/repo + +# Optional: +# MISHU_AGENT=codex +# MISHU_LOG_LEVEL=summary +# MISHU_BOT_USER=U... diff --git a/AGENTS.md b/AGENTS.md index 244be30..532dc3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,126 +1,76 @@ -# AGENTS.md — rules for working in this repo +# AGENTS.md -Mishu — a coding agent that lives in your team's chat (Slack today): mention it in a Slack thread → it -does the coding work in an isolated `sbx` microVM running a coding-agent CLI (Codex or Claude Code) → -it reports back **in the same thread**. +Rules for contributors and coding agents working in this repo. -## 0. What this is — and is NOT +Mishu is a Slack-to-coding-agent router. A Slack thread maps to one `sbx` sandbox and one coding-agent +session. The router is plumbing, not an LLM. -- The **router is plumbing, not an LLM**. It has **no model calls**, makes **no decisions about - whether to delegate**, and does **no intent detection**. In particular it does **not** detect "open - a PR" — shipping is the agent's job (`git`/`gh` inside the sandbox). Adding reasoning, - branching on message content, or an "outer bot" to the router is **out of scope by default** — the - router stays pure plumbing unless a strong, explicit use case justifies otherwise. -- Every Slack thread maps 1:1 to one sandbox to one coding-agent session. +## Build And Test -## 1. Build / check / test +- Run `npm install` once. +- Run `npm run check` before every commit. It runs Biome, `tsc --noEmit`, and Vitest. +- Use `npm run check:fix` for formatter/import fixes. +- Use `npm test`, `npm run test:watch`, or `npm run test:cov` for Vitest. +- Use `npm run build` to emit `dist/`. +- Use `npm run test:live` only for live `sbx` checks; it is not part of CI. -- `npm install` once. **`npm run check` is the single gate**: Biome (lint + format + organize-imports, - `--error-on-warnings`) + `tsc --noEmit` + `vitest run`. Run it before every commit; **keep the repo - green at every commit**. `npm install` also wires a **pre-commit hook** (`.githooks/pre-commit`, via - the `prepare` script setting `core.hooksPath`) that runs the gate automatically; bypass in a pinch - with `git commit --no-verify`. -- `npm run check:fix` applies Biome fixes then runs the gate. `npm test` / `npm run test:watch` / - `npm run test:cov` run Vitest. `npm run build` emits `dist/` (excludes `*.test.ts`). `npm run dev` - runs the CLI under `tsx watch`. -- `npm run test:live` runs the `*.live.test.ts` lane — needs the sbx daemon running (sbx bundles its - own runtime; no Docker Desktop), so it's **NOT** part of `check`/CI. Run it locally after any `sbx` - upgrade (substrate risk). +The pre-commit hook in `.githooks/pre-commit` runs `npm run check`. -## 2. Module rules (ESM, strict) +## Module Rules -- NodeNext + `verbatimModuleSyntax`: **every relative import ends in `.js`** (e.g. - `import { Router } from "./router/index.js"`); type-only imports use `import type`. No CommonJS. -- `strict`, `noUncheckedIndexedAccess`, `noUnusedLocals/Parameters` are on — index access is - `T | undefined`; handle it explicitly. +- ESM only. Relative imports include `.js`. +- Use `import type` for type-only imports. +- TypeScript is strict, with `noUncheckedIndexedAccess` and unused checks enabled. +- New pure logic should have colocated `*.test.ts` coverage. -## 3. Three seams + the stateless router +## Architecture Invariants ``` -Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM - └ CodingBackend (Codex CLI) +Slack <-> PlatformAdapter <-> Router <-> SandboxProvider <-> sandbox + \-> CodingBackend ``` -Invariants — do not break these: - -- **Transport ≠ interpretation.** `SandboxProvider.exec` is an **opaque byte stream** the router - logs at the boundary. The **only** place that knows an agent's output/CLI format is `CodingBackend` - (`src/backend/codex.ts`). **No Codex `item.*`/event-schema or rollout-file knowledge may live outside - `backend/codex.ts`.** -- **No central state store.** The router runs no DB and keeps no disk state of its own. - Discovery = `SandboxProvider.list()` (`sbx ls`) + deterministic naming. Per-thread durable state - (`transcript.jsonl`, `session`, `thread`) lives **in each sandbox** under `~/.agent-state/`. Router - memory holds only the transient `idle/running/pending` lock + ts-dedupe set — lost on restart by - design (an in-flight turn is re-driven idempotently on the next mention). -- **Pure core vs I/O shell.** Pure modules import only `../types.js`, seam interfaces, and pure stdlib - (`node:crypto`) — **never** `node:child_process`, `node:fs`, `@slack/*`. I/O shells import the pure - core, never the reverse. New logic ⇒ a pure module + a colocated `*.test.ts`. This is what keeps the - system testable offline (no daemon, tokens, or creds in CI). - -## 4. Sandbox naming — `src/router/sandbox-name.ts` - -- `name = "t-" + channel + "-" + thread_ts` with the `.` encoded as `-`. **The binding constraint - (verified LIVE) is the container HOSTNAME**, which rejects BOTH `_` (so an earlier - `replace(".", "_")` sample is illegal for `--name`) AND `.` (accepted by `--name` but `sbx create` fails - "hostname: value must be a valid hostname"). So emit only `[A-Za-z0-9-]` — encode `thread_ts`'s `.` - as `-`. A test guards against ever emitting `_` or `.`. -- Reversible: `t---` (channel has no `-`; ts is two numeric groups). Over-length - names fall back to `t-`; the full id is stored in `~/.agent-state/thread`. - -## 5. Boundary logging — `src/router/log.ts` - -- Always-on **summaries** (argv + session id + prompt size/hash; exit code + duration + final-message - snippet + `ok`) and a **verbose** toggle for raw stdout/stderr + full prompt. `direction ∈ in | out | - router`; `turnId` correlates a turn's IN + streamed OUT + exit. -- **Best-effort, never blocking** — writes never `await` on the turn's critical path and never throw - (a full disk / slow shipper must not stall a turn). **Cap/hash** large prompts and tool-output bursts. -- **Never log credentials** — they're proxy-side and never enter argv/prompt. A test asserts no - credential-shaped field is emitted. - -## 6. Router lifecycle — `src/router/router.ts`, `dispatcher.ts` - -- **ACK the Socket Mode envelope immediately (<3s) on receipt; run the handler async.** Drop - `retry_num > 0`. Dedupe on the trigger `ts` (TTL'd to the ~5-min retry window). -- **Acks are reactions, not replies.** 👀 on every mention; swap to ✅/❌ on completion. A *reply* ack - would move the catch-up high-water mark and corrupt the delta if a turn fails before posting. -- **Coalescing.** Mid-turn mentions only flip one `pending` bit; one follow-up turn runs whose delta - covers them all — **Slack is the queue**, mentions are never buffered in the router. -- **The stream reader is part of the dispatched turn and must outlive the dispatch.** "Free the router" - means free it for *other* threads — never drop this turn's reader (truncates the log / trips the - broken-pipe panic). -- **Write-after-success.** Persist the session id BEFORE posting; append the transcript AFTER the - reply. An abandoned/crashed turn then re-feeds cleanly (at-least-once). -- Turn-1 vs resume is the **session file's presence**; the transcript high-water mark is the - **delta boundary**. - -## 7. Codex / exec gotchas — `src/sandbox/sbx-argv.ts`, `backend/codex.ts` - -- Wrap exec in `bash -c` (`sbx exec` sources no env). **Redirect the agent's stdin from `/dev/null` - IN the VM** (` < /dev/null`) — verified live: `sbx exec` keeps the VM process's stdin open - even when the host closes its end, so codex otherwise hangs on "Reading additional input from - stdin..." forever. Never pass `-i`; **capture stderr** too. **Never `--ephemeral`** (breaks - resume). The `--json` stream's `thread.started` event carries the session id (`thread_id`). -- **Provisioning:** with `--clone`, `/home/agent/workspace` is empty under `sbx exec`; the repo - is a read-only mount at `/run/sandbox/source` (a valid git repo) plus a git daemon. A provision step - (the dispatcher's `provisionScript`) must clone it into a writable dir, point `origin` at the real - remote, and seed a non-base branch before the agent works. -- Keep Codex's native sandbox ON: `-c sandbox_mode=workspace-write -c approval_policy=never` (set via - `-c` because `resume` lacks `-s`). This is defense-in-depth against prompt injection — Slack content - is untrusted. -- Headless `codex exec` can emit **empty stdout** without a TTY on long prompts — `parseResult` treats - empty output as a failed turn, and `sbx-argv` has a PTY wrap option (`script`) to mitigate. Resolve - PTY-vs-pin during live verification; the boundary log surfaces "argv → 0 bytes, exit 0". - -## 8. Test conventions - -- **Vitest** (`globals` on), colocated `*.test.ts`, run via `npm test`. **Inject collaborators** (the - spawn fn, a clock, fakes) — there is no live daemon, no tokens, and no mocking framework needed. -- New pure logic ⇒ a colocated transition/round-trip/table/fixture test. Backend parsing is verified - against committed `src/backend/fixtures/` (real bytes after the live pass), including the empty-output - regression. Live-only checks go in `*.live.test.ts` (gated out of CI). - -## 9. Commits - -- Small, logical, **green at every commit** (`npm run check` passes). Add a runtime dependency only at - the seam that first needs it. Work on a branch; don't commit to the default branch directly. End - commit messages with the project's `Co-Authored-By` trailer. +- The router does not call models, inspect intent, or decide whether to open PRs. +- Agent output is an opaque byte stream until it reaches `CodingBackend`. +- Codex-specific parsing lives in `src/backend/codex.ts`. +- Claude-specific parsing lives in `src/backend/claude.ts`. +- The router has no durable database. Per-thread state lives inside the sandbox under + `~/.agent-state/`. +- Router memory may hold only transient locks, dedupe state, and pending/running state. +- Keep pure code separate from I/O shells. Pure modules should not import `node:child_process`, + `node:fs`, or Slack SDK packages. + +## Router Lifecycle + +- ACK Socket Mode envelopes immediately and handle work asynchronously. +- Drop Slack retries with `retry_num > 0`. +- Use reactions for acknowledgements: `eyes` while running, then `white_check_mark` or `x`. +- Do not reply just to acknowledge a mention; replies affect thread high-water marks. +- Coalesce mentions that arrive during a running turn into one follow-up turn. +- Keep stream readers attached until the agent process exits. +- Persist a new session id before posting the agent reply. +- Append transcript messages only after the reply posts successfully. + +## Sandbox Rules + +- Sandbox names come from `src/router/sandbox-name.ts`. +- Names must contain only `[A-Za-z0-9-]`; encode Slack `thread_ts` dots as `-`. +- Long names may hash, but `~/.agent-state/thread` must preserve the full thread id. +- Use `sbx create --clone` so agents work in an isolated VM, not the host tree. +- `sbx exec` commands are wrapped in `bash -c`. +- Do not use `--ephemeral`; resume depends on sandbox state. +- Agent stdin is redirected from `/dev/null` inside the VM to avoid headless hangs. +- Provisioning must create a writable repo clone, set `origin`, and start from a non-base branch. + +## Logging + +- Boundary logs go through `src/router/log.ts`. +- Summary logging is always on; verbose logging may include raw stdout/stderr and full prompts. +- Logging is best-effort and must not block or fail a turn. +- Do not log credentials or credential-shaped fields. + +## Commits + +- Keep commits small and logical. +- Keep `npm run check` green at every commit. +- Include the project `Co-Authored-By` trailer when making commits. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee3d376 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Mishu contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b9b174f..cbfffc0 100644 --- a/README.md +++ b/README.md @@ -1,168 +1,135 @@ # Mishu -**Mishu is a coding agent that lives in your team's chat.** Mention it in a thread — a real coding -agent does the work in an isolated sandbox with your repo and credentials, and replies in the same -thread. Follow-up mentions continue the same session; ask it to open a PR and it ships the work itself. +Mishu is a Slack bot that lets a team run coding-agent work from a shared thread. +Mention the bot, and it starts a coding-agent CLI inside an isolated `sbx` sandbox, then replies in +the same thread. Follow-up mentions in that thread resume the same sandbox and agent session. -It works in your team's communication platform — **Slack today**, more later. It's plumbing, not a -chatbot: every thread maps 1:1 to its own microVM sandbox and coding-agent session, and the router -itself runs no LLM — it just relays your thread to a coding-agent CLI (Codex or Claude Code) and -relays the result back. +The router is deliberately small: it does not call an LLM, classify intent, or decide whether to +delegate. It moves Slack thread text to a coding agent, captures the result, and keeps each Slack +thread mapped to one sandbox. -**Driving philosophy — work in the open.** People collaborate best in the open, and so should a coding -agent. A private window only teaches the person at the keyboard; a shared thread teaches everyone -watching. When the work happens where the whole team can see it, every session is searchable, teachable, -and compounding — the next person with the same question doesn't have to ask it. +## Features -Inspired by [pi-mom](https://www.npmjs.com/package/@mariozechner/pi-mom) and Shopify's -[River](https://shopify.engineering/under-the-river) — see Tobi Lütke on -[working in the open](https://x.com/tobi/status/2053121182044451016). +- One Slack thread maps to one sandbox and one agent session. +- Follow-up mentions resume the same coding-agent context. +- Different threads can run in parallel. +- Sandboxes keep per-thread state under `~/.agent-state/`. +- The coding agent can push or open a PR when the sandbox has GitHub credentials. +- Boundary logs are written as JSONL for debugging and audits. ---- +## Requirements -## What you can do with it +- Node.js 22+ +- The Docker Sandboxes `sbx` CLI +- A Slack app with Socket Mode enabled +- A target git repo +- A Codex or Claude Code credential configured in `sbx` -- **Fix an issue from a thread.** Describe a problem, `@mention` the bot, and it reads the thread, - makes the change in your repo, and replies in-thread. -- **Iterate conversationally.** Mention it again in the same thread to continue — it remembers prior - turns and any messages teammates added in between. -- **Run work in parallel.** Different threads run independent tasks at the same time without colliding. -- **Pick up after idle.** Mention it a day later and it resumes where it left off — code and - conversation both restored. -- **Ship it.** Ask it to push or open a PR and the agent runs `git`/`gh` itself from inside the sandbox. +For PR creation, the target repo should have a pushable `origin`, and the sandbox needs a repo-scoped +GitHub credential: -**Reading the bot's reactions:** 👀 means it picked up your mention and is working; it swaps to ✅ on -success or ❌ on failure when the turn finishes. The actual answer is posted as a reply in the thread. - ---- - -## Set it up +```bash +sbx secret set -g github +``` -You need the **`sbx` CLI** (Docker Sandboxes, v0.31.1 — it bundles its own container runtime and -hypervisor, so **Docker Desktop is not required**), a **Slack app**, and a **target git repo**. -One-time setup: +## Setup -### 1. Sandboxes (`sbx`) +### 1. Install dependencies ```bash -sbx login # sign in to Docker (to pull the sandbox images) -sbx policy set-default balanced # allow OpenAI / GitHub / package registries - -# Give the coding agent its credential (must exist BEFORE the bot creates sandboxes): -sbx secret set -g openai --oauth # ChatGPT subscription (browser sign-in) -# …or, to avoid subscription rate limits, an API key: -# sbx secret set -g openai # paste an sk-… key -sbx secret ls # should show: openai (… configured) +npm install ``` -> **Prefer a guided flow?** After `npm install` (below), `npm run setup` asks which agent (or honors -> `MISHU_AGENT=codex|claude`), checks the credential, and walks you through the browser OAuth (Codex) -> or in-sandbox `/login` (Claude). Mishu prints the same one-liner if it starts without one. - -### 2. Slack app (Socket Mode) - -Never made a Slack app? Follow these steps: - -1. **Create the app** — go to → **Create New App** → **From scratch**, - name it, and pick your workspace. -2. **Enable Socket Mode** — *Settings → Socket Mode* → toggle **Enable Socket Mode** on. -3. **App-Level Token** — generate one (the Socket Mode toggle prompts for it, or *Settings → Basic - Information → App-Level Tokens*) with the **`connections:write`** scope. This `xapp-…` token is your - **`APP_SLACK_APP_TOKEN`**. -4. **Bot Token Scopes** — *Features → OAuth & Permissions → Scopes → Bot Token Scopes*, add: - - `app_mentions:read` — receive the `@mention` that triggers a turn - - `chat:write` — post replies in the thread - - `reactions:write` — the 👀 / ✅ / ❌ acks - - `channels:history` — read thread messages in public channels - - `groups:history` — read thread messages in private channels -5. **Subscribe to events** — *Features → Event Subscriptions* → toggle **Enable Events** on → under - **Subscribe to bot events**, add **`app_mention`** (the only event this bot needs). -6. **Install** — *Settings → Install App* → install to your workspace → copy the **Bot User OAuth - Token** (`xoxb-…`). This is your **`APP_SLACK_BOT_TOKEN`**. -7. **Invite the bot** to any channel you want it to work in: `/invite @YourBot` — it only sees - messages in channels it's been added to. - -Put the two tokens in a `.env` file at the repo root: +### 2. Configure the coding-agent credential + +Codex is the default agent: ```bash -APP_SLACK_APP_TOKEN=xapp-… # step 3 — app-level token (Socket Mode) -APP_SLACK_BOT_TOKEN=xoxb-… # step 6 — bot user OAuth token +npm run setup ``` -### 3. Run +To set up Claude Code instead: ```bash -npm install -npm run build -MISHU_REPO=/path/to/your/repo \ - node --env-file=.env dist/cli/index.js --sandbox=sbx ./data +MISHU_AGENT=claude npm run setup ``` -On first use the bot checks `sbx secret ls`; if the agent's credential is missing it prints the exact -setup steps and exits. Once it prints `listening …`, **`@mention` it in your channel** and it goes to -work. Optional env: `MISHU_AGENT=codex|claude` (default codex), `MISHU_LOG_LEVEL=summary|verbose`, -`MISHU_BOT_USER=U…`. +The setup command checks `sbx secret ls` and runs the needed interactive login flow. -> To let the agent **push / open PRs**, also give it a repo-scoped GitHub token: -> `sbx secret set -g github` (prefer a fine-grained PAT scoped to the target repo). +### 3. Create the Slack app -### Watch what it's doing +Create an app at , enable Socket Mode, and add these scopes: -Every turn is logged at the agent boundary to `./data/router.log` (JSONL). Tail one thread: +| Token | Scopes | +| --- | --- | +| App-level token | `connections:write` | +| Bot token | `app_mentions:read`, `chat:write`, `reactions:write`, `channels:history`, `groups:history` | + +Subscribe the app to the `app_mention` bot event, install it to your workspace, and invite it to the +channels where it should work. + +Create `.env` from [.env.example](./.env.example): ```bash -tail -f ./data/router.log | jq 'select(.threadId=="t--")' +APP_SLACK_APP_TOKEN=xapp-... +APP_SLACK_BOT_TOKEN=xoxb-... ``` ---- - -## How it works +### 4. Run Mishu +```bash +npm run build +MISHU_REPO=/path/to/your/repo \ + node --env-file=.env dist/cli/index.js --sandbox=sbx ./data ``` -Slack ⇄ PlatformAdapter ⇄ ROUTER (no durable state, NOT an LLM) ⇄ SandboxProvider ⇄ microVM - └ CodingBackend (Codex CLI) -``` -The router owns only transient in-memory coordination (an idle/running/pending lock + ts-dedupe). -Per-thread durable state — the seen-message ledger and the coding-agent session id — lives **inside -each sandbox** (`~/.agent-state/`); the router finds a thread's sandbox by deterministic naming + -`sbx ls`, with no database. Durability is the **PR + the Slack thread**, not a state store. Isolation -is a hypervisor-backed microVM per thread, with host-proxied credentials that never enter the VM. +When Mishu prints `listening`, mention the bot in Slack. + +## Configuration + +| Variable | Required | Description | +| --- | --- | --- | +| `APP_SLACK_APP_TOKEN` | Yes | Slack app-level Socket Mode token (`xapp-...`). | +| `APP_SLACK_BOT_TOKEN` | Yes | Slack bot user token (`xoxb-...`). | +| `MISHU_REPO` | Yes | Repo path or ref passed to `sbx create --clone`. | +| `MISHU_AGENT` | No | `codex` or `claude`; default is `codex`. | +| `MISHU_LOG_LEVEL` | No | `summary` or `verbose`; default is `summary`. | +| `MISHU_BOT_USER` | No | Slack bot user id; resolved with `auth.test` when omitted. | -Three swappable seams (Slack · sbx · Codex today; Discord/e2b/Claude tomorrow) keep every -vendor-specific choice behind an interface. Standalone project; conventions learned from pi-mom but -**no code shared**. +## Logs ---- +Each turn is logged to `./data/router.log` as JSONL. Summary logs include argv, prompt size/hash, exit +code, duration, and final-message snippet. Verbose logs also include raw stdout/stderr chunks. + +```bash +tail -f ./data/router.log | jq 'select(.threadId=="t--")' +``` + +Credentials are stored and injected by `sbx`; Mishu does not put Slack, OpenAI, Anthropic, or GitHub +secrets in prompts or argv. ## Development -Working in this repo? **Read [AGENTS.md](./AGENTS.md) first** — it has the architecture invariants -(router-is-plumbing, transport ≠ interpretation, pure-core vs I/O-shell, the naming/logging/lifecycle -rules) you must follow. +Read [AGENTS.md](./AGENTS.md) before changing the router, sandbox, platform, or backend seams. -| Command | What it does | +| Command | Description | | --- | --- | -| `npm run check` | **The gate:** Biome (lint + format + imports) + `tsc --noEmit` + Vitest. Keep it green. | +| `npm run check` | Biome check, TypeScript, and Vitest. This is the main gate. | | `npm run check:fix` | Apply Biome fixes, then run the gate. | -| `npm test` / `test:watch` / `test:cov` | Run Vitest (offline unit tests). | -| `npm run test:live` | `*.live.test.ts` against the real sbx daemon (needs `sbx` running; **not** in CI). | -| `npm run build` | Emit `dist/` (`tsc -p tsconfig.build.json`, excludes tests). | -| `npm start` / `npm run dev` | Run the built CLI / run under `tsx watch`. | - -Pure logic is separated from I/O so almost everything is unit-tested offline with injected fakes (the -dispatcher test is the logical end-to-end); the live sbx/Codex and Socket Mode paths are verified -hands-on. **TypeScript** `strict`, **ESM (NodeNext)** — relative imports use `.js`, type-only imports -use `import type`. **Biome** is the single lint/format tool. CI runs `check` then `build`. +| `npm test` | Run offline unit tests. | +| `npm run test:live` | Run live `sbx` tests. Requires the `sbx` daemon and is not part of CI. | +| `npm run build` | Compile `dist/`. | +| `npm run dev` | Run the CLI under `tsx watch`. | -### Layout (by seam) +## Architecture ``` -src/ - platform/ slack-map.ts (pure event→Mention) + slack.ts (Socket Mode + Web API adapter) - sandbox/ sbx-argv.ts (pure argv builders) + sbx-provider.ts (spawn/drain I/O shell) - backend/ codex.ts (turnArgs/parseResult/sessionId — the ONLY agent-format knowledge) + fixtures/ - router/ state-machine, dedupe, sandbox-name, agent-state(+store), log, dispatcher, idle-sweep, router - cli/ args.ts + onboarding.ts + index.ts (composition root) - types.ts shared types +Slack <-> PlatformAdapter <-> Router <-> SandboxProvider <-> sandbox + \-> CodingBackend ``` + +- `src/platform/`: Slack event mapping and Slack SDK adapter. +- `src/router/`: thread state machine, dedupe, sandbox naming, state store, logging, dispatcher, idle sweep. +- `src/sandbox/`: `sbx` argv builders and provider. +- `src/backend/`: Codex and Claude Code argument builders and output parsers. +- `src/cli/`: argument parsing, setup flow, and composition root. diff --git a/package.json b/package.json index bddcc28..af1752c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,15 @@ { "name": "mishu", "version": "0.0.1", - "private": true, "type": "module", "description": "Mishu — a coding agent that lives in your team's chat (Slack today): mention it in a thread and it does the work in an isolated sandbox, then replies in-thread.", "license": "MIT", + "files": [ + "dist", + ".env.example", + "README.md", + "LICENSE" + ], "bin": { "mishu": "dist/cli/index.js" }, diff --git a/src/backend/claude.test.ts b/src/backend/claude.test.ts index d1cb8f7..83410e1 100644 --- a/src/backend/claude.test.ts +++ b/src/backend/claude.test.ts @@ -160,6 +160,13 @@ describe("ClaudeBackend.captureSessionId (I/O via injected executor)", () => { await expect(backend.captureSessionId(handle)).rejects.toThrow(/no session transcript/); }); + it("throws when the newest transcript filename has no session id", async () => { + const backend = new ClaudeBackend( + fakeExecutor("1780000000.0\t/home/agent/.claude/bad.jsonl\n"), + ); + await expect(backend.captureSessionId(handle)).rejects.toThrow(/could not parse/); + }); + it("exposes the pure helpers through the interface", () => { const backend = new ClaudeBackend(fakeExecutor("")); expect(backend.configHome()).toBe("~/.claude"); diff --git a/src/backend/claude.ts b/src/backend/claude.ts index ae08cf9..4bfb394 100644 --- a/src/backend/claude.ts +++ b/src/backend/claude.ts @@ -2,43 +2,11 @@ import type { SandboxHandle } from "../sandbox/index.js"; import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js"; import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; -/** - * Claude Code backend — the ONLY place Claude Code's CLI/output format lives. - * Everything here is pure (off captured bytes) except - * captureSessionId, whose one shell call is injected so the rest stays - * unit-testable. - * - * Verified: `claude -p --resume ` continues the SAME session - * — id stable across turns, history replayed, surviving sbx stop/restart — so - * the router captures the id once on turn 1 and resumes with it, exactly like - * codex. - * - `claude -p --output-format stream-json --verbose [--resume ID] -- PROMPT` - * - `--output-format stream-json` REQUIRES `--verbose` in print mode. - * - The prompt is a positional (not stdin) so the provider can close stdin; - * `--` guards a prompt that might start with `-`. - * - The stream's first `{"type":"system","subtype":"init",…}` line carries - * `session_id` (parseSessionId reads it live); the final `{"type":"result",…}` - * line carries the answer + `is_error`. - */ - export const CLAUDE_HOME = "~/.claude"; -/** Shell-expanded ($HOME) inside execShell, not single-quoted. */ export const CLAUDE_PROJECTS_PATH = "$HOME/.claude/projects"; -/** - * Non-blocking headless — the analog of codex's `approval_policy=never`: never - * block on a permission prompt. The sbx microVM is the isolation boundary - * (Slack content is untrusted), so defense-in-depth is the hypervisor, - * not Claude's in-process permission gate. Isolated here so the choice is one - * edit (`--permission-mode bypassPermissions` is an equivalent newer spelling). - */ export const CLAUDE_NONBLOCKING_FLAGS = ["--dangerously-skip-permissions"]; -/** - * Build the turn invocation. `--` guards a prompt that might start with `-`. - * Prompt is a positional (not stdin) so the provider can close stdin. - * `--verbose` is mandatory with `--output-format stream-json` in print mode. - */ export function claudeTurnArgs(message: string, sessionId?: string | null): string[] { const common = ["-p", "--output-format", "stream-json", "--verbose", ...CLAUDE_NONBLOCKING_FLAGS]; if (sessionId === undefined || sessionId === null) { @@ -47,7 +15,6 @@ export function claudeTurnArgs(message: string, sessionId?: string | null): stri return ["claude", ...common, "--resume", sessionId, "--", message]; } -/** Concatenated text blocks of an `{type:"assistant", message:{content:[…]}}` event. */ function extractAssistantText(event: Record): string | null { if (asString(event.type) !== "assistant") { return null; @@ -65,14 +32,6 @@ function extractAssistantText(event: Record): string | null { return text === "" ? null : text; } -/** - * Interpret the captured stream-json into the result to relay. The authoritative - * final answer is the `{"type":"result", result, is_error, …}` event; the last - * assistant text is a fallback if a run ends without one. Empty output -> - * {"", false}: the headless empty-output failure mode surfaces as a failed turn, - * not a silent empty reply (parity with codex). On `is_error` the error - * text (or the `subtype`, e.g. "error_max_turns") is relayed, still with ok=false. - */ export function parseClaudeResult(captured: string): TurnResult { if (captured.trim() === "") { return { finalText: "", ok: false }; @@ -88,7 +47,7 @@ export function parseClaudeResult(captured: string): TurnResult { try { event = JSON.parse(line); } catch { - continue; // tolerate non-JSON progress lines + continue; } if (!isRecord(event)) { continue; @@ -111,15 +70,14 @@ export function parseClaudeResult(captured: string): TurnResult { return { finalText, ok: true }; } if (errorText.trim() !== "") { - return { finalText: errorText, ok: false }; // relay the agent's own error + return { finalText: errorText, ok: false }; } if (assistantText.trim() !== "") { - return { finalText: assistantText, ok: true }; // no result event, but it answered + return { finalText: assistantText, ok: true }; } return { finalText: "", ok: false }; } -/** Extract the session id from a stream-json turn (every line carries it; init is first). */ export function parseClaudeSessionId(captured: string): string | null { for (const line of captured.split("\n")) { if (line.trim() === "") { @@ -143,7 +101,6 @@ export function parseClaudeSessionId(captured: string): string | null { const SESSION_UUID_RE = /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i; -/** The session UUID from a `.jsonl` transcript filename. */ export function parseClaudeSessionFilename(filename: string): string | null { const match = SESSION_UUID_RE.exec(filename); return match?.[1] ?? null; @@ -172,7 +129,6 @@ export class ClaudeBackend implements CodingBackend { return parseJsonlEvents(captured); } - /** Fallback when the stream had no id: newest transcript under CLAUDE_PROJECTS_PATH. */ async captureSessionId(handle: SandboxHandle): Promise { const command = `find ${CLAUDE_PROJECTS_PATH} -name '*.jsonl' -printf '%T@\\t%p\\n' 2>/dev/null`; const { stdout } = await this.executor.execShell(handle, command); diff --git a/src/backend/codex.test.ts b/src/backend/codex.test.ts index 2c4558f..8d9beb4 100644 --- a/src/backend/codex.test.ts +++ b/src/backend/codex.test.ts @@ -154,6 +154,11 @@ describe("CodexBackend.captureSessionId (I/O via injected executor)", () => { await expect(backend.captureSessionId(handle)).rejects.toThrow(/no rollout file/); }); + it("throws when the newest rollout filename has no session id", async () => { + const backend = new CodexBackend(fakeExecutor("1780000000.0\t/home/agent/.codex/bad.jsonl\n")); + await expect(backend.captureSessionId(handle)).rejects.toThrow(/could not parse/); + }); + it("exposes the pure helpers through the interface", () => { const backend = new CodexBackend(fakeExecutor("")); expect(backend.configHome()).toBe("~/.codex"); diff --git a/src/backend/codex.ts b/src/backend/codex.ts index af68e37..ebb800c 100644 --- a/src/backend/codex.ts +++ b/src/backend/codex.ts @@ -2,31 +2,9 @@ import type { SandboxHandle } from "../sandbox/index.js"; import type { CodingBackend, SandboxShellExecutor, TurnResult } from "./index.js"; import { asString, isRecord, newestByMtime, parseJsonlEvents } from "./parsing.js"; -/** - * Codex backend — the ONLY place Codex's CLI/output format lives. - * Everything here is pure (off captured bytes) except captureSessionId, whose - * one shell call is injected so the rest stays unit-testable. - * - * Verified against codex 0.135.0 (host) and run live on 0.130.0 (in an sbx VM): - * - `codex exec [--json] [-c k=v]... [-- PROMPT]` - * - `codex exec resume [--json] [-c k=v]... [-- SESSION_ID PROMPT]` - * - `resume` has `-c` and `--json` but NOT `-s/--sandbox`, so we set the sandbox - * mode + approval policy via `-c` uniformly (works on both subcommands). - * - NEVER `--ephemeral` (breaks resume). The `--json` stream's `thread.started` - * event carries `thread_id` (= the session id; parseSessionId reads it live). - * - codex blocks on stdin headlessly; the provider redirects its stdin from - * /dev/null IN the VM — verified live. - */ - export const CODEX_HOME = "~/.codex"; -/** Shell-expanded ($HOME) inside execShell, not single-quoted. */ export const CODEX_SESSIONS_PATH = "$HOME/.codex/sessions"; -/** - * Non-blocking defense-in-depth: keep Codex's native sandbox ON - * (workspace-write) but never block headless on an approval prompt. Set via `-c` - * because `resume` lacks `-s`. Exact keys confirmed live. - */ export const CODEX_NONBLOCKING_FLAGS = [ "-c", "sandbox_mode=workspace-write", @@ -34,14 +12,7 @@ export const CODEX_NONBLOCKING_FLAGS = [ "approval_policy=never", ]; -/** - * Build the turn invocation. `--` guards a prompt/id that might start with `-`. - * Prompt is a positional (not stdin) so the provider can close stdin. - */ export function codexTurnArgs(message: string, sessionId?: string | null): string[] { - // --skip-git-repo-check: never block headless on codex's trusted-directory - // check (the microVM is the boundary). Verified live: without it, codex exits - // "Not inside a trusted directory". const common = ["--json", "--skip-git-repo-check", ...CODEX_NONBLOCKING_FLAGS]; if (sessionId === undefined || sessionId === null) { return ["codex", "exec", ...common, "--", message]; @@ -50,14 +21,12 @@ export function codexTurnArgs(message: string, sessionId?: string | null): strin } function extractAssistantText(event: Record): string | null { - // Most authoritative: a completion event carrying the last message verbatim. const last = asString(event.last_agent_message) ?? (isRecord(event.msg) ? asString(event.msg.last_agent_message) : null); if (last !== null) { return last; } - // `item.*` thread events: { item: { type: "assistant_message", text } }. const item = isRecord(event.item) ? event.item : null; if (item !== null) { const itemType = asString(item.type) ?? asString(item.item_type); @@ -65,12 +34,10 @@ function extractAssistantText(event: Record): string | null { return asString(item.text) ?? asString(item.message); } } - // `msg`-wrapped events: { msg: { type: "agent_message", message } }. const msg = isRecord(event.msg) ? event.msg : null; if (msg !== null && asString(msg.type) === "agent_message") { return asString(msg.message) ?? asString(msg.text); } - // Flat events: { type: "assistant_message", text }. const type = asString(event.type); if (type === "assistant_message" || type === "agent_message") { return asString(event.text) ?? asString(event.message); @@ -78,7 +45,6 @@ function extractAssistantText(event: Record): string | null { return null; } -/** Error text from an `error` / `turn.failed` event (real shapes, verified live). */ function extractErrorMessage(event: Record): string | null { const type = asString(event.type) ?? ""; if ( @@ -92,14 +58,6 @@ function extractErrorMessage(event: Record): string | null { return asString(event.message) ?? (isRecord(event.error) ? asString(event.error.message) : null); } -/** - * Interpret the captured `--json` stream into the result to relay. Tolerant of - * several event shapes (the schema moves between versions) and of non-JSON - * progress lines. Empty output -> {"", false}: the headless regression - * (argv -> 0 bytes, exit 0) surfaces as a failed turn, not a silent empty reply. - * On a failed turn the codex error message is relayed (e.g. a usage limit), - * still with ok=false. - */ export function parseCodexResult(captured: string): TurnResult { if (captured.trim() === "") { return { finalText: "", ok: false }; @@ -140,7 +98,6 @@ function findSessionId(record: Record): string | null { ); } -/** Extract the session id from a turn-1 `--json` stream (no filesystem). */ export function parseSessionId(captured: string): string | null { for (const line of captured.split("\n")) { if (line.trim() === "") { @@ -165,7 +122,6 @@ export function parseSessionId(captured: string): string | null { const ROLLOUT_UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i; -/** The session UUID embedded in a `rollout-…-.jsonl` filename. */ export function parseRolloutId(filename: string): string | null { const match = ROLLOUT_UUID_RE.exec(filename); return match?.[1] ?? null; @@ -194,7 +150,6 @@ export class CodexBackend implements CodingBackend { return parseJsonlEvents(captured); } - /** Fallback when the stream had no id: newest rollout file under CODEX_HOME. */ async captureSessionId(handle: SandboxHandle): Promise { const command = `find ${CODEX_SESSIONS_PATH} -name 'rollout-*.jsonl' -printf '%T@\\t%p\\n' 2>/dev/null`; const { stdout } = await this.executor.execShell(handle, command); diff --git a/src/backend/index.ts b/src/backend/index.ts index 08a1262..ce3622f 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,45 +1,24 @@ import type { ExecResult, SandboxHandle } from "../sandbox/index.js"; -/** - * CodingBackend — per-agent knowledge: the ONLY place agent-format details - * live. Transport (SandboxProvider.exec) is router-owned - * and format-agnostic; only turnArgs / parseResult / events know the agent's - * format. - * - * v0: Codex. Then Claude Code, then Pi (the `pi` CLI — a third-party binary - * run inside the sandbox, not a code dependency). - */ export interface TurnResult { finalText: string; ok: boolean; } export interface CodingBackend { - /** e.g. ~/.codex, ~/.claude, ~/.pi/agent */ configHome(): string; - /** Build the CLI invocation. sessionId omitted → fresh session, else resume. */ turnArgs(message: string, sessionId?: string): string[]; - /** Interpret the raw captured stream into the result to relay (agent-specific). */ parseResult(captured: string): TurnResult; - /** After a fresh turn, read the new session's id (Codex: newest rollout filename). */ captureSessionId(handle: SandboxHandle): Promise; - /** - * OPTIONAL: extract the new session's id straight from the captured turn-1 - * stream (e.g. Codex prints it as a thread/session event) — pure and cheaper - * than {@link captureSessionId}. The dispatcher tries this first and falls - * back to captureSessionId. - */ parseSessionId?(captured: string): string | null; - /** OPTIONAL: normalized events to enrich logs; router degrades to raw bytes if absent. */ events?(captured: string): unknown[]; } -/** Minimal sandbox capability captureSessionId needs (one shell command). */ export interface SandboxShellExecutor { execShell(handle: SandboxHandle, command: string): Promise; } diff --git a/src/cli/args.test.ts b/src/cli/args.test.ts index c358b27..93ef1d9 100644 --- a/src/cli/args.test.ts +++ b/src/cli/args.test.ts @@ -15,6 +15,11 @@ describe("parseArgs", () => { expect(parseArgs([])).toBeNull(); }); + it("returns null for extra positionals or unknown flags", () => { + expect(parseArgs(["--sandbox=sbx", "./data", "./other"])).toBeNull(); + expect(parseArgs(["--sandbox=sbx", "--verbose", "./data"])).toBeNull(); + }); + it("returns null for -h / --help", () => { expect(parseArgs(["-h"])).toBeNull(); expect(parseArgs(["--sandbox=sbx", "./data", "--help"])).toBeNull(); diff --git a/src/cli/args.ts b/src/cli/args.ts index bcab01d..259c084 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -1,16 +1,8 @@ -/** - * CLI argument parsing (pure). Shape mirrors pi-mom's convention: - * - * mishu --sandbox= - * e.g. mishu --sandbox=sbx ./data - */ - export interface Args { sandbox: string; dataDir: string; } -/** Parse argv (without node/script), or null if invalid / help requested. */ export function parseArgs(argv: string[]): Args | null { let sandbox = ""; const positional: string[] = []; @@ -19,15 +11,16 @@ export function parseArgs(argv: string[]): Args | null { sandbox = arg.slice("--sandbox=".length); } else if (arg === "-h" || arg === "--help") { return null; + } else if (arg.startsWith("-")) { + return null; } else { positional.push(arg); } } - const dataDir = positional[0]; - if (sandbox === "" || dataDir === undefined) { + if (sandbox === "" || positional.length !== 1) { return null; } - return { sandbox, dataDir }; + return { sandbox, dataDir: positional[0] as string }; } export const USAGE = [ diff --git a/src/platform/slack.ts b/src/platform/slack.ts index fa56cdc..f1379ff 100644 --- a/src/platform/slack.ts +++ b/src/platform/slack.ts @@ -9,26 +9,12 @@ import { threadMessagesFromReplies, } from "./slack-map.js"; -/** - * Slack PlatformAdapter via Socket Mode + Web API — the I/O shell. - * The mapping logic lives in slack-map.ts; this only wires the SDKs. - * - * Discipline: the Socket Mode envelope is ACKed immediately on receipt, - * BEFORE any work, and the handler runs async to the ACK; retries - * (retry_num > 0) are dropped so a slow turn can't trigger double-dispatch. - * Acks to the user are reactions, not replies. - */ - export interface SlackAdapterConfig { - /** App-level token (xapp-…, connections:write) → Socket Mode. */ appToken: string; - /** Bot token (xoxb-…). */ botToken: string; - /** Bot user id; resolved via auth.test() at start() if omitted. */ botUserId?: string; } -/** The arg shape SocketModeClient emits for an events_api event. */ interface SocketModeEventArgs { ack: () => Promise; event: SlackAppMentionEvent; @@ -36,8 +22,15 @@ interface SocketModeEventArgs { } function slackErrorCode(err: unknown): string | undefined { - const data = (err as { data?: { error?: string } }).data; - return data?.error; + if (typeof err !== "object" || err === null || !("data" in err)) { + return undefined; + } + const data = (err as { data?: unknown }).data; + if (typeof data !== "object" || data === null || !("error" in data)) { + return undefined; + } + const error = (data as { error?: unknown }).error; + return typeof error === "string" ? error : undefined; } export class SlackAdapter implements PlatformAdapter { @@ -56,7 +49,6 @@ export class SlackAdapter implements PlatformAdapter { this.handler = handler; } - /** Resolve (and cache) the bot's own user id via auth.test (self-filtering). */ async whoAmI(): Promise { if (this.botUserId === undefined) { const auth = await this.web.auth.test(); @@ -65,18 +57,16 @@ export class SlackAdapter implements PlatformAdapter { return this.botUserId; } - /** Resolve the bot's identity, wire the listener, and open the socket. */ async start(): Promise { await this.whoAmI(); this.socket.on("app_mention", (args: SocketModeEventArgs) => { - // ACK at the WebSocket layer on receipt — never block the ~3s deadline. - void args.ack(); + void args.ack().catch(() => {}); if (typeof args.retry_num === "number" && args.retry_num > 0) { - return; // a retry of an event we already accepted + return; } const mention = mentionFromEvent(args.event, this.botUserId); if (mention !== null && this.handler !== null) { - this.handler(mention); // async to the ACK + this.handler(mention); } }); await this.socket.start(); @@ -121,7 +111,7 @@ export class SlackAdapter implements PlatformAdapter { await this.web.reactions.add({ channel, timestamp: ts, name: emoji }); } catch (err) { if (slackErrorCode(err) !== "already_reacted") { - throw err; // idempotent: re-adding our own 👀 is fine + throw err; } } } diff --git a/src/router/agent-state.test.ts b/src/router/agent-state.test.ts index dc90c33..181aaef 100644 --- a/src/router/agent-state.test.ts +++ b/src/router/agent-state.test.ts @@ -110,4 +110,8 @@ describe("serializeTranscript / parseTranscript", () => { const text = '{"ts":"1.1","user":"U","text":"a"}\n\n{"ts":"1.2","user":"U","text":"b"}'; expect(parseTranscript(text)).toEqual([msg("1.1", "U", "a"), msg("1.2", "U", "b")]); }); + + it("throws on malformed message records", () => { + expect(() => parseTranscript('{"ts":"1.1","user":"U"}')).toThrow(/invalid transcript line 1/); + }); }); diff --git a/src/router/agent-state.ts b/src/router/agent-state.ts index aeca089..ec320b7 100644 --- a/src/router/agent-state.ts +++ b/src/router/agent-state.ts @@ -1,29 +1,10 @@ import type { ThreadMessage } from "../types.js"; -/** - * Per-thread durable state — pure layer. - * - * The thread's seen-message ledger is `~/.agent-state/transcript.jsonl` in the - * sandbox: one JSON line per message the agent has been fed, `{ts, user, text}`. - * It is both the **high-water mark** (largest `ts`) and a durable audit of - * exactly what the agent saw. This file holds only the *pure* logic over message - * arrays; the sbx-backed read/write store lives in agent-state.store.ts. - * - * Paths are relative to the sandbox user's `$HOME` (resolved by the store). - */ export const AGENT_STATE_DIR = ".agent-state"; export const TRANSCRIPT_PATH = `${AGENT_STATE_DIR}/transcript.jsonl`; export const SESSION_PATH = `${AGENT_STATE_DIR}/session`; export const THREAD_PATH = `${AGENT_STATE_DIR}/thread`; -/** - * Largest `ts` in the ledger, or `null` when empty (which is how the router - * knows it's turn 1). - * - * Slack `ts` is `"<10-digit secs>.<6-digit micros>"`, zero-padded, so plain - * string comparison is chronological (verified by test). This holds until the - * seconds field gains an 11th digit (year 2286). - */ export function highWaterMark(transcript: ThreadMessage[]): string | null { let max: string | null = null; for (const message of transcript) { @@ -35,22 +16,9 @@ export function highWaterMark(transcript: ThreadMessage[]): string | null { } export interface DeltaOptions { - /** The bot's own Slack user id; its posts are never fed back to the agent. */ botUser?: string; } -/** - * The messages to feed this turn: everything after the high-water mark, minus - * the bot's own posts. - * - * - Turn 1 (`hwm === null`): the whole thread minus bot posts — the primer. - * - Follow-up: messages with `ts > hwm` minus bot posts — the exact delta. - * - * On "minus the trigger": the *previous* turn's trigger is already - * excluded by `ts > hwm` (it's in the ledger). The *current* trigger carries the - * user's new request and MUST be included — the delta covers all of - * them [the mentions]. So the only author we drop is the bot itself. - */ export function computeDelta( fetched: ThreadMessage[], hwm: string | null, @@ -78,19 +46,27 @@ export function serializeTranscript(messages: ThreadMessage[]): string { .join("\n")}\n`; } -/** - * Parse JSONL back to messages, tolerating a trailing newline and blank lines. - * Malformed lines throw (the ledger has a single writer and is append-only, so - * corruption is a real signal, not something to silently swallow). - */ export function parseTranscript(text: string): ThreadMessage[] { const messages: ThreadMessage[] = []; - for (const line of text.split("\n")) { + for (const [index, line] of text.split("\n").entries()) { if (line.trim() === "") { continue; } - const parsed = JSON.parse(line) as ThreadMessage; - messages.push({ ts: parsed.ts, user: parsed.user, text: parsed.text }); + const parsed: unknown = JSON.parse(line); + if (!isThreadMessage(parsed)) { + throw new Error(`invalid transcript line ${index + 1}`); + } + messages.push(parsed); } return messages; } + +function isThreadMessage(value: unknown): value is ThreadMessage { + return ( + typeof value === "object" && + value !== null && + typeof (value as ThreadMessage).ts === "string" && + typeof (value as ThreadMessage).user === "string" && + typeof (value as ThreadMessage).text === "string" + ); +} diff --git a/src/router/dedupe.test.ts b/src/router/dedupe.test.ts index f1a1d5d..986101d 100644 --- a/src/router/dedupe.test.ts +++ b/src/router/dedupe.test.ts @@ -53,11 +53,4 @@ describe("createTtlSet", () => { it("defaults the TTL to at least Slack's ~5-min retry window", () => { expect(DEFAULT_DEDUPE_TTL_MS).toBeGreaterThanOrEqual(5 * 60_000); }); - - it("defaults the clock to wall time (smoke: present right after add)", () => { - const set = createTtlSet(); // real clock, 5-min TTL - set.add("ts-now"); - expect(set.has("ts-now")).toBe(true); - expect(set.size()).toBe(1); - }); }); diff --git a/src/router/dedupe.ts b/src/router/dedupe.ts index 9231e7c..c80a12e 100644 --- a/src/router/dedupe.ts +++ b/src/router/dedupe.ts @@ -1,25 +1,9 @@ -/** - * Idempotent-dispatch dedupe. - * - * Immediate Socket Mode ACK stops the retry storm but duplicates still arrive (a - * lost ACK inside the retry window, or a reconnect race). The router dedupes on - * the mention's Slack `ts`: a `ts` already accepted is dropped. - * - * This is a TTL set keyed by `ts`, sized to cover Slack's ~5-min retry window. - * It lives in router memory (transient bucket) and is lost on restart, which - * is fine — retries don't outlive that window. The clock is injected so the TTL - * is testable without sleeping. - */ - /** Slack retries an unacked event for ~5 minutes (immediately, then ~1m, ~5m). */ export const DEFAULT_DEDUPE_TTL_MS = 5 * 60_000; export interface TtlSet { - /** True iff `key` was added within the TTL window. Evicts expired keys first. */ has(key: string): boolean; - /** Record `key` as seen at the current time. */ add(key: string): void; - /** Count of non-expired keys (test introspection). */ size(): number; } diff --git a/src/router/dispatcher.test.ts b/src/router/dispatcher.test.ts index 9d6a336..10ade36 100644 --- a/src/router/dispatcher.test.ts +++ b/src/router/dispatcher.test.ts @@ -42,7 +42,10 @@ class FakePlatform { class FakeSandbox { readonly files = new Map(); readonly existing: SandboxHandle[]; + failWrites = new Set(); execArgs: string[][] = []; + shellResults: ExecResult[] = []; + shellCommands: string[] = []; constructor( private readonly trace: string[], private readonly execResult: ExecResult, @@ -69,7 +72,8 @@ class FakeSandbox { } async execShell(_h: SandboxHandle, command: string): Promise { this.trace.push(`shell:${command.split(" ")[0]}`); - return { stdout: "", stderr: "", exitCode: 0 }; + this.shellCommands.push(command); + return this.shellResults.shift() ?? { stdout: "", stderr: "", exitCode: 0 }; } async homeDir(): Promise { return HOME; @@ -79,6 +83,9 @@ class FakeSandbox { } async writeFile(_h: SandboxHandle, absPath: string, content: string): Promise { this.trace.push(`write:${absPath.split("/").pop()}`); + if (this.failWrites.has(absPath)) { + throw new Error(`write failed: ${absPath}`); + } this.files.set(absPath, content); } async getFile(): Promise { @@ -136,6 +143,7 @@ function build(opts: { streamId?: string | null; seedFiles?: Record; level?: "summary" | "verbose"; + provisionScript?: string; }): Built { const trace: string[] = []; const platform = new FakePlatform( @@ -164,6 +172,7 @@ function build(opts: { repoRef: "/repo", logSink: sink, level: opts.level ?? "summary", + provisionScript: opts.provisionScript, now: () => 1_700_000_000_000, newTurnId: () => "turn-test", }); @@ -199,6 +208,10 @@ describe("dispatchTurn — turn 1 happy path", () => { // provisioned the repo and ran the agent IN it expect(t).toContain("shell:test"); // the `test -d …/.git || git clone …` provision expect(b.sandbox.lastCwd).toBe("/home/agent/repo"); + expect(b.sandbox.shellCommands.some((cmd) => cmd.includes("remote set-url origin"))).toBe(true); + expect(b.sandbox.shellCommands.some((cmd) => cmd.includes("checkout -q -B slack/work"))).toBe( + true, + ); }); it("reuses an existing sandbox instead of creating one", async () => { @@ -208,6 +221,16 @@ describe("dispatchTurn — turn 1 happy path", () => { expect(b.trace).toContain("exec"); }); + it("runs the optional setup script only when creating a sandbox", async () => { + const created = build({ provisionScript: "setup-agent" }); + await created.dispatcher.dispatchTurn(trigger); + expect(created.sandbox.shellCommands).toContain("setup-agent"); + + const existing = build({ existing: [SANDBOX], provisionScript: "setup-agent" }); + await existing.dispatcher.dispatchTurn(trigger); + expect(existing.sandbox.shellCommands).not.toContain("setup-agent"); + }); + it("falls back to captureSessionId when the stream has no id", async () => { const b = build({ streamId: null }); await b.dispatcher.dispatchTurn(trigger); @@ -259,7 +282,7 @@ describe("dispatchTurn — failure & skip", () => { const out = await b.dispatcher.dispatchTurn(trigger); expect(out).toEqual({ ok: false }); - expect(b.platform.replies[0]).toMatch(/didn't return a result/); + expect(b.platform.replies[0]).toMatch(/did not finish/); expect(b.trace).not.toContain("write:session"); expect(b.trace).not.toContain("write:transcript.jsonl"); expect(b.trace.slice(-2)).toEqual(["-eyes", "+x"]); @@ -273,6 +296,30 @@ describe("dispatchTurn — failure & skip", () => { expect(b.trace).not.toContain("write:transcript.jsonl"); // still not persisted (re-feed) }); + it("on provisioning failure: does not run the agent, posts failure, and ❌s", async () => { + const b = build({}); + b.sandbox.shellResults.push({ stdout: "", stderr: "clone failed", exitCode: 1 }); + const out = await b.dispatcher.dispatchTurn(trigger); + + expect(out).toEqual({ ok: false }); + expect(b.trace).not.toContain("exec"); + expect(b.platform.replies[0]).toMatch(/did not finish/); + expect(b.trace.slice(-2)).toEqual(["-eyes", "+x"]); + expect(b.sink.records.map((r) => r.kind)).toContain("provision.failed"); + expect(b.sink.records.map((r) => r.kind)).toContain("turn.abandoned"); + }); + + it("does not post a generic failure after the agent reply already posted", async () => { + const b = build({}); + b.sandbox.failWrites.add(`${HOME}/.agent-state/transcript.jsonl`); + const out = await b.dispatcher.dispatchTurn(trigger); + + expect(out).toEqual({ ok: false }); + expect(b.platform.replies).toEqual(["Fixed it."]); + expect(b.trace.slice(-2)).toEqual(["-eyes", "+x"]); + expect(b.sink.records.map((r) => r.kind)).toContain("turn.abandoned"); + }); + it("on empty delta: skips exec entirely and ✅s", async () => { const b = build({ existing: [SANDBOX], @@ -298,6 +345,7 @@ describe("dispatchTurn — failure & skip", () => { }; const out = await b.dispatcher.dispatchTurn(trigger); expect(out).toEqual({ ok: false }); + expect(b.platform.replies[0]).toMatch(/did not finish/); expect(b.sink.records.map((r) => r.kind)).toContain("turn.abandoned"); }); }); diff --git a/src/router/dispatcher.ts b/src/router/dispatcher.ts index 13af8ae..3846599 100644 --- a/src/router/dispatcher.ts +++ b/src/router/dispatcher.ts @@ -19,49 +19,39 @@ import { } from "./log.js"; import { chooseSandboxName } from "./sandbox-name.js"; -/** - * The one-turn lifecycle — the keystone that ties the - * three seams together for a single turn. Order matters: - * - * resolve-or-create sandbox (deterministic name) → turn-1-vs-resume (session - * file) → fetch primer/delta → exec (reader drains stdout+stderr, - * stays attached, logs at the boundary) → parseResult → write-after-success - * (persist session BEFORE posting, append transcript AFTER the reply) → - * relay → swap 👀→✅/❌. - * - * Reactions: the router adds 👀 on every mention; the dispatcher swaps the - * trigger's 👀 to ✅/❌ on completion. dispatchTurn never rejects — it resolves - * with {ok} so the router's coalescing always advances. - */ - export interface DispatcherDeps { platform: Pick; - /** Provider (create/exec/list) + file helpers for the agent-state store. */ sandbox: SandboxProvider & SandboxFsLike; backend: CodingBackend; repoRef: string; logSink?: LogSink; - /** The bot's own Slack user id; its posts never re-enter the delta. */ botUser?: string; level?: LogLevel; failureMessage?: string; - /** Optional in-VM bootstrap run once after create (branch off base; confirm live). */ + /** Optional setup run once after sandbox creation. */ provisionScript?: string; now?: () => number; newTurnId?: () => string; } -const DEFAULT_FAILURE = - "⚠️ The coding agent didn't return a result for that turn. Your request is still queued — mention me again to retry."; +const DEFAULT_FAILURE = "The coding agent did not finish that turn. Mention me again to retry."; -/** sbx --clone exposes the read-only source repo here (verified live). */ const SBX_CLONE_SOURCE = "/run/sandbox/source"; -/** In-VM writable clone dir the agent works in (under $HOME, outside ~/.agent-state). */ const IN_VM_REPO_DIR = "repo"; -/** Non-base branch the agent's work starts on. */ const WORK_BRANCH = "slack/work"; +const BARE_SHELL_WORD = /^[A-Za-z0-9_/.:=@%+,-]+$/; + +function shellQuote(value: string): string { + if (value === "") { + return "''"; + } + if (BARE_SHELL_WORD.test(value)) { + return value; + } + return `'${value.replace(/'/g, "'\\''")}'`; +} -/** Render messages into a single prompt. Plumbing, not authorship — no editorializing. */ +/** Render thread messages into the prompt passed to the coding agent. */ export function formatPrompt(messages: ThreadMessage[]): string { return messages.map((m) => `${m.user}: ${m.text}`).join("\n\n"); } @@ -96,6 +86,7 @@ export class Dispatcher { async dispatchTurn(trigger: Mention): Promise<{ ok: boolean }> { const thread = trigger.thread; const name = chooseSandboxName(thread); + let replyPosted = false; const ctx: LogContext = { threadId: name, channel: thread.channel, @@ -107,7 +98,6 @@ export class Dispatcher { const handle = await this.resolveSandbox(thread, name, ctx); const store = new AgentStateStore(this.sandbox, handle); - // Turn-1 vs resume is the session file's presence. const sessionId = await store.readSessionId(); const delta = await this.assembleDelta(thread, store, sessionId); if (delta.length === 0) { @@ -119,8 +109,6 @@ export class Dispatcher { const prompt = formatPrompt(delta); const argv = this.backend.turnArgs(prompt, sessionId ?? undefined); - // Provision a writable repo clone and run the agent IN it — verified - // live: without this codex exits "Not inside a trusted directory". const home = await this.sandbox.homeDir(handle); const repoPath = `${home}/${IN_VM_REPO_DIR}`; await this.provisionRepo(handle, repoPath, ctx); @@ -160,36 +148,34 @@ export class Dispatcher { exitCode, stderrSnippet: stderr.slice(0, 500), }); - // Relay the agent's own error (e.g. a usage limit) when it gave one, - // else a generic message. Either way the transcript is NOT appended, so - // the request re-feeds on the next mention. const reply = result.finalText.trim() !== "" ? result.finalText : this.failureMessage; await this.relay(thread, reply); + replyPosted = true; await this.swapReaction(trigger, false); return { ok: false }; } - // Write-after-success: persist the session id BEFORE posting (so a - // crash still resumes), append the transcript AFTER the reply (so a failed - // post re-feeds — at-least-once). if (sessionId === null) { const id = this.backend.parseSessionId?.(stdout) ?? (await this.backend.captureSessionId(handle)); await store.writeSessionId(id); } await this.relay(thread, result.finalText); + replyPosted = true; await store.appendDelta(delta); await this.swapReaction(trigger, true); this.router(ctx, RouterKind.ResultRelayed, { ok: true, chars: result.finalText.length }); return { ok: true }; } catch (err) { this.router(ctx, RouterKind.TurnAbandoned, { error: errorMessage(err) }); + if (!replyPosted) { + await this.relayFailure(thread); + } await this.swapReaction(trigger, false); return { ok: false }; } } - /** Find the thread's sandbox by deterministic name, or create + seed it. */ private async resolveSandbox( thread: ThreadId, name: string, @@ -211,27 +197,28 @@ export class Dispatcher { return handle; } - /** - * Clone the read-only `--clone` source into a writable repo dir and seed a - * non-base branch. Idempotent — runs cheaply each turn, clones once. - * The agent later points origin at the real remote + pushes when asked. - */ private async provisionRepo( handle: SandboxHandle, repoPath: string, ctx: LogContext, ): Promise { - const script = - `test -d ${repoPath}/.git || ` + - `{ git clone -q ${SBX_CLONE_SOURCE} ${repoPath} && ` + - `git -C ${repoPath} checkout -q -B ${WORK_BRANCH}; }`; + const source = shellQuote(SBX_CLONE_SOURCE); + const repo = shellQuote(repoPath); + const fallbackOrigin = shellQuote(this.repoRef); + const branch = shellQuote(WORK_BRANCH); + const script = [ + `test -d ${repo}/.git || git clone -q ${source} ${repo}`, + `origin="$(git -C ${source} remote get-url origin 2>/dev/null || printf %s ${fallbackOrigin})"`, + `{ git -C ${repo} remote get-url origin >/dev/null 2>&1 && git -C ${repo} remote set-url origin "$origin" || git -C ${repo} remote add origin "$origin"; }`, + `git -C ${repo} checkout -q -B ${branch}`, + ].join(" && "); const result = await this.sandbox.execShell(handle, script); if (result.exitCode !== 0) { this.router(ctx, "provision.failed", { stderrSnippet: result.stderr.slice(0, 300) }); + throw new Error(`repo provisioning failed: ${result.stderr.trim() || "unknown error"}`); } } - /** Turn 1 (no session): whole-thread primer. Follow-up: messages after the hwm. */ private async assembleDelta( thread: ThreadId, store: AgentStateStore, @@ -250,7 +237,14 @@ export class Dispatcher { await this.platform.postReply(thread, text); } - /** Best-effort 👀→✅/❌ swap on the trigger (reactions never fail a turn). */ + private async relayFailure(thread: ThreadId): Promise { + try { + await this.relay(thread, this.failureMessage); + } catch { + // The reaction still gives the user a failure signal if posting is unavailable. + } + } + private async swapReaction(trigger: Mention, ok: boolean): Promise { try { await this.platform.removeReaction(trigger.thread.channel, trigger.ts, "eyes"); diff --git a/src/router/idle-sweep.test.ts b/src/router/idle-sweep.test.ts index 37f70dc..ee2f832 100644 --- a/src/router/idle-sweep.test.ts +++ b/src/router/idle-sweep.test.ts @@ -46,19 +46,23 @@ describe("selectIdle", () => { class FakeSweepSandbox { stopped: string[] = []; + failStops = new Set(); readonly files = new Map(); constructor(private readonly handles: SandboxHandle[]) {} async list(): Promise { return this.handles; } async stop(handle: SandboxHandle): Promise { + if (this.failStops.has(handle.name)) { + throw new Error(`stop failed: ${handle.name}`); + } this.stopped.push(handle.name); } async homeDir(): Promise { return "/home/agent"; } - async readFile(_h: SandboxHandle, absPath: string): Promise { - return this.files.get(absPath) ?? null; + async readFile(handle: SandboxHandle, absPath: string): Promise { + return this.files.get(`${handle.name}:${absPath}`) ?? this.files.get(absPath) ?? null; } async writeFile(): Promise {} // unused provider surface @@ -79,8 +83,10 @@ class FakeSweepSandbox { } class FakeSweepPlatform { + fetched: ThreadId[] = []; constructor(private readonly byThread: Map) {} async fetchThread(thread: ThreadId): Promise { + this.fetched.push(thread); return this.byThread.get(`${thread.channel}:${thread.threadTs}`) ?? []; } } @@ -119,4 +125,35 @@ describe("IdleSweeper.sweepOnce", () => { expect((await sweeper.sweepOnce()).stopped).toEqual([hashName]); }); + + it("ignores hash-named sandboxes with missing or malformed reverse-map state", async () => { + const malformed = "t-0123456789abcdef"; + const missing = "t-fedcba9876543210"; + const sandbox = new FakeSweepSandbox([{ name: malformed }, { name: missing }]); + sandbox.files.set(`${malformed}:/home/agent/.agent-state/thread`, "malformed"); + const platform = new FakeSweepPlatform(new Map()); + const sweeper = new IdleSweeper({ sandbox, platform, idleMs: 10_000, now: () => NOW }); + + expect((await sweeper.sweepOnce()).stopped).toEqual([]); + expect(platform.fetched).toEqual([]); + }); + + it("continues sweeping when one idle sandbox fails to stop", async () => { + const handles: SandboxHandle[] = [ + { name: "t-C0A-1748600000-000000" }, + { name: "t-C0B-1748600000-000000" }, + ]; + const sandbox = new FakeSweepSandbox(handles); + sandbox.failStops.add("t-C0A-1748600000-000000"); + const platform = new FakeSweepPlatform( + new Map([ + ["C0A:1748600000.000000", [msg("1748600000.000000")]], + ["C0B:1748600000.000000", [msg("1748600000.000000")]], + ]), + ); + const sweeper = new IdleSweeper({ sandbox, platform, idleMs: 10_000, now: () => NOW }); + + expect((await sweeper.sweepOnce()).stopped).toEqual(["t-C0B-1748600000-000000"]); + expect(sandbox.stopped).toEqual(["t-C0B-1748600000-000000"]); + }); }); diff --git a/src/router/index.ts b/src/router/index.ts index b184ebb..08737c9 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,8 +1,2 @@ -/** - * Router barrel — the plumbing that maps Slack threads to coding-agent turns. - * The router is NOT an LLM: no model calls, no intent - * detection. See router.ts for the glue and dispatcher.ts for the turn - * lifecycle. - */ export { Dispatcher, type DispatcherDeps, formatPrompt } from "./dispatcher.js"; export { Router, type RouterOptions, type TurnDispatcher } from "./router.js"; diff --git a/src/router/log.test.ts b/src/router/log.test.ts index 870f1bf..ac8fa01 100644 --- a/src/router/log.test.ts +++ b/src/router/log.test.ts @@ -10,7 +10,6 @@ import { hashString, type LogContext, type LogSink, - nullSink, RouterKind, safeWrite, summarizePrompt, @@ -136,19 +135,6 @@ describe("safeguards", () => { it("capArgv shortens long elements", () => { expect(capArgv(["short", "z".repeat(500)], 10)[1]).toContain("…"); }); - - it("never emits credential-shaped fields", () => { - const env = buildTurnIn(ctx, TS, { - sessionId: null, - argv: ["codex"], - cwd: "/w", - prompt: "p", - level: "verbose", - }); - for (const key of Object.keys(env)) { - expect(key).not.toMatch(/token|secret|password|apikey|credential/i); - } - }); }); describe("sinks — best-effort, never throwing", () => { @@ -183,8 +169,4 @@ describe("sinks — best-effort, never throwing", () => { }; expect(() => safeWrite(bad, buildRouterEvent(ctx, TS, RouterKind.Error))).not.toThrow(); }); - - it("nullSink is a no-op", () => { - expect(() => nullSink.write(buildRouterEvent(ctx, TS, RouterKind.AckReaction))).not.toThrow(); - }); }); diff --git a/src/router/router.test.ts b/src/router/router.test.ts index 4980bca..92d8b58 100644 --- a/src/router/router.test.ts +++ b/src/router/router.test.ts @@ -48,6 +48,17 @@ describe("dedupe", () => { await router.onMention({ ...m1 }); // same ts re-delivered expect(dispatcher.calls).toHaveLength(1); }); + + it("does not dedupe the same ts across different channels", async () => { + const { router, dispatcher } = build(); + const other = { + ...m1, + thread: { channel: "COTHER", threadTs: m1.thread.threadTs }, + }; + await router.onMention(m1); + await router.onMention(other); + expect(dispatcher.calls).toEqual([m1, other]); + }); }); describe("idle/running/pending", () => { diff --git a/src/router/router.ts b/src/router/router.ts index 3657a5f..28c1569 100644 --- a/src/router/router.ts +++ b/src/router/router.ts @@ -12,21 +12,6 @@ import { import { chooseSandboxName } from "./sandbox-name.js"; import { INITIAL_STATE, reduce, type ThreadFsmState } from "./state-machine.js"; -/** - * Router glue — plumbing, not an LLM. Owns the transient - * per-thread coordination: the idle/running/pending FSM, ts-dedupe, the 👀 ack - * on every mention, and a per-thread mutex so the state read-modify-write is - * atomic. All durable state lives in the sandbox; these maps are - * memory-only and reset on restart, which is fine — an in-flight turn is - * abandoned and re-driven idempotently on the next mention. - * - * Dispatching is fire-and-forget: the turn (and its stream-reader) outlives the - * dispatch so the router is free to service other threads, while this thread's - * turn keeps draining. Cross-thread turns run in parallel; turns within a - * thread serialize and coalesce. - */ - -/** What the router needs from the dispatcher (the real Dispatcher fits). */ export interface TurnDispatcher { dispatchTurn(trigger: Mention): Promise<{ ok: boolean }>; } @@ -41,6 +26,10 @@ function threadKey(thread: ThreadId): string { return `${thread.channel}:${thread.threadTs}`; } +function dedupeKey(mention: Mention): string { + return `${mention.thread.channel}:${mention.ts}`; +} + export class Router { private readonly states = new Map(); private readonly latestMention = new Map(); @@ -65,24 +54,22 @@ export class Router { async onMention(mention: Mention): Promise { const key = threadKey(mention.thread); + const dedupe = dedupeKey(mention); - // 1. Idempotent dispatch on ts — drop already-accepted triggers. - if (this.dedupe.has(mention.ts)) { + if (this.dedupe.has(dedupe)) { this.logRouter(mention.thread, mention.ts, RouterKind.MentionDeduped, { ts: mention.ts }); return; } - this.dedupe.add(mention.ts); + this.dedupe.add(dedupe); this.logRouter(mention.thread, mention.ts, RouterKind.MentionReceived, { user: mention.user }); this.latestMention.set(key, mention); - // 2. Atomic state read-modify-write under the per-thread lock. const effects = await this.withLock(key, () => { const { state, effects } = reduce(this.stateOf(key), { kind: "mention" }); this.states.set(key, state); return effects; }); - // 3. Effects run outside the lock (they don't touch FSM state). for (const effect of effects) { if (effect === "ackRunning" || effect === "ackPending") { void this.ack(mention); @@ -92,7 +79,6 @@ export class Router { } } - /** Fire-and-forget a turn; on completion, advance the FSM (coalescing). */ private dispatch(key: string, trigger: Mention): void { void this.dispatcher.dispatchTurn(trigger).then( () => this.onTurnFinished(key), @@ -121,7 +107,6 @@ export class Router { return this.states.get(key) ?? INITIAL_STATE; } - /** Best-effort 👀 ack on the mention — never blocks or fails the turn. */ private async ack(mention: Mention): Promise { try { await this.platform.addReaction(mention.thread.channel, mention.ts, "eyes"); @@ -131,7 +116,6 @@ export class Router { } } - /** Serialize handler bodies per thread so the state RMW is atomic. */ private withLock(key: string, fn: () => T): Promise { const prev = this.locks.get(key) ?? Promise.resolve(); const result = prev.then(fn, fn); diff --git a/src/router/sandbox-name.test.ts b/src/router/sandbox-name.test.ts index fac873e..707590b 100644 --- a/src/router/sandbox-name.test.ts +++ b/src/router/sandbox-name.test.ts @@ -23,7 +23,7 @@ describe("sandboxName", () => { ); }); - it("emits NEITHER '_' (illegal --name) NOR '.' (illegal hostname) — both verified live", () => { + it("emits neither '_' nor '.'", () => { for (const thread of SAMPLES) { const name = sandboxName(thread); expect(name).not.toContain("_"); diff --git a/src/router/sandbox-name.ts b/src/router/sandbox-name.ts index ebdeeda..0b29b56 100644 --- a/src/router/sandbox-name.ts +++ b/src/router/sandbox-name.ts @@ -1,72 +1,32 @@ import { createHash } from "node:crypto"; import type { ThreadId } from "../types.js"; -/** - * Deterministic, reversible sandbox naming. - * - * The sandbox name is a pure function of the thread id, so the router can find a - * thread's sandbox via `sbx ls` + this function without any persisted mapping, - * and reverse a running sandbox's name back to its thread for the idle sweep. - * - * IMPORTANT — charset. The binding constraint (verified LIVE) is that sbx uses - * the name as the container HOSTNAME, which rejects BOTH `_` AND `.`: - * - `_`: an earlier sample did `thread_ts.replace(".", "_")` — `_` is - * illegal for `sbx create --name`. - * - `.`: accepted by `--name` but `sbx create` then fails with - * "hostname: value must be a valid hostname". - * Slack `thread_ts` is `.`, so we encode the `.` as `-` - * (uppercase channel is fine as a hostname). The result is `[A-Za-z0-9-]`, and - * it stays reversible because the channel contains no `-` and the ts is exactly - * two numeric groups. - */ - export const SANDBOX_PREFIX = "t-"; -/** - * Max sandbox-name length before we fall back to a hash name. 63 is the - * single-label hostname limit; our dot-less name is a single label, so this is - * the right ceiling. The hash fallback keeps naming correct past it. - */ +/** Single-label hostname limit. Longer thread ids use a hash fallback. */ export const SBX_NAME_MAX_LEN = 63; -/** Hostname-safe charset we emit — letters, numbers, `-` (NO `.`, NO `_`). */ const CHARSET_RE = /^[A-Za-z0-9-]+$/; - -/** A plain (reversible) name: `t---`. */ const PLAIN_RE = /^t-([A-Za-z0-9]+)-(\d+)-(\d+)$/; - -/** A hash-fallback name: `t-<16 hex>` (not reversible from the name alone). */ const HASH_RE = /^t-[0-9a-f]{16}$/; -/** `name = "t-" + channel + "-" + thread_ts` with the `.` encoded as `-` (hostname-safe). */ export function sandboxName(thread: ThreadId): string { return `${SANDBOX_PREFIX}${thread.channel}-${thread.threadTs.replace(".", "-")}`; } -/** True iff every char is legal in a sandbox name / container hostname. */ export function isCharsetSafe(name: string): boolean { return CHARSET_RE.test(name); } -/** True iff `name` is one of our hash-fallback names (`t-<16 hex>`). */ export function isHashName(name: string): boolean { return HASH_RE.test(name); } -/** - * Length/charset fallback: a charset-safe, fixed-length name derived from the - * thread id. Not reversible from the name — the full id is stored in the - * sandbox's `~/.agent-state/thread` for reverse lookup. - */ export function hashSandboxName(thread: ThreadId): string { const digest = createHash("sha256").update(`${thread.channel} ${thread.threadTs}`).digest("hex"); return `${SANDBOX_PREFIX}${digest.slice(0, 16)}`; } -/** - * The name the router actually uses: the plain reversible name when it's legal - * and within the length budget, else the hash fallback. - */ export function chooseSandboxName(thread: ThreadId, maxLen: number = SBX_NAME_MAX_LEN): string { const plain = sandboxName(thread); if (plain.length <= maxLen && isCharsetSafe(plain)) { @@ -75,12 +35,6 @@ export function chooseSandboxName(thread: ThreadId, maxLen: number = SBX_NAME_MA return hashSandboxName(thread); } -/** - * Reverse a plain name back to its thread id, or `null` if the name isn't one of - * ours in reversible form. Returns `null` for hash names (caller should read - * `~/.agent-state/thread`) and for foreign/utility names like `_login-tmp` — - * the `t-` prefix namespaces our sandboxes away from those. - */ export function parseSandboxName(name: string): ThreadId | null { const match = PLAIN_RE.exec(name); if (match === null) { diff --git a/src/router/state-machine.ts b/src/router/state-machine.ts index 387bce0..22c7b00 100644 --- a/src/router/state-machine.ts +++ b/src/router/state-machine.ts @@ -1,34 +1,7 @@ -/** - * Per-thread coordination state machine. - * - * Pure, total reducer `(state, event) -> {state, effects}`. No timers, no I/O, - * no message payload — "Slack is the queue", so the FSM only tracks - * whether a turn is running and whether one more is pending; the dispatcher - * recomputes the delta covering every coalesced mention. - * - * Held in router memory only (transient bucket): lost on restart, which is - * fine — an in-flight turn is abandoned and re-driven idempotently on the next - * mention. - * - * State table: - * idle + mention -> running [ackRunning, dispatchTurn] - * running + mention -> runningPending [ackPending] (set pending, no dispatch) - * runningPending + mention -> runningPending [ackPending] (idempotent) - * running + turnFinished -> idle [goIdle] - * runningPending + turnFinished -> running [dispatchTurn] (the ONE coalesced follow-up) - * idle + turnFinished -> idle [] (defensive no-op) - */ - -/** `runningPending` means "running + pending". */ export type ThreadFsmState = "idle" | "running" | "runningPending"; export type FsmEvent = { kind: "mention" } | { kind: "turnFinished" }; -/** - * An instruction for the router to execute. `ackRunning`/`ackPending` both add - * the 👀 reaction but are distinct so the router can log the dispatch - * path vs the coalesce path differently. - */ export type FsmEffect = "ackRunning" | "ackPending" | "dispatchTurn" | "goIdle"; export interface Transition { diff --git a/src/sandbox/sbx-argv.test.ts b/src/sandbox/sbx-argv.test.ts index 0640af5..04b495b 100644 --- a/src/sandbox/sbx-argv.test.ts +++ b/src/sandbox/sbx-argv.test.ts @@ -174,7 +174,7 @@ describe("ls + parseLsJson", () => { expect(parseLsJson(" \n")).toEqual([]); }); - it("tolerates sbx info lines prepended before the JSON (verified live)", () => { + it("tolerates sbx info lines prepended before the JSON", () => { const noisy = 'Starting sandboxd daemon...\nDaemon started.\n{"sandboxes":[{"name":"t-C0-1.2"}]}'; expect(parseLsJson(noisy)).toEqual([ diff --git a/src/sandbox/sbx-argv.ts b/src/sandbox/sbx-argv.ts index 31fc175..9914a13 100644 --- a/src/sandbox/sbx-argv.ts +++ b/src/sandbox/sbx-argv.ts @@ -1,26 +1,3 @@ -/** - * Pure argv builders for the `sbx` CLI. - * - * The SandboxProvider I/O shell (sbx-provider.ts) shells out `sbx `; this - * file owns the argv construction so it's testable without the daemon. Verified - * against sbx v0.31.1 — re-check on every upgrade (substrate risk). - * - * Two load-bearing rules baked in here: - * - `--clone` on create: the agent works on a private in-VM clone, never the - * live host working tree. - * - `exec` is wrapped in `bash -c`: `sbx exec` runs with no shell and does not - * source the sandbox's persistent environment, so the toolchain/agent PATH - * may be missing otherwise. We also never pass `-i` (the provider - * closes stdin so codex can't hang). - */ - -// --------------------------------------------------------------------------- -// Shell quoting (the bug-prone part — a wrong quote makes "works interactively -// / fails headless" bugs). POSIX single-quoting so `bash -c ` -// reparses to exactly `argv`. -// --------------------------------------------------------------------------- - -/** Chars safe to leave unquoted in a POSIX shell word. */ const BARE_WORD = /^[A-Za-z0-9_/.:=@%+,-]+$/; export function shellQuote(arg: string): string { @@ -34,28 +11,16 @@ export function shellQuote(arg: string): string { return `'${arg.replace(/'/g, "'\\''")}'`; } -/** Quote an argv into a single shell command string. */ export function shellJoin(argv: string[]): string { return argv.map(shellQuote).join(" "); } -/** - * Wrap a command so the child gets a pseudo-TTY, mitigating codex's headless - * empty-output regression. util-linux `script` syntax (Linux/in-VM); - * confirm live which mitigation we keep. - */ export function ptyCommand(command: string): string { return `script -qec ${shellQuote(command)} /dev/null`; } -// --------------------------------------------------------------------------- -// create / exec -// --------------------------------------------------------------------------- - export interface CreateOptions { - /** Agent kind sbx wires auth for. Default "codex". */ agent?: string; - /** Private in-VM clone vs the rw bind-mount. Default true — never edit the host tree. */ clone?: boolean; template?: string; kits?: string[]; @@ -81,29 +46,11 @@ export function createArgv(name: string, repoRef: string, opts: CreateOptions = export interface ExecOptions { env?: Record; workdir?: string; - /** Login shell (`bash -lc`) to source profile-level env if `-c` isn't enough. */ login?: boolean; - /** Wrap the command in a PTY. */ pty?: boolean; - /** - * Redirect the in-VM command's stdin from /dev/null (default true). - * - * VERIFIED LIVE: `sbx exec` keeps the VM process's stdin open even when - * the host closes its end, so a headless agent like `codex exec` blocks on - * "Reading additional input from stdin..." forever. Redirecting stdin from - * /dev/null IN the VM gives it an immediate EOF. Closing host stdin (the - * provider also does that) is necessary but NOT sufficient on its own. Ignored - * under `pty` (the TTY supplies stdin). - */ stdinFromNull?: boolean; } -/** - * `sbx exec -- bash -c ' < /dev/null'`. `command` is a shell - * string (build it from an agent argv with shellJoin). No `-i`, and stdin is - * redirected from /dev/null in-VM so headless agents can't hang. The - * `--` stops sbx flag parsing so the command's own flags pass through. - */ export function execArgv(name: string, command: string, opts: ExecOptions = {}): string[] { const argv = ["exec"]; for (const [key, value] of Object.entries(opts.env ?? {})) { @@ -124,15 +71,10 @@ export function execArgv(name: string, command: string, opts: ExecOptions = {}): return argv; } -/** Build an `exec` argv directly from an agent argv (quote + wrap). */ export function execAgentArgv(name: string, agentArgv: string[], opts: ExecOptions = {}): string[] { return execArgv(name, shellJoin(agentArgv), opts); } -// --------------------------------------------------------------------------- -// ls (the registry) -// --------------------------------------------------------------------------- - export function lsArgv(): string[] { return ["ls", "--json"]; } @@ -156,14 +98,6 @@ function strOrUndef(value: unknown): string | undefined { return typeof value === "string" ? value : undefined; } -/** - * Parse `sbx ls --json`. Tolerant of both a top-level array and a - * `{ sandboxes: [...] }` wrapper, and skips entries without a string `name`. - * - * VERIFIED LIVE: sbx prepends human-readable lines (e.g. "Starting sandboxd - * daemon...") to stdout before the JSON when it auto-starts the daemon, so we - * parse from the first `{`/`[` and never throw — a malformed list yields []. - */ export function parseLsJson(stdout: string): SbxListEntry[] { const trimmed = stdout.trim(); const start = trimmed.search(/[[{]/); @@ -195,11 +129,6 @@ export function parseLsJson(stdout: string): SbxListEntry[] { return entries; } -// --------------------------------------------------------------------------- -// cp (getFile / putFile) / stop / rm -// --------------------------------------------------------------------------- - -/** `SANDBOX:PATH` addressing for `sbx cp`. */ export function remotePath(name: string, path: string): string { return `${name}:${path}`; } @@ -216,18 +145,12 @@ export function rmArgv(names: string[], opts: { force?: boolean } = {}): string[ return ["rm", ...(opts.force === true ? ["--force"] : []), ...names]; } -// --------------------------------------------------------------------------- -// secret (onboarding/auth detection) -// --------------------------------------------------------------------------- - export function secretLsArgv(): string[] { return ["secret", "ls"]; } export interface SecretSetOptions { - /** Host-global (shared by every future sandbox). Default true. */ global?: boolean; - /** OAuth/browser flow (e.g. `openai --oauth`); else the value is piped via stdin. */ oauth?: boolean; } diff --git a/src/sandbox/sbx-provider.test.ts b/src/sandbox/sbx-provider.test.ts index ebb6d1a..f5b5692 100644 --- a/src/sandbox/sbx-provider.test.ts +++ b/src/sandbox/sbx-provider.test.ts @@ -154,6 +154,17 @@ describe("execShell + SandboxFsLike", () => { expect(await new SbxProvider({ spawnFn }).homeDir(handle)).toBe("/root"); }); + it("homeDir caches per sandbox handle", async () => { + const { spawnFn, calls } = fakeSpawn(() => ({ stdout: "/home/agent\n" })); + const provider = new SbxProvider({ spawnFn }); + + await expect(provider.homeDir(handle)).resolves.toBe("/home/agent"); + await expect(provider.homeDir(handle)).resolves.toBe("/home/agent"); + await expect(provider.homeDir({ name: "other" })).resolves.toBe("/home/agent"); + + expect(calls.filter((call) => call.args[0] === "exec")).toHaveLength(2); + }); + it("readFile returns content on exit 0 and null on non-zero (missing file)", async () => { const present = new SbxProvider({ spawnFn: fakeSpawn(() => ({ stdout: "data", code: 0 })).spawnFn, diff --git a/src/types.test.ts b/src/types.test.ts deleted file mode 100644 index d23890f..0000000 --- a/src/types.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Mention } from "./types.js"; - -// Proves the Vitest wiring: globals (describe/it/expect) resolve via -// `vitest/globals` in tsconfig, and the shared types type-check in a test file. -describe("test wiring", () => { - it("runs vitest with globals and type-checks shared types", () => { - const mention: Mention = { - thread: { channel: "C0ABCDEF", threadTs: "1748600000.123456" }, - ts: "1748600000.123456", - user: "U123", - text: "hello", - }; - expect(mention.thread.channel).toBe("C0ABCDEF"); - expect(mention.ts).toBe(mention.thread.threadTs); - }); -}); From 556d4d98836e57de2aa840dafb6f9d6efc56a0d4 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 23:30:36 -0400 Subject: [PATCH 46/47] feat: support optional sandbox template Wire MISHU_SANDBOX_TEMPLATE into sbx create options, normalize empty values, and document sandbox governance as operator-managed. Co-Authored-By: Codex --- .env.example | 1 + README.md | 10 +++++++++ src/cli/args.test.ts | 6 ++++- src/cli/args.ts | 1 + src/cli/env.test.ts | 43 ++++++++++++++++++++++++++++++++++++ src/cli/env.ts | 15 +++++++++++++ src/cli/index.ts | 3 ++- src/sandbox/sbx-argv.test.ts | 11 +++++++++ src/sandbox/sbx-argv.ts | 2 +- 9 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 src/cli/env.test.ts create mode 100644 src/cli/env.ts diff --git a/.env.example b/.env.example index 6945ace..4ff4403 100644 --- a/.env.example +++ b/.env.example @@ -4,5 +4,6 @@ MISHU_REPO=/path/to/target/repo # Optional: # MISHU_AGENT=codex +# MISHU_SANDBOX_TEMPLATE=myregistry/image:tag # MISHU_LOG_LEVEL=summary # MISHU_BOT_USER=U... diff --git a/README.md b/README.md index cbfffc0..f42cb6a 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,19 @@ When Mishu prints `listening`, mention the bot in Slack. | `APP_SLACK_BOT_TOKEN` | Yes | Slack bot user token (`xoxb-...`). | | `MISHU_REPO` | Yes | Repo path or ref passed to `sbx create --clone`. | | `MISHU_AGENT` | No | `codex` or `claude`; default is `codex`. | +| `MISHU_SANDBOX_TEMPLATE` | No | Optional `sbx create --template` image. The image must include `bash`, `git`, and the selected agent CLI. | | `MISHU_LOG_LEVEL` | No | `summary` or `verbose`; default is `summary`. | | `MISHU_BOT_USER` | No | Slack bot user id; resolved with `auth.test` when omitted. | +## Sandbox Governance & Security + +Mishu does not manage sandbox egress or network policy in code. When using `sbx` as the sandbox +provider, configure Docker Sandboxes governance and local policy outside Mishu: +. + +If `MISHU_SANDBOX_TEMPLATE` is set, Mishu passes it to `sbx create --template` for future sandbox +creation. Custom templates must include `bash`, `git`, and the selected agent CLI. + ## Logs Each turn is logged to `./data/router.log` as JSONL. Summary logs include argv, prompt size/hash, exit diff --git a/src/cli/args.test.ts b/src/cli/args.test.ts index 93ef1d9..07c3616 100644 --- a/src/cli/args.test.ts +++ b/src/cli/args.test.ts @@ -1,4 +1,4 @@ -import { parseArgs } from "./args.js"; +import { parseArgs, USAGE } from "./args.js"; describe("parseArgs", () => { it("parses --sandbox= and a data-dir positional", () => { @@ -24,4 +24,8 @@ describe("parseArgs", () => { expect(parseArgs(["-h"])).toBeNull(); expect(parseArgs(["--sandbox=sbx", "./data", "--help"])).toBeNull(); }); + + it("documents optional sandbox template env", () => { + expect(USAGE).toContain("MISHU_SANDBOX_TEMPLATE"); + }); }); diff --git a/src/cli/args.ts b/src/cli/args.ts index 259c084..2d169f0 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -32,6 +32,7 @@ export const USAGE = [ " APP_SLACK_BOT_TOKEN xoxb-… bot token", " MISHU_REPO path to the target git repo (the --clone seed)", " MISHU_AGENT codex | claude (default: codex)", + " MISHU_SANDBOX_TEMPLATE sbx template image (optional)", " MISHU_LOG_LEVEL summary | verbose (default: summary)", " MISHU_BOT_USER bot user id (optional; resolved via auth.test otherwise)", ].join("\n"); diff --git a/src/cli/env.test.ts b/src/cli/env.test.ts new file mode 100644 index 0000000..4135b3b --- /dev/null +++ b/src/cli/env.test.ts @@ -0,0 +1,43 @@ +import { createArgv } from "../sandbox/sbx-argv.js"; +import { createOptionsFromEnv, optionalEnv } from "./env.js"; + +describe("optionalEnv", () => { + it("normalizes unset and empty env values to undefined", () => { + expect(optionalEnv(undefined)).toBeUndefined(); + expect(optionalEnv("")).toBeUndefined(); + }); + + it("preserves non-empty env values", () => { + expect(optionalEnv("myregistry/image:tag")).toBe("myregistry/image:tag"); + }); +}); + +describe("createOptionsFromEnv", () => { + it("omits the sandbox template when unset", () => { + const opts = createOptionsFromEnv("codex", {}); + expect(opts).toEqual({ agent: "codex", template: undefined }); + expect(createArgv("box", "/repo", opts)).not.toContain("--template"); + }); + + it("omits the sandbox template when empty", () => { + const opts = createOptionsFromEnv("codex", { MISHU_SANDBOX_TEMPLATE: "" }); + expect(opts).toEqual({ agent: "codex", template: undefined }); + expect(createArgv("box", "/repo", opts)).not.toContain("--template"); + }); + + it("threads a non-empty sandbox template into create argv", () => { + const opts = createOptionsFromEnv("claude", { + MISHU_SANDBOX_TEMPLATE: "myregistry/image:tag", + }); + expect(createArgv("box", "/repo", opts)).toEqual([ + "create", + "--clone", + "--name", + "box", + "--template", + "myregistry/image:tag", + "claude", + "/repo", + ]); + }); +}); diff --git a/src/cli/env.ts b/src/cli/env.ts new file mode 100644 index 0000000..009c659 --- /dev/null +++ b/src/cli/env.ts @@ -0,0 +1,15 @@ +import type { CreateOptions } from "../sandbox/sbx-argv.js"; +import type { Agent } from "./onboarding.js"; + +type Env = Record; + +export function optionalEnv(value: string | undefined): string | undefined { + return value === undefined || value === "" ? undefined : value; +} + +export function createOptionsFromEnv(agent: Agent, env: Env): CreateOptions { + return { + agent, + template: optionalEnv(env.MISHU_SANDBOX_TEMPLATE), + }; +} diff --git a/src/cli/index.ts b/src/cli/index.ts index 4cb79a9..a6092b4 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -18,6 +18,7 @@ import { Dispatcher, Router } from "../router/index.js"; import { createJsonlSink, type LogLevel, type LogSink } from "../router/log.js"; import { SbxProvider } from "../sandbox/sbx-provider.js"; import { type Args, parseArgs, USAGE } from "./args.js"; +import { createOptionsFromEnv } from "./env.js"; import { type Agent, ensureOnboarded, parseAgent } from "./onboarding.js"; /** Boundary-log sink: the router's stdout + an append-only JSONL file. */ @@ -47,7 +48,7 @@ async function run(args: Args): Promise { const agent: Agent = parseAgent(process.env.MISHU_AGENT) ?? "codex"; const level: LogLevel = process.env.MISHU_LOG_LEVEL === "verbose" ? "verbose" : "summary"; - const provider = new SbxProvider({ createOptions: { agent } }); + const provider = new SbxProvider({ createOptions: createOptionsFromEnv(agent, process.env) }); // Onboarding gate: require the agent's credential before serving. const onboarded = await ensureOnboarded(agent, { diff --git a/src/sandbox/sbx-argv.test.ts b/src/sandbox/sbx-argv.test.ts index 04b495b..f74cdeb 100644 --- a/src/sandbox/sbx-argv.test.ts +++ b/src/sandbox/sbx-argv.test.ts @@ -52,6 +52,17 @@ describe("createArgv", () => { "/repo", ]); }); + + it("omits --template for an empty template value", () => { + expect(createArgv("box", "/repo", { template: "" })).toEqual([ + "create", + "--clone", + "--name", + "box", + "codex", + "/repo", + ]); + }); }); describe("execArgv", () => { diff --git a/src/sandbox/sbx-argv.ts b/src/sandbox/sbx-argv.ts index 9914a13..646f46e 100644 --- a/src/sandbox/sbx-argv.ts +++ b/src/sandbox/sbx-argv.ts @@ -33,7 +33,7 @@ export function createArgv(name: string, repoRef: string, opts: CreateOptions = argv.push("--clone"); } argv.push("--name", name); - if (template !== undefined) { + if (template !== undefined && template !== "") { argv.push("--template", template); } for (const kit of kits) { From 10fae5240362a146997711de504384f6f0513767 Mon Sep 17 00:00:00 2001 From: Renxia Wang Date: Tue, 2 Jun 2026 23:31:44 -0400 Subject: [PATCH 47/47] docs: add README workflow image Add the README image asset and include assets in package files so the referenced image is available when published. Co-Authored-By: Codex --- README.md | 2 ++ assets/mishu.png | Bin 0 -> 671875 bytes package.json | 1 + 3 files changed, 3 insertions(+) create mode 100644 assets/mishu.png diff --git a/README.md b/README.md index f42cb6a..67ec34a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Mishu +![Mishu workflow: summon a coding agent in Slack, run it in an sbx sandbox, and continue in the same thread.](./assets/mishu.png) + Mishu is a Slack bot that lets a team run coding-agent work from a shared thread. Mention the bot, and it starts a coding-agent CLI inside an isolated `sbx` sandbox, then replies in the same thread. Follow-up mentions in that thread resume the same sandbox and agent session. diff --git a/assets/mishu.png b/assets/mishu.png new file mode 100644 index 0000000000000000000000000000000000000000..76c8ee124937a1a8bdf25b86aca1fe61109bcc28 GIT binary patch literal 671875 zcmWKXWmMEn6vqF%EZyCWba$sTk`mI5(%rE%3P^{v0@4D~y>vGs-AH%mzVCdQ`8emy znfu)Pd+z1h|pHCKVcK!mJ_x3tGYC5C*&YwdXs(lnRzt^O)+{(X%+7_LwjX62*6 zyq){K)tb286lX0;vD1NpCX1(O8tm)lm5@7wtzKIn#GiU))T}qCTCsIb4n89Q?Lk z4fffsG6}F$kJNZm8}AzTPWh_KeRs5et~TZFeE%^03)I)e-|bU(R^ZkC>g)5ZtGP~! zzwJ=Tm+{KPwRqnsMY&w)Cm|_HQ+>@)R+6Xbl*jQ{cY6yt6}p>=LJk4q(vQe+xvz=>B20ve z3^m-|l^9!cv^ksEyYSg5b6u6B93|QBrUq778{~V*722^TDTq!+Fg=X;P3KuXE#z8B z{v5GX6p(&jCe3v9lYJwFrzgpbUNNqZm7+FEYb-`dx%%_2qVS5hUYj?qe~{>Ex%2cV z`_uN%UFy6|e&T9ud671iWyL@A<2AB_>W2gj#8k|JQ#+rx%X>?E$oWkEv`i}UgzQYN zRi|`VIeqbo%>Pm#|E_gJ%h=?P?O#6^fXznGRKdEEQ0 zG&66gE)}3U{y1nC+y%ItILGQdKw1c$-sl^~5sm(TvhVN*{mtAZW>)S3e!f#E+&UGB z!*9Px#)&b3s*Z)Bp4;}+O&vN`o40uKRZjSO9s2H#XU!1-tk?ICw!I zfFfFk2F(S{FopNc50APY`CrLT)Hds5R>bXA6nOHeUoJoV^WipV{1;ynf(ZRpg$MN> zdYK$;ToF4-{AI9HDIE^Y`Si1%({hq3Ak^}m*!a+G7RGUXtg%%ploy&3A=QqTb9LuL zqlzZ!f6lBYJF0!a%em^J1|o z#dCgQ?*E=I=OKwM@o=)Q_o*Js^m4e{v=O&Iq@_OPh`R*Y#_1AdoiQQO%ziiD< zyHVvx6=klg>R6Br9$Dg;rh4HnVmC7XKpz}R6YdkibKodWOJ-7I;W&~-#{4%WLSh;h zQ)A0i?M~p`@#Y z<8doDW9h)(jt^@?QpXU}Pc!WpO>W+BO?xU{@Zt|&&)2%}z@{ZzS zBi}?m8*^Jo0?qo|r+e5L7w`z^G5Se-e1eYITW2`q9pQiYJ?LdC+-Az^;^>&FTI@qq ztbKsI2#8j8gw(Y@A}QTbxfYdHR#oxX$73<$0KK0jwEd6Yt}a(`eWeNlUBwFtsplFJ zntQ0%j;LtKtMVox(X+jfX$`!jvL73(_`nt>my zIE_YGd0;r~w{}_T)k7fZdp*5U8Iy?bbW>_NEY;lFD2pB^CdWhdFSpSNRpc2i;T~E zSLDLek5j~9z1&+0ft*gTNHe<$`IcVXRR$pV| zqtzHPN8-o9pc5&WK)c`<6c$Y49e>Z*n2C&@mEdEy|7BXXmCvGw{N`a(o@`EIfVb>@ zlv>~A@2ux7I_+^>KZ+>}t#j)d(eyX_#L558FDS8rgcp5MNqBUWYQ>Ab-LIvwv9TJ5 zokOYV_4New(nfkOm%-{^Wo2cB2y_a~z)rM3zlU^HZp8Cbx}oE0Q@Ff4ht=7n~T*)Tg+ln?OVqCmjS z1zJBG<kVLkEl7+sfG(orZ+lNS(J{G?&W5RM<1OW6qF6jqbJL1N zXkDoVjnlE=RHiQ1_bSZXzY1~Q8YG+xZk1b^+ePo_ow`hP7pM+sRz4R0#3$P(;3D77 zx$1GWFs4!InXV}iBwr$}{@wePD5w-UB^y%ALnY`H2NT?DZq6Q< zggb&mg1>c65@jIXBL}7f;l)MnGJq}W%t1pIp#}x)I4Bo5=tPbN(nL^SM}u=vAXuum z%%l9L^OL{k#>(ko`QfP;0_~pXT}SW6mi)Eko)3401}cvng9|qqMy2=k6glWq9;{K4I?Y72XN6{pV!z27DId4h8ka-rS><)kLM;og?#09w`jw4QdP5?w*jzF+*l@5#;l<6Ev; z+lU{Pt;gD0R3U^n`iH5n;6ijl<<+RBj>}W2^?NNebKA4&?6UyZT@Ci2E{RVJ{xdCW zOSrzXz#xVkqkJh@C`+i;`%eg`3T!e;78YoXWk&Pp1wrlC&U_wf0h1RkCEYs2mNotr=)2@3c>b%cX zOBPZ#h`h0K#X@#Ry;6f-L#{Nf*b&gAh>1)E4;b!`LL#hPS(kFZZ4^!*CM;a#{|a z^`c08*p=kC8~;`z;gJ@UbwfLGzZAzYLg3QNu4*=vkUbP^MWn0AjJux;&do^{b*0J( zKL3n`xG^q~^8$S>287lRz|c2M92k zn=Zbf?n0fqWX>O-LzP27wmN!Px(;`XuKf1{Iu?GX|()Ebb%%zK={gJXPp$!`g!%5T} zw1TifNMEHmOqLSM;ybdVCo26;R0%RyzK9wya0Fl6UYDAD!^h&)-W{z)u)zHq?W6+y zF8zA&QQ}VopE$)FAQh_|n|xnqlb*|hhCFTS)i4A~-?T$B_kT>yyaW(TJhA$#n9{k( zi|((rw5Dd_7hLHR)Z(D}-Q5+ju@$A<73Mx9@?+7_)?qF7-wk(dS1Sti^bE|);@X_* zu#LL)IR4=)=@#r4Zn#2C36UYH4wQbX%oNi2Ev^{H8mKgPg#|O1_tT+%4v8QK>Mt+S z$o-87u6Qx=`@>*Pq-LgQ`uDM}r}z0Tugo9-zTG8(S%Gq ze?jXuk*Rz}RB+#E6OZ}xNrwow4MbxlHCeY zuPxWx0R#XQ*vsJvUKFU99~x7)6SAwcE3?l1%y*D6D4<@Jvk_w0X*9JryczQz!RJR> zS$VgR+3~4!(D`t|--9b>##?*{>rls3;)1}yUOy*5r7Zqn=A6N<5r>*@-pfLB>exo{ zT}#dI_*i|5$&JtYPb;-FL3r^K~kkB0NYcdembOJ#BsQsjA292k2>2HAefFo6=Y`YAT`0O7w zKh$cwyC5eVj5?1tas<@BLTfSD;mtyDr4$k_nOXq7o$cF{1Ny{NfC+ zO@wcz(8aZLSi ziVSEm(AWZ8;RTOdJ5qEdFQwe>B9bmyVo=4n*Sr6pV|95Y71(!TM1fxArT4wzi!FOH z6c(D&7qoNRbs3Ax`V{6|^rsK`&AP2Rb9=eZ>&3E5*Ii!Quk758yJM!@Vq%6T9&T>{ zmB;%fxYGtU3Q~B8xTh|7x1ur10ifm~P7%l~{<@q4eXqc(KXf_vN3inUUqL6e|leq7zP1!LE=PxWit@x|G9c$V^CR zh@HF9O~Q*SK7L74$`Ix=BPKh?{ao883%z_9)rHGBroi!Sl+-_nSG7*l_1n&`IQ8I;lDj|K%*6!d=X33W*)&-n9no-wV`!y2;t{PYgD9D?j~VH_pmjhqWqDMwNEZcmn`R( zj>i=)$7>XTYV8cZ^|zPaMwZ7z9geVFBpeNy^Wt+=hCfK+r9#0psM6fx-cc?Qf`}VG z7tQjeaKX?lnrNX?|5i>=id$(I_iVcZ#pBy_Vio13cYVy`Il?U*o`0~U{y&1uZ@qCqXn7RM$3}A1xSvT77FGczg3kps zQK1*^8#jNiAk6x&rtD+iv)-tN3C))bs`Uh21o^EY{{tE~MPyed5blH}VgkF@7*w+Khd0_Jj0| zI6LA07^OT#H0LPbvQt|Hst9C`f<1zm040W@M+!!bj8%1>%=M4!Szavzk8&j;MCL=H zVLUv(elNQL4qZRLsl1x`nHR<*6nNv;wx+M^0e8O(lGtS~v{R2Yi?E6bQ(JDWQ)RU$ zGo1aN>A$PKN$g@LX2e4+#wvkZ`!~enO0yvHzR?CBuEmq4oR)zJ*)0j`$7*}j>0Z^| z3};wZi#z@kr`~&T?x4}Ye}V{rZc2cu0%3QzYA>;zKvDA#H9_sBAJZ*q4cK25Wx`|4 z^E%qmEzzWJVBK~A4%DO_5k5ckkUy4?agJIW^!TL+%#BG%(?u^$2~j-_Wqgt%^GEMot!b6>(#UbU7hV{i8>OjZ8d1-3>t8dN$YaDePplDf z_PF#cFNGg8E{PBH*@qwp`i{L%cZgCIUo7c9QE`5!z{HIfnx6{KMwX2UKki6#VC)Ya z4rGA+>6mRRHSIfDq8SC%BJ8sqt#Gy`p+M^GaOjA^P!$4jB1-b$n}`Im9c)$0XiUNw zIVva*XN3I+Ih?R5^zwQCRsShS(%@#|B0~rbSRoEQw$b-3&Nc(EO$q(>L?r~Gk)({d zhbF(GHx57p=_rt;;w6ALEu9F=F`d@7E+`Ex>x~>hNcHUv zWt~w4!v`!3SNnLl7Rs$Wlk+zw;e%0}i5GmHkItLfbj_t=V_4fvU&kqdAIR%EK#W%u4EAg>71E9(N`|F@g~@fe2e1JC;OD%K zAp>{EsRrYOSFxpObsNT$w>w5+7aU~I)N(4yD*e;q<|}7w5aIsW@fZ#(T`&2(daUfB zN_s_kPVoh8(xIK1P{cW~ZkqFmY)9J<+#Tk@CE%*a-=Kci6pB=kjR(*Ruj_Na;ut`< z-d56jW-aGy#T6t_F7l1 zt_oBHF`EG@my?3{dE3d7n*wTeTtSD|wpK_YgWC;9KpG znR?KRIHI&PI>^__dl>>`Lw#rg*$0$AA$A}p-Z<-gPZK3g>Spp-9Isvcw@r-+QrrQZ9<}0ao1U~@Z`@hf4$;delLTV= zK733)b|2-zIX3E%c1)G8BAsSJ!de&)1I?zyOtM(lu&cJ*csS*h zpG}J}=pG?*n2w06FOgK!l4R1KWLjw)x-hcMU}9Cb*p(r}O`H>Bpv168 zZAmN_&8@kR;mN0`kwL!D@X638UVAx)G3H8iSi%Da6znPqq45Fi!wxyrSes0JO55Wd0H!sBe8Dj$%6*h9)RuF;@ zb)GTpWK`z*B$fZ&P(b$NdW0|Q`O~lQBOWa}FUCnD@CZ#Y9YTy1*X>%@R`f1cm}dt}J_mxpN0{M9)&tCH2Iw zlcwJ+SQ?`iVFxxVqW1nv_PQ>LMp(I)E4=ZlOq_~eavF!H^QY4k=c~7^# zW78ECZzU%sC;H-Hn~M9}KmSzgRYWY8G~bBk!uB zG2jDS3CHgSp46YP%21QDa9d-c4~JR^%^Nznufdcz3D*`cx_f?h68DQGVkJUmFRoA5 z6MyrATpt!QhV`f{E{?3Eh)tx2E-pd4a7`43EVsMN+XQBAUJm2Nv&ESJ9JGKonOEg@H zss!?OJdYT}+zbFhDwg1L-hC-speJnSr69ON;b`@)^NzUZ3tqQf--OT9Mek6B%tA6$ zqEzhf=IPCJ_hrNKpR~x`i`wF{mdn={Ikp%`Wjpt?x5c9=MKaEr5+O@v{or#yIch9@ z9|N5mNZ!)h|L%az@*koJ;^A4bTJt3Jr)hy6m+yC8+aYYI@S|PfFRNI zydU()a3oeZxw!5O)9J#XZ-H{L5!2GHXOl7i;0k(ZrdN;6B9kS<;6^yV&T;z?@YFrp zNYq$iZl zh5;8!ObZBwKY~8O{@ksW@EaKk|i`5?_m4cVY}|OcLKA?3B1|SdQz5X#+J#`y$H-YL6u- zIBobLokH3Z2;SHvbvym$uKq5+W$~04dstC7%cm-LR}pE@@_5C^NMr<&hY%w^{SaXS z+ZRP1$y^mAJwYbqh%C&Ne<-a>-67(1UuD%*e%|rpMxi(g3GBc!kqG*XzkeK>G`fVH z_i(VM`X;A(nP<){>;IL_b~g#q(J6lYCHTDeSJc$ce@~{}bPu$sEOXe7yO)6cSBOSG#7|tV2-?B=Maa1$&`KPC)3lG_ik zRc%i2Qg8c9ZNBibD5I@nL`aK7bEZh>bD)m$TZAv!kCeG@0W=qN(*T(X#8m35!prK# zE|CCGu5cSPtal@H3~emWAO2yan%@%AYX{6>R2AbDSUpPQ?s?k(^=!a)Pkr2@ImG?|5YDkmlf9z%W(4x&(q!1iW}3p_!jj`s<% z=%EXu+^*Bz6IYFrfgU7#7`OWWhw!P_mAP80S!R)+BQHCa-B^q=D z3?;g6u2Q9|q0R&Vo%-ikL33c<5$U`c1(tgVmtnsT@BuAiK%-mTkZb1l5E8hrUKhW8 zuf#K^WIa0H3J=%%VnOhMhf6XDqE0yG#IO zM{4(;L!==n=$@j$Y@;VVNy*k>8;`dTkJoBTr??P*12a=}y*QNOARo8VXyp;cg#xUm z^l&C{;`}E&yPx*m@-T@t5>H6&lAYUT4#bsH8gr;y;WlW)g~fxafWymVCM5_9vRZQV zv|HcYdD~f4GAjG(p?Y60O=#@G{DL$ZQF8zk4i@dUsMLm8UX0(PzD)x{A$ZN-;th%>52ZGv_& zCK^=G`%;2|0u@jJ*!TW#Q6Jk1Xl$jDRAEqZ#n4!?ai+uY6Ad1C?e=qW(Zcnulp4*upvoR1ona zx$}dj0&6|jF3E|VEX@hgW8^q3AwV>~vSns5MJ)(M(d-D=UjLsbRIQ*@yx853#*_-A zl9DY5eHzP^o%Ci6U)gb##9jR{jUB|DzP_!{09o0@zoUu@8@`9Ip{}f^umV+Yofpnx zwxNz_t`|1Ms=8Ld={fD)Q?XEzK0?AF*#-Vg1d?tz$&>2DYWr6lWR5e5rVvvv1-M+CJ1U8z)9;UPzQL2Eh~moJ!>2PDlRf=PF?>S}^h z5bu>b`CY<9+M>@!I!~RO8E8Ud287X>jirYn=3(s|nqS72T%)i)NF1pZF0BmV08P3r z!;?7UT;SgXBDC3nsQTF)5e?9=*tmGmy;ghCgYJ=*)==EP8T7I$v>KEdYKTPM!}9iR z;*^sJmCctgK(1gh$qi+-!-K_J# zTD;U#k96oQF2`@PL(xmQx3S+e??*yfbirfW_^catQU#e8K|9<5-+du1DN=1POb8_o zAvZ|)CIJj>3#}-5F^h&?VbL#r{E^6y2fej@PL7fO0buZwpLpbA7b0K{5dS8c0L48d z{qML6P9TUEV-Im|(aa7EAqMp;AS{zy$!8(>Adccr2W8}bCrJ415%7Gd5&1Qpz?oYw zVK*;_{2YSfbD>D+X4rrt%mlpT`?SuO?3)>Ws7MvL&hO>23h60d6ffwi;=a9p$PK72 zfoxE{`7HrXS5JIxK_O@nS0w*J03D?6I(1V6Jf+SF?@aX1z?XCAB8W-{`I+$1C}04O z|Br)0_l1;?cfW{~1=Nh?=Bm)ztA6hmI{;X4W(vs?w-VmuAOviJ8CU^qFD^+fY6K;z zpf~Jiebsvbyo?wU8=qdvfb*DFek@hr@ndh0a{K(dgQ3=i+kS6Ma#%TshS?0SSX2V& zZ23cZ41nQvx!DL@YR~O%Z|Nd(kNaph2e;D^*2)s zleF^_TjNWG(%Rn$P4~gZ+J_%*lKKIRb->w?-Z1P*5?i{g7LW!hWr#@rq#ax*{kTQ1 zX6_glSpOj<;`~!dsM5eq?QwoJUqnqzs6$+`CXdx1E=ml4Pd61?CzL9`^q3Q@a^+u= z4>6CEwgUhahG(Tu-A=vF9!}^$Z>M|l^CVvE5|7D6Lb85+Hy&?S{IvuaHoMxl^f!3z zpiK5|ZpM(Xk~4Fb+ykiI_<@6?v(`~xJyjR@(v9#p{GRfeKduO0{gpUgzv{T=9zTeEYquza%TQJvh(q~))6Yln31tHNpBOgHhuu9V_ zQS1egnTvkglLZwg&@yoD_H=Ndnz#1Nw6f|mkm{c=9{6-rP2HL8|3isV?ZP&}K6bz_ zqzBFjZ|zad!EB};{5xT&qA5cmpK6`sVm(>Fy{bAh2!MuqKyK2W)2Jqg1D99Yj5${l zbefRW(GktM;jnHYbrv$Mekm zv4Xmawv_sk*gmy%zZZ?ojrFyL1-A+@8)o<8&@+~Ac=rRfI!*OiIUw_zyf_)#4Hv!t zOV>RPYi3WX0<@=c0lz7Zx1Dy`x?z{3r(TLUDp~HJQi$C)eS-L3WZ19Y6X6B$^p=KV zl|`hNh^hyc8*D7ps3%p~d+t<~Q^e%fwe85-@s144ObE`Rrl~5%i#u^`a!@Pt`!VDd z!P=(h`$8P3*gN2V>~{ap0v}3H2DI;@K_kF8o)bJ$FqZ)wand0Kkab{N3-figGevT< z3p!2ZDzEW83$}m}ee#1EAdC1*6Lelk!%su{1Wn6HdX$#@xQM~OmjM-RYo$qiokf@r z%lt;RZO5_lQp^l)GHE%B{gaggYMNP<5D=CWe7#mL{@siMBB5rrKyn>Csh(uwB;{mu zM?b@1t(uXsj@$|7!n$lAoVCgQoWg>NSJCfpYW1E={gpuUxLS-!X%8T}=eYDm=?Qw; z@UNKp2;QJBQoPp>_+X~22BHB;Ul`yc7DR$;qIrO1385Wc)y1xt{T&akpy!K0E&*yA zk2SNooKhL8;|Y61II-6Ldxm|S%TyI@ee94)Dk1z@j$h83B|C;+h?3hNAaBKRb+07Q z8#vSn9Tn06BCt0zQRXddY_hV z&yEhk57^l8J}12)P7=Lua#2em!Y>XMm6~e@6yJuhJmh3PoAMC@3x$qGp*M`e=Nam> zK!OZlNpHXkY$8}*3nc+>Lc)R&jYX}0C#I32-xh9QIB$+n$@QG7uPFUia#IHnJ^Dtc zUl#CvisA!S5s@^3|EtX!tbJCK+fP31`&wt}_tEv~NFjdry%DAdR6vD-SM_ zB&Uy14$DDq+{U$Nhm(R~yO0eEQxU-l`B0l{_yu*yGyDDaopS3T>h10}`&;^05ta2D zIo^ZD+Ir-Nd`s##R#X^p(j88OaqH5PGTOp2=41?53*iZ@kniF^XSNe0 zIKdnDugPkFEgoX#9eRB}@`Kc>Sb>leJHE^f5$?pmi}TOz5@$+mtM2J#$M>j_zet=g zLZZMhQdkfxKnFToBw}zzoH*OnHY75FXpa9vO?+6Xi(cHanA!+VrpNU>7J+tjHMM)= zjn+3dnJq6Y2z#+Y%s-Vi@(VTbOEfh8-r<9~FMr;8=NeCZLV~7Un?a(rA;>?Pasp;> zRBiS*`-!T@)E>>~xkr5~*g2FR*7`_dw)vCfMzxYNq}eitL~zyExZWbUcupwb zFsD9p|4o!aCO01$3cP<+0OsEUS+iWgISxE(f$w4EOv>(lySNYo0;z)|*Y9z_PcX}* zfwK2C^Y?6gm+c?!Nd6cF{N{n_%B2j{M8olSMq$JbI5g*Xy}`twREEj;6D{RdpS*47 zkYIA{;jT%pQ{>yMFF+97?J~S5J!Vm^=LB0_C@n+3qRyr;#OLtZ$Fr#sGxRL_wu#7k zWO0cuF*o`t>T!B)c6v5}sXasq=K9_0sQ?l#4+_KriqJz6kr*@8->Um)(g8w6-~t{% zlQ;Js1GKl@MRDZOpQs3ske{o?ONZ6*d{SQM{N%GCVxq|k+ zK<5eI2ms*f(w~!$Z;8CjbB(Kx$??qHZAJ-p7|q)h5sP@w!k+BD`F|^vBPj_5c~?q4qpCk3ZIDIS-e-~Kt}Rz@)EL;(ug|6qMoy^n)U`$Nl77RCDcdRUO#p?3{Hyj&UV-N&*Ayq{e1F$D+yF4NeAWrH3M7vX0UV1bk-FUjUQ z;vY#=h@qp*A=OTZSo44N!11yVw;aC~zBp`$w0#X;)hTtVPK|#BrH+IZfI|9ZGG+;nK6cU&dVO*JYwI(-L+Yle%!Bfid^~-^mjXz2A&hE0JRa~ zw)fM=c7~~#QtcW`a}q<1x`Vy}D`JMw@ zX95x6L``xQ7z5`js2lxDFSL`MN!zk6BOAfiTv_Ioc-T(eI5}Nkr1P6Hd;jZQV zDjQkRRMb~4HG2d5(bM~GS05|v74ca`{IA!5F@XO{a!qf=qlI#TlIz3=puq@nfL18R z1yAPAK70YG=GF0J=b3&!VI$?XUO&H*BUhZka?`*-B%nco%dQzOl}OV?^3TjB&jHIMN94GPAq{kaTDvhZCrZiw6On(AbB+_?ZD zXUn<&8AUwu+2^RmvQT?Je}8|h!RYtpZ%|;CV*oIm1}EkjWGUgWeHC$cEV!+?KUU2M- zL625eelDsa3na9}vrGNg3h>VUdPTjUC!&`<5P=_Fv&rj%Gx)6c61>V0&G5|cyH~}FF)M5>?xL9Q1||plf%7QmoEJjXjes#n z33i(ZPm}dJ8fF!Ltvzrk)!b*!Xv;#AbLp%>ZAHF8r&=?ua&$x%6!i8M)XRu6iu%d= z;zTKmsfnwZibg4h9N&I$Punrb6k>(M&5iS(t8|^F(iRu$atPUQ%LNg1Ce2E-0C9jO zIA&QdE9<9vQN`O9eBth~N9E^)o2fd_7v^veauO5i6+GAds;2wTKCIFm&STgk4rW~@ zEL5oa=%^Rjh%8H#wsLJj_y&moS0PF)0Blr$gjZf4dL0Kxsu>s>$`GH8K6$VTi9s+6 zxt>0Vr`+iKTutq}0WvAaatXdjUq;F&&E?c(`#LklRuPMyiEa%Oacz({j^S=Nt$zO# zhoTe@d@MAa9oiG8)&csC*D=>*zo{vD`%nld~XBr{T zp`^e(r1s9mdVr5^Ga52mcH1G_aOJ1oK%+tqME0p66;^=aQ0G;YFB{*#N_sFoyEMzkeH>X8Yq0CY%p3BQy^#ku)cq4hGI<&iAS+sYu*pD^ zNNHKZEFqyf*R;w!^Jgja)##~2rRp8!A6;5F41YPB96fTJ$rM45U`Rc8VbPe(fo(j^ z|MoNFf8}zD{4=@(Q&w~pJyLl z()Cmv3+(aH>Bx91WiXBK?ZVH!mSt7EBQ;W`^gD{j^4kG@zUw2DO6ogY@k@p8ZJNH8 zNQHq@&qg(Gz~+hKUj!v$T)@P?Nnur-Ku0S+Nlg&Y{~pkcS|0~XbCx`uwL$jhlvY>0 z3iu+_lc#lp)tX-u$^kS?SizuE#2v32YlOYI@iTogkpgFtRT^jmQoh(PAhMM#E;AL*i z+zNRv1kQ!`Llj3dft`yL1_+JDWPJgwx^F*iq-V#uwfEKUP@@-C1{oTIbXkieCY)bf zRfq@&CJ+V+Fyqm*i?cKC$%@^nx-_+HnpI@ zWRZ6i>QKcmiS{bqS&2DJZeqOE#c+B{kNFjo3~Ht_4pW~@20(T zdo%~v#G_wxwRg2dV6?En%5>)~(`c!8Z=1>l-ehkZ#`#P?oen{S`_mL7Il$SAI58MEj@o_f|7kn6hu4hF)BkfyRC}78kXeVp zwPtsT`FjT}-!Fk3^K*sJ>iUzY1<}gXdc=ee`{69wFMCs)8y8tK;)kS2k?+znxO#ixOT7Ngcoviw+ z*|O5^c6b_Pn$Zp87Xs^#e*{!}SJ0Qg|5w`tywOpg++w>Aa*FA?(2pnR0xA>(i(6h( zzned{9Xuzg##`nSGY)11+0bG$f_~3XL;L6NS3C`Dr0*#|PfOvgzo8*&Q3FQW+Ms0U z(tOBUdlWVh=>7di4^2jW$+ku{6>7W5XW9+r4&vC!LdjU%fYj08apb zCM}7C^ZG*Z(tFqSYEAzBB1idii$6w+fD34E0CNyjEVH$~wbcI)6^Db(;yFZvj z*1hllyOhAe&R(5I@UTvl8xp+N_cx5GYt4Umh)wb9wp4_XbrSL0?ca;o%NYrjQeSCB z3&_*B9sh>JPSN5h)%LyU+KkZLAao6$kHH_5okC#~`GFy{kRA)-w@L%xOvdU$vtIGg6g3>Hr#$xS1(L{*#sb~0^7gF0`)@H7Li<_ zktb+`{mq1%$k+-_!9s7kuMnw3x3#t3Ye|v}@P#DdOJOK06zyT%(G%`t2=XZhDDDdn z2Wn#N_xrI2kEuZBEILpVDFV6cTIeEhM2E3QB@wim;A4trWJZKg(6x?semBa^`8Je$ zrU~HwvgmdDE$F1d8DsQk>GSVs{}J&)?}7O1%+<@Y`NmfA}PK4w~Ybjc=V^?79&DM zT{e=28N%S-kv`5S{X`Cx+Qitf$A|jR0LI9y8~4=2Zf7dri#1J3)e~;YOTd4d60Bcy zQsLO+D<&NLy3S(PeRBE~a#MnxXB7o=fy)xXj<#!v=Kmg?pC^5fr;ezW)z;EaQdfeo*}-GmuaU_D!|*v zZl>wv#z`|W<(b(?c1Srvv%(~(>7S3qvWp| z-}t=-{8~{b$f2s^v~v($qRV4q?J1}3jL>Yo>CVuhItZmV1c4Ch9EKw8sP;PW`2k&%;A@5rDJIhzq3Crv%RFm^qLQ9ZzftE7;z)k=G>jP4`_?7T{@OST7yR4X3Jgmc^+SUK9F8g{d?gv*jLvL6moGKce4~VO>Sr5Y_~9 ziG^0N?AQ1P4Bw$Y{_8iEJ#gI78ii8{!b=aLLw^|;5-2H54Im;ybAOLS!0n;Vkd2Hh zG@z5|E9_>iQOAj1-Gt~h4I!@Cr9t!eU&Girzu+LDy|xsk@E3e9AVE;21y~N|&}EjY z4Pt+Fy6+OStWzDdLG5qgPUkR^CjH+ay6b3p@>Td{iyVe+fC$B&s}OmqTV++-1KDEL zAcAX_BA?^Q$ti08qv$OBqIkO~KD*1p(p^805~NGImrxKS6a*xf5CQ3KmIg&aK#*LJ zl12&XrBS*&B%~W@c3<8?@{JZ_>@g9s5yzFy_!QP&o%Ego>UPi24bA}s#k%C`5y zFs{b4pC*gJg_JE|ze@=o^GgL_lDq!u32yt74&vh9l}FT&5v8UenX{f9U%3q&P45mm zIoLjsrI|8i8C`yK%TC)~A=^7Xv!s@3=C4_AC)~r9l98w%V6fj_1%r1>oelchF#cS4 zEuAgpUF%=PtWn1Bqs5ZF_fjS+a$z=(^TyI-p^XE3Nsok~wdw)Htuqw<^;#mX&2cg7 zy+LIVcHaDUHU7OhoUnBjOcjz7u%*n5z6!Yzk%tDXD4*#|=YE&~0_QJa%iYH(+rZ%a3P?C8?AMuBU@3VS)J`@OaSm(i zmzSH>TH|Z^#RwD~k|bVrFNV z)`0S-l%DVq`Ri_0USJ9jk#s)q*j`L7Lbz4-4tT9#E6C+!zuhY8LX4X^q}7Bk$0vXvRuz6CHwPi_*}|` z3T^Ek*C^LCA*^#~20@Dej86CoT&P`kKn9-&_{a{F0rudRx;_UeU4MsN&Y)|#$29v3 z4cZ-6ON{SLxNNSrB8I6ah(^1cSEa3}v<9A+28m++18N5QB#p zDa#%(dT3JXGCVKd z-|1~j14;Xwd}4jQPe)`Xa2`IZY-kAN?E?8#9@u39^lcgTuoF9p z2@{NZ|1&&YQdJnVWD{i79FIHry_{Mg6AzU;rI>ntx#ZbUc*^=zI>Acd*xa zeOJIxeXp+hBpRFkzBP}b{Aa^i+Av~pU5zPJB)FpwHq+-I!|!VgSQ^hx7Twn7_DnxL zHQ)#7F`_`Jg0beZuZ84a@6BdL{nW|2PXUN>Y*79?Wle~*1>+id)gmvsd<0POn)=_R zMIbXxD8O)_o_e5kcLiJDHs^WtdQ)vPo~I&*0iW>MZq;(~pODX=r__&XNU3xeCiXzM zyvbwqqg}2K`B&0|`l4Y#?04vYtE9HmroHI_U3b2<<@EwtonTC%LEB;T($r{-(;_fNU#D?_(QGaL+?S64b6FHPq zwgCo8$2)vvEWQzTb9%0$DCTb-ascR7lcOkf=^@56c_r8jGq8P9Ib*Uhh1VpN-+yqp z^oGuyo**(bW@2n1*RJv|dvZ4et%cwS#(~g3(Mhcs3ksch!97Bwrc}`WiLSp{9>s^ z^94B<;Z^O*fuSi!D+L(T7W-sHUdI3MNM1fA;yOue!pL8OVQz2AWbYOh*FU z`lB5Of@NTMSwFiY4!QyY3@DZJ8{C;HwA`I$HQEjxJdB|^m7R2Rn}-Gpv=N{JYX*HR zt_!uzip%xVAE_jFH0j~5qFVNG5bZ&RJVNry%%SfvgDSIyZZuuA?=BX476w&u z-Fy?X=DZx#bxCSnZ$1qc+a(g`(3P9^g2Fw55lO?d*1=?_Z9NyKa{`puZv?3I_$0!H zKbj%Cxj?}wKHOFwgnsjy-eVz*7nhI_*{q7qhHGn+)-ReIYF&K2m>Nr>BZ^3{m^*2M zN;1L>DPQSXQBcGJGu{B}*$ut2 z+JVe`(?X;INE`lzYK1~ZDcx0{hlWHR4u~qk3MbNp@fYnD>6HZ$GO}j#Gb3X;2a=rT zlTkMoIE`uo$hQyh_6mVdDz@t-(*{3Vl)j!B+kt_+)=^)`@N@IgkfEWWcl?P{n1i?} zMp8wSku83*dO?Dc)oD<_5`-MtB6^04w@ZZT-^Y=6I1dOg-o2JFm+p4w;0A5@imbfd zsZR|o*EXrT`&swj5!V|xRNYH1qyEnpZ#*;Soc`3AIBoqXI_42pfxcn`xFsZRY;RjT zW4E@eM(P;mdg+?Z^Q`G!f9QOOv>}x=%bYnZlkv7T7;qER12;XIX&%>M@dUvIe3Vxl zRV_^iea^k`pmO!Yeb3Iuhs`~`UwcSCs7 z|MtcB#g?Iou}K#U$ug5FE+R)wusvf=pvWb3cX{j~w1rXQN=}RXVBsCCavv7V<+a|< zNQt{{dc|&h6CZnN7cVpmJR;VNIjXN2s%2!uTezM+SjMt3ZFzFFSB3y(xk}}jxleYX zrm{pG+P`5>yqd2kGMt03au;BY)JGcvkn*Nf9InZ=WNF%hA;7~~pkyi?7`cr-Z7nP% zj{K?-XtuXK6JAW{GkM)xT6cWJY}kQE8Hnl0JO3BGT~=X~9u#y}_-ZAXU{7}dw-;_tGbc8Dsqz3ri==;$NY%i^7hHMqWlqVJHGQDq;;szJPNDF`|GTY@#|`rv+a#ey~j+1%rlb_5lx~mNmc*r z^!jWOfF|#ikaDwPe=chl%s;>8;k`t+&1mbm1uJbb1V~+@I0>AUHJa9RB#TcJj4=(0 zV&>_1dWsb8a=rTyGEkKWa!Y;YBmYgQxo7Lbao@|viI=v6;T6Mtzl`bL$EHHAo+=Kl zchA`{m15_Q18#R&H~jvQitMgAe5~dA5}o(Q>u^vB>%ZiFCMV-z-|>`Nr;)*b<2^;o z((Hvlahbzjp|X)%z5fMk*FyHujq?L?uW2&vk5r$ekn&xWZ-N$Yvk2+p9Xjy74UPmi z8F{T!+knVGqfuNgoUI_g(hop~|ITY*H4xBd1%%26GH}7qzlrvDF%jMRgMiR>rsP5? zbHv-7fM=`u!CR~*;@lE%%fKq+=@t3dut7s-__DJ+`F{^8`2I^LseamE5Wx+Ok(0~) zma;_68~;@5W3jhGRExCb_Tvctv4ZW}t;B&6o#ry3c55 z6;bu(Ht$UXeS3GJ_ua}Q=W@X5pz2Lg*sq3X8cDXXl`daGkCdSBO`KKG)Q8069O-S* zOuJvY80~K+e`?}BFzlVw?bOakV^jYcv=0fQfnX~;cB{!xny={9)T{j_d4!3%PuxrU zk;J_fULGcUCkEng!m!k@hr&#bem;s6(y|Cb~t$%EdRMVgg03KEj z>0FYXMA?(DnAn5A5C5DfYNnWh!JktlnA-p@V0RwdX9-oc*B^};Dg!UNa~JJz;?o_U zm*ZxoHDyJ?qj0X>N`bBl^=n~w0F%J-#w`;EVK(eY80T{`U4KkBX7J(%DPygs=HHE@ zwEBjT;IpmKi{=l$CJ*LWTv`48Y3@PK_?c;JOFhmXCA zS$=76Wz$w3ReU%2sB!BdBKHpy6Lo&N83MsxL>30}{?NbFe;y5RC0~DY@sIp6GRciD z^1QNdKXh{tFH^ORYRyz6xW$<+`}jLFziI z^l*l@>mA=UCbE|XL6z=gLD93hrtTs7{}j0|rUksrD{=uQ^i zE{uPm?QQg91-|`+V3(Jdju<4-V%nC!UsfE$>PiGj;uXsw&~c z*sO^4AM$j&EOH`umLQO%Yu!PgP(D4;8adB8stu#24dBL;e#HuepEEZch%){>8v}iv zW-e3ET|Iun_R)+)Da21N4QdHc`rS$LskF7dm|pdn8=CNz(weGe_`LHPl<%T^$U)FI z1fZvd!7ed2z#a%y>eL^Wc@(d;?|)`{aiAJmG-__Iye>_M$cX;wy-ke7o2b!QjhBG3 zif{)}9EKgw;WAa!i;8`XeX?%-VYfZurm*`d(2n^!C*1XhHz%=>R?w{OPj-$(HGszT z@OaC|V&o)E`#7F8ur&#<3g@*WoHcL}E(p5L<`OvePM}sp<;ssu)V3eQ!k=Hgv}e+( zgITzylF8@X3VH2%=lN4o@f7sjp_UN00Pr6f^16O{=m#@e2nW!iEg{ay{}VjCjNR#3 z_Qyn!vf!{jgK6Ha)%Lbn!&aSd4Y-$P?5Y0iu28@kHsm6I?g&o2e5GB`>?Z3T81oz3 zzQjfvVJq__kKos7NGrGK{s<9T;=hLg8^uC&DiHC${Z+pj>-(982o(11_ zebiQ;`9pa92^4n6`N~l?yLDAD^(fde5rVn6h$qAvI2w61Ony=R6df;?y%aiWCIBin zYHVKgRuJ1^5}Ly1Z*FHdy+Q>W9laJAkvn2(LbcE>SGw@rA9?RYx5$y|KNts|+yU=r z%2}^of_yxFrz6il;1@p>z=fqUWUU-5$BSA_PKLPOl{L+f{RR(5ybW9-C(`L#Ty@;&FbzcM;zP24_LQ_Zt_=TIyAa%V{bkpj+ zGfK#Xe0dj_C7^{DE!Hg{kk6zbG7`;uBMhMlZE5m#TFGfv2LmeUQ43GAr+)qFyM4+6 z+5fenTdz~~S!K?AVRjmAkBlPbmW3cdI<&Q+PFFGdT!4LJK4@k0TzD`b`M{NPti9g0 zWmiu1;$CE`md*ef5h;vO^*Ju$HXiBBEaQgEMYBBes~Mm(=V_$cp-tEXS4gBuKYxLe zyqGw2+lS%KP0#~J=}$~9+b*1g10MwMUQL6^8fh>2W60fhL>go6I(}TNW(MeGRM@Smm@BcNn;#+jXme z0Fe=2e!|KXO>Y`G)EIXi6$kU5w#8mZjnCazz8SoM>9O1Vf5ckB249ayEpKxU_L(5Erjb@QV~=7=vWu_AM|1s;%GHq2-}R zQf=drTPGFmZ~r73!QdG~K;GI7!)0{RD(Waz^fOIije*IyA@d|`+7Hp_&55(K&c$LQ z$P4iA;1qU0OD8Tr-ryZkz$BP8?~Z&;BX9l)%A-7sWQgG1Qq7q)zH%TEN~Up7c0N&b zR91;ub{zR)oz%KuIG=FtNr_F!w#2A@C;|k{n`1NMiK>H9QUy|kA^@Utq}i`)d+5zh z%s4dS5@MnoLFavZK&dA@!&Y}_>f9_@+cvlyZWt^#!H8o!1}pz^r^%Nv1io((6oA=d zIU55ZJJ|Mom$NiRHKe~U#4qe(Wr6IqA;7mwuM`fPabcUN5f-4WXRx?D@*;pz01~)! zO9)Qe81@?=pc~dvi@H)gVL#Jso@{pq+u-3dn$9JqkvTlY@MHRtbd)AUgyMm-!S}!d z;rXMk;;bRi&+q6cnlw_bfjY^MFW*o?Et-VL4V5Ci<9;lOHBn}D?5gY z9|!p~zV_SQkWN18Q{ra6USVgP#=Ev2LFWiyA@FkHi$>tma*ms?Mk@?x@}D+{D|l4v z!x^uwAeB{lJ3Qm(du}!KW`S$SgOrGhR^m^p4wdv;!y_pgabjm z50x*nSXZrlT4!EdIg;tj22wwlyo{6oMV<3c00b25C&+~yntZ}XOX(yOs>_?4tR}_7 zgszz*z%bWdnP3arIlKL<$1Hzo-?v0PpNLh%?YUJ-_OBJJXc<}$`&-xtjtrE2g+HP$ z#awTH{bI(9rkvbQrKz_HDANH&uO?qrPtIve2;|k9J^@TP5c~>JUVDgT!C{EcZCmj2 zH`*qK4}rAtLfi8nQqX_#Cgnivf(foTr@!u=0C}&KVtT(O6gEcvGfQgAwW$^oV{&b1 zS#^2W3>4${bz=c4*5Y$!2@I2o4uFV!aT9M0@=L}>yk0vGoZyDr4k-WCiL0k0?#Ll~ zvfL{h7g8jZrl!DJP5{&UZVAV+F$7j=1o2di{B91EfrNo~CB6K>%av$CqVybW`IlHm zvMCL7;BKhPA{+rZ}|EP(V zYP_>)3ChP+rcdR3c<6`o7yC;)8ngUL=p4Z#?AekV9C4!X8k_nZcRgbvM>cQj7Hi^Y z8a!(}hj_aAWCcrg$UYz3qwV}=eyFE^=yahVSL$$s-{yAs)l+xnqlUGIgsP|ZGEfA& z!nq-ctF%Je%X3BP*$d_BSAg@c^BGfh_|Bt^PNOD4A*K%v@0qtQ%`5Wr0)<78x=#V8 z82r8-x5%rPMn#)B(X|JF9?qMZqvpfXdE&`APnPh+_5+9_7Jy6xE-p18u(VZMr z@ux5gJmS7zM0!ko!cXv(Mi>eBJmTcdnA@a8>K}cd!Mf)PvF$rJ=3cYUGCH~Z!>MSaqSpIz7 zjcmsdl$TJAwMgeG?l)*@M@omK9R5mvh>H4rma?;7B4}qHGi-4t_ygH#>7fVm8N4?D z^6Bx=UFMsr!=YL$**XE~wu?e98-m&czFLnSa#JV>{&8hK2m^W!+efwt@Dfs=#C)p> z202=hF;Y?{=*~fk0kv{--ld2id;qWNEKYt^Sa6b-0Mq%m^Q0RU0(jn&9A-j0oCGpy zvY!&v1cl!eVIzKKAT>1&CbyMrAG&;T`KKWj-`-6brVkgZNcQnj!Nn8_yuECx#!PB# zt=zbLF1olA6mV7~oEeyiq0@pI6tqY0=3(!)IPHJk4&y}yzkdf%gQNA|isp;rW9wgC z-=%E#V1lcO*4&2nU8XMr);^iQ-~wR9Kl^VS=1*>Y54MkIXJ;Gm+%;0L&%g=XTy|p( zndXi!YVUT?-DhJqGI(5`|9Op8l;f^0Q+LMKUhEnHt+n4+2C~uSxz;Wyc3yxHiZi8e zcs;GoFB84z`}d;8gNgB{PwiKRM}eCZrw#iZo`F6%PIGf2)ObU_$inwS8k*g++aurG zO|wck);Ws1f96ErU9ba--rohAcf!Ferx1Lk!W|Jla-=QYdnewMZtG{5hrfK=mHDV7 zoB*{JG_HHt6s7A!w3!VK;b9KbU(&j~U2vD%L?& zfysmw6)LTw<5){f>86x9(A0!GZa7u%GdD98MIlE|p@^KhLruMSm6NIr@A{9R4T`Lp zyvp6wS6h~UftRaFns@-F=&+lwWYnC+hN;2B{TVbpoo+6LAQ}FMwTWcrWy=B4ucEUW zVWsBBu}eFcgwen*H84=K(^v*{4FjUD;{1@FV6v!~l@2hP66tgM5W(30`@7SQ0SI1J zNR7aOq@VYY2oZ2O(ZyU10^N5|hC!1L}uaTdrQ?8!*%vW%DD&!r0TxW-RwQJ1YN!@sO%fVEB zMkIURjh~O&BpgVh99M5%VS4Qr(L6v=8IZ<>igP*#6%+j2!m%!_T5zbzQqBfXv3Mo z^$l<|Hdo&+<E4({SqKXK%tzJHsLM9TR>hps+!U{^?lB#HxT$pPjJ^E~iZW#M!`A#I%%6M{+zSr3SkP?A1&l>aYSQ z5Q4Lc02zDN;2{%ig}xE~%~PfJt{ie*S0xqh)KY%z*A44+)XH=BUxe*lQ$rq@YL=&t zkYPAMXq@KDfq#W{6(pojVh^OB%@f8>K>ob!ksR{&S*C;CFq-?HgSNsGSY&K4Kx!qhf}Cyx26K?d{2ES;v*Ew%UwaJ+o~U z{k7Lj^2}vPXl59ww2YY?{d(v}Yuuro8J(-DoPD#ZD1tnOeJ{y!CMOyXkxu`e`^N2i z4(o8j%($4$3dNnFfXkP+a2yFGj3g^yrChK*xWsr2s)D_CIZ{Xo=nlW!bC(HPw8$X? zBYNY!K{6_ARMFUgH4b}nq~&Hu;(zb32Gf=Hij0@kw#$vk7qFTFMbtTKD?TakT9rjH zx5hWtHUt)VfVYwj0*=8b@|)yqi`6zNpz3y_7zUSpx}Ue)1=Wo#JeQkbCd(vYAfpdf z0kZvK-yti%!Hw<_50@iJMOK|6ghy>)U+x|?ATv)zoccA!`w3`!FPw1t_tvyGopQ;C zR#wt-gB4GYG48|;M6M-;xSROTpcdXCyf_dI&=d$jDq`19l;YP~Xt_Acv@vv8{T9+B z-&w);8^12}CC!RlM3K+p|3*`;H~t7+g}i^#ek$_^m{FiMthwiCs@!>z(*;xTKk_Xx z1le|ym>C!3=6>g4naK$VluO`R;(X4%3nh%SYP6ijM*0^L*%VHM~g5wED@}T0} z$Vh?cGK2kC1o1NX+GS$|+Zl2c;=3mli2Y7tP@1Rf@eB?C{cYSF4SW@SsgKq)Yg&-^#22deXNieRoHIq+A zr=}tuFv;vce*Vm7myvlOgg;6(Bzq)t^4mk0!PB>A$kN4$yuIM~7DpWuKWKt%56b*0 z>JRfv^ZBt70oz6I;~XI5@47ng=X--$n}JM zcyLd0A#(a)umc&9 z0)BU=_U5G0h$CvNe-Sl~dGC&i6$XdIVPCAxeG0b59Jf&;b=iR;#Iy?`@)}8etPGZR zU42;vQ#-ObuN_+UZryQeCdiQMB*4-4o^&+5Blz|Jx1vL1euZ2zu7__NBg_MkQKEAm zG(cW703gZKFs4OASub3YzmZkMdzDes4X3y+N3So+Wj>C4bZ`U1?%Uv|F?rjCwvL~X zi;q}brVs8xNKdg@PUJ}U0cBM~U`Y!Mf0WU}4!JQCy>51#ZVDk$tZ#qC2rMZ0bmJ%D zY5!LP)FeI(`E*K|PDczExl5s;b`S{n%aFR9t4;D3B{k;3J&L=AX1`m#GX;7%6U# zEN&brjt{BH;5Rj8?N@X-bMz@+*=ahK%w#|R>$XxfOZI~!6&5ZQxi-c3jzcxvM7 z2u8n5NkpEsyM%2)q~ytZ=}(Csg||z0qt*;n4K}{M9h&iPH>@xgAAuQd4qP=rKdvGQ zIKx#kth-I4MyNr7?9dT1ZgdVx;*pw1$Ra={UxPdC=f?6}6!Uk`TMowmJtW7&sE_2C z`akp>{}Hgh=^O$sWQ{!Ur)u@46K|@|#7EhSrNpH*&JCJj| z#%+mJE?NL1Vip8$zq`UsU{B}dT9YwWw44k_7mHq+)TnGi1ZvItoi60*Ew|Yn-g{Ct zqpLke1svek83#cIxx~2Ba@kBA=O}v7{3hN<{4?xE-iP?uD;ez2U)nLd{NEySo48*AdO)KZAkdDZl5ZIR%(t{%LfscxV-8-Y>iXY} zHH28r^pi^lrL)M0gH2M%;|vU(rGqk=K;9u%HTdlN)%Pnw6lYId$+_N09xkP@vBw3! zhfY!#Oxgd#ny9lim|9q5+nMn+|j%n?KA6X|2NPG_E(s163K=Y4Zbw(pqtNV zq1Tm|uF^>rf)q?hmm3WIezZn(VQ3*I?C{3Q8Y08+co&4QCeSJeUazMlk*nb#u4HwA z!XUk71j1M7WNYy5BDCN05K#wd0KEmyW2`|5lwjm1#9!#Mewxa*-9!775QBpI${Rqm zr!aJ!QwB&PaHKH-<6|YYy_@>4-wzjQut<10p{EyrY1-dd7y!KJu}xZ|+d%{vQt5uT z(6BEqQcM8U+Liwo#uL;5=a>7W{u(%MuiB>pK7uWK+JuKGfL0x3siNd8RBAPor-9}( zpWCP@t9g%0d|Q5B*gElIJ^nCqepBE9k%o33{4L(44C7MJN)P{KQhdaPLnW*e<&Fo2 z)8`xWNTMk@58N3m$+9F`P{ItYVKVhjq^N_&WB)mdzg{gT3hFW-_!}DrFg4)j-TDYf z^?3jgqRCT%zMKiO;I%B{`Tmkr0n=s=?odFI-hpCqhMVzj;}ySI=v$vK`K_ijGrFWD zp0gRfT6;<$2JsK6LME_0XlFYFxodKvRCVW=S!U08A9j=YJE}u1TJMY>=VjvHYppkw ztkSHB1m8{P^z9XK1;m_$^AnRO#N4Hv;8uKY=Q%UMXV96P1p+<*n%&o6+)JWt-Ra*! zG5aiH%4^xj7L?xe@?HoHs=XPR$v#-P6L!&50)mULaG* zfRUkKZh-I-2yKxgyavFhUyvrxR3Z4*+pGcSg|}%L<6!IWy<5}89nZ<%L6m~Ps7n+* zmRFY-D{_~#38z8^rG&$sS#i*WAhq2$zvKEfSbM%0QmgsMQR(@2jEzfZhZ$W*qeB3a4=ht1}6`zW5N&RQD=1`Udf{lK4wT$$jORBcR-<-K* zdD;34A{B8e0bIF<1e&0Os1-Z`T4jY*KQjZ+7pJm&pg@0EJn3CwIcM1QQN-H6{E^Zm zH{w&P{NY5D3m6brxVOOHiY60HeHN$up7qjvobeLd{w8jxUD}K>+|l8>Jr2~d?nQvq zwSW4Y@+z!^|2f->mDPy4j=!OKTU4lH#-5T<$BO|rb9w##;v8m>r)r)eT%5Y=Yu3t- zX(*~Q4*LZovmI-?u0ic`X;?RGu%K@;HnXIHWoIDYwW9*4sUeyPxWQVkj=IzTN_*oH zx$&$p3=$)xi$~=PADg_rI>$d0YttY-D3I4jaI5ilzibAd>)lFWl272wfph;4^G8{W z^_8A==;|Z;`~&*GESvJa`{={t{bMEsIV1^cWNt#ZON0>2Kzdo)3WDL1y^NiTDAjW~ z;5EifVNxKeC8_Bxpl2SOT|*$%Oe-jSKiNH1il3oUmC9#U_CWs+JMy36RcZ9K>mQJG z?)=No(H!iAMdMLJMaeY6re8(T41xMIq5(eTn(PXU?uT`405Kz*fBl!nZ za1(n!hrC@g_!cB&C)Bq^i*AHQiH+Ek36njsLr;EQlavyaETM8jQp&IbhA@|kuWS2F z9e79HjZy9xD|B1#b8iAJ<-u{_1rP%+Fa7cGj@b2UFe3%C9v%H zI5{60aNO`#c|aPNcmYDCeU_xu(LM~8fgk}KnmF*wMr}hM+>wi}PC7r^paDC+1Moqj zVnX%>>oLP03uZ|scG7#qINQ;eNYB0a>Gl6$Jd3!T`c(iC^tZqNI6zk4e^P*7X0<9i z%Hc##cjNbslMQKz0v^icavKI`)(0(-zae|;xT3x^lj0xpgwjPxCarPAnQKa)nhoc1 z(S0{eoZ{Dg8PT9Ezv6d5(Qv^W`n}bNO$r}a&a&T5idUtIog*A`(qHE z`2O8~ntrCUGnZ?E3D=|9AK3?$`k=GWC#Kb0U;2iBC52R9 zXnY1P&W^5535NxvpX}z@<6bE;9i`hdPHV_Q1t*M&Kr@awD+`gPcztEwk=_IXknyjD zG$>rwOq|cr#MRr(qHV+HRf88{G==zLPXVvV5s_}MmcBKOj3`G18wbTt_rZ6KOdvlR z=(?{M(!n>CP!?8KR?t?p#RcKddrmR_QR(CCILoq5VxqJVRu_pYoI)jEk~MW^k)k7o z2(uD^4695J$HeH4Yuh}&xn_6f!a?_gkEp;v{)-^V@WUfPM}rSg*op2Z_)!VpMJVtY z_xI~AQSE2jc|RgoJls!5*34+C|B%`K=V8K>|LX(RmF3694_m4Nj{jyyt!G8zD~U)K z`F_9bPkjw*u?!7jzVw=xsy&o9^Jl^p@dU5@ceVET0Cx=so^t$o?(Rome}n(Qu3_Pj zd5OC(hY+R0d2nI`aDpK83iTi13a-BDD_{uyll2qyIpzJmHMEroLp}<# z!o))qC|!LfH{8|T#+sh4Z15vNTP(-5>VVc$&SJR*{Q%+T?;2Tvlz%mo{^~bCo?mh4 zT)H9fU1qnPKvR|)ZE=DLf5$yd=A*=pOk-Tyczeh1B3L8#9&4~7W58-Xyz<|jBdk~{ zy9&5X#DQBHlI&Yk>hL~eX#n|DlW&H}xJ5#db>K$^n_nMb`c_KTdY8ZBggXL3cwD2r zC7M40*>1%-mWcNe9#6P6DPmq}d|rL58xi_<_A^XqVu;Q?_uID3_+WL$3(+Q)wY!Mi zfyz0+`ICFL>Uq(!l4Z@a7lRykS|u*ueYV>)#jM6!z<0!PYh*vB7B^iyWuqibnmwxf zlQeU`@&Qd3F#yZl&Qt{PpRnNyIsZu3HsH)W@<;4JH%72XZ)zqi{aF_KJu80X4gWNq8(jwca@4UW+ zedRs2;rpU_%?4`d|N0OM7i!G9{ZBv*@6)_SVW;Ns0L7<#?u0=XX1Zf0w=ng znUM#$;=eyj`$nYvpu-MiKULdYE$G6n=8GFwDrSyY_{k1uJyw{vl<#%g2ytDH5SZdd z4ol%fqyGzGpO4oh=I2C`%+1j}1d5Q7ppoPuMquI@hxt>ASW!l)?F%tS1^ns$hWne) z;F&j5vZgW74v2?jDksbnrb&aWyN3KpV!tu%K3T>1 zhVbrPp~L|HP=R4;5%YYq#Aor(Pb(&yRs*&IG=X)EhSd8B-v9oQ z_a@CXz$4fe_Xg&&ssz%(f3Q+?LLyPT2I6)#G6Vz!L5sarl3El%gF}pl+4vLwP&?iB z+VhRsuEL$~6;p&yhPVeCaEkiz9|8{^_>1p#ERg*AA~mD>UCw{>!w-UC<+5x6YJ zDRP82z~9<`*gz)GWmK}9{Tp>tAy{$vPk8W zl?DipmT!HWN+$$H3o_9@qocR54b5uj0XoT$*)hEmKbn`ueq1Pp*mg*TA3C&3jnl5% z0dH%AjOJuBwq62RZS3JT&6n3jfpR_g?Kj;cX#Y`T3R~QxDYM79z%OVevj@;UM0%G8hQE$z}vmsl-X}TCLD0FyXWaK*LcaeZ6dNz4fcX~El@J{a1bZ| zm6jFuV-G$V_m(D?HYF6E$UCxur~=3bcg+&sz2Ovch0#IPjGCms&U!!o)Qu%>X?-bJ zx1G(jHz@ecBf@rY2wCJ;UofFeT%-p^oyM|i*}0f6BWlJ;q7!eAlNaV^j%Sw+T#24VO#yMkBD@H&|{Za*QbATpOwR9xt%Ph)d^Sw`#N4v`nk4Ex%T3TGA+eqXzStHvw*EkV%;TyzNP zk#q#kN~kbspPlsnW;9tq_aJA<>`k$3PSlB$&0FY-draoUl!ZK(d1KEM8>Cd?U%j}B z;`(6W10!o4L$Gzxrq5l_ON9tdn(OB!mohm@3z;+FSP;K)Lvz|D|MLZ!Qo7=i8d9Mp zbJ*%sA)yfHb$UY2)|d?m2(XlyC2`i`l~DYWW-SpIFL7HF>#Xbis#A-^`B4f;D) zF2MI8=yvO`9pczHZpPA^qSu?Bwcu2Hee&7B>74Xrsr*GsIsQ zm8}g_J+}ifqg9X`HWtyQE%njSOlzC+KOIKTEZTmfER-oTj=A0?Yd*Ce z{=)SS_|Zsff#5$#D6?0L>10ra9A z6H83*>F=DAr6*qf7b*gPS;$u7q1Ce03I+fUP{=I*OFDcceqvM0P8FoxBn;x>evrdh z5>Vx5?9_9=sqyL2xwsF&mwe+pn_7UKo(gwN`O}vCa#P;fQQB8JM~SfaVvnAS+g^Z+ z8a{-V*kBV+4TOq2&?U4?2qWpy9bht=DpEj#W|irHu^rX6G1)io{a{<55a=2UYuj}6 z2W=NpxS;Yn6NNDE<>-E=Mp|$YNPIdVomuG(Gyv?V0B%8j z;HqgDoCF`uqjH@9=>37)4JgWDIOJ1v8^3D3+j2P_vGF7Qx@3 z7O}M(u0I+I-}d>G*a0m^2M4{$Lu|PdeU$_c0MsplZo)3wmz(b%rZMN0Y5|dYk1@=- zuRF2{Q>71T>e^x#8ZhI`m~)82rBkQHuS-ZYadBj7sNHSby+GWz;iq&_d@sor0!*5C z4@|KoBFKnXcyN>yr>O8S4okvkn%iC_Tok++yak~K(w+s#SN{vc`_gI<8|n9P&3i+I zOIY;L+I7YNc4emv6+@QQeW>I`mG*j@>jq*irrJk_dwJXEUhp=w?Bpqqsr zpOs+5XZMsxk6beEU+(O~HVOq*yQ*FS;4;|P=5JTtKsJ4V!+jSJj|~E1mJrVA#U4GO zHOXK2;PgF4!|ig`ZND?K=`{&)30MKUFJJI=F${a4BV9ANW#>w-AY_xoO#IeyY=LG{D29`kb9d= z(#baC#jpP^b)GnFd|*``rD@o*{>ZOD}Sv6%&8(TbZL@bR%?Sv;0X zmuplaaNzl(<*HayZ}=|F?s{&tPA#}p`PUzkXdvzR&|`1-OML764{)HvXD{?j4^=>b z;2jW}!Ecsm+-Kdob3C*FA)qVBQ(`^$Zmq|$bi`CXW(w){o{hRjeeDzwYbxEc9-;%m(UVLtfF8NS zKCDa(1yZ&n6JG3f5YA->PiKeB*R$0MnlD`)30+n4f6s;Ss!wmimOL$8qNobsCO&nNyCXw`nQTsJuW1W5Zv*`6^ zf~7tIJqZ3?XRGt|qKUj@PmY-sITsrzoNNIb;iVe`HtqYS7&OP@{ywctV=Kwf>Y2@1@9*HTe2b6K@(m zohd?D#Kic8{4AGMcAiFrhbWP~6w%!@mf#a-kxu$-nby{gyaOCAH({(45e4+UEgaMD zcr$+ew>xWMC?yeRwYK_M_N}C+Rnp;cU=ep)#n4~(sed=T{1cZB4ljECt_R76HE5pES_)bHpeuT{-4a4)e-=va%SKk@vL7dv?KcOf;}E zLdHU+PZB$R?&a#*pDpbt@^bnGUtvaJ-1|gw50($|-eS3^+GL2wbo={6X?5s}uBn1V zr?z3c%F>Yv4;Be`f9RKh3jbT$&gY|l#81t?U!q*GMj#=l9V=>0K46qFm*(VogTfR6 zPA8SX4~m(Cw_ISBM=ZB7zn`*u3WKYX-U2+pWBW=rs{6CJ)GAyd@YvX%{8M?cqTT)O_5QYXpT1up(K>=wb zh7?2zrE{c9K?I~3NxA4WeZFD8*}E+ra@k37RpmjU zdI4>cM39-`s8Hu(U2ablt~yrn$~JS2LBN`wL)}Y)H@Kyc`bV5pJdZ{DLh4O|g%JMR zc%jn#=oXGcc>*a>QnqG!ufVR3LaDE!$tTmWdV2Tfe9m6-6<8suE0HP~3&ZWpEu`_l zl(9)o`Qw9sIcM;&*3olcZ1}UHV!DZeNwW@&Ls~T;ywZAHQ;}o;yDA9t+PzVrxyTvu zeUxZPjj11sd85N7|I(r~_Bix!S54QbUe|%u#vQzFMy@GuO6&9Y$58UeLLr*>(vkY( z(qqNZ#_~rURr#pkm6r6FWthM(^Hy_D7!g(pDC(R9@A@Y=JvRvZ#rBcu)k%+wH{3t}hG`^?mlrgVz-?`K;gBxhzvZgJha1S`wa!s(Iz)B1Noz1te_U zzSasH3yQ#IonV**2}lh1)5xoBRcKEYN?Z>1(B!Wwm3AC#1L{ZO_LZOID+xGm%c=g& zspHm#P>V@_4?>CDF^z3&m2FPowT+vRL)$a7xL4%#Z2b6YM5t%X>f+ffwGm^ueuhO< zKu3u4-17;MpF@tuiykBGq5ALy`ckAagy_777%j4hePwE+tljsL;adJ`Y2M%u9w|P= zu_cX1AuB>C%igQ)?8xKWPLuS4HoU?Z!H-{F@Nr$wxv%r7um1<}a2Im{#;EzG_CKU$ zkta-_r0b=uatPSGw0yisZSGmGTh8Rld!~oTgU7dyo=E^@NZFIjT@wiH1>_i34y$N< zb9}Hrel7&0eRga4P&;vuliiQ}NNuV9je^L0RaNZUZ@e4Fzfb;)O7#uCnOz83=H4YZ z#vT>RUvUbx%I%H+d_;}^jz%@4Nz_UvGt_TiEb`{C#L_?+$ImLPptL}MsHb2Rjz8Q& zlTSlKlONF*f1>}8Wi$%iwVuRkV36MeiAf{HFD%YuILgJ`kr0u^L+MF>+cRPR&zN=9 zW7pwP$Ar$MbZMJ1L7^7xA6`r|J5uNBrys(Ir}F)g+mJ5f%8{jSOV44DiN>P zT9B%#mj6c?t^mvG;AfANp>4av)aUS{&TuO|A>y{8_uF|TT^gt)9qe8Wx}-Zr^ar@=3HwEdgs z{dIpcnM?mKXUFUp$Mu!zT&S9~aJ*>4|Fy-U@f})4YMTFRQSxVnNmp<{P z`FWnOx5)MzL&1(Qnj~RSGmWdh{_x=>-TCsYS%Rby@4F1Q5kW4OeMW7=`B22?y#1w+ z!2iu#loANhrzYTJ-x(01_3lBu@wfnMa<%s#Z0GLIvbU8i-t@Px$BX*jYl|o*WJFNM zCn2wR{l1)9Bp>Xx4oMUP$@EdDc*w%&UHAmMms#!O?l2K$XY;8f8rYxSTT7za9fty< zfHq$za+I=>mZ7jYt02w=(V_5Vu+kvuv z$=7THz0lvEto2SK_sbdfRJG&{q2?20jWFO&goa+R!W7MciAjHup7S02IK8BapM2B? zRLX9@{?j40`h<(xB1(8aNKVl?{1NOeim9dlw?Fp+Qg6e?0mu9K?-46g=UmYx)bE2v zgznyW4rSOG-sce*ZLBY|mGNsYzg8Q^fr^?owWYK996>P{$iK)Lkeau*diO7j^?PVq z%PQ{MD%@FrBY3xX$1F>$U2Z;k^wI6Vu~C^^5W)4V<`^~9aOzl`m}zI|>R2~nn5L6i zte$_ZOsUQlEhL7)hB101U`mG&W6fCm6qlG7KKDF{tg(Jm@wtP;Tp=_! zl?SLh1^Sm77<9Bhd$Xg(Xnu2|AV2T#thN{B?$@<#^wXEw-YBJ*pF8|)ps}1T1?#b= z35}$I21XV39}p83ByB5gF?EX)d+dPv}^Fnz}Xp z-u4|Ql+rY>-rEgVxxbK`7mWlvoCFlyFvy+<>X9;D&1I4uclrG^+~7NuH`^RaXeJ^l z@RRT2B5IU|J$qA=8-g5$G$=at`w^qHko`!B-wexWU5a1 zk0K)Mr^kyjN}Todfvx;OPRnzMEoX`+9)a@>=z|s80tJnT7Piv7@W?oW2ZwuqUFl;! zjtuD9<6n~KGG-InWW$DK!6zC+Vdc-IbGsp)rxO=;KVe;ecB6!l-KyfP-h{L8_J)$z z$0~cRl`ZNYB`c3szc3aPq4&FMjO@GJojslCRJUiyG=_V2SHI$%U#%8~|D}a|6?Xk7 z9ib=u>Xh_p>5GaY=dr0I25&qWekWpg$bcI|s9@U37f?~yL5Nt{l*E;;OZT;{PPN6& zOg*2R&BXlpyPGF4_8jtwqV9ybH2TadYVEAamYAbnuRY&egLOh)-?~j;!#0HtRy(Sj znG+`^UHjv+D5pYdvg%qe+y*i; zrZ#ZEYWCFAyu_kY_Bl`G?~s7dj>flke5h;TH#BW{8)9)D)S7?o*Rb>l{z{XhJhW#q z^yPjt!SosJVJbaet?q%T&C?izX?)0MLx_49=zT1H8>CJ%cx0|yWC6ZS{`M%K0I&0D z@xvMim!E+rKpCLm?|17a(NFP)7`>QlzML^4zQu z6KHl;+x5RTxE;$P4I`vS$y(Cr03(`Mx5x6i|5=22V4Xs2HA3YtWqCM$Qr2)xFu5kW z=@W~5^}0w+#?4BfdR`p7dpz(67O71G0~VSR*Rf)ZZ|S8tfgw>G25s5{t&+sC)Ei(( ze=|))j$n?rWyI6axmN3+z3RT1OYM7Y*UN<@e}{-k#2bppy)!CAr@d&_KQk?-%_n0@ zw*q}Ot-9MwR_3Gj2p={H5Ufj9Q;z;i0ohva{l~1iJtUiWh=6LP%aY(sLZFG%!l|EZ zXCar5sDSyUI>1kPwdfIIO7=-oJB$#S^>9GV+Cup0%PNuWF9pM?@7Iq#$1wk?v0I~Q&PRO%FSFIe&<5xtPJPH6ldusWz4e~}hs~}Z1Z49I zn`xc(+i0(Ub0qt{T77=2Zd1!@G*88)%GC(JbDHcHU0^#kYfSsg1e%P$lWmX+sZ-l8 zDN6gtF$x9Y;+T4!A#K_~8yJR&2m~A-t?C`ceVSy+Lvs)!ZONFUh@zJi2X}=<{1!wx zoOYC^tk}H^SGtA4sVO)JfDDLm$a4V)TzX&M@@BV~M!JebETQleDp*yBM@LvqE=whZ zI5Oj#*I&-HFDSAP_lxap@mCRh(WcvdFjci;`&G8wTaD?dJBRxjq?BzgfBRhz@=AK7 z$>jMi(JcnT0Jk3bz1eN!gHFr&YQB0^;OOfWtY7p;S3Q;l0pu~B05;#`Hnhs|yn143 zKFU5FEI%kD$7LSoq)ZidDpv0?(_KS22E+Kghal%63Y^ce23`%fcFYxt!06A@B03A zcISvIoy#U){<%zNKau*J|80ID@WvF(J0AY9SV3w20L#&!7Y84$&;g{(Zk@@?PNik|p-1%)+G}rTkvpDYcyXnoNAo zj8!vUGrh^3$VNDbrb8NXn?}WEqX3JgEEln*5k?(K5Pg!I(rG2SY9e1z1I~OX9yFNK^IWYY24kiWDaSCH@orrL+f>@kGx%N@m?1 zR%nE2G}R}xJXWS3=+kioB@Jjm>LBgr?2k7i0qJ)j#mz%AqewH3iMNN;>p&?<%b?&J zv?>b658Ssd=ux)}jqp}6xqT9mBBG>?nIUYo8$Eyxd3dxr*l>w|ux?GG2^^JOYdsoF ze$i>nHvGu`xQW_?_QTm*;n6xm@)T8A%+axNWM`vW*;)MA3v6Fp%7m4(|0 zYI%RESI&Us4fO^h4Lx{-XlHt4&?N`>xavB`QcaGbXR%!Lg{4SVECn<Gag4R>|jp4{BKv2=-y2+LcI{dF+~2XCp6 zx+$*qsZdgS3AEp3lENu7DAZZ&uEl=QUi@WW}# z<*nT_fu&o!;85~-33Cy?8+1BK!r~QFHT}U>t;!FwH^YQrRpTyL%M7_mJ!68QG5Mj& zBZ2GJZa@EWvwRv~oipeUN+ry&g-rj^+{&Q}pq8|!(D?lWoy46P`AC(adiTX|G<0u< zau|eFdm@#(2WKMW;{Mt~2z9}SJCb6Uyjpu&QCeFFQw*liP9{GD4Do4yoMy zl?SqJxwjuM=%)Rp5wYguXmxAo&wNa7l^$Nj%K(^iCF6u{CS`ZKIqgH;4W76mJroHWhC6 zd7rcnMrM9*ZvB!uF#p@J4+MH?@T0Mb2BRa$l|isWmtbwBoZ1W&?`3<<)>BRd>x z0m*dz>{A=A82cfalB#)aVc zACDOzeyMawW{#7pSJG3o5*!tN=}Iik8ZEba|7XKF?TyEsBnX%$5MJCY&%r5 zTYbLNMUhGg^RPUjcs24!cj!mxSK3w(w`AD6%F{82i?GEng)N;E5g&c+!orMbOmN~8qmYDg0u#WR zO2q;x&9A!YkRv|)%v;xy-s2GDj}!%;x8Olw=C^RM$c@a;yCU*IP8>@9fJXxO8hXGM!J_ zV=~~#-6I^=o1Sy}yP&?VMz5#PqqUw;GV0y$rFQq)ghQ$uoZ_^w zp>OEfww@tWYSg@UR{9!dqAEJL2-$gFA4o@Dd|?wx`mlaT97~*yPmi2cbF8;iqXC{j zzd{oJBLt0K=pfTX5YX_q>#>ytL{Nd`M>(;VGfnbm*B``tPS3j^-$-wViLC?}64&G> zJbI=K>IDmNZjkZL@4L5%Q{@2sjZZONNei&jv$6@vj0n6 zjGNYK^+O`22G3H*~LyXVG>SzQCl?Bah$4v^G*N$?-l)(8%UstZnP zH+|wu^s2d$IMAgyM8M^8n*x2+W;aH#NwU50d2vmi-wl0f8F-cnqS3+yt(P|keOJpA{pUntBD59Xfh1cxn7G|kiWnX^+QhZSu6Nn-+Z z8Xm#cLMu-{9>~-Dn^*5jYOvPBPAXdkv{A1MS3d6pc#JB$BchfB_y`KyZ ze|?w{q^9sBl}`PM((ik=hAYhJ$cMkL7cZOHs|JAMLPyr`9fcD}G;kM+o($iVqoe~9 z!eG+%Ql1=wvToC_)0dAENW-K)CI}kp41SQRuMxET>ERtUf@As;Uj+rP;X-fX=nxKO zg)UcNx9*?2J}0mLeI!7OD@hXci_zqW5gOCCTj@l+LYFSu_XrW|6}B-bH+l%_wjV`Z$zvY6Me&jOo)Tki zILahaiY48zf9ywr+P{`eb@XL5SYV^#LA?J$} zvp#?Q9m^fKRhmA$n>BqN#0_a;(V6cj{F*C9Z1^099#ny&-=XhNL5c8^gKT&Ry?#&W z3yK1QXW!)zUz3}**5JQ%Q>mhR`+v)c33lPLMb92RdfpbJ>i_u0%*?RKtUTe7SCA6b z!FT=l!Xio158M;))SH(yH0jHZsF)V|$4Lsj!hES=o#qSs{jCiV3AwTygjNj?0JMuJ zWTfWi)u*^ZZ$tVEqj|iO7MBO1o1VBiGj58$gjaH;*k{FA1#{!PUh#J={rlOTmx(!p zkGWgDr?CV?aES{NDfh?|tv`yZ9Bl=z*T||*s zRM-4NHR0l|MlPO$NBtKC_OqQ-PFx$u|I65s#1p6^sgRZAY@-z@YoP~EyS|bA{~7c8 zOpnO`(fPjC;@8+T@^q;1wjLz9vmA4E6+bF(jG*KhsCdL;!<=N8zFaKVugcPQ9r(%d z&!$v4kWDH%|E8;$*OwW-{a@z5R@B*ny;#sQA^P`yc=R-Zbh2ee00%aem15$P{v07J zR`sTS?+qwh)g)aNv5AmX>Y!hNxfmDz>B4~!?*XBXOMp7 zuLev@5td|0# z%IV92z})PA5izTtEWHaK-vrhXy;-#Vaukr&{8>YIC1QkhtF*LKNFRuyUWeC?J`;J@!@`( zd;PspN=Kz9r}cy$n?Vp~!h-yakecr&wuLzK1AG{0w-b>ffGs}ois;;cSp*%t6b6@u z;Po_tu1bX;t8x#INa~%CB=lrGcdftj%U0VhF_iYno0?p!7?yjvCACv54&Ui_ZV-QB zOO#S)jr;}*X~lMFndO(m)RE~Fy0iF^dA5)<6LJzTWkQWaq5=@ETFBV%~mt;elr+R_Xrtdp2 z66+Sc%`=Rnh5`!;6dy_&k|4mL@SebLv@I80rQHR0wQ)ToQS%CusChoxYqTj4Ej!xV zUY$>f)~J34i2MF5XR26oz~t^hDYZ1c!_{S^lsb;ms&!x}(1mr1S-$^sc>dG`H}wKK ze+|tVmUA4npM4_g{GBB42F;;1H6S=jAmk{xmmc6?hfg`Cbn_QBbm0RxqZ(uzBoUL- ze4`jwBswL65mbQrfJ=UdsTf35=~QlUjegY1dzr4~I(~G=t7uzG=4G)H3Aucd z!a3eW^X{AZ>ys*!Q?BQb?+$^@k$>yyL5rou(FVf}2>k0hFkC4dXO#!m)iD)aPFx-X zP!uyJ?%pIDFMg3zgR}X9ifOkbE2ISd5rI8&^UhpL@||p4d0Jl9OrVWF0Tu5I{?Nl7 zy+Hgn?6RlrnQ3#NKe^&+x5%8jq~b1nLkbIqL*zkoo(dnlMDX07A z@$p@|vuD2Kl78Y6VnN|Wlu|iSlAxZ$khB<*Hxp3lz@f6#aLaQ&Bcz%px!j3&CcNsv zDqby0MFFCHhdq-KIr1GBbd5rG4WVu-N5M z=)vddjn10>I{oR%_;CZ=2YDc;3XdklXcZGE4ZaJtg&;-~=ShUY8(&iM*1N&UepvCx zhEuzNn=)3{Q*CE2iPs-%ot`|igfEf&!*i_f&bbRV9pswByV#qEka+X3x4oCc!6=y@ zI$P8Xlxex^Atd|Q)|M6@2%v-+K`6W6sLkchGwkV4VC5PG0yimQxvP40F@%UO;+(%~ zC7J~;I9SQL2oD^S(9b=^gX#XqcG=V8$dBvI4b8ah#Sh@@;pEw2;gpx-UF4=1QVu$LBr$m9^JrL~2Z|wZy^6OiM9IoYr{l{Q+_T^EHzcf|rCWJ*@!_lbl&Rab zIuG|tSEi=J!^eUXf7|)K!%JVhFnk!7_w(y!0$;&pIkZ60 zM;fMUTzq_!z9F%zX|%PM84Y&d+uIS)QbgmWb&RmpR5PsmS(z_=Lf9F^`XN&;e?1@O zhPlV$q3g~=I<5aLKdqsg`g*)-n7sD2iTNNpe8CLxUK0mV7%R44?HjCLa0gWVSu2+f zJRV>YU6}n2uZf_X+0e8TF(ZY37i?DlWMVB$xRFP#7rNE31KpY>j@)L9T}ExQH$J7Z)UG(W(8AhG)&N@70{Vnd zScnrv!Yn+PuH%4n)n}*~A_G0r9F^n&V$ndDr9*wT9@HoNvMm{=Q0;b&Q{wPLY9R%5 zx#d3p*k>~q{H08aCPWAkga&=&{BN>NYn}&Ea(MfIk(CC`2o+vMK79{K4fxV9~-{?D*By6 zMeWL_S?nhtrk|4XH7U{Mix&}hdf2d?>}>MvIEN$KI}Q@$Q__b?Fe+q1eY8;XOM(qM zT6A^UpK?lM=*vrgTI5LM3HCwr+Jl)l|29ews>=?9jAs#5*6qrP=hgd6&qas=UfrsA zZX83a1i|KX+8yN6=XrCq&H zajsAis@JLBNMT?LflvYsJYI$b6Tpg>VA@BbBWvA&{0IZt!46znCB}W`!yhzzw+3An zflX6;uZE4^v~-|W+l9C2<_?A3~jUMfradM12o4NdM$K9LDYvloncA}i#fo=rs{KZGNxzZzCjDLNE zRy}+`2*|sBzR~XQvNC7Tr3j|SL;l&h^WNkoII2_u&H0NJ?GJ_oJOxB3lk(F2!h;Tm ze`Sf%o)ntjyk?tD#`}xuvo!&P$B7T0;5}b$8jI|7@Sm4J2JGO5afTd)bplV!5v^r} z!}z9@xB8A-l#1Xy@8BG~@L*ojQ|A4Lg!5}^Q8^zx0x|yYj5lRJ>i>HnGqT$uNr?>O z>>>^9XO&eP)Zwk^{u(Nh@X!!G+4oOp+mN5a=bd9eJN9fsmxCoGw2+ZP7idBi%~*!w zB8U}kHtk+gLrPAct|S>eQSkwxz2BgFM-iP-jCI?1F+WyPlBZU`)-QXw)Psol9y0gR zf`?M>D}Yb7!gS?0xy|d}H1C8f>@t4gp+?xZ_KvA{3oG?`*$Nf@h^=jW6};bP4Rgu1tXlTb64MI>f&cv^ zTcTD8`F8(Pp9yy;LA4Xb`euV5DZO^*3gspuOnjks4OGD0g@fL94?{%)W$M&>i7cWC$C>6nIO-(`s z@t+ZYsHFI5e{L@r!Q0xA#l6>&8xI18z_&ja$ftn~dYIX(>j9UrKVLJsz@1y%$)>9q z5x&=n`<3hG#q`K)fp5i_88dq1Qz}ww%~Bf(8Yz+Iq)c~UBWB_m{0#)A!ZEdsxDw)II6SGv~Ab7=0q>tVQ%AwKhOA_9eqzyT{L9e9@u|9(X*%A zh(9oFyUOjMkv4-Oc*FE%obYWsY8h|JXWDtXQF?>X<|caT=2_>H3Q2R5*QfDuKi?A& ziiwbA>!i_gF!2#?4K?ZWv7s)Q7s~z=dDyNZbH02u?Yc2~Tue8SFKDY*wX9$0a;w@W z17FdMGYPKr5)6FTGm&=xZl~hmzbzzYudyrVc_C{YAs^(A!liWv!`;HLEY8j)Tmq($ z2J$HsA^Ht9zP1#k1kzz$3(HBmSMF1Om(q0{iene~E4a;Da43E?5%*@$kzqI2v=jk z`vx7@UGFN)qpgvUTo_N)G1;!G5iYs)`x2k;Y4q?`PEO%tiqM_O!_GG`cnF{<42Y`% z>)Ut=3lO*a%=7XsC2}q4#z_xjK7sj?8?@s4-tr2jom&|UAHX(+u&I5^ElQ#3DH*^a zmHToCoQ;u6Z!JP@`R-g@#q6~2jJp)DB>-js@k@YP>A(V_0pp@ElV253_R?3mBm@vO zi&9!Som%Z;8$hX&*KR-gT$SAnhG`^3Y!xa)1P;{Hz7~gZ$q>Oy%UL5145hDR5*5WZ zI`DCw?E!57vv<+?_pU{=q0pURhNml_iHPGXf8Z;r6fWvWX2$0Q&v?MD2^2}hMH02) zq<(OBW5To4F?T@Wlanyu@+Cx<@Ph0TRb%bLuKdPCQu*<74r`z*vMwt*>`|B^c0lKL+{x8)RkwrK$$~AVaY) zEFPrfhaDCQGmn;s+IZl7K4dSe|E<6Et5opqX9Ld-XPsgHj=Qgf%Wy3<+^~ z95)QOwAQ`a(9l5MAlF7kAVng;)IOo|7OGZ41wq7-wC@KRJM-*t|9~*q`1-%{l{Qrd z;wtZmfu7~EaNxrd(m4%E21-Ikt;ZS<{AeUFC3_)7>`?TH0&zSnm<2a6rVx8x>Py9; zdhj}Af)I&}y_59yb>fmBe8^v8y!am>(s9bg10t}_f8)j!aDvY#)o)rv7h(}DLWyjY z!V*3WMgsIJzmrBcQ}VH=iMGrXv>4;)N4sD?h9^0IJE%l+JMco2J8=umuaxi^7qsUU zHW*ctFpK?ZMuo(CI?ueTNr{BqFAf3BpI+SP9PMoFHU!*YhzZCxUdk|0^A(4fbz$lL zCS3m3OfH}(PdM?S70ms3rRSNPU4l3Z zGNH9Tqzm>xWH)8e1|P&R3ZN|Q6D#!_1n0A+1>Dnw!H0WpiH|G>%0pW!-G0dl8-lP$ zcoP;YmexaCo?X2b^9+edP=QnTw4ofLC~iC4IpgjIXlxaTGlK#WP)>+^%AtVU9{?uQ z315j3Z(NxMmU&e?#|;b&Iy)Wt?8le&3Pa=++7_77va`}i6d@CMQc@CkKNa!5ag__v zr?>=?rK;9?b*$-ZZI?I8dnIb@M@QpglG_6rIaw5A1{zDzDSN_!+JYpbZ)=fbwrT8S18yk{O+!$O3OOI$XZooR!zvO$T(C?9 z@9X#LuF-rk(^~dd9jVkTVR7h6}a6jD3h;JPzVgJB%!;gwK=bL8S2@;oOnb4*yNX@hJ~eei@ozLc+Dd2yB`1CbTEcFPwJ7L=HcNe}A6>=+g0e4VTbJcco)r3oj7S_U3l1P>tc89y z<9Z;XL(%K<125Xch7fW6yRsYrA}C7kT$u!Mxfd`XJpbzU)l9u1BcphHR}Wo3epg7= z*2{NREPN<+_ubymZ$PVQniTR|`p&i1Z4$tuKnXH-65*J7J4g+eLN*<$aqtyH)|v>M zqo0tMa0b^AjM(Uf(YtAnUQJ9a5B&MF{^zP|Vj?g@7BAwMY2)x{ip$jDoc15^@?!OGkytgdLXFv`80N1{rfQD0+<-RZ5to#bb*3)(FU& zq&~}M=CWkFhT-r zUmfD8q6F2YeBJ>)vzUM}?EQ;;#gRTR+CzJFFSQ8W!b60Zyq;#9Bw6&*z8ZsP54Z9` z_soP@((f?x{oI>u-v0?H#UZ75g*h4MkJ;iwGJ58NHo)XG1z36LNh6jq<}98NXgBIDALl)Q7IcwM$q;(hot z`Ey!H=7IcllT^SHVdNGU>g&$W+EK=L@@aw$#9Vqg>}#r068&xJPXIl#XkFh9^@)}v z4SWs`BSvz_$lS)c=Y9wyvsPB-Z!bCgJjF?-m%}*$*1{!N;g_S&Jr*k4{Xz}Izd9$9 z8|q2BACG-O7`4hI9G{fd+I6d+E592rEnk{Am+#ra3hvXaC~1`)x|ufw+~K+Yb{%Ql zLg3P$h-YX)4sm?Q58a!epWJ`IsRFa*VHE8v*sFk=aDX?hH={;2@(01wa5NVtGCNS| zdaQZsu%T6M<))wnlAWoDI#7PSLP#T}dn=)W{ek2S`(n^cjf0N*`mAQYZY0`Mm6+NA zZXo~`K%D_wk_q4f@xRnDb;Yf(OhL^nen{`wy9@d@v#x&=1~xieL7Xr9E=ZBs;x)?O z#Po%Ka?>+1$R6B%V)zhC>Z>aZe)w>8@PL{Bj_-ebVa2NOyKF`tUkH4u|TWd!jZVL+~OVXKXbzN z=5*;){=M;T0seOCc~}uN=|D56Bz&aP+(-Vn&3&rQ@p z-JS`u1=k~lG7(h}Q~Dz*1n2}G+CtXz2D8)TFJ^gV5x>s+xI>-B8*}Wpo)+C2z#_b0 zN8}v)3%QD=a&FOpH99Pkq~5%Gh)3gS$WR+%I*o6{tGnowQ;rl(bdmForsa4@&;F3l zbHKM{2|0`JkT1n3&9wf}^U3Gn{T)x%C@G3<$Aq5XcnYE*=D^0!m zj#(pL#_(Afv{{r8$qq#r-uhtov82Gh448z|Q$p-RlB_-D-Rh}j&gM)gdva^AZm6Tm zFC3YV+n@Sn3PVhcYK2azVW^HJ@|7u37a_}gY@|(dv~bjo{L7@4{ArKXv-BPBxU7Sx zt=wu;^lM+GB`7QE15l*lk*P{16s#<}e=Blx`dK1w_U~~nNb0uby*8cnD+z_8mSrF7 zCB@?A7F&jpY1g9g`-xy5n@W3dmkRlu2Vhq{fH1_Z8U(sSMsC_hsy|?B^YGj%<^BdN ztGhMH#=cZ|k7{xIPK?G3y%OQ%N2i~Ye`OItKh8c{2I8V+cxsG6zMHCB@W5Gni0~?( zK7)QWyvpIzbFk+y^R5wQ1EHixdl90WZ#$|3YzGDoN3mdnL=#;O(Jr@IbKbIvlWu>ix-*8U2w*AE)X$`8wikjZU! zUvu5i5#(!QYw56it274Or{H& z&fow#Rd7VehLY96+>!5>Aprea5#ulbbG}AM(QCkpkx|3&3!m3 zNHIed|rQyhwTue0UO=eKnRzpW^)Zw~X+S*7 z7=|h{C=<)#?WW|8i2XqRbo6--WWQ?`N`0pjeK&?m*(yLiM~ESjksrbs`{8rIYHe6a z?wMuab?^;-*FBwMQhKtJvlf{?ga<8!DO8uBi+_|860NK;Q{qTK0^~~Hi}sjs<#VTp z>O`aFU{zLeXYXNFT~lL9OCde?658^@Zq5nB{Cl#e z`d#L%caUG$PP5Cz)))~I8>lj{75p*iYH@Qa9qB*X2PzWLt`J%B9s+tVmu)BqEj73myNZ2>|75DT$%X6mWD>2Gr?wt;Zc&hwxUXp=d`4 zH7Mr`6FmBqknRNZQ|O%&?ghCm23J|V3a|;V559Nj;e)#@Ui3wFZ;Vn=o{(K@N^&_J zqZED_UJL|VStP;Fdo~-+zZOpRx5Ml8O-$MP)ZJ&%(^5GAp+$gX>de!8nx`@MOk3~y=+oT(`YZdASWVMDDK|#U-q3f1tUDf=*Tz`mN+FK zXA~0ux~b}(CE89UYxR6IGA1`_YlTq_nPZ@YR%-)?`NodyA~=p~O0N>mi+O2pGo%7X zML!%-zOH~wSV3;!BJWCy{|{!|NE%;U;!?bn0{2Z)Mt5!LeM zaZ5=m5F6iEcdbpYNdzQ89!n0YMc>e^KB&GiyB!+ z|K75|!^)6&XCVEV`o#YI7OG?Ao}l|->u3E!-a%ee`U@Ux-!R?{^V#y3>-lb1TmNcr zPbC|BtmVpLlMWl$feZj9#(M4hs3nUvny6b52NvRF8nD>b;a( zG<)+k-f~@L%e?|q`5&}hh(6PDTUzWmk9Ymg^d*-d(l3~UpyRA#u87uaQ&7#cN423~ z<~nLU@^aKJWgZ)Jp}(Yor1#L)%)gnD-hl!0l7~UT_I09ZiHUcm?yotW z84|trT3u6iC3ocH?d8T`i+@@1h7bP!uu60f^9o{ZNy(zpOCK$StLVn*BWb(9lGJm5@*OoL%ug zoNU%6v1@+HCnS%0>O|-v(W7&1yiv#xaLQiN;79(d zX##6VW@WG9YQ&!)r6{jtwE;Iy^>rkJ@`cFM)r^1uG0tiim%++D$jJA-*}T}C>n?Xw*7m`%Ce5(hy;%SMTNDBME zODTuno@t+@E!D(?c;A3O2eHkvw7)KV$aFV)Vj7Vqz*(<)16k=j^N=||Rz0V!lnv@W zwnHRx?{}-22Jp4YJYz;ZHJ;BDDs_^w}jNJJ~tp` z#*MBiNs#*{viN9v-~dqoK60;wkxx>9Yiy53GIuwArD8!vpVahn4*9 zR9~(+sCLrj?>VyioE(YIuUM+z$({vzXUsD3-|!ZZj|U)L z7rd0kCxu9pK&Hb501x;=h`f4GxiFhgk9HtyFPE`Yi)7N{h*qCYpxv0{tA z?PY7Y_D47(o=&0SL{^tKxRfkootz6Yy&DN|LRr)IKI4^0P&1iD@$mZo^Qkay9Hw9b z`@{IED{qCpgWruT>($Me+QP-Q<^1(enuZk-f?^RuB=YF$GA@v#ok5$F%em=TteO@w(3Iif)&({n}}5>_$99?Fi9yp zBNf1gD-N?h82Mga>9(*>GxzLbpi6AC9Xq_-Xg*huU&HPpJyg3}7xa{k=l8BmxYRP; z<$oHadvmQX&PdFNg6T1n*&n87)vlpm#1-R@O|Sj4wJrWF@E}FA$k}cOz5UOJ8~h(d zXTcR!*M{LUMGHfBhX@ELNJtH$gfxioDvbitAe}>lfTYqeG)PNGGlXq&Ntr= zIBU+Dwa;36Kl{F}+wu*7P+^4D05P!YlPwt7Argv`eNh^>9sVlfrqiuUV3^>E)H9<* zsn7OoGHD^5_Zkl0p)9tZPGBcNaq$N}Ng=w@OGBytfUvuHjG(ZPls7e4P!fAVo^xS7 z60X2N9WL2cUu3ddcrpwY1g)}3?MRz8ZC&LGD>9~p{6J`qzCP@Oei|58A%&v2?I~o; zS^PX{+(JV`%R>*Z>NPyYtf_`sZg=gHQj@lZ70|{l!X108d6n`wiq=nMdFu=`i18va z#-8N)@6+_O9Mk9{!cRbTZ-7b3hG;(Ey+-22>$}$&^GKw=$WxOv$?3<$E_mTUlS}R| zuL=byAOwPA4y^UH0Iiom95U`~UxJz5+g9O+&ZJP=4fEoB))$*FIA-G)P1}I`74PDE z#&9kRBpoP*@!LdX`ubJFbq){Ei|V^dKVS(yjQV4tyz>QZ_6j!q)j)1Yg$Ex%))p6k z-rbLCw2yYlj%1(=ul4MA=Xz({PAEx3EuKY+lSE(t-KDchgR&T4C;^YkQ}X2i&ymH# zPhCF`5S;Y6yfb?uy)k%t5g+?7rV&neozLnv8=pL-nfNZwdqs2=YHz4XJskMMzb}i0 zqj-ooCs_6wA-CAJ?WZ3lMdhEYjvrr?iTT~X+`H0{6LT1O-_o?;_G}~u4vo2Qtc1{9 zaYDWsd7i8%E<@g>dhHI>6Yt;JTYR4=$S3@nSyD4Y(bRv$Sy8VQ`)eZ5!?Vn7U+pg$dL$>$$O zH4`s=lV5lG28{Sx3+2lqfh57o(sRfxpu}cuPLK7d2i}GQOG~lly7Z*lxGevns=tlP z+{I(pu(Ccvi$KxIXG9UR81>n390OU}=2mRzk#e+OXttHfZpeV+17L^)-eB&q3b^Vs zc@M@JYo!<&%S_?#dwme9U_o_ZEUFSXHP#z3mie6&Z8zR==ckpNOVdcNHi=5RD1{I_4Ls zs>};8vJKW9s2rbLn{janhNDEtkx~fAUyD~jh3~(uIv@s|_8zmj?zV`zJQ>)o<_S$@ zLCS;1#g>E`PZT6?*&xd8c<5-JMqJ!s7K}m{zygfaeOwHLA!zNgpq&+6SwjqXNFTn6 zTz@AGA)XlOYzGq#A&-|ZGH^K*#<{hi2BDf;CZ3N&;0gDB!x?0r^mu7pC|Xx=^?}qK2IrY>G~Qf9}&)xF)(2t=;E!O-Q%%ar|twL*mMq zBV0<}7}xSh4b?(^>iV8}7DCS~^%|d~UW)z|z`wNf(c&dg4Ytg2FW@m~`RtRz!&7Z1 zWyT=8ec9IaI2AAz;G+Q0`~bEJkF_4j0?dN217$GD!ZBJid%{;3Gy1{q_`97I@+W}u zG8&ihwy(|d&jtmvhGXYOTxpssmsd^PAs=9pplRGT2b@da$-S)Q<*H81U*F@u5`mV= zRb!i-2Aj1kKjGk$j-PmgtaYiPp}McD(moK41Q%^(jsEc^1E#_8J&%6SB(rafac>bq z(OV3`@{z=C1WCQ^%ii*na}<2vvqnEL1%21!8XAz3=goYGhn*F^=OuJD?!Mlw%A9l> zi9Yz7Ulq98^v-Aev@kcDcwvm;c`3q3fT`VQ}xX ziH!UAaq)rA@1IU9rz;(!OE1G6bRGjUkuabIB39#Q60FIk6&N;1&Vkf-es$u&J0WQ+ zq|#{+fV+~0i>8E7QNLu5i--ru=*U(nT1`n}TPUxP0=*=PutWG>y;As?klckU{t~?- zKDQQu$eNv+9(W~)*?y1`^7>GH3G+NC($Q5`kXz&|!Fsg@3^Q z`_wbhZ!`%770S}ZP{*DtZ#;K(K-I$kE3iHDuh53Fl&mq6z_^d;10M6*+s~i0S)@!+ z89jHi14Ka*SF>fo0+}TXJFV*tZ|Y1s7MfFKTqaz8;G7x-9y^II{G-+ZRAe=Tz4`%E5`~+N6@BN%)#jN-lB;BivoboU&5}6XwEu-J9%I-pMaPNDq z^yZYMNBQifrZVz27<_GOKCZnQr?QG=fBERs?;A1nh+A3YcqbtUek>h;MC5Gsx4yoO zc4p-sATat*y^$0^XklDfE#YXg0Nqtmvm%^#T>LC-Zg$MFPa;>ja4M3OWoba@Li}3~ zTfays`b&7qr-iGe^D8oVt@=TRI#}?%vmeoK0U3^g=9@L($(*k4`# z0&j`fxg52`%ZaUWq>LVbEobuKBY@Y+bgrdc*6f##n_IY2q)lmRqX{?)WWIUm&CWx8 zD(&V+_cmch0yIMwhlU$L)ljquXD$H(OSug8&dOl!5~B`6z3#%Vq>)leZ@xnzKymwX z(U?jI4?J?i_nt;RfN|p<euQ_LDFO2CgF2wK&<#R)BJm^TKIy1FepUep zvO5ep9ps&CC<5(UxB~3&PeNKjREVUJ3gdn1`L=g>UlU8EM(3Jb_xjQchR+f>#JwZI ziko6{`UgZ%_4+PdZKX}d%xxD&c>-=@a)1^O?XCp@%^fZn>ptT{ma1#Njo<{LivW&q z{z6{F-~*{`2hc~}R5vE+kb#^oSI33T3d1ku8Rz0DZGZUdd^5a%Z(J|QC@_9(ZX6F1 zT{VOkR%Yxka!Wlp}s8oZA4Om{i+!@N4esij~9-ikRe`LJxO+;60hnV6MjO?vTo_Hdc@kt z#SX%=Sc1Ur?k-<($@jLt@nlw48+`Rhi;)COzd(KbtL#tvnn0;$z@1(ZO&S4`0?G+; zCpf{PEG0%&%cTdcBTGOjiwYyPIc zV)=mSB4vDld}}n)deu7ZUbP@l^i}|jei5gF=>qgTU(z(%FrDX;IkKe_hRUc2QFz+b z6gl^iCGi-t*mykB+Z_?$+*VJAmtS91(oxVzuoWGYCzQHxuEQMXgA)gRz9Dh3figiI zWE|-JV*K0GuL%?l_qTw;V_85g-w^M+SjP-TcXA3RqWyD?$8UCC-7@P& zm+SVAYiDaaHl~h~x}a&q`(93kD`i}U@@F>2AU&TEx9X-e4%B4h+Q7`F}e+t~VS>VGUQe@XF!i%1omdVo%qM&ReAS$NB<*ZHgNve_?_3==T5PztlE-zk|z=lL$Cp$&1U`5u{|CY@n;1!s}SwT$7XbD$@;opcEF7`$!ll8p&1#dQ5nE)shO%_hf*QP>p zkOSZti_QOnSL0a&f^T;5b(xUkP=dt8Gm{D-@jXk;3b^qfY>MbXT%Q)Vjb!;ogT-dO zhmyd$58FM-G~AElQ3ocTjhmO1f*P#89AJf##Y3DTC&54Z{#)M;0Y;{chD)_=MPHaa zf{^Av6USrGeH7a1b2Zu)*gdrPeBSZhr_A>dD&QM3?#R<FiF?X4Q(A04k;fNjQewQ-rJ(ksnaqS@pmGajbzg)+`TBDK>n#dT@;Z2ea^1z zW@KU6Ntk`I(N0v_)ib@{WsL}mj0;IB`8MF8zBWvNT^fg1lh1wtsJwy)dpohTP~(3Q ze?0s}FXCVoN-*FmJem>PLM_Pd?*XzLss*9Ll-VB1l6&3VoyqQuSlEjdcXrGh9LY1Y2^^>9D1d%b;y&bGp zc)(o@OKH-L%59JIB-@|O#3r9f7h%5sQ_VzH+>vF6dJS@w-i z0x;{l+X2}+dkyGFL=*c?vXgGeSPqcuh_o`cKzp^-PU#}`s`bUMgvDl0RMX6 zGm@7EFCX;2mHqFY)Gb_D#GLTO?}Qp{<>Qz&P7);@!;%l$H>i(4J%-(ewmQ;X5vnc8 z`z`Z6jV6AVEndUo7irlaFg0hjfQ@0p-xoB)ls{nyx$apXU*F=;;jW1(M9wwe-z(qA zG0$~0KVV>669#L_Py#XcLpolEzsW#U$a7BBB|lWV!HV7PgIHh=I|JhG@S84Q%w~YU zyeFe0_-~s17)kiP2fqn>4u!K^t(cu6H5kKL2&y<=n2t`XWmP=vlYC9`Yk*Qs;C#RDIV)2vQojCl~`chx-3p z)Dz|cBS+Lbo!XmYLg?yy@rUG! zN8%&nEBMC({)c9x>77Rse(kp(1piaG8zN{+Nu$tRodc?6_^km&NLlWA5q{yq03b$h zIki7;Z9nX-u(eQ6qgVdt)#`oTF2>VGja$B?{V!7#xPAGgJEMJMy;~|#NQjWIG@NRxI=nA=jG4ydiPx1Sz~z;(|HM`IPV6Cbfyu1f zFDSb{*}~UZe*j8f`P#;o_b-yIIhiUT-1ASRzyRqz?yvd1U3<9J1@SW~w8RKqc0G;& zHC_<=a({HP4wkX4Bwy4 zHsyZoH=&~wV7s|%V2d1YykhA>-1B8qCf$-izWqNhT#!tl z`^r#fL+-2L!%Ly0m_zRs-I#9_#7eMc>h+_XEhQ}7U7>I5pDU?sfR8z>YTM92g-3D} z68VN^Q>5L6zkH#KdA%mXtil%)8<-@xcat#4X?`NQTOWFGk>dn|yNMK}&vT{e11&j|BbQVH0Fnk)4Y3$xM&~@F@2O z_wzqr_%%CLI$o?;!`r0KVinrI;J)HA;4)I{w53k z{URI4edn=FEnsQGeN6htadj%g?t`AJNASGZO)6HT=MTyag)U? zFD+}J#L2kY?Fml~ukp{9sYb4Vo?ET8-m&45u7aroT)GH}?!sM-BL_jgGSlyXq}Eq= z*6ST!vA6RG$e8CgDQ~UES5XUxo!o9*OT2(Z8gZd~L5)`{X)( zyH?r+{Q7kuU(PvvuLLR_M!(2f{ysisG@6&?3b*(h#?c=5`O0r(%Se%?8WJktX6ykN z9+Ln{Oy?SFo2r@*p;=`!RjbOE-*L5tT9)`m@K`wqxdR^HIY;9j$HzU*ShbK;R?3nX z9-}4%l)|sPL$fko z26fsYkEz!}3nMw2vC7!(U^vrOhtUOY`zJG^W)t!nJF%KwIj z18V_-uD<6mF)Pq2!!?q(78u`+qeUB10%|am?>4YK5rayO{cnD@*1%wDQC(u4H|N$%PeYvV-`D3=I+eCNPb`LU$*B)i=)8U7 z<*#X1hmXm$$4+9{Ha#@^pk%nqN;=53BY~Ee2Ib?9N~Ey~z9+nIm~M`*8h-Zx&bY*8 zr?N}KjGZC;AtBz5LN1&ow-k!irbpCnEi@{R=rAk9bL7LvamXftiftO1WS&2TU$@PE?G4Rsk_1T z*#-B`4)uFqI`<)yr=t#De{*}Eq=;>o!UCe#_S>mACK4|rPE-*IH;$T~C`w#y%6RGg zM{eUPxo{_J?=HitZ~grY0Scd$=xs4Uk5up{>xbeVjx#8)?k~m`Y+_~@(djsuj4{{t zGQ28hr8Zo-#B;G_hHZ&-u)XE#?^i}vOR=|ELlk5`NYaX}JHFrSpA$HO_#DfRh@69a zz%T1rhy&Nf$qW6fx5#{=fZfufZXy;#AnnR8@<#X75*qQnv1a*Go#)Q-ELFBw`*MHc z9+AYr?84y~YrmDR=b4thiB3dt^*eF@uG)SU+idF{p$z4)`TOUkXTLitK@_)QPUmm18Z8e_aH2eK&J)UI?-Y(g-XuRpI4n68<5R4jIIc!$ncO z|Xz(Zh5HA`vyU2)ET;1`CxkSFZN-OqD)IiSDW+B|iO;gaA^ zPh#T54gY?CVp}`;wzby2ZgKc=Q>ejJjFU4sQ&|Z*2BGEE_IKtHhl=a93`r`bAHU^_ zk6YUVFstzm{7;@a8jzMMs5d1i_L<=KirRC@YbJqvLfZr^yfN^|2F#Nhm zXv5;PtLG25nXCB^dN=5w*|IN8*#}Q9BO4TU|Lai5Q1awh@(^C94d3Qjmdly{Xy>*w z?lsTs-~A+K_p7`abg6mL-@nD*j0_U=K&%$KS=FG>DEaS>QV2j_JbQRIHn}>ClXjbK zJd{ojtlK}^ph;rLQkzU1>^(YRwcF$Wc`aBoHslc)UrO6rp2PFi=gkQ`P=J@{uA4sK zLwVxAh^r^UWha5bFED%2+;Vd-5k~wzk z@LnF0Ftdu)|Dbz19!m>3aQ8nYLuOZr+tvafKCZ7hYbwqQry_|Ja|=d&ZETGPiB-WB z`<-R2`}44e*z%DjK3sE$y5acQ(Fu>8M=5*!P!G>#-*1l3KzrNw48^EbSC6cMKlDu2 zOq)_sX~R>^ZE9=7)>a2`&pO7>jAj!BC*CeD};kmRV(=^+fvd8y2@bRw^E8E_0N?9r4yy1?@89)~l@|{YIK& z)-#?lAuY39723=K=>|a+A4sLO|N5H^;O485QYhdcZj6U669=K3Q5=8)AAs{mv-s?2 zw`X@vAvlaZ;}06O`9PO;(ay1+&_G*?Y;TeN5IJY>A@a(rWiR6f1cZ{C?}xGs$}W_jLNBFpwJ7vxGEw+T{SssA(3FuR5%}|)2{P{=41;667 zFS-fL#SrkVn&)4UH(C3U4xf-trxMhkG`xs?17=mJm<3*_eOA-<)KGcuq?RK$pU0Pr ze>?+gvk@Z$FBgLkA5bAvL}hoM@d2!j+}<_ZfH&av#gAPGP=ZzWYg4g-M(~kEw-F+- zys{C6+-v-M+O@g4)4QeBwxjZd2dUZoPEtCZFX{}sF3w2>i7lnJU9Nj<_bdpYslO!r z>TcnUp$rwGT}=L(5NYy0-RoZ@$P!57&uHip$umc3;0LY)Em-hW+w6`s#I4cdt6oO5 zJk#R2JJaAGpw{rz%j>HeRX{Q%XX8KWk*mpZbuYI0NlW4DD_83qMUvDT8JR0F<8Ro( z?>P%gR#CjAPahxMPV!^oC{|);UK@-C&|Ya~E%E1(Aux@5o5G4BF#5B&LMGekEP5rg zYK&*tJNs98SoJ5%!?mnn1EPQPFD8#Lf@&my-`{6~ZsshEX3S$hbe7>iL285K_Bj9_DVsz`8BoS-=)&0d zrX49pSAGCU;j^zVHi&+XdGUdGnCQ1w#|g-gQM%tnp2-5pv;Ajp;|b8Gr%&?T@vo^t z0K;ccIH79M4-Z*DqzLGg2hsi$T?gp$mwWPvh#^LeJ1??_am;ldE%&I~Sm#=ieSDnQ zc7JIaTAV1+9gt$^X{~{DeMEF&IDI+G?PQrW=@$!oxU^2aZ)pEtM=TD@+}E4+js?_X zVB_auJYoLw5#w7B4T4H@NYitw{q<8)zzjzVepU?FT<}dNeie*Hecegb?7-{i#Zt2^~ z7V#y}R_GdFy;R_i{0>TizxXMi0{QYeIy#Q_mI;Nc+OhA0x8^=V!JI5Q+wxc znMjE^3DNoL70IoFD4YAEfJK}#0*c!zlVfvE;dq!2!VRqs&OaqQpHBVkdp>;Oy5cGk4zef{DaoMZ? z$!#bI4<|v4{JvdL(CFcfguomAQ!$xORNQ0V+=Ct71rP=DHh0H032$g4J$`>G3)E^I zy!inR3SN;p`?9-QD);W}iQPxEB=y)RljOij>{Z{}FvdBBPk#Py9766_yDoxuX~nn++%98T^$8 zzl=x9u}a?p(gflhH93PrXYqCX^`FAJ5GpvHj`CKyGL;T{acYL$CK8_3WwuQ04hyKG z|5VBM3mR=2FGW1sr5+~`6aYhoKv4^LKwe-7Sf7d+{f-RzGjoRs_|8U!^y=jR`gUI2 zzWX{aTi0ZGsV?(Gf2 zM0}8N;ODa3%?*wQmHRJ%4B$-C1*FdebU_-#X}EtKR~<1tQP6C;5u>a2!16{emX^#S zSnbly3chsPk+jsN^UJNpt)<_wnPaujVIv}j07T+_FvZiZqK+%;uDe?*X`u$R;A8Ra zSKGOjGJPxOdQ6XTG2Nk2*S?lnlJD}|9V1mbhasoWp$G%CISz|$FJ8JSAwtvB^S(Pd zN{0>LEK?z|YM!kg^kbX*{=*phm-m;oHVo0?8O7QhW0-J{{G)Z>O+6bT`en%6g^h%p zI%q)O>@4^tumqbf6+-G@wQV{1;o6`}At>tKM%shV)cD8&;6=V2HA>D;DkV~8`QDWH zg4v6WHl>D#W>acZXV&R3I@VZ0b|>j$TTHR#$L?d1?LbE9SXR*&zU`BCb}>et{3dh@*67b&{c9*XX7yEOB9M z^_)5+7`JwZ2-RW;Lq$y$fZJ*&ka*|=L0wag?K*=DY}t1==8{`xj8?l+t6qLHl~Kgy zDrLL5ZxcI-*eXAWJbS7ra3Ysy`-vu>2so005_v6JX$IPCDi%VP-YJBwKEN=$2>kvJWt{IKqYtEzc=quORN`cB zFb~@NvJCIFyeJs}>;}^2T3R1oFT@rN=ln@tK@Y6vRIzvXPrg&X$=@Gd)+kJOsyBbu zoVE)YjOw2U$WFB8i#mF4~%J{bA_s~!xpW6^Lt}8hF z@!_h3&GmVj9L%_d691@8`uR#`71a~BRYq?)6X9MzdF$6&%&XYl(APA+6=9UOurlbM z5iW{^E-Cir_;y`jL{I8^vRg$*0$-o zmh94!H+%@?r~OaNC%94AK4d{{V8stt4`c%>ESjQ4&&EB#0=!Cdw7g*GgQYeGpeyBJ zT2}lW&MbOou858o-43BFYajU}hX2%h_I*fWT@_t;5QdeKk>$*v<-ZROmHvNcW&<)D z_{`o(ugU9cJX4W1l%(FhBXZ0RJIMNS$eM_A=eAPgqWcPX7nrt!3wEec+mmT3V&$%t z|L}hcoyLwX444Fgb)XPqm}k!Ih2I1%@-d(r=W=@>T%Po~<)u&Lv-!g^y2zL0mmP|e z1rJQm@Tv?4z_4by=CdF}{6~p^ zzlgC{fYlQ0Ychyv#1y;bt!Gao8oxvuD`gZX@h6n{;;Qn0~VuV?HPp zKMcat+>&-#)7+2R6wMhk6Vi^(mRdCN8ujS68hm^%`TR<||EH5hKGTJzyoM_n`Ys0U zNo0ZmlOYp_0iqpj-)?1YB+-4#W85FV-rAR(-k4h6>a?r{T|IbKEF1VZKbP&9Q-Qd=K138X7ibA{WJGjbv`duY`y@fUV#cFuqiabnY?zAm{XnUAX!_z` z89Ni2FN`^CGpNis;{wNroE{smO@bd0L}y3y{)TpHu*0RDrnM51l2%&Zj&g%F)`y&H zGCSTG!$mNUN*kRjw?cKlP(6&jla_uIqfI=1Y`^AT8A;{EN|jp4w{9pgikSVxQqj6$ zXZ=#d3I#0&=>9s)OJGk_Iu<~6I8dT7BU&WO3g~bEjSC5;+;|QJo`BaN5!xLX_*za6 zI((~P>AR^EV`&hIK|pu?%5o?m_wOG}g?@@UINO=(NUC8U^fOSIDt#aO-?&r1PJCtC ztO^+3S<_>1yn|D(yb2s7G2I?C;|p*V7} z;$0`;GZza$*jCFZ;WRz~Aa+tqx;EWB8!Y&LHJEou{jv|;J%#_dE4$N zu)Vs8I%md|Pu3N4h1uNQAK_njPxfejyb!}jDIwddi59zu2~FG2%{&E!8x)$|`*nV< zGExB@7_bg7s7B{Sf0`M~YUVo%tlv|l006Isq=eLyzZX>l`C3}Y(H~@Md0VX*>*-w& zs@DC!xH!U(spU4B`=ZtMVHwhNJao(~4Urdx65M_X^keFEDYYX+$Vz*i%n6%~0K@qI zOkIwNP>DXkL@l7GBxqCowj>V66>7Dz|K*Ek3dlQflwRX2Jsz5L*gg_S$~LI1M^-a% z9BtGiZ-x2tt))pg)$R6f zDpg?tS(#{N$w-SvY{7hzKtV!gW8gy8*!!JrV*Q_dUk^)iP|b!FH^TUcS256~+VvqR z>Z6q?s(qL$UW_^Zdd?$3{rO!J5ReAOvf3cH0NUSYod>X=sv<#Asu|aWhn2qC8q-_b zBSL)D0gWmQO%U3ITz2IOmA9hZQ=^*11{7yxCWlDG`l)v8v+3dF*_|WB{oj-Qh!pm5 zAKW@lLP7vB>>z~%ZHS+?(1am56NVsUD-6zw0LLU7F!*a4h3{Kxhx}*C#pr z&8PbU5Q{d&<~$JU6sz92DXXb2>S4Ms_c7ZWBL6+e8NvJ{EcV#;!zhB< zZKw(AQrHwv=VG>a?2YerEuJ)-I5a;i1hee!KKQdKZ8{9Z9LMvh)OyhE2v#B*ET<}T zyY9rtbU_054pM89cp-cVS({}@8AGF5`(7SPtS0I zV>og)>ckk#{6K3?C5TN;G2Nu}b8mGRFd{v}JQC*T`}Opd5BRyNV3y2u8j-zPUQq@( zVC*#aoOfncFP}LC|1P>5Kj%DC52Tn(j`S1(vF?dr)q+Ti1U}m1O|L`L%i-a# zwQ)B+JcW!>ub*HPcRwv(hXvonf1Hfu!;;d}{}whqyw`H}bN=Lfw-&aVf{C^E8n|-+ zTbF9i6kZ*e4>;{LU;Mu=e`PHrcvQq9$lzWC(BO&l0~WE)ZJ4K8>R~by7cJ)Zp}JEv zg&YpDue%R^Bn+h{lTS05p4j`B(a%h*TT3shvj7-LZKbma&Baf1B=6xaS{Ypn_$F?D zpH%bmA}h&h&)ou^3piPgSqZf$i2syaEZ`UOzmtTZRe5wsX7yctvmb;0N~iIupKL^J zT=8mBdYFzVOhoFMf54z_?F=6vp?y2ieXerb>nN?cA~B%pp8B>J@Y8 z2$0ebmRX-pOo;@Er*V(keG_5WWwh#u<1aZT+f*-PdNC|dbe^Y}$c_9J!J{|>UOFdw zr6F$P1{y`zRFAWrw^pk&$l;G zSy} z;tDy2x%%F*HfaNfP3D=K>R#WXlp1vdQidb_JV{;YSj_z^Di0!{Nkj;uvEMx~BR19} z&XR8Q4#*skc>U?YNT|hvCQQ&Vlki2d(fQY(BDGrz3Kwq@nIIl-0fIjtGldX0ullcs zUehj>s~sAl6c>aJ7Miv$K3c5impk23XG$`Z1bI9(GmxJNRQGcb8WeSOyo>u`)aG?m zm6ykS^S$+Ezwuiu+V7TRY_#_{M`sx%&8If)*eT`YBso}|wJg7o)N!?K zGTZ9-@X^_9AyJj&$zb_s<>#FyF7JcO17J&H?~!p5bBuY}Qr2^+rLM1`LBVKrsoB%( zsaF8QZH|TjP!KT924-1)Y(GY1OpcEzw3Y@XNsxPaa~y-TMD?O@I~?Xkw0v>h)W+t` zV)5+8d|u;FpHEgg$E1or?6)=1i;;Moi~v1mT-?Y`0ijlD-0eO1E+534iaRmqd}-;> zE7YD};j;U+GNa7nbeBiN|}v zYS9hj018GW6aiooIcx)>p`ijz>)zlIm40H8-X>_>*!C4Z+mz2*f0RjA$?L@$XR%gm zO~IAUKFQ(K3hTL*iwLQNC#`rg4OYLeZ(hWjj(n03%U5#AZ*3|3W%W4BtwK$5z8Q-r z0nS9ot1Y=CBY@wM0^VVAo@CF6H1MJp%(3E})@3pAf1Bswr@w&5{@}$d;W`ZbDwP+t z<3@JNY!;^vhH2aj=Rw1QRC`(((tl9vI)rR1Umw5bv+k{9ey#D*pMG1a4O<|>_W?t)4_9u-`nSc36bUpM9z=Wli>m;ms zZ!5oV$r-&Uwzkv%??mYZPV(i+WSu_s9m!N(n69s*Im2JxMVS-+rAGOg17#(DfLrib zAnp8%7Ro#TMc{w$R`xg9$Tb8AkuUv^-z|yH=ngGj)|BrhrxH=H z_Df9!C7NjFQPvp0`jXp&3uUBh=90vTObGUJ`+mh!(?a2`Hx}ajTH^-ORO+FXIMFy z;%<>IHUp2Yy}{_;seB698%M&=KDhvv*V#E)zRq!07+&}U!UAH60f5wZrBq%xg0)dl zV`hA`2pJ$j8I*+F&menPL8s{~MV&4*-d+afvE3UI6fS4juPgA+7>`SD(Y^YM=)OON zx^$9C+>ps+4V0O(gtu29f0Xy48 zfhJ$y#{T)q6RC92DRi!CBjU+9M$?)WAE^d9%xjDm8g9oGj67;3KyTFukRqqKdxQHS z)_Vq-k3lnkV>Ys>RYdTw*8*JqAWeT_?!Cc>o?Cezah5QZgmQU=Y`}K3U`cd-HCfuh zH(w`RmS$jaiL8GnGOKnNQ=IqR<<9{-NQ3I3m}L7&8L3+Q%E>31zq75nBEPSTKPt>s z75jYa=jA;SLIIzAWs(*TFqa*n2iO1M+uiSW{u`9J=U|m#UygT*n9k@58p(;iyJ|_D zx}>09EFb7m9AH-1ndwkTq^B;mw(wmxv}!)^qh<SmL9gW^@$DwoWi+pfyncEJS>mjBVzUJAC7PC>XF0kX?U}y*-pH7q1w5sUc?L?9gvn8`(0yef_QFpiphHV!MuIzNvgDSkUga zOr3q5fy~epjV})!S}SyEwmg7Cw+dfvQ0vL$+drvT?+FVS!&lYV8uhyNjRp2P3vOLr zbYbKnooj(oW@W$SQ4iR_;h({W24%P`VBjHo8vf#lfcRPxB8d<@&Z*5S^~%Wu;Rlm{ZC7qYq1%Yx;t5$y?ac0OS^6^6emdLXU@5wpfE=>Hf~>gxh!ZD6Q(-RdK3Fx4Rc(fAiXp zG13yoQ;vUDzL`5OZ#*ylk?Xqt1n<6|(*sirnmq_nQl+Tv!T#}wBI5uYR$(Ud@gc%2 z8XAU`z&8v~!&p8*(KN_`1^OmQqOZFTLS$Fpfqkb}!6j*ND2kK?Tf0p;iGv1$0-Ug8q9p9b9HA=pnhrV<@ulNk}N8h&SsX$9zJ0@~&;0 zV6OV$@7rhIL-MTbICQ!kq3%5tZD=u?gr(pzN*-LbXe1AUZ=xS&@mx&2%M&bury@>s z1U-ChObQ%>jCY5NShke_ePIXXzv5?}mNHx}+7zvR zmOmoNui+=d4TLS>5IN=MOo7j;=Xk9-V^{X_^$MqJxk7k~-kmU8A4bMskBAgE00C7T zi|IKNg5*VL5Q!{v_zs=ly%VnedOwG3F4z9N068+qjp|AUwpGv5c}t$@)LuE=mRk{l zTO~>enC{08Pw6T(sdLc+gMH9B3gkMhKk+j~f&Zw?U898d)zV!AwG6GyD*L^W;g;Xw z)^%E(H3@c5`WE~xkUFu!9W9-duArGQ+086aGp9U1nIhZDyyI)@y%Ne)1g1zZ^x9NE z#1Oru-I*EoRXhr(<{gWeR7DXcM=CQ*i+MR-C1?JZELhFget>c0D)}#PrxSNc5Nx~r z2w;w=pm9HwQsx006U0<;RUIvJa$QqilwA1G`&i8Pr*V{9%s4EJpf${%X;DZujf{`K zsW@#fO7Fg~nX5YAX{{YHB?Z`pBKWZMLn zfXIP}P2(AUNY_OK9(Vm%+uZN`MCkP@0~np>ON%wX0Oz1Wd~QP^L`_p@9s=_zJq)68$UzEHh-F;n>5NgyD`?gF&SWasa(#x!KDR00ABbqS;Uo~Fv&1H z3ikAIDclAu8+^5u^OltoNkj^uu~u9-Ru&)G=ft?5rukG9ENG_Lsm|MVuN@q$ZPS3k zYhl`VTx~5eFmTZRM7ysfj+hnx^g6dfNsjRDzjs9ydStvm!916r$IWywG1yJ|;3h-V zXoyNfAu|ngk{+)ywQ;qOd2n|w96Vc7e|^3@Re7Kdr|Cv|FdLdc5tPQOl_swtD^4SP zSa=I(8|!O5=!plT^@zS#I4vE0LKQunbY8l^N!YzR5DD#yb2t6)9~AwFi(9s9`L)8o zmgCm)vRU?!_^oKvca^QbB@)WSl+^!VF~9r32qwl`n4^ZaBaBma0o0z~cBvs!}sTqPgdT9Xt%*7Wy;g|aBfR5>W z;cad<6V3EURGZ1gkj;BnW5QQJCuE#{Ut(~b*CE65dKiJM^JLh|z=f1B?)KQ7_$b0Xt z{+DdUVr#~%!zp^DSa!0dMu6=fCjy&;hi?0{R_$TNX5};fXWITP@E4HU0u@M97KtUZ z2Zg%HK%h1N2@VS=rJ!N&!hree3|&N1N;~p}1+%yOfyQh@c`@96uPb)+pR_!J2=>{X zDD`Y45|iSgF+>AyETWDo8))GJpFx>D`mkHNocx`R^Uj~|$(Re^LC2NY5a0qZKn!S5 zum=WG_gNarWQoYXs%5NmKHi=pct!Z$Ci+wfTPXj}FJt*@!a{)exUhfV%n6vNydj-Y zl7}cu3x|Z<{#PdZoCo<}xB8VOhW-XpbGbl^&9$J&9q6zo?Qjxcv z_(M}^wQuI2Z|71ci0HLM`Fhc#_UHZ-*M1w6$X5D}Ne719ImC;l4<5_PYEnLAa!tKd z9$*3(Z|})b>v5`CcQ#LE!-ez4V*JoniQb}8ji@ymO`^xv_8-T`BC~kZqL!~h^bN35 z>Od8qS}g%#r|{>csJ3A@s;(&qW5N?Mv_9gdxE(sGjdul}!oMdSpGpZV z%+2{#kI10c5lE0lDBmQDV1(QiEzi-ZGqU?A&QHG=Hp91s-m`z@TX8H5O0?$-|MwCT zZd4q=)qOsGd-kFh3B51@x+BGJ3V@W)1{9XXU=N)at?57N&&hZzBb1)!IPhlFP+{~# zFFD<^go8BgK*%L}^!}5Jw$Cf_otI=Wbj2}PxMUS*9AM}I`<;$T^sZBpiIzG9P=fFO zu0(}?Mt<@Q=W%EIg|F_GnzUwv3Al%06)f&f+T}cG-?23bU{xJ}dgvq81>RFw{1bSM zoMN|Fah&tpsf2L^Zw;p}9(xl;o|yh~X7flbIzL+iwk%-(6MP$q0dI5xg`lbUUqvdmOKj%Xkf~ebw*uDBZ`#Z<@@P!oaPvMc$P0c+-Zfe69T6F)aYE z0oI?OCQ`%V`rvOsf~XnZ{cu!xMxs6`BYgXBg8HaU&;(+Yq6Q|@1daM2CCS45b0r`W zPF#)if=cydih_}OT6G7GtZn&-;IA>T^h<7`1`M>Ky#}}dWtVh0eQYbt0dPuvFEpn2A0b<)H!&BQk8*aNA662| zG_``_ZNZ9&s;t)_{&}e8&Npso@br9Uz6F{n_RI?J#{Hx?flFBjtbmxDz!W%A0Q-fU zOx=G#g4gPEymau9rK^fN9g%9#^pB$f#^X)K zw)lVQW+CAylZ><$puw&ogl5KC=<9hN&h`fdltr0uJiW!2YWoz%m8^(d*t77u5@RcU zWmq?e)wOHs3>Fn=8J&Wu=<;N4Ye@?w4d!Wdn~f$AWp`|7-@IiL%$9G|`x?{v>oFR2 zVVi~);~TsY5NH+pR2&kv8NTATBH|JH#q8&}R^Ip=>nbJ70aq85>vneF$Fa{j25-+_ zbX!2+TK)6sBkeJMBQ5X#EtB1bi>U3t`Wk2%%K1=~a!-~2x5+o;{WL*BJrJ$-9aoUI zT38Xxi>~@1J?A@bH%oy=%O|i^Niqklbz2mEcwGP1nU`C-s8`{ORA?w7!)*q!*z9~9 zEMl1PWPq_ft^6XB?k$+@^PiI0s=LD_-2UQCAJ%CZIXFKC5OBOT_KTNy$pJ4#24Hx< zU`+#E*UBM$;$#pagV7_GL$It6Gt6*RLs44NzmsifJ{ftQd(9POK;TLtVw^fcr?Yby zx?njFF@F2xL>&_8j6Q6xzp8u#5$pTne~D%hMI6Kkft5v9TeV^V_*INZPSwVievcAa z3Qm>{NYn$Fz&wtg4>UjTAp8ABsUb2!5OeNJrjD|=3#EO^^`f2jh9pD5pO5~cu5D^b#`U>Ul4&7joOMveT7{)xLFc#&+3pA8LT+F>thXl-r9i zX2$oC!G_xxvH@1ltZx9&p(Rz&AkX$p!u#NTbNP~~si7*y%}6UuhcUTqtQ0<_vvERE)oSd9AkIdI$ zrfAtXs$J4`Yfgv$xjZ>roQTI?tLTVAI&`SKf|oEpQ_Ge&{pp?x+wgD?r|)-j!?RR6 zr5~w6ipyl5vj{bq;k?rLpt%HwH!`hnAXYc{bstdc_EnbMIAR|?nH}EXWc|t*3+0XO zwtcp8tbGcfsD0t(`+{;R^WBzSWo%tHx*^(X)Jk)BZSZC|%2(i#*5|<-T&HhD3LBBe z-pD8my9=&C`}GkE#sR~ea#PV7*BB$%+uPeO8`<@$H{r?#S`^E{|*f ziT>tQc}|^Yb+1KnX!A?KY8S7_I=1D9%G7hzLRFn&A1R2Bn!2}JWKXvwL*DHOG+>Rs zB%F%?hO|jZJdwe9xnRT?{U-s(Ctp5wSm`=Lg9Wv#@-TK<`aF6#U^0KJiio+X~;ZnnS~?I zg(#B0KE_zA6>@^1?-uI(Bd^_3b53H2n;pbJ}ifMg-3Hn2>Jn@j_qLLtYdCrLEvPDQG6YP(rsT0=%L60~^M+Sm)Sv*My zS;q(FBN~INWj_Fb8P??w@mz8VoEDb%;oXe(_I4VM|BYW#ng)J1smDo+;4E z7}YNT{(hr?VZo9h7Vp&vH~<+WOCjHNsK!6rquQmesYXuDiDTbDhkyDWW@9Fu=Y=?*wJdvLC zl|_sVBPo#DRSyYRFTs?BRiOO>dmJLq);fR<$ZXe#lWNoR>E{-sgZUT;MVe0&=jkEh zU6AtTDMB8oX{M(_W)gQ6fw~CPGYE)aKY3&Fg^~Zy241|+Q-{bv(Xaf{R^PdDK$4^5 zVTWHPw)ZVw=L_(LJ`9j1j2dEx!anh0eoB%D6R@Z2rP|_7%a~nZGkz6dpU(pB`&54A zJ2g>s=>U&k&!5txlJ2@hjK-HF8y{B9Y&x0-`tC=t;vsaI|t3z@`CsZrum*@ z_tCR}ERe^3xGB-xYs*jNUEw)RVrvtj9La4;`IW-cbaD7yMHRizJ5_gruLaHhDk%B2 z@%CwaWi?gOO56C7A`@x?FI{{^*x{uoyT_QK5mVJb{^mr?rh0Dr^M7?%&WwETodvk~ zP(z6Of_IXa3I(T3Sdt8t3CSY|{jPi;78t}}z)Yb-%OU{xN|S;HGo$-7+k;^zs9(^) z?H&HXta}i&Q`JOjQrKx7fbn+3+DZYYVt~0E;Co4@eI1iO0G`P@ljR1NgnFCX3?Eix zp7Oy-6St`Jp(wq{F*#@j0}~)j7+Qo~1Ch=D&9h_^%E&WJM1=LNidbCBS0*4p6DOFnz0 zH>3LM-jSD#5|-j@y(Hmtd=Ry#2IAJ_m9wRPq-g}viu)*{)4nW$n*s&c2d|eZu`%G^ zxUz^`3jJ z;dgW}Pg3@?vtD)+(>HWI^(Dl6H0Uold*{&Jy?9PIBOYz3AMI0IwtZWw6&xA2R2XwjnR4I_6whPPbi+5atKiUb!cpCDK~yT zCme~#ErW|M6&AM2-S{bo<@tlKCcg>1a?MQ%o_6&1jH8*gZ`rR{lcP^60lT#X>pp#Y59&u5=!eDM`pzcFETh1(ap4%S+vY@6 zkC|@~(x2`?<5)WL$u&8A990XLV1f6rYL?ldx-S1o(f4sHp%eS@myWNP+|Y>2K~~m1 zv-Vy|${eRdUS|r5KpR!`FqjB*c9wtvdjk-_`fGjIzhj*ey&oL>d-}A)y1t!^zrM{G z-kdzk5jd}#Y@P;fel>12o@~+Y=4eiTB|2Pb6JM`(3$4i(R+2NQ$BwE(xM0O)lo+j+qw82n0POyjL#ePPkT^V|t6T{F;{0@qG-Z7sH zEW!ZqX5b9deBBG@$3aau+Kmj1{+})Aj2cKSA%q=JJGhSREkwu#3af~qqw$MN%zfGf zCzC;%uCakM+V&w=>QQ#g>qo(M*|_`B+e(KI{f#`WAozm`O{)$M`OmTwGIF$gmOYxE zXng?RiRwN}JjL6x*e2d+722LUmYWHdBwFn>Zj?1&@&ot(dqdEQ`>T?*f*GkUGXyB{ z7JkbDhw5+D`ZbWy4LP!**z!KYmc>Nh#nSLOKaS-R79xA|6;TT;f-`){AK@2zwq0bFrr-WXu6Mp3uO z&B`}?8zc-IOjFQ_h8rEs)SP1{8LO7@k+Ui$%{3<9=7 zAYd(ysN8b>teuj}Wd~Y7;@_85766p#t|rI6!1uWnhxiFpce*bpAKh0WlXrruOMya%J@ z8_V0DnS4sX7_LXRr^l`6uiko8fPBKH5!RrGYw+(N{kK7)ZFRKz!5KQtrBvlNesE6V z4K|eYunsOlYto*Og6--`c5V;%(i)_26C^_wZa~v(MSN%WVg&Er5d-=`lJiNW{~H6Ztu@IzbvK%^Qg5+GU6;ST>&zcOqiqU% zICIh|&uy-gZEL?9jy9ws&*Wa{7#klYsAz~%pS>#_$ecd<$_1o)enNkgHN)}xslA(D zWDm0a6fvA??e_B%z1c~LjGQ>VW87H1qAVDSED`GoN2anV5SkP;iN03bhgePAS|Kx2 zBI7=z&A4y~0qy~Q3|jK~;7UrQYtxqZ?)CAqzRmSyl7AwOaIg75_#&{-od97F6c-Q+ za{nHkk_d$O?6Mzu$Y?^8)J12aNErM~JTQQ-@R_lGPe6dc>N}B)YqP3|DPRfm(`T8$ z4};2X^uNLCs}m0$Z94mPxuQT_$%V0b^mY~{_OBuDrB2eM2KAj4PX*o2Ii})D0E+#X zaQgl;Y;V6VQ#Db$>1j`7=V|{Rath5*I*p-eQ9}mPtd%^*1fC7JfHDi@?Od{A2S@NS z^numzuXYuFc=NvLW)J$qL#(r55aK5}ct4&T< z7Ut?5RlJm^%oaNly+ZJna^~c|NDh)7iXAq zm)0s0?p|%VliFE`&-&Dk-aK?!TzsJ)NiPJL`;N<)ooS~* z#4SePE|v%M_gE%qZ580UYJU+-_z4Fn;J5TB8H_i$*;)DNE-Z@zHWh~Y{0D_(_D9hb zj&dJD`c_X)Q@nglz+VA~o6Qt`8nf6!5y!z{McxHp0|axSWXtxw|Fk~>Dtw|zAYk0A z_5)~Q;7r&QU};ox@K9uhz&BVsC_q_J0I%$YeuvY3;oUj~%CA+bZyDaTjoM#c(69aE zoCTLZWBAnaW~gN&Q0a!LtDwvZZui(KT=~otnLZ>iOz?%M)dHXji9q7;vEcu<`*oIx zHM(=@xxyxIub#*NVXWh$2Z;+ax+FFES8JH9Jt*80Ei-2;p-0aWmjC@r4@FEX3D)t@ z9k7DhKF}~Q0(09+;DGQF_gffBJ|RkR9^1R znjvnS+m6_~TLg>5G|m}vBkfy|%=V`Ki)^MCJ^}Bd@OFqjHYU(`a>w_q95Gog&rKwf zP5esye&T}UtD+!ze0q$*fXtm5e~LRvY{}_}Y>b-oSwMF8nq6j)n1~V{LxES-Pp}R? z$jU;q7o5d}E1A5<1q7r_`whPB`{squKKm(MO*zf8E8HNcMq5+k6+U1n_5^tOgj7t5fIDE6K#OO6a<8w^`^=8?KbfG zv3B)UZkRxEa{$--)JY|_xj+uori}s2iM&*_;|JgW^2n6VAr(>~E+4mcIe8L%9r5*;4&lEvmY9V# zMFEHqf@?4U?Wah5_)z4yV}>Xp48H=HutHJ~Zu@$=6T-N#T({!;r&tE*ZOznz%B_p= zDaI2lc}8K4C1UbUfP*M7ngItZlN%?DgB^IM%|Qb4 zKxx?k7+3?YZZCg49Nzr?7EuB|`<>|g4VWR32t0Tyzr{hly|nMc{~daD zJRY1Z1$T%@>LXQ?4VNwNhpg%IBB=h`AZ>o7@P>;DGNKfU{{{|Mw;n?Z5ZTBeVe{}(``r#& z4krBQX(S>Y5{Zo=VH@do)aG_mhE5{2c`e_1;%e_?PYk7zrR~=ouidpEBY|VUOJ{Qv zAO|V6!A1w{5E9_3xy6ZjPk_luzD-Ahd*w;7EEG8$|lgdT^5#;qN4 zexu;@1yq1~WH-waC3Uz@m!%m5Rq zR&gM+C!Q%-fZlC5EAU~c>`P(m-h!dgf5uW#5jE(xC8Ute5 z0N`kR2)=ZIZNyL@}%x3T%})z$sS316(lJWlN+P2^OD>2 zH8*|urUL%(1UdTyv{1YvbUT10m%v z0n5Nl07C%?Sc;}UeP%KR%8+DE!z!b*8QvzB5P2@(4hg)JQ^bH?LHL^M>r?dzF6q&q z1gIhfrgimTR)o4}@bvfkLj<%vG z%ccePDtu=tXe8{3u>dfWUz`|UUvKPoeJc3xy|z@!kX9{zZO_Af*`1VS@X4jv+c5Y4 zZmqB>f%UHE%|FseX@&fmg#DUAZDvD|3V_vha1=Ge^Rp(NAVXr6D z0GJApLm&BV{|>jE1RGF(E77P`CfZ%LG8#ygXLL&RbfIIxEAS?9Tp>SzS2b$E6!ol< z>1)2;8hR4D9yS)pfrI1blf)`G1{h!#vIW@@0pe7$=;Dgj0$3f&s^6p=$X8aF!U$X< zR|4t8VQpT}!4eQRcWOv7X%06NHh!_&!G?sQaEL7m<;N|)iT^|@M(!Nc8!{0l%|(Yy zUY}R)=yIpBCxVzxi;UN&a;U6_^Ad_bIdBQdH1P@=!@O6=$emwOd{2JlyzGqRm**Yl zQ`J-JTQsP2Uqqrmz3ieqx}kV{th~4M3?c-WZ~=4E9CUP!1D9p~kf*I0`)EYWlEhvg z8{U9IvA|R0qQsyIkrZcd5o1e3RhVc98Ws#;>RLvKYTr?~-u5q`5D#?JoI}N3>$d19 z1f)MEf-$ag-xPWMUrFF#m3uc}i4ATQ;Jt@u zdXBnp*huptZA53dqoiTKPbtI?V*Kf6FGR4;=Bmmp2MjBq_lZ%Nu2dLk_vk-ynZOK6 zv+>`NF!R41nytv88oo!Gq+Q{F5d=P?aq{wz1VpeUB5rk1h{|wK)!tr=C)N1JDE$i& z2wvK}FlUr7J3Fi0oG6G{AMan+ zk_@m0GGv(mbs{%u08P6-@}-JmWBV1yle3~|_MP2V->h$M5wu5QyzOyv%iU@7YvG9F z+VJ}ywH7Fw7g5T(>OQMmTjOip4=djIWT{xTk!bux36PD;_v0t_d8@tPvmFLrV(Gb( zq#}|uhK{1G!#Yt5)nVbYBik~iDdMlwH9JV3O|F~`&x4;DW^T}o3e90}W`+{ipYmjT zhaDnLytP~}dvW43XJ{f8rB8tX#TC})vUhH7E1d8&4bo{xj`5U{Mxc#=nt<|!!Edo` zg|=%2mJv;5J)q2 zz0tz9r^-@!>>uLB)Yw>(ZbxrvDx4NrhvS4?m;=LPIt2wc<0x0(md5z+-}AHnK03 z7n&7;^tCP&(>ZSl{>)(n4AI~`i6G7cnv`;w83DPDvGu+?+>y=-B-!ji0r>6^@p!rE?Sq+x=0EMt=goCNz-Tw^K56sAxJvV-ZgZ+XgjGfhp3?%x@r z!?WEX964)UnlM>OU{as?(Y^Sgwip@2;G&v8Mwp;F;E@arW0AQ=%Fhot zq@Bu-9y54*TpmGhsW2wEz(pFS!6mQqFtmrTJdI#h+$~oER?+u%o(ZjsDpUBpv}uxn zFSoflH)o`|c4&3Pe~;2}Ri(u)?xSA=pQ;mIf2U>{aw;M2k>qa(BOQRkKIe?+8xIiJ zt>@YKeAI|Lr->xQsqM=CQ$2NBYwByK$gFB`XNlw(0Z6+bAF}VZy#Om4=z z!Vv9O8y{O*GY~>Q6Cwce4HR*oNJ$;-9D;Wh*l{h^Sdd3>$_E@B&FD3=#g`0V8mP&9 z{%KmKW;#=dT4SQE`LR;6JdlUP*i%`KCl$OXW2Q2shOw#+U$n8?N`ei70wX^%wH3Ml zE{Wel7uTKV{;i>v$WQnmp`^&$MW7&3AV!L(0Jv0GO$fClFehJ3Uf>R)*d#+?K zvPaB-Byuu9po%-Gk>5>|-q)vb=YHP- z&1gxpJuWz^R1tXjL#R#d?Rx^WkL_`4ZSp&p#cy9f2}nl4Bt$^+EGr^&8BXPj?cay% zLx4wo5c1vZu>`9G98jRNRS!UpXjvN~O8xL7*A>~?s47WrEYnJ5;yUm0dW7I z>tOvI@)1`YPh{OR@SQqBEeq>b=kcb?xM0mc#Io!x90A3crgLfcU33RLNaM`QA+}~v zwWkoAG{qSZ^uHQ9GT3SX5VAW&g(CV76B8T&aX&jB9zWTzc!)Pn1bbk+Hj`Ej_-k8A zvc)P~F>E^dC)X^L+bFBaglr$A>WhrA@yR17_?qPV25n!3JeEDZKcijJdY{sDJ6%OD z^V2(`qN=PzBTpsLOc)HLU}Nm2NMZw>KJEO>-`)V15f5OsVlvo(r?F=!9U)MR+?@~N zuiWW-*MD}<6Jqe|vfq<5BkTnsyw?;83s7SYCz3uu1e?EA8ZL;hx7zl7(ROFPz^ z6QKRu>y1r^Itr1R01kU+gajNA=(iy{OBEC17!_wbBJ)(T`9JA(thk$>7RkS7oB4hC zBpTfCO-hJODIHBLNPte<;4Sx9fB892aPwr<_lRTODljbOCE?`?Y{ouUJ+S8N15n4) zCd+YVl+_#jZ5dzA9;Ha&N3`2bx>t!HaYqJQ>6V!xF_?gx>1?e*y+>w(Rgv23V)S5P z6fX`?`1-Ez=KfAgeL~eP+ZlY7gf~Y zNXQ=3Qs%dJ1!T?MOf*{KNSpzpou7N_Hu1=WIRkEsDp_-4m|; zQic_M3VpPZ$3B9p9tOLy<0r~uar&UZ)fEonGU4`F|$^`Z}s-S03DX8ik!+4g**13F)QOY#wE4%a4}bY1 zu{mv0lXgdpUh>m;zx=oK*;e#;(8=`3NH0Gn7H#;1Ri?*7L>!0P<_uQ>`)4m+i7rv7 zpYS5lD-s5vKK@#cHnj0KUj<5A3y#u1mu7c|{zoPzM*h?|Mxl5Ts6ZyLMm-eHMO)b) zd^?kip+^d*0GI&!N9J<+omYGS2Ef7Rfv}S~>+ZLFBlvgx1VQauj3P1~x>Mr(iYqLv z%hyZ*>i&1xCCVs?X3g3){kJ4byrp}q2(yKmSxE56EWK1f&=Yp4#ew>(&f6)%qh36k zATg*CkVI3&c2;*A{@X(QzboEcX7u~g0oi#J0a zDV+Y|Y{a3mw8CQ*c{so+Djk&2^Aui7b*suD@DyQwxh{l((*0YNn_#@B!?Dp91s)!+ zLl1G*;Il}^@TNF}T7{mKZhn#CO8nCLpnHeWRltp+}Hzk7gR zeSCNgg5kEtirabtMN`v-SY^bg^{-Uoi+D~C*UZ^&J&yVoNGnLx+z;Ajg#+HITlQEL z&1(rRy?0Loc^pNT0>|qL)jV1Z|1Q;Wpa=B(u+8uWAm{}k*uO~xnus@%j00QUnJv8$ z2suYVw;Tv3Ul*%zt?X1-7xTUQJ7Tc7xuKYwt3oY5Y5JPS!Cv&gN{(`aKQa*rL9N2a z^fi5&&P3UzS5sHX&9n=Me^KwhpK)st{*73FuFyWFRYEwO*-08_vwkvu5w=S7kOts1 z0=)2}0>DE^=TcLqQ>!$3rT)*keoqYW_Z0hPMKGaAJnBEDG}LGcZh#;o(AEHHn$gvd8YP4@t+Xy&E5l0^Sc8V}MNRf^>$MmKP=J zn3D}UFva3QW?H4r+{6?oc&*;| z&s!7{`DA+Ng9j{)@EL_uIh-A!him?TA}=wK2{!=ahJ7zP;c>zY-$T4wYwsD7uD{*& zekJwv>@0HJV$3HoI?lF)Okt!-JXAk?{OJE(;tM5Q0^f}ev&+xz-;t~Tsm#suLkfO2 zgswu)-;Han4lm^DcRspkl^^&m1AJq5TL-3Y3(o8E`7<^5RhtBM&-~b9>t|t5;-fs1 zD0o_;9{aPtkyj{s#m6shhYe>rxrG3RYVAt~gV_H_p|IWPNh(HEuCsvQW|CE8@JM4_ znP^{@q0Q)OQDdT?cyB=tiY85kIjPbf_sk(zadg_LfI=u3hLF3Mxx(YF-Cu0CCEp|*usok$P&8*(( zsw9P!xp}q2cERl7ac0o(gAj}saSEA<$gt3q;T%yZ6o?);KT<>Dw2AQ9xc6I6vhvyG z$g;6J*aadfI-31}vX^amf&`cW=LK@);H;dlplgKL7%jloB`td>8* zH;rEaJ=;brz$)T~b|si%De&HoYSEYMt6tLA_|FOY*O`h&x}bFuK1s2&5@F8A4<Uikjb2z}#{d!e|GPPZKDJM<5v(l>KuHFTneLaG`+aG8RwWLyn^ z8SnxU2eej&Ow$8UAhs|T;GHl)14kKAc@D3oBglULTD&N2gK z{6MWxuRjt5FpLaZ`}!uOrV#7i8Ead-#9#F#?;p-wg# zsuv&6$HFp5nMmJ>06iD%ACDBTZP%8SdHmk|t1)yDT)2EXtj=>b%8=b^?bN6qqLjbJ zSH_KnU+%)}yZq0}XJr}EqJ!+Yj}-L2ao6I)AHH+)A$ZYFMr~rzbhrEq{89ZPJC4PU zsmc$MG1~l^>)iw-tbS%^i^<=rVTI7+HgtD3iR4`Pd~`WaOMqu&M18OGrp0y_*sv(T zqQ*NoL_mZ@<&FwOK`Ygm8_9-HZg@?>fAhYRtlOLK=Y#F~*(nZ%>c!c-REA}*&{kqL z2rxEOA|z$?2YlLfvYTqZ%e37u9UdO4YghY8mx@Ouck&EGC8tvQ{A&x~e_9}(z}TS9 zKhTYw;AUjR06htT9QZ_!fyaIw3X{tIYGy7-*u=L_CANrtI^$maPdqGQwCTh2C;=O! z##hQbtO9R(vq8T;R^~7={7F1dGPf>&kZ(kwsHI{}~#3J@h4Zj7v~W6C06O z3HFeC066?L?uMikCEP><`ZH>U$y@xtB0hqs>fFaOr5h0Yw^s)Y6jmN0ED??rRfqr} zPTC;|M7!8e2Qg$-z9%4Dc)0#x>V>|E$rw8b< zL+!&v8v2IoB2lqOAN0S3g2-q*UK@q8Vwcn~2k$HQ07CSolBdhY;%<5^wMChg?JdiB zo_vf_iBBjD$Gu7XSdZO-L!V`PX?&JqBo6ssh`$b|5rgIW5-l(Q*04qeAb1#cK@|d% zdkt1qf&oj~hf~^7V7;^&9Wh`Pa($e=JTb#HP+@{?Ym<8V`8AeI|5f z|L}|5U=nvB^e1>!bbtEd>oh6 zUa1=W`0@F%#jJKei&Y9~mV)2)vAjn(ntXV4DEbcW==g&j*ivj&`9oaTHgVreB1wWQ zsy?QoW!C@?Z;=d5a(92o8lB5r}y)Ef*y|*cwL_ny8oePu+jBW**Fcp-~>yJjcM3$ng3yRM58l_nNDAP(jtcY z1)Ci&rhj5P(LfiY8Na|vjsR88Oz$RhGbj*2Md_WrCH!q=kGL$7vSTPDiFG^i)q^I& z-+c%M!t6|*BE-h01@Wv3cvjssCnH!{``@1HK1af2!hTyG7iA}Kgf9zgu0&k+nMr5n zgpr@^w=8nbO{I-Wm9lpr}dwae)!v11kxmkBcc_>WS=F=+BtK4(Loru zSa~pL(2y4W#h=fd0NKWpX{tDU8vG3Q48)d`ZjQZ|vGm;#&DrkbE<+EVi0We(aJRpB z@O((lJ#oTmwN>ve!XWtqU3#ny517xi?0&kwU-oARu5R8Ws)A(!HSfR-d97J#4W^pdkScwCpL{Umx zf8^vg<_B-;UU#MKqY7Df)C~$|?(mRf1d=Fhn4Jgwc6aywv^Y;K zPYjKv!q!%`(gcctD1apkfSGsAdv_%d#tWR*kp21_JU@5(UtCFtN$`1)-O)TIZ398G zbPQ2|TYho@d-}D&(fod!*EOlpwI#*}HD;ARhHww~$NdBnI%1 zOanc5g$c!vu}8CVIY!|{t2|mfW-$aeeW3wP*ekIlwS!V<4Ae(zn8_iK3<9W+@rVc7YTY7$6@hV7>~+>xJxAp(+~t)=px z`(~*EHNNdL@?Y&(HTc%)0#y(N7)-8^$teS6^#XU5SVgYA@oSdwu?bE1(%3Css8MVKnei~1k`j(B)9#l6 z2-E9=i#USeiR>oU7+vhni5TNj=C{ky&hqcE6ujGB(cno@;S}Ia#&qZi`UL;S_0_r% zX2KjZpzGbGg zF+@fpTeZtwO~i_-LwaS}cv0mVsEL}I&er6ZM)UF9F}&5e`SlTAuFs(ubXlc7}{KHtiU6SJ<_SV=cgY58G$XA+b3i<9pc zVs_{qK2+&QmIb&52p(VH1;oulg+K`fB^Zw<(_%F1lb;H1)aI{@KUO7IuitbW#b3=2 zaGqlQ6DREdku)!XZO$`)NKCG6&Xrd4@yLhdMQ_O63ZbL>ftmSo>uERk7wFliODPthJnG)S zKLhg(ve*noKCO+IiJ@Cu0g8Yw;rvo`*DqVcrjV8VD8&AaPK28wOFsCtc+CKhVf&j&;3Noxnvph`cykTP_?#1DHt429(Q(bKX4}}1QWu&0isgS zcH0ZIZy10C#bU=%(W(9+-KT5oAAsRwEHMM;xcO^^fB8f%mCyS2A?^#pjLU;lEUmM; zDjE4GF40C`U|r|}YO&v1?SFp6Ori2~Mc6yy@s7$sUlQCn^lS4r$^3J9dzvHc0dkM6r#wZZqKj{DK~Y2hasDy>R!9lMz5kuxd}Zv5gCM|r zk1rVa_p=AvO28o{|KkqM&U&PC-+J|F*@d&5?z9-bq00>6>#HX`s(}~*%Ykn^K)`wo`w&FV=3+k$Alfr{DOiA;93N5Y8S+M;C``b-D$d1e^A)x7y?#UlV#J@{Hw352^dAz zRFI+{>*eCORp(&!g2he$B|SpK`a930kEkE7xYy;proLp!#Rag$UcYD+a^?830cvQ=|lX{DYmDj8WQudGAgOZk9PX zUp4NcPM0CMu)1PO% zOO3-ZL9FnNWP)hFMnSV^Fx^;=tRhf}XJ8tBwTMLyje)s%f^}YRn4ZqQfqp=4k_&le zklkRrz}*Rii@zK&;pn=0>=*^7Hl9pyoI~@7Yqb&8CBT> zMkN{Ek2Uo))XK1(Ym87=tPpZ!GG-KC$BhzFi)ocweFpU1g}($kN{p7`X@3tS@bpBn zemx7|g}84l1o9ic-n-l$8Z5czY*KW_8#)+aBL@1=&L!!htQG%9(OHHy`L=QR+1TiA z>Fx&U7@>fqfOJeiK)R9K2&F@sKdm4jDj?mX8$s!iPU-HwykDO0JDwfeb>G+Tcb?c= zNor-Lk%4<}*|=2rg#0qaDB@1^FYL$a=(5dB`_DgrPk-&DCz^)QWxFja1!6;tkiC~G zE$VyW$r4KNLj?WJ*kaAfSl8O$-C$ezD16~fD>RxnF!{Tk`G*Z@0R!^N$+ydbBB$b? zrSNHC`I8ZvE!PFl)4`cxj~}dL#DqHpE9pOc@EAU<8a0d)5BvwfuS50bGNxYlOYbzK zx&G_b3=`i6i1FSLv*5LDv~hfX`cU41KSq;(^y)@wOjOrGNi?4mia-y0+t>7g79Vh8JiIO@BEZE+^TahS3;l@&>t%FX z1<`}0i#<_i;+}vz@L})bqWmIK>CMk~rJBG(?TLd!`y+B;DFi+ZM<^>J_&cvGE+Z~4 zeFuN4I{8qmscSI4>zSMg3E*73NEZUv#U@ID6Xq=x^W-jRV-+40doeAgx{)3PP zBdH!weMW9PI|@Z#H2uGeUz(}fn3Ngvj0`xv$nVQdPO@?FxpvH>6eadKH1(eEbL2~a z2e3gQM-8*bJTcYH8~SAl6dq^p`TEVqCvkjh;Vr``v#M zyL=8Wsw5Qw^&Nea18AriSonj_OHvS>k8Ns0?+N^3+<5OI?)tcBhobSxe2``Nq#2Zz zQA>ySM@{{+WD%-tob*-Z!B$U9tBNKD^S)V4+9U^;7hl znb)_mD}V?0nRLtF)_`pp0N=N(#dZETFkdRsgRB4NyD%6Yky^+@{iS$)aAu9n-@n3pLk=p z&BPz}SaldWTn6weq&*N(4MaPZoG;v9F75p;f0SuQ=?cG>B+ z6V>ma9Lk1&fqS?-%arCEn1( z<{er;!l<+4rnTQ={D_wM>%6D^ZwKsm1_*6&b0?P?gO8 zedFD&f41LVxnsn4RAL7wM$YDZI}aQ#vqOy=zJ0nypP_hKXDbOzvAI=Kyi{t+t^JAm z&wxEx3!8wkCjZjxzC(-{8SFV)uf~8%&W!8jv55Wh<%Ep`*@Az<|5H|IM!i&>ze=FL z(A?M$U0#vQY^pu5b77_*HcJv)&yO4iIm_m;4a8}{hqj#4NpXmf8|tSqec*#s+vx3t zCvWlwpe_f=6FoIyr^-XF5@DK9)ohrSsCCbO5FHjgjcgSMDQp`ifDAbZl2u_qO}-I$ z2DI9%_G3tzy!$YS#0Tj@9`G!jMlNcf8Wjh9dH@&Ex$E(-PfwfMA9;K-X>0rOG@Jng z^esBJT|aF%>^k<{x|_l zsqm@uMsBt{aorKh(46uxMkuZcM{sxdS^khWBophCg4=2z0&4L-KUz~K@e|+!LyaCrCj&?N2{4^*S#RT(lxnm_2*(jr9+?YpYzty2$XIR#v4sb)T0mP|nJ9?ZfaFUD$mqJL+*#6q_8|4g7fWxbLSUc_3oO#npCH8XQ)~Gk zdogb_-;BrIhFw0qP2%snM4_XV3}t4L;w>vwl&~9?PLC{E5+l%E=bn1f&~C%;_*3v z8UJ_{TqdFdV~sXpf+Fi`{%^)9^=ZSs&I{RTIpbcVdXaD@S9@B<>L%8$dbfOYP5-Yh zgmro6NdiZZYXA)Q6dM6Ib z8o{|e8x`Qk^=^SV4XJ0~@Y||YRYl;bhINmf6c?wc#>_KeBW)<9B|n%sHB;`I(y1d= zlvRp#evQ-QVqtL5+#qWdlEBKiteQ7hh7G#~vgtcaaigeFbQ4lQ4R|T&-@PVqzYanI z$dyh!72qpr)Y6YmZ4a>By-MM*RA^l#R$_Nreik6`Lt*Q@Y2C(D-~fD%c@~?YZQk;3 zvtx^xrG6x zCI*8U4dHYJGJH<-vBE0;Tq8jyTHTz#ZIV7SnH21B&G|~{l4Yt^7=8)g<6fkzwxs?t z{N2vji>M32#{Kds@W*$x+>_5X`tx#*CtV)v8I5*JrO}Sn&0(m-1qZ{a zJbhMT_QewNFw_kv;KY$d{u)C2s5eF&j2m60AIo7}<_U^~!92i1-pC0~{@d3NPh{|C zYkx0V>?j#t)tqzI)P59KnQt`od$~xE^LH^YZ7N#kYRYg@_o0}(^~?68Z^bu9#*>fN zf){YrLvNG4j|A1NvLNI>`e;ZKB!(mJIun1@F8*M9D&eH~5(+6TVFx$7r)r1jDK(=l zGdO_H8nK{eL{1EXv$OtkuC?`&j^@4Xc9c=xKm7|T95E(2GjTfH_Z3Y)tSH4L-tlr` z=7U~N>|PN~M_N-kB3HiL1e4ph2^Kk2Oyh@Q{>&R+d7LBgOGv)FUC7}uK{S2k+a`mm zaf@LG{wh?yC7Mhu!WobFS93By@BmjniSa&r`dbOH_WN(V;9>LGMlq2% zVJF6N0ZHz*1{2ck6(Ld%Y3a-afUyglmZ&_4mYK~l!I{)kpKv}%EA8qt0R%P-Y^_g0 z#v|YE+o2}nyW2jLJ&_%pGO^;Xo(!98uD7Hvn74QBb{N!Sk18)+>-5Ko1E1Ly zi?Hz1cD84)oE8~n0e=%-6|kIf&Q zSkqp%9Un2aF^#7;tw>PW7PA+7%~O-)YF{tiq&;7>VPYh5CxLxFm!9|&TuwB7vd=UA zCs|AQQi{NQ+Mow=1fC4FE=NCFTc~xb7?mluiFDI0c1WH9!e6Hn$54C+H_yn?nFCnh zh9DZsY#ii&T}h;q5aPrFHevwQK}I;K1_=XmL?p(nuvb6#mMX8#0&6hjXyo4rEMxN} zXz|~EDL^2l81*Tc4(r0KVKjJDU#>?^?1qmKG3AGbq(6+EqWtaze8w#3mbg(Ko)UD%7@m#(5n6vpY5Z<|ezhY!A7aTD5 zLPP(}<#9^xr=7^&`*T*QTtRammyFhxm$<7=A3qpb;wX5z>D!bFpM_bRMH-iuR8bz^ z{J4287QMe&u7oP|_NLVpmn3guB_}M?UTu5CWhKUXcAp0pt3cIN?a?0GxR|pK<)21F z6#Fm2EVWzlGdcvCJ8!cdd;NiL*M@Z3@4VMYvElR+N(@+@0NF7eivO`FzyahvrudeL zlURVMuyCvbY`Cr0*MJYc&}31bkQ>9<2Z$h3D3I*G^9x?Kp#rtr-JD#bu42VuteyqW z>8cfjS+WVnzqA-6`}s<$N6%S6GMHyuwHCYLHZ3^FDx*g})Xyew#XT>fr&I$g&i0A8 zsgK5v@?Y^+$GTpq@C^z3otg7?mqez2H;6;Z2wt7^>8H$Xo`^Ur=YO%>qROTw$0XdnI?ccW@!InQ}7DybGkhEZGj^IqN4 z%r8LKO}R9acl0aGGZ<+AB?QG>knlKuNfQ_0=d*-EDldreTX@E!3lO-oM$0k-v6sU( z55f%eQXJT98#01QVWDeWAh45gv81+T+&zkq`5JJZOCe@DU-(T?gwcg)5q4*O&k zo3~7*)_^b*qPx1R{>(Ze=5vD}u=y};HQ8w?_z&GXfo|TJ5;A;<#sZy3pQ4eMA@O|y z&N-M<4L{FlVAH_Mt9(EK7HQZ&VR`Q0MmFdzCl5A5#N#&iA3U!8^XO%Ez01%EFRv*F zgJC!h)XlDaX+9NsPCVY%H>#0A8_o|;e?I%~*9Og}hl*LtRCJDf**;nz7nnL-il0=l zq=_NrA{t(@b~|3^{7A3~v_u0!*7yvlbbm|fAhKO@xve6}z+b$%#DS7yi3^5n-0O(6 z-yCT+^7^-X^p+nHr2Ew+9J>f?^ccvCM8x$~yP&1GRcQT|D_Jlpdn~drB^yV#1IJK~;O{}J`-`+X^Qp6dH5fX9OiiGpdTm~+L{IVCpFs+6^0O_-=je8hjco? zZ_&;<2{T3h*SIsIj-zaAsZXvF35~aB6p7U5ddlu$PuKtBB#ARl* z`+Mt}R;|uQ(ugP8s1dT#QVi>nQfgDL1MWV<$u+qf;ip{u!2**c(w9mN`B$RUrv|x= z-_hnTTst$C0_VrQ>+VrEGUrhSEjR157JY4TL6hh8mOuR81_jS^huLJFY6&xWfc?Sl zoDgIRveE(rR7$H}4gh~@FH4~)F|>G$TnpalQbX5L9oi!DNT8L867_HWRx#(DvO?(U zs9X45@#(F_^xrU^Lcn#V6%z4CAM?-=-3%?}H1zY|C@7=ZR=`0X-)EaqYSTC5KdN`RRE|zzaSgD+IZSTKlDYg8uBZj6mXAwViFw z$rmkT47oLt??^G-iO90 zE2Gk7DcyneK1HTJyg7gT(+F2pyEwCmE*p@x#R$E71zIQXv!nVr)PMi)QaoVEZ}>O& z1Oxt)n1Jdzw~Qp!88VSY^MLk2J)Iq;}k6vS07BDN)& zRMu|`cjcm{rBnT_o1mwFk#i!o)}+x9UP=@?ZF3D}A}{!(8D{5kyeFiR+_iFWV6@zo z827w9SfJ2Fen9BU{jqKR&8V#-+S9JI&qF^LFHD|qk-GCuVh|?CfjV|eW-K22 z&W#W71eF1m&is$!x_E&2hp=d9^)rEjV>5+kQAjqJH9!oMzZZasc4({U?6d|V(h#xj zxDQ->ocS%jEHjyz`P^2fQZWR`T;KCzFaMAl?Dx|bi=Pn`QA*XSf5viuTq#)zxMB;s z*r+fu(VTD}k|Cq|UFm>m=ItkmyU(b1>!<&;?vEwfROaegL78VmnkXL9o++W81KLpw z*X+`uNBZ3KJ4%$Abgpp@^im1XomFi{ynZGpfenPUyVCv+mw$J;U!}t!p0=9j$2eQS zYL%rljyXN}gyX;wNOJrW+Mlc|x5M6jdu^&#rZL=xbzs2i$6sdw6TOTCBT z31T#7>|bPgc*d&S7}5Ojr0m}>QE<>L6JQaOO!5wa4mCm)Oj$@Lac%`G;Eqzr9;o?s z{}N_o|JsMCKx8GmDP;*UcQ5%+he z`7N_22!(G;bJ&2oXt@T{r9`iMY9dE2@8Ku+(3sIFUH#bC2g#{^Za3W&m za>XcYBpT$2p$Ne0zTAvzvZO_^*#7C9_yycn>tIKw+d zW>+Ps0ZR5zE*P8G%}Q`|PyR>Y13I;J-lns?jH0Jz67n}};ZFj*W#|6+-JT1*L==>X zkck8-*@$Gj{&R&4Sh1MRUkK;Bg$PS$(mZ1h@NAT0cB$)}OiM{|SGpnY$bIXWC*^57 z^Ie<$So~2Ux2&9Sic5MPm@Gm>^-h68S;DFee~zAT(^@&J%v<*NOYT?W6yv&a$hU1; zTo_WcNa|;sR8*HlMv?^n>K584vO0(!1SDgjAjPfi0Q6s6nl&NCix|KwldV#tYQ-evQ&-+#-t zk1O$C^bM)p#y8g|)cFE(e+@xG{DD1~1*~r3M=?SDQ&735Zrqw~g7>Ly_6gv5-r}cg zY)k49NJ7(xl;`)}+^F|b0U+e50RX!!T^0Z=MM!oET++_m4lU)rL0 z!{|bu&)%S~X#P zBNR^J(*b^@TRS`wi3+K6TtJX0ej)+R+lwM~K&h4Y`+%_@`9bc6iAx)cG(eOj6Q1FC z%dy~aQe8UlA>5E}NPL&X2u{lOS+b&-Dp5Z6< zadwa#S4CUqeIU+BKyBqORVU@R%i4c;tLg`k(B$#9nQ=Ai(Zh1%RW_zkuqie_o%GqH z9~AVsW7PcMT?IwPLy;D+-(=Jej4B`+W*`r?ppGtgJueY807gC|>|B|5MFoP7E?RR= z{C}5Mw4VLeaB?yxqLV!8y}8Ne%;AOnayBpUkNxr19#@>V=U=d?vv1#5a!bvbJDgBo z;&i-k9}eD#?(Yz+n{IRmbb^7>`Dm)bAOf`Mu@z<`GQgDy4|W4Gf6wGD!v{z%4}h1g zo%c!C(=?f)$K_Yi>%FuRslU<&T%xQ2@yCq-8|s5BXj$+peTQu2naXp>@~RdDUY&=U zD!UmX=!gN}3FTJ@w{IOOLz|F3i>d-w549!#mDB$HRQKvquA!}s=-c0=+KV|@d)g}# zT#letI04s|6RWn_r$PBY?t)(TQ(Siv6dj{;vX)QsTdvx(GQZPT@fTlrW^IhTZkq0p zOOcytYsm*4+Ym20DeCt4&Sn(!&hMGR^dR2NA*0B=HF;gPG|(zF@)fzk&(P5p?1a1J zXS$)ixm*#9f1{0shda4R`$vtXWu(Hj%-9U&R+LW|7!y=_*NEcdWY5NwkE@Pf z&tQ@tP5PxiWRELgcZ8>(k#lmIuAvP$)I#WwcW86ks-F)3V6?>2a&=(+{;reala3Ln z;fn#F3ECpYcnGFMI{ysZ`6f*4897hZ@K>RU8k_50FzLOJFNBRQe&fU9@wK#-Y~N>u zbEuFWpLyz4oHUjfX|)CRUppN6OGLXB3D1d-yT~h142sE3i7!;n!@ILoe?(wjtaeYbd z8K$$^$Xg8eg)e-90!$lr_hO72FmL_`|HkmizqY@|wmgk87i3PKKR{Z0$4My`2Wn}6 z=Sc>h-y)36_!w!82+Hk-{ow zK`Xtve1K6kfNcPLshP#Y1FZ;+7h}U*sq-#v`F1bxzYxEVx3gXabxu8hCTnkD5UNr4 ze9^RB@G;rKP+aJ!sl7lEvtPn9F5-Hq#cVoHK+8ltN3oqp|J{i(Ua;b9BY;;OAiTt3 zGsrP(8tz#aa?n^lT9IoF<+EdAG*Nrg_gs~XS&uA4*nh0dSc>$k`=0>{P35S*?kQ~g z8y2wdBIdjhf^GY-W4jBm-5z@?)WM;fy<5oej8BcwF@0fpj^g zC}9JarCS#v=)M60EXSi^G1rWe!6E2mB~0@N+mB>x2V=&7W}9__#q2~8lX9*nAD5j% z^UR#xYClMj!VjLfBZ1f-HC&oEB|?Y}LdgW^;|5X-@@TSGB(?Ju?g5XHFB1E;a?jhf zh<`Dv{EBq;rO2 z*Jy58;q2Qeb+uXlM$bd&gRNz}+1PkbD=oVK#wTAPc0uYdS`C1|7$_1`JetNAw=}_oo711vt)a5=12*QK=>&DCN^HIuxer?ycasqs|I*Ba`Uvv&?iy5p;jTt-hqjY@}m-5cWYc*QH~_E z@WX;U)_Q7@2EeFT2Np3ND&e2B9||00K1vBd@f;Ar7QUD`nfN}vTxAUPmyN%I zf3?&t{#z>*x~q_q_T|dx25TJq(Qm0E|B9&ytbp4#e?2KjjWupV@^37?459nrVNOF;I4TbpvlMV=*q-!L0!BGOfB_&f{x558U$G_UYu|H2K4fjZ zm0+uz+LHX@yLwI;5cFLS9@vc?_)q-pyCuNnf}I03^h?&=xBX-MPyJc{M5$UD-*s`$ z4a0Fp=3xJ?xVnRjuhIeRJ#DBl1-oUCS7D76I2gyCP9F1ds2J)mbZfabYxZ>>tnaGb z8(-Ok#D#$G3-zDq)sz5k975?DPU7LmdyjO^x=jQUie%0Up_##x{Owe*!3gmz(S?{+ zBT?uAb2X$y5n&|OFE%NmePiEYy^kO647v}QAQ928p*Xsw*fY{&V=yPYNALU_q5Jx{ zmY@G=3C*DM!BObbj*jJm7f8Eni9G)sS5J!?%LN*`^@V);*ZjmPN6ulRxm5JLU?xR4 zoiRZ8i8urdE|3dUZo)f{2AH~@hSlV7*pt`(T@}DQmb$2+8=a7Laj(~uJdiz!FwGW< z%ZREP2CK=q{9*2g+^LK@h%2uh2homZ+kq|93T%`A5`_KB{^G^!GoH%5MHfE1qRo)y z&GV&i?!R5S(0>)*UCJ*Kq3i6n7%qd4_*0W2(8o48E>NNOlGVIl2UGr=E6aB^Bkk69&1XWF56s< zn77HEFoXOj5Jx*)ed#Y73xZgwv&@j2epxi9oBW0N`M+fgg@Htu4c(|0shI%&J?7u8 zE}a&lDT#$PpX7vh-rf;{LFlVCtiyjiqm|riByFFbb6IVI&GvrPB&O`YxOz-@PKL!D z`jn50!VYw9A-tId@M5U}wLWrinBo2RHFXLH7!5UWoBq=$=A`wL2&Zu6t!QORs=cs^E7? zC<5vaj*tAyCiIbgjXdnI9~&8|;ra$-tl57i7|PJGLX7N=`*&q12y}4YXuD!&J!`_@*n(x?cXsAoBdYV+1;_?iq&muQ>!rzt zhzMkAW1&!N=<`pW0F*a&-RU`+tjicF9umaY59LtOWO+P;y z40xG;5P4^^Yx(W`Fs$#60tzxzcXu<0Mc{)UP*ltW+5Lb=AI!_6&zOXp`^o*{2|<~33>m_LAB?kWTwnkXxr)kz3W;=5qUi8ozf4Nc;qi;? zv}%Iu3q?V$;t!YdZt}LGAvofBE}s*CoxPebp{@)NOg=m2Cq4A{s))=0G{vd@YF5SN>86-qrfnM0u%>$IpNJS z;HPpwh!k)6qWqD=^2k94a(V=qe8AihO~R+?ixQsv<$6DUer#zr z<^7+Dsf-HJIp^%ZC&QYjpOwI|8_|*k93Z)XdHmkOHaC1JAjf(zMPl0#6nKy5{>f3lWp7jcmqAPEsBcbbpKCvcm#lK5X`e)u!F8;U`4U!g&E5;s5D zVN}ZZn+@#vYp05W-)|IZ-maqSTpW^d*_T%?b{xT(>9O$u4vNYA=#MQ%(WarutlInO zp2o(Wnpj~>KzU*rY=_?6+)NR=B`RU=I*IWUn^I zkHsW5g9@RP?rvgY5H?ogL4zVcvf0A`Wc=88^XQ)Njv&#!k{FjugZ|pQxzm<`0Juqu z&+=%alzn`8#pE2h>+g;F;Az_i;bf$9|WH6>Xmx1j5Bc&$^hCdLpp^Nrj zz{_8hyIUQB@qoEGrCcy0X8PyO5FRib zN|_n9yO=qRyr>1*&W9K)uBQb1+L?rrYe;OsqaFJ(Drm=o+j&<32K?qaH`)L=%ZvzJ zO!ZJ()`jIz)@9#0Mfb{PcF2aDpdIcP0 ztZ;YkxPo8}CODhBQUDR=Vx%AG^jV6a6_VMAt1>=_qNf?l*@h3yfK|MY1)&&WKV#Z{{d{lRuPjPfIv|l!==-uHMRoaPfu$nSIQq4jnUQac@u@`boi~yq*{EPykJPf09P4_unMuh43jL z2?+WGP<9OYMR2!ILkc4$4~iZ)^>3|O!0u?0Z8@KaxCg1T!4SWjD#-GeBLyL+#Z8)J z>=1k2!RT{I#&`JUvTfK6!l521ktKoMF`u8eVu^P(Se#e9eZk_Xa9rHw{1O{o0SMh# zfd*c4clH+VH1vQgg%A*a?8Cr&7ek?A1;}#G`%-Acx*rL0veZjg_wna`M`TQ(>MJ+O z+p>e?i8@8W^xAwqoifS~{~aan5eq{Bs-*tmD(1^rQT)ykFcJdg&U_Q8?GE~tTau{% zTB7p>quEOt@4f?mGhY|p@+{SVJyAmNr&o&%>Hg*lDMpi0ffD8TwsLH}wpG*5s?EDRBj1XAQSLS|+v~4uM9hHT-C0I^ko|_> z5;Le&mhcR4GJORg?PtN6+>`(X##eLMYzp{I=kI+tFMK2vPJ#zR5P(|D8jZN=b)iXh z=D$xZRnId^{wb{UlVId5|2S&T0*JBmmddLzPIt|yZc7RG$f|>{_Kc}-=wXSDJJgx$ z_tl7ra4SfEe?H+Zc-4B02k;kqoU(Z&80SSci*R7I{=v3~a`{UqH{(=&SJ^jY9+i27 zV}~$Hf4NzJA}!vS@+TGOXcY__;B;Lr%BEPqF*g@3u*wHSot7`#nMDVh)GqGWMrS8! zIWXtk(Pif3EK!UdRT{(?%bGzNL*(n!1uQJ=)W>+9+cBNq$zIsy(Q8I4V=9EsYBb%X@)VZaMgwQG+mW- zcyrYk+nWI0xp@uGq4tg>^-uwX#5q~vZwWL2U9Y-tL4+IMk7P?$Z0xl2WZ%*~3o6m< z5ck6OQoy|nz2i=n@*2vUPRMm*ao_y)`i=<@0!2MIeW_r55adxhAyKo0AlYG~&pOeh zR0SzWF%$(ra~%upEjDLB;8c771z>Nnyc4?>KhQ5zM;&A)EKJMf2e;ZD!>ah=+#`vh0TlKaVubIBp0-m+k;g~ z35f&9fJa$QBWnuij!9@m3=efS{!1snHb{mKX4ENYvH z$L%N_u%KBTeD>37v}hy~L{qfG)c@sj48LgEO!metptR9+oTs^-EEsUGfpNldo`wNu zXUQQJuJ6|E|JwuBt$PC2y=H`<=_Rkh!jBn5GRcTV+@GIgjL6Jv|?m!_-PE<7dpuABsMA1V!3l`+Z2H zdFseDfI5apy>ZQs;8zq65F0f#8pY_D>68@Ft6n6Vjksc2H z2Yu|M_+TTyVF8tRT#7*Ek7E2JL7;jTn)-1Od&;Wz{D|%ASfw#}7#}dWRK!4i>XzkK z^zHSr^~8BZwH@am;`6^R4AxhY_Zt2YpA;?tP_) z-Ldxclt*a3+-HQR<+jiZo24it1#859hsVx>u#XP67B(Sy0qZB%7||z<-(x zgzdj;d+f-zr}F+u$_rEV&E(ulgOCx?)&nG%bo;Cq{SQ^YW3#ofJ_oxF(h9^Ky#f|q zjNzL-(X49#j*{UlK-Xm@xEl)ZP0JK%-n{(B(QG!PDtHx85~=6$9F2LQ{xw9idK=>% z9aq%VQ~q4F|9z@$|H_~__Xv>-(~qcGa&r5 z{Y2;+@%}f8G*vYKLfyvrcCIdPj=B;dZNoJIp9qBA3xT+v3)wszxNKR;aP(l08o@AT zE|tR(sR+rqR?GvmkC!64<@HS!E|O21wLCRm;6Ge16(MJyQw74u8~!Yypyml4FzNx~ zI)%b5W%2nmlo+ICy(F&hNia$aI;0>-#@`&dLc(x~G;gwbfW|$@|M^XC?mxftZ!d2$ zk}3~SFS1m7VP)un`GhlPxvCGYxeoXvrM6E_rVn|Px9jcg9ey`5*gpJO{B9hnl%$zOH-;9XCmvcS@Qc8?{ZaR6N zN9Gm&t!P{S^E>}vVtq5q%w{MF_zIE8I>cHOg~JPJM_dO$~QZpLbN~H_l(}BTos| z4_9=*2gUnNf*Rj?{C!HG6>sJ-q5(xNWhd|(+Jv$#E6>+CyE@DLbYzfLBiMcMElS$v z*5h|k@0XahGdGyun|E9tFPg%DILqtKa;(kPBb%?7O)6U zwt_Ts*wu)owBM+2o&Y9}acr;~$OKrcr6kt*Y#md(O0Y*kgAQSS?Spo=`dhonX@krk zWA&?)%41R|JNGwReEyYU6_-ZJkbtm+BPr!izO7$Wjr)8P?_v4OW6Q#vHsawK=*qg~ z6^6(|eKg!l4KQ?DdYq4;qJ8kYM7@)A^16< z|G9qf+?yio*nMd zj2=LvjfBVkP4sYJm(F*WO2UP{R?KRRt^lX5P_*k;^w0DZ%2mJJ(31~xX$jn(`M$6H zYmW6;ROSF~e%XiHak?TA`l27+q^UrsARZ=&xgwvNLO8If!G&(Sq&`?KADb%9joPAggvRuUtWSSnc! zH4#LDac2yyG6Q!pl1ksfmDmdvVBHp9{zq!Is`LATNlap#f)w(o(hh#tCs*9QF9AwW zlH>@P_)UJF>H~>4H@xS?i}_T_#(a5*bVkTbgQ$v9KoTs8TkkJYp&w6awJ2YaC=Eb9 zj_-N-4`-uYej&$y(%SDD<^J2kL$Nrs0n`gAP~ZZ~^wHV^D)8nu5w{2VV#9ijo&jQ< zk-|(`J9+A2<^DABz55B9n`B2{YQ(}B>&MJYm^Tz&KeC?4*9>82xl)c)h{sgO`yaJ46*;(#ac{e>I_}QH+J**`wRkDZ@H6W}N z*g!+P-CpPh(tROn?=NEBF72Y;F>lK?;P{2OfFN8QEU|yvdi}5{36b!@xasE>L2lCP z=;J=W*E2AMJB1f=UeL+IH^ZxO*$$2|lRh%7Audk2#%wqHNU&Z3&tAJV%CG*SvLb?& z@YRCG0NwFFo`FFaz!n=G3ck4b4Xt=>3tsn{M9hZPJdk;|UAOfuCZZoB3mcnv@9$T* zz)=qD)Vc-{m>>6=STq`n3`TbBqaS>@64efbC(cs$MkOtkzi>7Nz3VD0+sPbeMNI)q ziSH1GW0wwhRyCt#0E8Vrn8E7w8}fJiNdl4r`whV{ba|HTF~J0+h_s))d}r1o+alVS zkBZ(l|MAYKdr=k%4H*HJAMgWu&8*d5x|}MX^>hRXSN$hpBIXeS0 zd#5zu<#bfbJq}st-C4*F^f!&sWhF>QFIwpwv#opzi3yH+cm)XIAq}C1c!07SDe^fb z@|^v@jy0ybzvZQ)nTl;Wfi>G1g!fh4=ss~f0|Ct2_JET~>c8!2R6J$!#7(5JX~R)+ z8Nt;hH_$gBQnc27zyIOPC%h6D6X)YU`~!ZZylc01sZF!no6QXUe7EI-`&gv%+dbv98l#)s7L4X>}v#k9ig3Ka>qsJuz)>LWU>EYubon^Yq?%& zBjngu$^uiDxLF=4{^P$x~xE; z03hVIyps}w28|)h2w(T|nZ1#s(3odNjx? zSP6?C#LrT&xZY3TKTHgoDSdQ>&tAOodj;&8sXu-V>fhd{YuEwQ))WkxL4M0@5wGpH zCmqZ2Vl1u@T|=Uxc6Lj#i}>%AN*t;(%>)r1T^fJ+NswV#f{fAUuY^i(-mjc%2?2Gl zU;s^q>l$5a8$VKIy@8)y*X2Tb@6g5}(Gty1rPf?LbH&)+_8re_)%Fwf*Vj$((=d&g_`s+6*-T|IUmTD0`4=Vc6l3edAW3d?1#8p zTWZ&pzP&Enz1ebRk`hJRuwLB>`d@wKeVP&}?y;YQiQ4#Wuvv}BLNHCYpWBU~8a<@r z@1vWG_Y&P7Z%kfv#Jis)H_-l=!0Hx{Hf8sE1zgDU10{(gf8qo>3eR~s@4ZC-Tc z;1r;HA7cqjd<7=92EpC~kGaZXVx)SkkX0r-97NA~1BUOU(&SG*G3ox9wf_f~-ejze zvhFFScz(;DtOM+QNAj6HD1yUS0UI_N$lu1xU1sNM+yIl667QS#d_U1)V~tY1I2USsbDc5Qgh!KT1P zCwRs%bVWhj3LwTb7Tz&22Jp#{moWf!L{nCmKc7Nnur|lmj*Wz&(XaM8et)a-0O@lX zif!A!9LIZOezQ>W0QM9ps^yjg;NkN&e5fyG{r-K)cj#F^qPs}4G8*|EKRkSdWNidj zsS;Qa$3t2JHg?wP@GxxHzjOji|&R{^nxv6$z;4f%{Y1TUhq> zDE-OtsS(oMz0o2_2uKS^tCTbnqd__ZBu965?%n%s z-*y~3o;~;dyYK5dPfML%mTb-Af&>Z#cke&~C2VsFtE@}@3Z+zDd%}&%QQhZ#8g#st zeM_qT?*5GPKyuQ>r~1H(wmT(s_a|6tZ;}JuHAtecgQ9o?;Lg;`b?<8Gv;)Gr9uAqYQV$ z?+X3F>}`bB0p%$SvTsJHLa~nIdFDSW38ee3pMNjkTXRVHOc*PYgm)n;yMosr{Q`on zS5{=@?~gRe0`S1Fx*Hmx7Fd1@!yzZ&Lm`&I>BWPL zqyiic>D}<-(mIoOc&_|c;2bY-_R8W@JwPm;*PD;CPndXh<~ySCkbB`1j!QHSO5#zs zv;YeYQmOh8>f7HI00kx&bFc#SMuhf!0<);ygm{ZYm<63Bqm&QUwl|z`osuCMUE+hq zv021T0*MV3$^o0<+c;aPD&)NNXLe|l-QN`e_^Czq;h4R-{=VKKQIu}y1{i*U`1{oR z>6I;vge0Txj0Okax)LZRK{8gT3UMktSqtI$o6aA%_h%d~DOIst z4SondEgx+(X$82LiTX;Tgqq5S34j3dTtHE9gj2RtDGO-CtWJ#Uu}8s|aCc*v_S@26=l*7TiVsZlMP*Q!dh`N{-V|zJi$11!yiDU?*+CDm`z3ODZ-% zeiM7ej49TI{0A_xQx6GHF>ciA!ilA=T((Ol+##lf6Ip)36_?kqDGed7a>O2 zC4vG9b__yZDSlZ&hr|P7Z{{1*x3j_X86E2VOC!bwku<(*m{H|#! zVM_QQ8%a<97HF~r(Y^E}hIC&;Yzd~ISs(v&w$AO!>SjEWPm^&PosL< z`K|S{_@$ezHLuF%2OZ->@}|m6_k9kKl`D$ z6O=L9S811~5M);bcvuER@&s9*>ymFD6hcK5mJTpr_(Quuo0f|r|##AVh zkJ$l8=L0Gu?U~A!h0X<)01gQksW=FnA_yP=)|bXE00_OJ#^P)atawj#xEucoSnT_7hWlpOd( z)sH(ZuWssoEb~_gjjfY@e#LU1Z9;=EYfrglHpW9yKBuk+&O$Ltz?1o))!#@P!6%>` zHF7jYHu42f)|BaeU<(4C)!|<5>+Cd=9VA?i-_v8pm)IN4D}GAL6cM=Me*|Cf18-|3lG#Dqgy#3w zsgT1}ezrufq|IgH5Y@&-goHVOM)e~1_r|b3qBoPlVaV8P zVpU3{rE{xz{4)2k(DA@N**s>Vs3P!sJX@~v*yokf_*eb(hvmDiE@v60cegiYv?B<5 zA6uTw);-}P%%}6fSLJ~v6nygl2(CJDK&D&>X% z)81)}%i8H^Qf1$5Yshm#u^s~pvj2xd88M&ZEc-AM=vTBqf`QG?0J|2^2RzKj-6-e+ zshWbC>bfo!D(mR|qnuU29P!sr|667CkC~fB!m46Cev;k4`I}IT1Uc&AuK2XL4dRUB zo8L2i!lLdshY`;%kEB~KyQJkY4PL=eEnHBJn@<^_W&S2m$G*e75VIAn64y}r7B)|~|a=DokHQK}-EcvldaqdLqV@j2>4{rvUKflz4!z!G&} z@F>b8$oKg^{*vKe+ai2Lx3@N)w-PUM_=wg4Z5s(XJgJjQ(~oUwfJ0Acmq;Iq{*-EVTnDe~Q-Yk{8{nV2hGxy|$dhn1v6&i7<; zC+9q6e}p}a5nV=H63cSEyR))gz-7Bg19NgRGyC!bIHK8mk%+^`E>GQR3D&?Oq=}1E zs}&mlxi00a^-}|b9TV2qDWHr9tGm25`@KZbh+6Ls!Vg5BIxsZ9!f8Lqz>ISODUnvr z6A!h)e$lt8$Y$y|WX@v*p{zZ-@x$Oe9w6yP(=ay~Da4~$i|bMByjn&X|2PF~ZKCdOH1RLw#hmqUzW=v7-OQxkkW$)`h9 z{xITpKF!qbWvtm~29o|_e&*d%MmE4O_xgbrWqdDjarUE@VY~@J@)WWSmL=j%2e{XZ zC@|WWf(S5z4LC+uSZmHbn0aQKqpG+VtK7b$lEFB=PDwitE3N#*wgA5A6T0$MXMF8*%M+v}g zj;4CSId8{jUClw8@(WyA_WXvaPvzaQaaE^^Y-pgT;=yxng9k`?_zoVOZ=PeWp}(dT zC=u|%!#-)7EA0v`_ZyqcKYZdT{0g%%D#1LO!JrH_YU9F4hHs|t1}W<^Pdq0e`G@dbHY zCuMq1M#bXrDpd6&&N2EXFz-`==->4*@?LS9zEF|*i;jTjp^9(J$RMTig6o3Dwf zgAp02D2!kKYUKU z%L;C>0NFfG0&WjlbG%o7c0;`6K=vL*p zShVqTIv}+1EFbxo@!`|Bo6z4)car(aLu?}AO?7|ro)AxdsN0(%R2naQn5#iI{Rw|o zL5K(cJHxCh(Ug?>?N`x>y`puN@cmLtT;n)zOCaI4ODU%~p`jYX_#ML0) z{ql+a4i9mVw$Ar&jtkX1$|XN4&}HpWF^Q3TYxhktzNiY$*J#xWW3EznsG^X7mhluvBIo_ zab>O7s2ZEL4qxk{yS)p<&7J9NTp2fWT|ssAy?Rr`$eY!F$b8SYCh|Aq74Z&$Hpq~l zE`Ml36P0GtNryN+ArBcujr|)Ay-KS(GTTR4cpcW5$3*O#<+fWL_Z8+{L~#CD9V+QrH-gW%>{F-am?3-8r}`_)7X( z>13~57>Z8xI63h@B|hw{M#@!mo3~57fQ7aa2yGv1wZ%rvr|Q$Fd4TwXDL8mT{?>ga zQndhvISGLw4&XrKk#2iIM76TX#f%$K(O~(YBdIad4@D;qr#{K_E9YnL+*hZK_Ydm+ zDk*-dPGDb5_DqAIjcwm;v`$?+ox9e-!>Cg$Y${;G-vG7E`+qYn<)#l%Ec1s+SIkyO z1EBvUFvj@|Pz3ROadMij+EF9W3$6OkxcZ&uChR}6P?b2c%o(4P0y-|t!auq~z~G2^ z?|={}i@Cbue0oMfD(dxtQTxOc3cFk!xyXcvKb|FTyPslh5jSewzw@2;?mwB9brZJO zT(fyX&mi$}swe)|#HVHBzp2T;e?5!*6Nj~IXa%p7vLC+v?i(@yx67Zm?;sKaultt+>VXa&X@1{mM;Cs7JMfWvlAT>bQfGJKZ$px3Dc=Jz-dRDEqg8@ zYQY~j@w}{Sd$_W5zJ0^XYWvYiNZA&&R;~8soSPSAfb7ugeKT*f|ClqcfaTpfN(zxLbp+OIcr)b11SkyzbuDaVN%Ww5o>hbW{I_*}V&$&wp+cZ-MwMZNy9+@RbupcmJOT&MO*#Hv`u| zL-Sp$)d76H{wm>a;Tk0O*3cLC(+Xkg=2X};&Oi^#*u$U6bkp+8|je#=cWqpp$y zwE``-YB7DjCSL?hK3IT&JA*I^agIRl;E#n$U#YXTUz0zzd@}8?2iUxTXGU9xsbUGQ zXMDh{*L)*5yM++gb30Pl{8)c&T4))>>Mu(ec1zN^hqWOX^)}ZNRJcAZ%5##QoYwjm z3BW*NPuE%rVcIR4YJ^Q_Zf*tYbwr{QA=@)W*IdD+Ia?DrGXe7bI`HCQ zkr`%iqS5~A6wMm8$IJ8*5VP|w`J&MJfb7-WAfK28Ug*si?%=VWn=(&j`y!tL&yz$r-= zb{alY<*+OGp2_ky?)jI7AVN|3yUNUVzQCC}qF(+RaU0mF!VcBLJ9gs35))Ro!$Tdi zlmo(Nj$IiJJ4l^;WIs=pj4WWtAT$IjP7{f7`+*xmD`!lP5ou0`_y zQel*+d`HjAwl1Y@FeMS3k;XH3`D=X#eC#3^HxA$aV`r;plQ;%n@IWvpwg)V% zxI%%KQ%qOjim+>q%uWFoJplCRO%p%nZl#GRal;6I(DwpPgHHnlC&&)P&ML?H84mGK z;J8#%{AQM7n%y=2U_!b}mim2<%+kPh{Vy$}*GLXG5lj=@Sm%9eb=9%7_kM85j-kSXqOS8I0kHt@KUwqM)B@cf0UNg%CX6ViC?YB=Z*(TfCMokjg>^SC_v0HDM8S2D6Yuu9 z|K5@iWuVZI>Ot(ayO&NTC8^oQf7G$ANxeGI3D120!Ph>QZ9|vd$~N;0?eOZg<g~ zyWv+R*Ik=yeI|bDVo)z-(zXxTGcfFqC1_idhPl)SN4U8PrN4xVyLWIHDLYEXX&&fN zmS+ydQ@Ti&1s!2j?l*ca>I4=s!#z(6YeKMTi)vZR19I7bOZ4E=MX9^)hGWLx-Um*+ zUE0EU!>yB-gK@2hrS{trSGmG4>;Y)Kofs~xTJuzsAZ@}*tALqg{5xRfQBebvL0o%8? zi+lXxySoa(!o!;%%`9Z^y$kQv4Eb^CFHU4RF;sXzh;#qRHQQiUk)kfJ5Qvjz+hkvSDVzgMKzSt*XtWw?lZZ_?czh9@z zfD6B%7mu)(22?Oi;Uvl?OxvE`kn^c|e1|2su_t`fia` zlb1e)9u$j%K`1}Xr|FCCxprzSF8qE8q-~-%BP4ISM~1j!7s4A=jYVZ%CA^~m%n(=H zd>DE4cgpi`JI|+Udg<>}!4kS#zkRgk8uLmkdeNy|#GN6=+O2k0Hdu%&h=G3uV$(-vSblGp+c5!kWE)-E!#o3(^~&nP1n^#X%yqk=VqMB$)9Q3~?NJ?YkIpfp%dyV=M*X(d{u$ zCB6raYK(X`I5Pk{5b8$BvCZjMgo}dKDd4w90Q=q))Itn zwV^?F%zDkqT)j0MP2p)?QZ(Exi0UQtWRN}XG(*sDUGRhcN_^KHiCg$C_aBr7sMv7H zrh(BAAoBnl5Y1)HEn@19xon4rdV7x6OzOYWwWA}l$+%gUF`_`61fI;zfOXm6tHX`Z z!(99ce(GnzHemr*X`@^YaV{L&0V*xz5J&%c&)ZKnSnMrl-98*%eEa-2dqa)h4LF!P z5A4EIpq)>vGeXEtJ3H%=NA>*LhVx75v6Jq9RIOD2L(H2R#c~C_ZK)zC*jTS-wDEfn ztw3$tpW^5tPHayc8S~>+X=%8E?Q*L~>(>BKFC{_zCcnnmgE~!cR~45k1B>VMaL_r& zUqp^=w$Jh}`JfrJxFUyJ-V%Xp!11N&^LCK|`Ram(wf1WFoI7T-m5xxVO;*Cg=Q9Bu z1|*zsvWz{w4O@oWJK!MvdfijP2yJP`sJp=USLEfaD&|OUzRv@<*dPJ?t8Zf+eOB`iyY6WQA4rN3RM<$1&wdmP^RsRRKFTP zSyLYyy@6}66CppP&J!31^xAf~a3ywMQ~pxYT)6_R_w?8aBCFC_ZucY%=4h??|@5S{zq8l^mm?- zU4QW4=oh1asg0|~ndqBxPZpMp8Mg<|uLJiSUvXv$yxj1@0<=_I#S>F z+^^AB;GXM4b+*f%%ONi^OYC2eRV#%`){KMQ;aTy%lrf32$mh9KP+%Sh?##G2Uh`^i zE!${k@8{ti_BA!G$3q&>+cR2#*!>P5C6cr=;t~NmXL=WUY+7{ex+VIir}-?eZt=-6 z;t<{TJ(BbCsK^-o?!l4;^9`MS`yFHHmC|yp{`K0X(6Qv><7mn5^m0vC;8_B0$P%47 z3C!Tx06xL$XQaVHmR#S)Zg!)Ur(oyYjO1XP`tnrhj-@1B39gjy-t53_0FwMA-`n`W z3FE%PH>T_(Q%@t3&$Ypde&(VF|oLOWGFPvZ#f*z{6jy;ONR$c#%NR3$&)xM6kZJ zK2z~4f}hZ7xrZO2pM)QVX`|K96EA7z%jXd9_i zZD~%S25~>J(12TW5HbrdQcwZ-R#U>Qx>Zy@Y5W-UL5w|uVX$ql73ogU88vk<7O z%c8&5eQ6m{#51v{gs;+=Bi>WS=I5{1uOKWCPchlLsPF_S9{IQ!A`~j_Zr7TbI0Kqv zz3%%wC_M^fVY$EWd+`GBySSUvm$7{$<*P9m$sBOF)DgMQg|WR92VFA5wnyd*O9n8j zg)gdsk2!!P^m4_|ja-P~u}Sz{XHNu-E7B097K4}{j-CI{ry{vyXf18XKj;-|Qg&>{^ z+r-F7VpO;il`f%2;f@H;vf^ zEx5Nr&Grcl$p6`OztEYzm9ZTt?~JQTj$VnNj=>$k9Z6J&M zzII;0R^NjvZ%dvEy<5Dn{rMyL{q#hYSUj6d=Y3WxASr}b4k>z}seGnx!380vu(8{n z2dh2?jRc%nf^zbRsDLmlp#(lc%@GSht#?p}s1DRj`9a^Y2lep9s`RK7gZ8(Ql;8I3 zNs|v>HB7$otDqvYn=SOnwjDZDp#A1K2x3mjv$lemM3Mg7A%u=?_~rYT&+7Tvg*IMt5Z_16e%(F{Yuld3 zr^4IGfvmKf{Q};xC5Q_)vu6^qD9dbg*Jvny%cDCRjtmwS5PGZt)_PPx%(kg6Mz`f) zKQ!nX!tFrLC6=%6N#W(k6 zZ+!w%*#E3Zc*teaCnvyd4n6chTNk!&E_z#kd~3{x;CTcdRT5QaI+6lRL-FH%W9d+x z$LZr6wdMS25|!|&gwjY2B;4_i|LJU`m;2%6!|d7!S~F@d$7qY}rR?m&fX4yqxc^O4 zUuYkY9kog#A?Bp@=tx;t3RLd#m!qsBmf_lAD1_dqS{n|_rn8}k(_uZ6Epf^!sdnw-_cZHdu$|C|HJ_7MX$1`*oi*kc6= zT&-am*h2`qC%c(ew5X*!BvA!ZZ?6&bYP-R=kAMvS^2P2~?~jIdtY|Lz?%*`(X$aTi z_(%$XKg*?ZmWF4yHWkoA()74EIBJ^H%A^KQ1WS(#;!jmuOX7a|KtF4Ko}ZElSM|Sm zCwKmtU^h|gZK+n^tTs*o@o7z7 zk|X;sIU{#QruBOSSQH^byq5zox4r7;%sGZxJEl>{)3`~c^ObC0;TkVpl=QVv5J2Z9 z`-qRkhnB4{cZ_QS40EGdY@xy6?bxf^gBqw>aDzTV&@Vo^>u1R-W9mOHGIfH5D(s&! z+zMRmyuQVJq`Ei=-3!FOh-T5^eTxIQg68L|JjxzeuShn!UPokS8_QPpiXYBDg=}H< zT`U(95Ft?*d-Jy^^#}4_Tn;C;qQh@r0WKN2)ZDiZtsX>teSIvxiTFp`>SN3QVefqJ zw|AW;A6G9C^{xcs{TQQQr~gvO#>z2C zKgja~v*W)jS=ikz+JCQOgkJmp*+!J`&I#i`*Z1FYtT~w+z$}yiXxdO~xtzcOe0ck@ zoHQ6v{r(f{Z;ps7BDe|N`AHK`S%VJ?DgQcj4Z#a)cLj`W=s$@SI2d;8cRg=mvyO$R z`jgiNm3tC*KfN^c$QNxbX9jID!t{$%gquN*(m%*;#wm!X@m^q#qD)Eav&lgtzHML{ zAPSU2Lv9G1)VUJpFwvma|83Mc@?IScrdTMW%|VyaraQ8zOUw{%LU5GtFn8tyHmSJ* z|2C0ehD}8v0v|T!2XKd0xxKGXvjlE?#71<*0fEV23@e}_+*wBCZQ>L)A=lz6_LTCc zTGYhn@qkM`o}V=5?!hb%bjlL&?%;d=U{p?{G zn(|~ZLV6rWh@dRkHNj<^iEfPG1cgHsx-k3A^?sN&9_nJ{2H#BMIzjYvIU6~0jA`r! zJt^Q9YUNR5Mb`$V^idMv_>g!EfJgME#$FqGv!abZjp>HO#5T@oJ7Zh?e_uEKoxk!? zL;ctb+cie7YHh~!MyaHf$3I3JM^Cq-mhbhiz1soB(I)_;VP)tb93*s{#{p#b@iAmJ zjY)^nN3kNGO^<#sTD(3Y?SC(+b^lq zcySU;B{g;5fZ6Wu?x<02c)%N4a;GYA=UVXBQ392H_JtVcd3Y-Qkr!<8tGv$o# zm!kIVipg^3Z}hNj+e;t57>=Q2lVp~ko0$+|qwISq>9CWrLG!@USziCmYaRE1osZ8W zxsFsGBh!foukkc_HzSnXOTWamrr|}RCn2A`SJR^K>iBMYNPvD~_&;$&TrN^vZz+Sv zy1-O%n(D@sOb4p`*#Hw6Xm}>y(as!r80kcGn6j^(tp_g+W1*)I19ftM_A3ue@KTn!Kxt( zJ%z5wuw{9VH``N3t%ATR_#J>9MRMlIz*Ej9GZ4__St+k@_Ez%lmCeQ6D&qVgN15xX z`1>AP(~k-+D?^MQIp&@%*)U?pN0BC#S^b8&isZ=jKPI$cFQ`FunG86WWK_YA=Y!PX zs_Goc5J15Fz{c3lA8o_w+pliV-sZv|aruyLz*|`l(4N-r#m~pqS{M#t{ezxn@xI+% zjr+uIP3!=@>Ot`iiy25y)N#sOmHFGw7%EdpMg`A|X(HIANKYvcgj&@6tL<2luk5(m zRV2ga0sCTQmHPb*&pp)fP$vQEl1mB`;Cz5I$#?aJfL$v68FV+fUJ~J>pn?|fthaAt zB*QrXZtHajd2Hp_hxzX6(5@Q6EhEOdo-*g6vbl*t4C?Y2SO7l@H-T zv9b8T^KOjlMM8*^1SNmy`D2Tu4-B0GO7y_Vsc1O8vT7|^>r484^X=V#SL*%i8p)mj zAo*SCne-zbV8=`2cG-GyT86Sy{&QhjX)3lZ&Bxcm>D+RL9(Qhg9CTA!<;>qt1p4>* zx5kA-IPP?Nywvd2EzIjzn5N`A2G-FOXKaSz2|(z-_*Qj{C(Mo`HTPT2Zn=scQtfsm zX|xp|hW0;vDWnJp@b{iT(E&OgHUl8TcU2yfPeoY(ZNVrgOLZnTT)7V69f=5C+a_x# zBd2Zd28CUhGRwQ(VW!j0Yukg~Jl_tz8rQ6K|Z3ENZ>_=O_q z`@w_kpX)A1h4JKw7>T=vl=8V}**INay`z|{7Jt53_MSF<1DQ4u1iQTCM=n?ZG|qaYOed=l2vL^-*e2K;4=O{F;WX5{pr+C63=Qw4q zq>N>ZJ$Lp8PO#>W@Qx>b6GSg1Llxx4!Gn4Y4Tmc)_hY=EaEpn_{b8h7{%1hwu?~%v zGVlg~JjvswjPdeMo&C?{<5T9utR*mEo!CmHzMteHV!VsNhf{MDdvm`qFU`#Acec;I z*jU*Sbw(M%(+CLds(#d5LXb4-0Dcx?+Qv1dZMyyi-ZLRjxZI`b;q!rhx;d2xut)pX z2RiIGwX#O zjka}6lx8(fLsLU0;IQ;&c%U7l7aj4cHE@g9dDr;&x%N@Pd*?ZU1&e!JbwVglGZer>lnbjN5DF z$5;T5lmfV-@V~?V{-5ClJpr)TP$GPAMVavFCPgJ81ZXl1K{?^mIl51|Q>|*o_aa5) zNYl3kPfP7lTxRa-E&N?MS;F#XpJ~t-?2T|yfEeT1!}Jzi{4}C!g)m59GG@(^6_g<9 z{R%D{4OUgh)=^3x!i?}0AS?%;%1gUb*#rO(4ZsB~qmns*$KEZ&qHP|l_SCujKj7~+V$tf_LZTtcHiy_xubsNSFV6R2o?WhHWT zcCiV=pDSPyRSM|gncG>iUP*pTG;c9q}^IAV(eSElH@7c-tu?pc?)o>46 zdyE;5^Mw41KR2DRuX3LOZS8-a1#+No=?qQwY_4c;3*Y zPEI>D$LPua1bOYsf;L|Chy2aStEhiTi!wa6Mb6z_W%$Qt{;h0%OTutf5MlJ1+-EYk zZc$QBT;y`VKik(0)0U^=+?pw zrysrHhGWgcmh@$Al3=T=3~JcOH>V)YLU&KhG~*UQ;D}1bUy@Gg(c7ld*ynv@IK_l; z+WIo)wL7L}W1TmX<2WD#UkxZA&+vaR5}*i>3K~zrz}QaIw|YR882J6EihpZi@(SG9 zW>INkzW*$9x}-Kl#_TL!bCb*H$5rts7=1tPQ&0vsi)?^vMHl+4#5toF)Ay6)Ui|Ly z54)sPS`NpCq`^KM5$AYCobD5-kEtc>Rvu;qkmDHegSGyEM+hr`q?9?|EGVkLe+m=# zis!b+9l75Gkw#lSb3H%&I_(b4eG$C7VV)wuC=oK|`nT&jPrdc-n_9wXrPLY&kKO!i zymt!|k$@`sB}NXVxrC*e!}-Q&s#VH zU{BXQa?75lzJ@|`9=R~d^%ts|IjRwLkrP(2oeJCw4>HMy~i1Uj8k%$OrzXAi`_K8rc`lpx9QIu>AgEraXQ?mR_pihYaqZ63N_vA2?xo zSfvx$9kzc|OdX%>LKC1>vM7euVb3~3Ji-f50v`&H-BGhEi>JtLy?XF0_@ zGN4nRZe{pSB#7_=OIT%8Nq}Ve=ZybRB<%GJ+v;At{#)0r+!ISDeVnE#JM;TubT%&N zy7@r%a^jA;+4Eqq$}qc6-eX_8t?(5U(Vydk*2>0w%e@(>I?a5`D@qr?okxgVlOpYS zM1jY?uDe#YT-~WSLWv);Rj8f&Qs5jJr2GaaBSrW6t%f#)OAS}iVE6e!gw_xyCj;jwW+wEDElk(%PFrEQopG* z+)R8Ab6(^f_BJ918r%oIlpsKf2tcYkA#lT;uWuYasqef1_5(*fM}+PSwQY#y8>xO= zb#B?UW_ARR3~p~vHGg{g+~T`ModWikS6;yT>VF3N$A6&9=OsAXAJjYpL5ajTyjJx| zIIb1`V@Q8P4u}Hx`k)*cO&TN-8<1f0J0&)Ci%TO~Wr*X++nRpn)KEocYM@ChR@v z;GhkO?ZJrMO%dF@GvB-;3tG2@Ug{2wzlM9>97^+mLVLlRQ(osS{r7}tnAw$uUBJO@ z6X(Kyok6bl?Yx`=6s;&D0M-gS5nYCW1J%j(H#5U?Y)+-J8^Rk$Q-5P2b08Mm%9`GK z^l(^1Rm7{aDV#5m*LOf{JzLJyPfyK-Ab(?HjiKpCF00cs>q^~=i-n+@l*Oyjogae6 zJTL>Fe_Jb>v5Wj%T)r+Cei_9%r<>0;_Ubfz{$AEx6;0NIu1sn4cl2V}DhhQXP8?@!ZacoPhgMAz0JuBSvu zB@{KX>j{t!g~*FT;DH@Jj|%z=Yy4blp>J>_1rNR+{ka@?R?iwAL`k7a59iKuAUv!| z%+=(#-C=<53o-?Ueo(kIM6OeB`DD)`c|~i#SKKS9LqzbBR5LU8Y|e5AUvoGy6XDG| z2*DuebGs7UMr~)QwY>Gg4ob)-w^lkui$Lu5G*6Q@{s%CW z4i_Q0ti*dnT6nCmy_ICXD3YZ1c=c;A6g3I!E*{I%^UN$_r$%r&WsXuj6X0AJkP!{% z5i$)0Cfe@geK-F$+OHx6V~h$L8cTXBj%H@!V><2&xQ3;0MFf=DvmVlo73FIMM_gHd zqt&{h0t1Z8A_{z}OZ~Lx%zLEDT(z>|qLf3cHJ$f1{3P0mln1A*g{YS+mEdFF`L~y?<)+rH=@4y516C|?^rN=j6IZzTj1GwH-CU)vWYNIWKuNb&H+ z5ZE?!~w(L9q{3^QTqFKq5ufE#7zs%U%nV%~) zwHbX%-$j+u-0+B6D5Vl`Fs-Py91l_Qj>e&i8=zXICUG(ImBM}p;RN`D^#vD4@_1|c zUxrN;@`9x=QDJ@VzGR445VigvV7cGL8#PXQ3%zJH_9!^&-tlM*3Sc6#R zL!wwznB>-{WJekR!3FxWIoGY3*UZCh=Y9FR|3vN12cCxrNsuQ+RyHGaj4*^pSG-q- ztoRWGsO@@SAf65=1RZ|IPM_r_3j7}|*-_s2bI0OC_*pT@J`01gCY4^D+J~IYQ%7Q_ za}hG-=&?S7Gxc0`Jkl_6J^fY}^aw>9&8IXU(J$!hr3gKUY0Qv|x^NE|Sp3ug?gD`vU#SL>uJLCD+*US5%d8%l#ugF}I4*=- zwQ;Mqk4kNzWHTylBRpumM(N1K=Z@ z+s(OgyrPn8ETd8&!HQr=J$xMT!`io>mZX$KEX6*2d%(1eqj3mb6m!ljewJ3pJgIc^ zDe(C00K1@DwuOAIA@ZWJG;P#=)8KhDS!%HN{wV#UjymD5swyIJeqWkc!=I~l5Cg4W zCG|dizp@rOMYsUbUp}{<7cY#9Hn$ZH{Kzt(Ui?3FoD$p66Q>ltc!~sk6%M2Vf z3P%WJQ&57!mR6h={By(Xo~4fCjaah0W4P4)X}vr!U5Xkqa9Ab|%rs`Wfhb@7eVj}C zo#+=?r+KzSxF>t7zv$}x_m&4U2D{Bn(engmA;Hfsv&FBv2ly>PaLgFu0kBrE5D1$P zX`^1OgXv}N86pCmjAWMUvO&5NPG za(_N!QF-#&xzZH1t}YeX~50ySiyfq3A;FR&HjESn#14moE?V0*1Pt3jmSTvhL3;V`!$j`yhY|NH7p*;Pl)<0$@yM*8 zFj`uL%k45@yV}fK9}%t&LPfekob8P|w2*wf^T~yy9XQC`wiu&EIz+szm=bS6+e@!_ zD-d%USQoc*pL^GwO?D-a^Zp6Y$gx^0=vY?1=y|db@!4Pa8zK~nTtdqOf~^)93s{jrxJh$e6}-F8?ho!UKhaxgOc1fHo^$4b zN-*!Xe;S_42rc0LTJ-JX-^e*p8f#*0m=|I4UPHP@GR?GS6`Ad4)oBJ#Rzf_@?&`7E zz4cqV&eAcs=Z}|Q`5=Dk9$DM)o5*1Dzxb%>Uf@nT0P3sGq!x-%rEHtbd<$w z88hnMmBvoV5=k*#Y^>XCx5;g|AO~XlHZm7uB%;a`Lsr{r~umC%T1%Fb-;5{ z0HJY0fCmqLHcJav*nb^y4-xGt9mp0VqrHHTR=Nv1-fULR=P)M*;rfpUX2+vXgt3*q zXTwLodubmK!4qOI|G?8n@2EaLt01xwhvXExo>n&11D*T;>&8Wh+F!|z(TQhEL&xOG zm>dL>1Kl4y=8TKSYCez%zC`n&SlFD-{;lM7_wWeLr1;VGWT<4nyM89A*37a|{*U9o zO0MNf;10KrQd|Bnjms2KZ5N98UJ#EgOuW`nK6!u3VH~U9!4E`#rK2w zhqubVx0>fXdw%Ar&x?eHPOE>rsG*LeYuSJYEYb6FQh2aP%<8)k+}u~-L*g4u2fO5~ zinkQ^ScN83;yR<&WqP98tYhqnrOE7>;`E}yec$n#Ch{gQ2IC#kl%@HG(Hi8!+QQ;6 z_5d_(u%IQJ@#n^#hm@2^Wx!a<|9ZEf6N4or{;p6h#VK$fV8B(r^hAmUz<@Gwj+v_w@TCOMHShePlkb6iRl^`6ZE_^TbVq-GEmz(k>W=!2N4yyc zAk`5nfaoL6C0gmJ;a@7#gHIr9_nOk{YDDyQLfH=HvSdGv_+bK5MVFZhZ_P zQ!no;9l^1Nggh?byM0hubkH}R@DWG;NwBf=rpjPRBT;;A4HFg{YEga98tYf#!2**i zq*EdfHwn(Splpk3W_@gEr>5XvX|k*GA`1WpTsaVe#+Lkrlorl{M^%OZlmA{nRh$lJ z&y->uwVcE}N8FT23E5+%|-cb*WiGDLmgl8?Jpok z%cS3^O(>djmKRa=wfd`h?=F`zubeFo|I~lN#d znD9Wv&S>g4IV?c&G7)Hz1;fL3YxN;(nV4?qZdTgq!O5c+qu>2W{YS~XX;@Ifh!>qX zs5e7#^Z2AX4KDaOY1>U%S278a{SmIW2>iA=qA?6pdVE*{JB@QRAm<)PJibRmOH-UOEAK)D0F2CL};CGNKL`xSm3?0M+tI0AfyGQ=Z>5FES0brFtd0 zZ<#t&rXqQ$cC9wQ2CMpRQprV_@5*?LX=DC@siYmdja8UI!KcnbuYk=MW*v}IHM|de zim5r#|3Dl=Y$E-O5BAPVnby-(*}Er+!jSBFc7_C{SqRq2Q~Q&^p|((1vMNop`;<~% zohgq~-acwc!F!DZ6zCB6@!P{J|dU(Ygcd`T6b;za~59GU_H^jG-LB{Fli& z*e%J00l`CaF!{cCGlVgT8K^4mpNb*Wb>JO88yR+UPc6k~k?p^D_D;kE<3Zl{!zT~B<29Jst*xa39F^zpMo`U`kCO{^ z!o+mKihKn0mjSOn4`m4w$~BX7Tiu+}yueA`qrN5D!&;KE3xAf5L3uuoqmdd@xkD5d z3U^LQsdT=9eDMq%OL&jTigx$`XdX!fc+vqSYY@Vm6}_JP*-!%Diw(ODE6(BaiqvUc zz0r|eS$#IR99k#bJ=bwHq#%&8Qt`*_MRq@_o3hw;yeHtHU(VXvT5&L)yXi$Mi)na%=4Dke z>>>=XRPZE$^Izp-;V~f+rfC5xOc=|}Dr9YwO|=2@1L4|IR7JS?e!t2>DGxj*ZR`sGv||$s7p8ZPZj@5v&JXu0U&&qr2CVY zFE_icQL>-#qvQ%j!X;bt&Q~$_?YWIY05FTy9M%%9_Z`{sKfvhX{$^=1MA79u8ZpLcjUE zE9tD8zcg+&V&1N=1l-YQRT_&|x{mKAoEm?(G4odx*VfjOs|Bd|J>31+$8U@-uD7<6 znLPnjaQnSSXs_YMcAdQ1|0A{2{`3if*D|eo&UMh~??+~CzUz9*$$nlpP*qiRqJ2WU z^KydU0eyfjE$|DqRTY>s%uZnSTGamOYhC|Y`>b6HO!bAKhW9TWGBl2XF~pB_?_=oZ z-Gyf2>HIB6e-8Xjp{rLRR0B?=OEWQLFMpI{z25*&%}&lj{$4NgMYi+vhZLA37~@nB*9J>I@KgyBKIuli0#28#)LkDn2pvS zG;^=-el9-5*LNqfqFJq0?YQ8o%K;_|Ab|CKFR3cEgb;!aWV*LoxhN`jWeb4Pt<6z% z31O?+j7If;{%p|CGyigl@=8ML1vm4W2pc2hBf`jkg>vu#7BGX6=lNp=1U}Gxe)~l6 z%)Kal|NH*X2${Nl``ZhW?-2S#+5zwzcgL(rD)wb|0KUn|&wHHTl;Bgtn1`V4=ZoRC ztDN-O3X8S0Q1*_0txd)YDX(IBE4C<0UoruO5$0jen98D3=e|zDtvQ7yNk{3C-zRH*)=EC9p3iR0#V*?eEw+G<@WyV`9|_1m)t%|7N)yYSoh*R4M!IFHY0 zls(^b-eDjFYd6@w80w(_Cp7pYl)`fE!La8Zmx^WKM~Yy7kE3FCwzA>!rfJxuzsW3b zH3$k?Jb2aF*~wE;QBE~7nfD?~bn>pBh5EeV)#Al&_+&_hUNKTuyjleB?T&F4eW6jC zVQvyrkmk<1m>M$MJhNDQpxsT%S-FuanVk=TAgYyh#X`NMR;NeT_2p!9-@T5-fc z;GP@pkRiaB(59%&-4Xr?=LdP?rn8PiR=Q_a61e}P`H!chWuh0ut3=02?I+p9iNse* ze~U)4oAtUeNr`N5f^Lsm+zIUdHVax7NmD#Ur0-PEPUN!IXXlK*71X3Hq&vxfbwZ zcs5r9Rk(p(ag}cRryG4Y`LRW(d#{!{?Ro#bQdj#X@VC{|$5L=*c4`0GbvOj8g*PN0E^1C9+H?I`l~DkvDtY5?K5Sy@7ZI61cvp-2TZ!9M zYwpceRv-1${?&Mtg}jD1K9%nJFtlANyYQB?UO+P%H)8uPZza}RY!yqN*)({Cs}c{5 z16u8Iq@-hUDItq=-H>Up1nnEe)nVM3Z~Um z9bQSo53l!0J&pq*fEMp;A8r>t3`Cpzo5=1{ijD-w3k!Y<_R6-nxZJSa%T$V+$#d2fO4Jh|JLsxti}Rv8L5k1LjPkbKOfy$+}3?a z*TuX#+EA1P&h!A~DnJ$QCQ1u%#{k{nFLa2|0xLPz4@aL^+MWyPz^Ax{{x@*#Fa9#$ zCf`4(Lb4FKlA%iV*Ri)Lc<&eIRy)_19@cd&50rXOPo@BwtyCyZCP^# z=C?4gQ_~6&d}6@PigzqFn-=qhlW;xFkRy$OdoCuzJ%ce8IVrVeZ!C04JY`bo-^M0B zC)PiEgXNX$qeAfb0U7r9tLQQfG#!=IJWqV<*%*p$`_prj+{TR^y-xwj;$w;y*x8A&3)#gI7c9qyJWOt;uM<@wH89Jt50)39mf*G!gs) z3pH-G9TD=!?tN$=$$`^3mFK$KZDanMY=I0tNBJER=7fi>qYlhox}mPPD2r=0-#L&v zb8|aKHY!wF8lDcDBU?j(ugOjHjFq3ytt4&@h7v2v0>NX9mqlx1Y+?o^54S-0rdXYF!AFk9?Bt|8gyte+=bPugqpS9b7AKQMo=va;tsJQ@dvr>FK z*h!rgS?u4HgUXlPbA}NSKj!NdGNvCdZEs!dxwiwilEB+%L9V++;#IFX0CBsjUH))S ztuxa!COo?NY$4a)cJ;2g?JBdV>(>MCF`t94-M1%A&S=npw!E|S*cwN|yd!5c=9Zen z^7|(bit+3kQ3{cd2~Wh0yJLT}<5(%F7>xTdU_c{lNMmjf6WZO5ELxv-W_xGEGXX7u z9l6|&x|XUg2B4&*iBz^!*P4P1HwVO@UPGW?E^j$-#RXS~)vB82SS6m%zbB)xPIIMQ z2Y;v5jO>sHjF>|K;8_iL2#4t-!`O|~4{bAmfr|2D5$Q2Sg5Shh6YXRXGa5x=`v;`; zWGJlfERcWT&@uZ+oAitEqip311qn_LyZ1k*7^v2{<*{Dg0T>2}g1OZ{hsF~gk>hMt z!JtZ3NbU`2$EXP@4vk7K5fhcVhBgiBd4Bbj|KH5fm%Ho6A92-QRZMaFx7(oC4tChN z3t)&Z$L{EB;)|xtu(uq37PRN0286qWDjRn`c;H_Pl>VQx-En9Fit{}mup6V_;)vaI zU-G^K%ERI#&PZNn6HeZ?%O|8 zbkA8nPH_o^@W0Qb?2jU@+x6cqr(3Ih%x6!G91#gwr){D#P3r@qN+UZIj4IghRSJfM@n6KR2Dd(fdC6exBg$Apo7S;uW_|NH4#mEaUt;rXQ&^;-CF%^wY} z%eCfcxN?B;-z-YYIe6`0S~x>0AVA{h`+s7Tg^?F(azsZTzI&Gt0a`EUBII-bT=dUQ zWtFR@wqXgtq`Z^xu5LMU&j|lm*H&nP;W;=r3e$@=F&lC-l5y)Zpp?deZ@8YGrIeZv zg}`gzV>krlxe!=^-{UD3CW17yiLHe=g*-d*9kK%H*k6V&w&aJRw!gc7Ix2a^G@LfY zF#wz1o~YL@0ZCw!8lo(Ox5X+%h0G}Y5_SYD zLSCMss8rD3+lVS`7atKRZvX6c^9{l;9N4vhcUP(NJ-W#2X}QJW3$QM_wje65-t2uo zFT$m%c_SQReJtwkxZJH`@!zR`zD>8z=PklN;yejHr0FjK&ctG^@k@L~GZ5%do zHgVL|Aqo7FR2ATe%?H+Bx1x2wHbDwjjCe8y6WK5Ou^Hg4c-^-cbzPK^mu!4lLCKYB zbC?i>el+&&BBY<1qVzFHdHZMWq!u1_?vWYu{ad6O(@F{l!|{#}uR16g$P((mnW<;U zzIjM(bNeerO$UOyv8+Zs#pA&{)qzqHSSniBNU6cC%2?Y4@aP~zze}wqJw3epPsR zs|-w#$CpvLIJeYa+R&m*JN1VE@}bDzB0F9Uv_K@Zdp8fkoYRk>hwb8)6Qt1551PtL}2K)flDR5aXA{c@$Y7_Rs>`Tn9SsVt)E{9v zOiRK~%hOGw@#3y~sqCES1yvrI<}D^275PO)a;eTJ&yLyNPJ2(&qyx%`Txk<@SG12W z@+JrQCQI$(ad8QYS2z<;D}6Z?;a~w0arui*10>_Y@7_fMfCwtX(*>3U0HH$QUW|I( z&89!;0pSJdW`pV)HGFd!wGyi>$ZLD!6?4%r95`n4P$yg#a>Br>IxU6hF{m)sKv@HrROaDEHhq>_=G?v zfDc=>=d1u(^m_roSo2LnnkzTiUi6$G-1*Y!cv;=_jK|HeRC>WagC&vOb zhYj*XpU!+{5x};gddFsA;~9@Dj{y=_4%+hkeA)k{@;O1mMEG$S0~gO1{>%)*_*zFg z{OlFV#)s9tNB>^`+x-c1(EY!R7`7P~*vZZUh^~lXkXKIt;L&$2LwF*%RB!L*}ctVd5fr=Z^$L!d#@m?|SzYe4xL5G4cypiUJzH;{w zf3-L5W0*UC`R(jJ^>DEFR#(YsqKF#4e7jFaZtGi%e^fB8lzO*p1k(nn3$M@jMj2~? zUnWAq*C2Q$Kt_aw&{Y-Z1Kbyb0g;|hf^k%3PBX?VB`eq8Rd<*9%VP}NJ|pqq&Xg?F zF*FdN!**EIaB((q@4~P-)s3~#eetAmkmp(}3i%r9i7@a+MwWGq5_KqWYR~}+!0U0n zY`%{72PykDiJrpjre4hUJ2{+h+!RRa?3G%=ep7eYwaFJRInzuIfjGRdfwncSeGHRq zBz4f*UB2QxEpV#TiRpUQwV{H5;E})yfCsrGOptsE27vcN2$cqiYC>hvdAI^ewm)0k zS(on0Tb%6#k5*Sd?zJ}2)#5cowbS!9DGQI;(Qa>&jU$yrj<_#4XYa=vemqg>Q*d#= zy13X~&SQMnf87nihwNV%aS$Y_PiVAAPzZ6)C)iV2|F+rL zJzr;N3cKK!;$z2`KmmAp8CmN;b7UaEpgw1%Vb_dvFO-j9b^o}_SewfwY2$sXisj56 zKbpU{(YiBe#i>Trjc|Jk&pQqXR0Zwj_cuY`bkXqS#@wB{7V{<`K~zxr|Kom(f}nPL zoyZ3oLDm8zSXHo*EEG+X!Nt4AOmHt5Vu-i#lLQGINf84+Z(x7{ujARBsPulSGH1na z)#6Wb{^bic+J+^s+{z=zJ$+%mnV(ylw-3&zD;s9S_h>2LCzD26U&&1kqo?(jbh+-e zmU;(bHD(A2zsbQ+_cb|deswEZLY)#=Pb;y=PW@2X?mZ4py)?sJ$W>J^c?-TcdD%_c~<@hdZ<6_sN*rAkIa9g*8phuq!4Lbe@EMb5iCg9)55Y@e@7o>-+g03Go_)O#S#b!rBBR4^q|g8M+l0Ym~Y8 zRPW93q%-|Ie>+MiQN@4nrQo~9qnf1!kkw!x?Py_j5P{ELd#PxNs*$hYRacNVOZ#u;`!M2d4u$mLE8hiw?Bo)yMBNy-sODxrNC1EEHljRGXG^fkbyp%?jbV| zRFn;~!(T78rBPTRdosSMRqtqF?S_FnS?$?f+%(CHyuWo6T#AXjR` z7y@R+gKNO?;JDv(O+Zi-II5xopk7^|5v*}4{4MB?(fo^C!^5tDBkv_i!CT)l_A>U!kI3KBTU&Vx zg^h@%y$c4L@Wcgaj>%{Ihl6QiHX*?dt)kb}1JnWAVb)w1M110^5EyhbT|xDXReU?x zbgTCHf^ii06;P}Ow}B`HF2*7ndZqSVfF%%KXF^M2EPAyUAG^Nh=-}Al@D}8` z)B;&z6Y@e~hh~5MK>quaCbDqHzJOMU58{LsB#>`@SILZcCA}|E8q44Ob?8ejWgq&> z^@s6BT-r2etMWhDb#S<{!_XVOPwZ8$OB?4K(G8m_=4zJO^{FTZH z1r9r|kMY5(Ob}T8yN(p|N<8>yGyi%}A=QIRV%j5%g*(q+i?5`%{(_M&;th$Z!i}g@ z*yXfup3MhqMaXpEn-6t=^757vq;(8?GJPq%#UU`3J&6TZ^NRX9ihEVU{jYU z3lnnztW-Pzeq)RVLqZKOB{@z4L-!@506!(L)8#&kF2>LWmQi-ak`=PCItqeiG{=AM zA+ho5@0H?Wyi9>4+SmP9Ff&SS6gWWGBL?S0RPJN$4vx4!FyZjzD%ia#@B*N5lL6!; zsA&MJ)@(1lRYk_0o?Zg5XV-7D5&&-Dl~XyljnKNF5i2K5+_$@z>~(WMpyiW&)OfSd zXX7g&ury3P1cr%%g8|RAh=$Vs5Y4!k89)*3y?LoAPi(A8h^o@Ujx;xSr(&(*O9JcL zC#%=-WsL<5$lc1Z4YkSkAMJ4RXl0cNZ45rS&CerAJDXmV$p&p=9l-;%`O@hayR(Xt zb0g>D3XS(Dn5Pzk1fw5q^hu)KR8x6$i9SS;*qTC#=S<5-jds@>1he>?cy)gpgRUn& zdfhSaLEbHAt&H%Bdj*l=w5>)3YqrT>341l35}~ z{lM&U=5^{VW%-_W0JdR&cy{pb=AZ@fHAOc7g##0FjimtKNC~xbpVge!=!HA^IX1TO zw8MR2Fl;8^E0U?Pp?_X|c)yY0w52{{XU0~iy@ROw&duq=w+77qY4=T&uo z_Vv|;Ol2%Pvz<%L`uzLAH)CPJK+6bNwLKAe?WHe3PlD}bb4qJ14#_vN!LD>Tb{A#Rf0nh4)5=!U8Qn_*JTAbg!Eo^;!r|`=-go&ncoSu!#CKkfKBL4( z_UG%C-aa21xYG2#(AA7k9u7~ZN|CLV1f*2~xdX8d-j2*xITW8z8g$kS1B!pzzbPpH zr{vKsB4s#C}Pd<8rhjz-gY+eqr~IsE=bY#5q=x~ zH)}k?U0tH#_=J}~GdUo=Fcb34a=h23aD(A_UZVa&)P4`x9GB7T>!$sVKd&QHhqv}GCPdw?$nx# z;riK&G(4NkDERZLOg>cNd-}ubfP-Zm_$#3)=#-hOm?)3P#7<)6`r*y&|I2^j$t#yy zTU(3QOR!Ck!;-d}oEP2q>uf`Bn*=!x7En*btl0mOLSqaAG`D7wW^}f~O41)g03xlB z^WxnbawYg+iY63t2kX`z8|@^w(0Ena#Y*FQ>1;`ATq!wEp0bmY`-NMBDA(7G_X<+y z40{2)pQyq^*mF-5KmuvkLT)@21u@%X3Sf9PqBu~6+yn!0bA$8>&4w5S4t5riNU?nT zi2-=>n6Km`YaeJ4nOk?ApKw0>@Kto2z}GIqU8GUGhBl6viSg~i#jnMiJUsYD1cfsO zhMD&_JE_X|Czw4L+=}^0sKMQRLHZ5f&CFlf5(zeQ*`4L)9jZE^C6>0?xR~e6q=aj~ zsfuf@scihQHSo2-iI5^N6iRhKfl7%rYdZ zFB?_+7v0cD1_KB>K!X4zh~CidO@l&P=2%f`c6apFGv_P**}}C2n0*q`<}}Z8p+R1s(D6{SQ1UZvZ!0Fr4=f+3 zBl3z(YUZWry}xko{@v11R~^cvQF`^(%&Bv(!X^wqY^ap5Mdm#QaE<9V z&}rDI6rc!|g$~EpWk7N(V3QXKG@Xi;mtd=Lfqjflph!xO>VEi(k4ett9?9k?7}zHT zLuc3u@AAWlQ**4&o*|TL=38!<_oD~F&IJ+r%;(`BpPvS@GvFq9YYLCLAU4>N=* zo%5`cQmeho@b2B1Q`+mbr~aTv1eQR;Kufe0`FG1Z#Rlm77z>Ynj`Z?k_}0+w^T=M! zjsI`!VJKtYaGDqU^xoi!ZUOxKx6bEUIy$84U1PxM>nqd|otdDUuxlD>*T&z!wdIWA z8Lt_^EA+M{e{?C~=lk*b%=G|3?}P@7q}>cxWBv^@A&ZUU0!4n33;F1wJt3a}@Ifu| zf6@AgrQi)>l8oLdyxB}Jogu>d{$}4L$_^pffG6}Cip_Hmg#a`rG=M6cn;{zytQv8D z`5%%ACM@|BfASM9I>LtZ3oNRCW>t(9IKl(KUeO7Lr;7y)f`@(mk+eyB)*Y;!4jFoN$*RxGD`2NR+STX@B3O7vGo## z7}xhd{;q4D-n;Uc9zS($K1R&<-`BYK=bUTc6G~_EE3f9bXLpO=r++k_SdyNd${^}O z$|cnM*v~Y~P52b;Yt2xoZi2EIHPQi&^98Bv0bQ%DOn{q#KdK%Kcu~Bwi@b978oY97 zy|VN_t}+80*sHfJ;m<#!D#9NlQ>p6AJU`p$z5+O;05J^+2tjSB07BTS^O9X;`ZUoh z^}|(;=2PKMrN3U@)ta@Y_q2W%#VDB)V+D zFWT)PE?5&9*m!)#{WmO+7O;^vc>${N)x$5NA+;Hw>6P*dCSCJUWoEX2Z)fbBQi#e7 zTS-oS-v~x;mP3OtrwVvcx6^D~H{)qwH0lANC=1ZHP7kPDC8#UG9@U&Ae=OQ*r{=!K zTuEg1P3I*&2^nD0V~kEfQ)YU`_~kxCI#8P#q!0c=k$aQQdPT(XXQs^N=cJc&Wchb< z=iDYPGS0mmd))Sd+n;Rj|F&OcX2zgSfl?0<#S`iO)j4OQod9Awi_j)G<;o9bD z^og>MIS&Gp;QiY5?ofy=7WCcLzLtcYbmF6IK)FlRxb^{||%!#Xvzs zG`WiTBzV4aV8k6*>*X#V&wg`-!=Qg$;I-B{lBi8R&ZHVS<@a4k(-RTuM1~ApPpQFXA<;5$otkW*s z5I|L9b;zA|-&7iVgT9Xv+pRr3Z)wfmrqZh}ndA~?At9OE=U5S#qX!TkV~p|`xN$MSReP?6H~ z@pAutci9Lv+lkWzfvMciOnyKfe`FoVW93f%Q@{*)N5OJ(vbL(tf3qw0ufT~?fVX}W zy#qAvmh#P%USpBqTuOvf6=mda#veZ+dN51Nw#}So8sM1L`rpFw#&SG~Pepxx(R&|e zhMh(d#Vf21{+9eVt+xx0V;2Snm;)14L*}qZB$epULGr5zI8g8) za;^Qt3Df~*T-M7rEQtr8$Vp;z*&G6c|WrM&S z5q*^Mb55fRv94AGiuz6j0%D8|JUNngqjdIJ!K=*T)ks&1fPe@OUf~XWf^8G;M zO1=pK!#v_E*FV;7_3@D0)pYv{8+n4alAHyAUcQjDc%Z8ps~msKahRW2 z*#J|plUSTD-XP$}46VE`bLp*YZOYvd4htwd_ur#WPy>e`kAscS9>4<=I=be7(oG!L zgxEKUMLrEc1bTlW7D(=jRTOdd`v!O#Q;q)<9#;r;3l96Ib7$+}^Cj7bHeM181v<5S z-Zh*w`Q2XnRCr;lFm5*A4>mtFkNU3lELW9OBHO%&_d)V=o!oq*cTw9pvOcJ4h#P3> z$0k7oY`ccP2tWyMZ9i*obl&@K{>+?G1cGj*^bf z&5bVe>Ms94sKp6+e&Ro=Zk|2+YQb3Z{iZh9BE{rFmR`ME38DF5YMoX2AY&A^?T3;2 z*ONFbZ5g2pmmt40@2~0jvG}UQY!2EXpP_vbD7=l3o@2+ba!z z;^o*`fu0czmM0NvyTd-04R4z zIrFg;rGge6hU;y}7iJPfB&$DC%{x7F=#01ISi1k!YIhkAfROZQB+pY|9Tjv~`|d zL<|pvl8PKi(MKjgz~9*p{zqD0%C6fcGjp-Nds4=)`~e%7Ljw!wAkii1qrGUR4j6zD zCc@g_F@P39jtY~-wR(WmT_{S(!!M3b;&=nop84Bcddanr<-dmP^xmOwq^oluWH&TB zY8qB7wY8esA0D3=T}$dv?D}};B;(oS-;SFCcfj2qklhyUMt|R;~y!IA0F?y6c*gw zWqKRzTQej+Kb@6wn7O~-l_O61j+6Tvx8S6k|DXB} zL0)dsxg6w%j?koNJw4VJlt>b?stO5=4jqOLfoTS+vghJDVj4dN>Q8kO-uwtCdSEeb zvcQM0uj{}LFlC260MvfBI#aK1yl&FQl-C#=srw~3f$a7ml`9QR+>3E`sL#9Kx?eEH zwC6#AJC;f&pOJ`2O={JS75)8Le(PU^*yxq!`VW%P0ca-!XHV*kEw4*i{-T#d|5TzJ zYbb%9d|SpG#imk=e64ef7#PwJD&Cs8P*72#Tar7vV-GE*7ZT-gImA9X*y^hiCTKUf zdtsVakZUF6^q41KESfD5dIK*>yzzg_vlvqFJmxxkDR^I!^Z}<=N4d0dvXy)0}f0fyn4NhTv&&x8SWT(-~pH`_Hf9aQh z61`Psx$?*2FCyTvegbT@bc;Vf1UiAz!~H+s26*(w9?4q88I0X8?OpGZI=(F$3?R)( zu9NV}LbfFamcPzYd#-oAA^}>c{>W+o@DLqjj}HUA|L}9r89~4BR$e!`dv4%h@Uwm) z$Nf~^$FZ=^fAE;qTXNQYzSYn>u#tRw@3`9RR6?=>Q9U0A+*>7W-|G4#EPl<=P zF#Yy(@@!6z`24=zG$W3X!B?k~8K=kc3A;_63hI=8*u8Ddxovp!3#DGxU2LIQlgk!U zyMmpWc`fnvIr*n+{9Rjop$-dfUFeu+LZzdfSY1TFwMK$2k_+j2 z-p)UIMm94|bkXAC;{FQJkWwNNIN_oggnBHUdl6@=eSieem;3`p!^DK9a|<;!y!33v zx}m-JQ%$yMKz;VSYrjnUlv_9{)io$%T>tt{p}p&FH>6%Z7xJ|%X?c2EL4EeuweW`t zf%Q*fd8ToBR@?KR1>UW`Qd~d9OZeegqj|?XHRM4@at@S~)--rQs8@@syAC#lR%W6d z9auw_i*=oNSQd|Qle|)kMA-GVAq&;X;r~tgE4{Pe!2rqcWu`;$X=7qumsx}TD1S+C z&k%oC>*!JT3`$6>qJlDr@+EKZRcZr%IX&g*vF6@ZZ>!8uAy8|7f(L_sLBk2KJjqK- z4-gVi@_(Nzn&gp*KNE1`vjcLx{FUOzMMaFh_Vr0++jk@8?{oXM8XNjQ^*3Hkh(Qj* z=fRd29|R8Lx&kXiph=TNmEuCcaOBCiI)hdbKCAv_0RvZCYW+lS(32bTS4>$=d1$tY zC*!}UkM)DB@vwB^x2}ueY*NWJH-hPAa0JzjaFqNr!o-DfT;9g+$bQe^sF46YBveS- zYCpIQZ@(OUT-AZeP_}snpNM%2h2hQ5LgD~Jdnxp|*T;naWgw4wysuND>7-80v{qa8 zNn!;^9haybaC+nc>0Cr*vuqV)jwGwoHx1N?>ysyFA;;O#Dc+4@OxJP&+Z9j`kOT?j zy^Bh5grLY*W-ORfHQba0SZd@~cybeU@K~Xd{7F&vfk%Z#*;JILx?0$(0%gP>bHQac zfvYu2sPz}*0VW>X)e^#i=#{|STSd93(Df8OnDM!feXhUS6-apgm6AKksR-RW5vMDL z735~F=3WXJ>TI73t2}k-OHTEt8pQ*T zl$siYIe-YLL7MIPxSc-mYr35f+>Gz9AY_7*zm4G1oXDCOULR z&C+bSwgdosq7(|if&6j^Ik}m+#QrUIgArQ-^B*mcr+qq6Jh)I+>5Cn|I&poATo@CO zEzsg2l>ehkK}170ebPalJG3K{Pp=4LrpuwVOcp%MziRKrk8V2J%f`0h&gLU?LG|YP z;40wn+VT;ako{L|ah&AUgK{i?y7rr)m>%^iRzW^=noQ;1AwR49TRCoxtGS^9ol=ui z^B!CcB)cwW^*8=LoT8VD=r<_2gc|{9e?fYyOVn`8K3>7pY7m%H1`KbIN2260gxyTl z08^&_h#`P?BpG}c)#P(v;Km`wPaOx4v`FAuz{K#!H{*V`%zOg6Skt1D!wR|VKkXt@1g9pJ zqHKlf?#^`OHD%BmMUBcCH=P1^v3=mT)OZ)vFQtNGuU_-F52Cq~rq-sEq8`wZiH4BZ zHc!G*;49*=+V)xb-CTOlS0Qu`-<+`MDp%#%<(!ZMDfV24 zJ9H%O75b%dMCd}TCB6>=QN9XKu6EC@XrN^GRXYm{{<fA&nHq}G&m*2mC6f{Gp5VE@~c+MH@hzCyJiSB%wyXT;P zs)j7(|G|sttfs{J?Db%&tKqT+3#rT?NE!ZM_TD%Rv5cL zkZA(W#GeKMk+$k+BXOb*PR|1aE!mzB?&(!~)*#Y9r&c=)a z(EvrXfX`4%$;80fAvp=~*Z*yUJm7~6y19s~=p%!J1&>I3kqVMe)%mQR3XvG^(>Z>P z7-KmSMEU*Gr7yL8UxEjmK8&YgKmlE}`WN_?;@N43@8NlX6PaI=R!PwCH&ire8FJF% z5|*4O0S$gfL#J0zpvX5t368?eW0TUN^38WxCDrnPr)2h=>{MHkPM4gpFi!yA_a=Pq z{W_=6ENq%~S&pG8;d+zf|GxHAF4#f2IuQAA8HYur@)Mh~LZC4t$mthPh~!J)w^1EW zLerx%tk47)$^egruZ_~O0tE0i)@7%7Kq*9s2B5LVES4R5g+l^zV~Ck1f1$@8TlpS^$4%N-Q^uphanRS;TThuD_;^ zB~$+1;FCN0vEcGr(1A|{DmZM8CQ+wBH20=BSV&Y%4KD`i?VgF>F){2^O!ncq7WJQz z-eYtbpZilH#={ro07V)Pd9uf> z10(yR@m`f=qg7xL9oMf0u)wI*Q9s}AEh5yFzJRq}K7Ysd<;diIqjI!h{CuohBESI3 zIr=FHCZ!Pi`*y}-Bxmh2Kri_umm_#0x90pa?aA2ha{tJ4>*yL9Y2{<|*nq$B$Sx%G z`Vn>Wd7*W-hrIkXH_@c8UdQ>B!-EDJWznA(pVSce^x{aJ+qb4RqZDur?VqhKL0bp2 zaa`x_BFeohH#!B})~ArcLQUsd_IurNK1-r9)KU1k3?p3Z2JjNNO}aRVs)E-T8Vq%_ zZC$i=7bghEOF&AL9gfe4?gjZGjhUzm8WF*H34Duuh9d)WGGNuj`;WQ+hM96KkE+9V ztbf++hA$Ja>hDRTOMwpGpQgK3_*mqcV&#BDFU&vCbNnSDmlcgm`4=w#FCq>wwR4G_ zk(W|ejE}&d_gU&Qv3hDma0d`5JX&_*W|5ONf%JmNu)%o}y z@ASxB|L{>pD@TNO;02FI6!APj(cn3a#wMN2XfV22@Jk)iy8-Y5%hGWY0D-<#gq}VK z$F_9UEmW@SiL{f>25Zwk4@Ax%BL6;DU*YoBe+Li5f)FO5BdBsy`@sV3$7r))eRY6L zj4MQ`jvRQBbHj82=eg_mZi1hiC8eAq!U7+U9`pExQ;4sgGb`+39rlGbxYiR`6$r8P zsN9wQTf_=Oc0#Ph1*;~QYNUaJ>^dcbxZ=2yCO~YoA}D4=T7;j#No9nLf#a~uf8_IE zMkU5?3!;5I4oXT3wRz=)853N0qfd(`_iaAT&Zvjytvha`Zk|tmJ3d(__T33z>`ovQ z7Pfk_dFhVW=&!C`Av37Nym@9al2Sqx`=+^?u)LD8x-#hCK((Sqs>bd(pZhPIGRQ)z zA9m;$)7`qZB|V_=lBiAu@hqJQT`49xOc>e6 zY}z=b0|LvFJaU4V{9++O#{FWXi=@(Fog*Ibd?U9B!b6w&v>;D^74=7%T^dylGFtpc zjrS8x`DB`Re>PH`ycfT7`F}y?(EZ?}wvs3%sia#%12k&6_f!egzfH)7M~1(jugbK- z`*JvK!*NL?`D42o3{wjNp*Q}rj2KC1h~}nw*Np)j;F_EsjvZuN!I=K>gRp?-YqKPx z;Li^}{>~LN%wt_eD7+KiV|6}N^!lLS;Fnnehy1QZMlI|W#x=~6}Qb0gL zkQy3P8YDy*X{41BiJ|#v0qO4UW`LjDvoFrge%72!;kNCy%a#x-D*|_uS)3AsHXF7X?)SGu&$? z#aXg!C9HGw9{o)X5V3|H86rl(L48#_EYQEFP*RQY%d=lEgUUwDcYpi7{xqC40D;;u z2^Uny_{m-$lr!I&E~nmp7-Mw$<9eBKk{KE4pP>RtblFVG=Y*;N`zNz)&!%&b;Y`Tj`#N#c^bhX^^0ZaN7*1=; zLqH%`7E--Kfv~=BTZTy8f<%`hb}9u>w&U-;C(2cgP&8Q&)t`nAR>Uln9FB7u_ zi*L&Q;{jxw1q*g*7TLtUh9GDBU4|<>o4>G@0rXI(X8?Jri=jdaz%p(mu)GjN;_?`V zgMM0E(!_&j_%wHM@kuZ;0LsvrZxgG`rFBb zEfUb+x3mA4l|gLG1Vk_aMToVs0l{3>OGS6HF-Cc?51isLKoBBC!~}*`gd9o!_}dG4 zh#7^n9(z=Mh{fQAz!I*0T(rRC9_}m=X6@47#LHejFh_`-7drh%m2+aw%bD~AV~8S$ zK##dIf*EiEc)oD#Y}?fbE1(-dpw}hT$bgNz9>UPa5D0pr@by3*2&Jjfl@dW^N!_pi z(c@6N8x58HHj~YeoHqvKM792RTm$rrSaj#*XN1b^X?#xjK-lhY-54MvG?G03nRb{!j(-k zgOCjhXN3Is5bh4!2$zUn266UiSlwVo003&n6%8z!y7|hgQyr})4Sa{EJ zNkF6uWi;kaWz^fRL56ml=od9$sX9H| za@N^v9klmYrg?-zd(s2ZN;&i;pQi8!!($%^2qFtupf>xlT`SNFc?rR*2l2<-*Jj;A zvSqq9ibcqX z+DYbqVo2yKG=Wj9G$9Hqmo(3z;mZK9YG`A-=lIpOcUx{bVhKv6X%PJZGdk z`Q}&j8Kog|mU@4AmUIVHex*S&li7jCAk~&w){?|X)#$aVABN^cKO{+Z(d&$J8A8{y zK*Po1K{)}8vbYzh)fnR66I<>YhyIrpA?>l&YEzJX=(wOGMU=B?XsgjHk|BZs8KpjHl2ujIN;<+fn{y= zj2i;CLr6m5zS6dLHEVlbU6)G150ZJ(j7l8We^Y3VhbZM03mj|dokLn-`y-UPND5tf z_bLU~vB_fHoO<_e{z$Z}i6V)8w_dYs!A!3UyHjwE&Tp)mioVi>aLDZ^9^`U{*0V8$^ ziBLC*S^G<5xbZ0^0lrUsfHYj0*d;FVu9w_sE5W5uMNZ(JLMJA~+okVM!!l}_XJwbg zicxAsz5LgcOXR5u6u1@C99S;Oj1CR{;|*zhw`b6+1-8f%WxsM#uPgR9>ixHiE!#2> z=Mzo)DF!V9Fv271l%w?1E(HpJ)Y+*&_{uX7sdSype**aKv5QE&QL1L^CAV_P(Cr|c zg?+~6N6p62ExNpzYlI^);HLhD*sU0RYd9am9qhZ99qNLn9TYR6g^xG*M_8|+po*U% z3WrG5uV87-c{f46`Ee&Vi}T+I$9*cPh%E`so|U-zH@zNOhpL&l^Gv)Rr(SzCTQO;v z*jft_`oGT4q2hMRQ3cU9p}`Lng**Z#Dc@D`#32vmZv}<~j^7*Plj8$Ya*7}nbOM3|-d&9?Aps>w<&M}NAufj%scVLc4k^kA2GC1LC~4e1hu zWdZeF#+p(R*7vL3IG1TR82JC~mdfEwEupqu^^TYF#I_0A!VRM8K-;xM$TzBIx0FtR zdnf=+HuyX~P__W@6Olu3M){G+0c4>ECGlvrZ$?V$+LM*)?`JC_c8*%jVLQYhai3D0 z*x#RPMPObp{D(mdIKFjq&sCcUy|-I?d0+e9rQDTFby`)}VgbE=Pw<@x?E?Km`Ul%; zdtPF;PAfMW4 zqm2rd_=!>UuQi)}9rqMq2}(Ia@bplO*7G16fSZ-cn+xJ`c-MdWwIxmZHNAy&WpDjr zd9AinYx9gTo68MXzmt(J4bU#%5|*xjV+Wx*Fx#7%qtV5OlAWCv^iXe5y{OMeH+&ml zd5P~gwv|3Xj3&|&TokJ%<8>SQ2nDhSWUE=z6fYeOFAJEbkfhajtdT$#hJhMJRusW# zG`U(ZmtFY%kNIu@iT z708(&aNdi{X{QzbcyqAT;T#om%Q*unp_J?`Eah2#3Kdl!MQc_j9r`_cf{Vf&<-vh- zLxZtDhnZyXLG>tng+cpr&201`&>QY7EwEE2yN)%oP$=)y0ZNnFSeM4?fnz2V2H-rj zeP9`pc4&^;=FbG&J#V>309=gg?-L?c;ZiQ>oC17myv+GTrOGvs8UREHPYT)!pK6*}(2rH|+=I(AE!{nNfpc^2BI~eLY=29#sL;2-;K~ zhD$VL9GJqe0DW2iI00;P^?kb<11=+N=*ADa;YgO1|5g6Q_4?@aYNR(?pEPC9IK%V7I!1CvGJX z>eb3l_PM2+5wjy&v(?Pwt-$eEX}edcs*GdxxUWmeOpwjEi9|H!nw!Z9X79^@ju86& z28GbM-4MYib1DC6#22g@8rGCEVY0b1Ca^+BkmgNbqCzM<;5P=j7#oERV|a z;D`_F7X@JOq`;88A!Ow{Ai>|%ARYa1ZZqD=p76KX_QTDyJoK!DAlueCFbU4K6= z{PEf0>EANF%x+-A#z0^ z>+X)|m!!w#Mf5Sce7WZ3KLs?bFd#XWl^D)Kvak6Ev)+A7v6|qT1IHTFh?W`&F4u>l zGI&J8)aM$G=+%tL$h_Y&V~lpd+F}E7=f5O#B%Wn*d;i0V!12Y%SJvkJ4(O;QeAy{r zSFtW$P5mVOUN?Z+6QKo|p~0SBM2@l|Kn#X4@o*1Olc@{pSRN}D^^{iG9%Ae?=$EMj zt7KXVG`<}p!6iz_XYqmx89yRdwlbuj&3Dm7Y-fDV!J&(jEd&V_#DH3MC|t zm@vo(qY^^SFD-w6sQdHhPjNA(dd|+>Pp_dzYc1S2WwMi;L=we05BKU~4mgiJV^_E)-Da=^Vd9;} z8?uCHjPn{(_@I_8d+@a8F!0)0x>AR;@2E&bs;B1j9=aCLB4*dOa~PD4Wk>6bVIL`r zQ5)?<@N<(Y>VPdfOCm`v?AbHs8d^W=RISDL0+@q`!4XM05r7!6|vaBk|$ITAZLp?QWp!c#x*ahMEFPSuk5G(*sk#(!m5wD=*1H# z{DbVaN*!^DD!FV?;_oqE*NX5YPCx!v+;%e_-CkrhNuBeh(2!;?t)=8IrKLDJ!>jj? zFi5Wuc)KR-o45d>qbIaYarB#Vj3bdVkwLP3N?_{)Na$cfB?Evz?x-aihVbp=r?t-6 z;NcZ?+Tay#F2ok!ro@s>L>hG*B=kB9S1-|Hb9@IwCQfs?Z2;moSgkHLsx`IB(>|_d z@L@)0b)yK!$;xjxi%G*T+5C#|)tWbrI|B;yB^CUMyU4ZqfL7;wU2nM4_bV?$n8b!k{Rg#?+qI; z*@gr@0*l#6Qc4ggw!+GlN(J38~%5%z81E78LVsLFfHRcKq&5M_KV7~^m=ifbH zH}QwD9Ku}BaS0jpkLw}pewf!P5%ys|xZs0t*UI5H z=>*b}P&!nG2PO`T?YB4RG87(;1t$sXNeOl>Fq=zvu$l7UPTt<@Oz&ApVjBdYz%|b3 z(LA3MUlCnB+PC77o3joy*K^8LMg_oWx1n)-hEA>avwz!9`>oS@Z#ISBS{oP(gbXHw zicZegTc4WX?`+U)fLL@}Re^4bzwgx|i1_^Q#X-ol|`{U?GFv?Ntmwl#Ndq^uVGGW}=PYV-BFpDieujmO}gnp)w zK%Qz%Ko*h=+9C~RXHOG81&kr+9X#fk+NzvxBO7m0Z!5!fnRIhZuoSo3?Xb#5^})6z zilx|aISR1e;`O(cU++iYz+rX}X|+7Kws4HcZ|9vAgUQI_iy$_fOCJoqjNn6Tl*^1? zRntKxX}fX+pz&$YHTjKUk`93z6Y!ify%gKJLMJr6Ccc7$t0t(Vh=b8l=!@h*U41O3 zbS3n3tw%my8?i14Gu9ajZ`5XC{GF57*4a4r*Bson)LWAqX)n+A!unLO0J}-Z%Zl>B z@*Tlq?)mTmI(`gl!-aNz@2*Ecoo{Uvz{fs%7 zVzikax@_NNQy?y-tK>u&9`qKi3elmvuI@rb*a|UlM*Wbx{C3q`?;NWAr>Lb6CZcV| zri8XK)Z|j*l!*xN#RJz3!j5m<6nGxbn?~><`Krk?fA7}#RV~Zs$N#R8;8cpmVDF_? z&AlFq!TB3GA=3$QMPdOi*fao#6H^TywLCs3|k-q>T?f8UUni{dRYmAMB)l7rf<@ffUd_<$&gP7@lr3AJN7K zv?m=Y1Em6CF#0GCfy7Og(7Ro~v$qgF>uM$NAw-KAtTs`Y;=^@?ykY4S8U$DyjzPHA zMmeJu*{xKh3UQJFb7O3w^eqQ}uX-pTGAW&DucZa&xCt5N9c5t)BYlz^LPZ-gLrhD2 zJRmI)@Q6uh?tD)usj4eru3apy2FkD@K?X4cffVh&7~pBiHcO>)xY$1PY-yHrTNB~4 zhfYl^#q9)=(DzT$^uI$sZlh>wP<3U;)~JUKYKoJuMbbaWsi;B3p36Oi_4!`hhP(r{#OEzYsm9>Hw?(|0&Q4*>X`g2t{m0X z%W}tm>kWB*IG$_MnDmUJ)UDmaS#RSD8You-d8NY}ELOR34+K^PMCQ;-|JEAfCp&GG z=d$i9gXRI>SNzqB5-N(`HH|pbXXI89gJZ9@q9ZVsSv7JQ6-u`9{z=zKcyatee`RJk z#>?{{{L?F&(6XZtLtRg>B%I27=`&{XMqAe%EYRB~f*ugDT{O)Q|g&-nT z*JB4Chvs7L(gWoC+aoLHYS5Nb%$0##J?p;GOzHRZ6kxcB0h4MoE=W%+Iu;;4cWi?{yQ99RNqh;X;=%z1?^$haXJ8Q}e%alp#hOUx%lXn#o-s#maVpAB{Sso5unc z&fwt0Ed?Tstalj@WTDgjjg`l8Q?sMfza>ywGupAQ%2;{;W?mE!sEyvxU2+S1) zwUEz`H#M48Pr*j0@k36iIfUZoaBu3*>*Kb1uS#8wi2yu`Tq?H96}1m`DV>zKI9uP@ ztNFgr5bCY1*@CTgKq#K1l+8L5p(!p5tnbt`Hm3?!+QphT0pCQZFMh2SYEnE}zVXB( zobvCs9V+ginQ-F96=vd{1j zhylbt0vYEQ%hE!4g8$GZ_ZLX^J7dG6JjRtO*EdvSKYaw2A;^kABm$(4GBtxR!aZfb z>{M{Cipmm*gg-7|3x6ZD$s0Ewyi$4XKefawz+T^M^`g5N;9+{|(lU3xPyAP_y>}sx z`Y=qm$;va&d(nsc@%M}Lg#jrRiN0>MM_woc1aOf<$_T;!o1=?jge))P4N9ESKZ8x@ zz9Hc7Gw0V(evp;{6|A@W&FVX!Nmnm&;6vvzHHjQpJyvQu&z3oI;Q%v8m1tLuC;-n_ zd@w`I&8*Xgn%!kA*!vIhKM6sT7z;knxNb`?w%p;EtO=I1h=2RftC*?NsKEZ>0}Cs5H|Dj{bnTL4JULz5xq)qH^d z`t?U@B4#-6oG~N<*nIW`|HG($FTBz`Z9cJRfnMB5Eo9+~Y>V(1i-GbskdG=EsdfbW zF;mKDz8PnP=AU~hs}SLX-*1oAX!)~h`q0yHcPM@QVYJ;A=Ji>2JsA0Y;(Rk$110C4}dG zzoHlqF4|5r3x6g)*Z(}NE}h)>U~cLg+H38h7s_rOJaax5Z;YQ6``NiR3x7lg1z>dW zCUNK9_i_+R(x9g{q__Kq`_Z{YuK38m|6SA?bJopFimQ*Rx_K@LW0JzVE~>~)1nvnJOs~%LjY&NfAlgR;*YDFW1?;+Nn_Wv0J`xu zAkq)KUigpBI%pRQPu6C8ojB#493h(Ae&KI-9GU!T}YFSA`Id|}}$QPT&G z&~ijR7_?k9&Jk{-qj!aSJ)Z8hK^Dl#pMPj@?=SB$eL=Z^>x;319@RFLR9+Td!wFK- zmRg}+HX0NYpRXrC$Or-|=b!XBhL`!pv4DWUy^aTnA}lUKIurp_NBf41uvT>NM-hgg zZj(hOuvLj(*OD(^pPv>oAHirAx!j#Nt;|{x`VnDy(2Wf$40Turr^7Rg`sK;UiPQQ{ zRf~1n9`j*3<|1{VKm(~}OTKVZ(LNP2{j>8PdQlVzH_nP9jM+FI@}`@1i&C;wwR0R8lLyYXUbm4hh`Uv62c6sb24HGBl0NScL{bL z0MvDH1w)5h`LS=@P)B}N(9y|%9#c;D^E7W_KJVdIjQc?>uRPgUQJ^;0W_pAc4$P{B zZK*015TyD(c^htv#{WVnf*ofZUGMW8_-cMKsE*6Ez^C^dom0@!+$5Ag7(X68@){XJ z1pDG0h!QM}!LsgMWQx3bx`tr83#o;=I`d%U?6moTFEtZ@!{N0p_==qq$ME(46J)dHn1>taINvuxDFu zE*8d)o8)q2;P3w+?egTY@Lr1@-2#?}h~h4a;{XW-N0WEn&%xMCaEalMa@J7=^AQP&y?SF~-|r;!?d{#+FZY-Di;xX!&F{yB!uAs5urb`c~!+!G4-P3-^+FAULq>&Vg@TYpImd z$8DNl0IkD0pOU~^0lhHb&3oQ8%A&Ns`mg2@XZEIsH31$`RJpn?J;`V@BaWa#Ha!^I zDZLCnpqTVBs`zlqHuj>So2I}UIO{>rA_E^=UM3R4#Mp{3qOh%tS;G(M*x@WE#%U** zb*(%3oJb*d-lVw6Q9aTt`P=P$@g>yoZUAGOX9fSgjXgpr>F|%&!G4Zxz@p#UU>)2Q zQ~3mpk9OE|J_J#dcJR+XZ zk68|&3waT6zw2`y?%p*WTm{f2wgVMFE_zy}=yU>I%`oXFeYJM6tiagn6nH7K2nv5v zFPE4bZumw8WXY%M7a}Ove5@)ewAmXiCr)0C+a!#>UMdm7VEvzcMmoj*=Mn)BZ;nm- zqw6b`>pp5^7pO!(lejnuXUqLVZ zG3Ix00dp4_w)qBt1*pr&#>Vs#t1o|KjiuS{fxzU|pfNb1@@etj9igl@eOGG)aMBnh zs_SqNSpkN`yc1j!b!I>iGD@fln>NveAPPY(&iI<>_msb4Rc)HOnD+>&{484m$Sp}qpK6WC|qOm~NBZvNLL<6(|CW~0*D+|l~0wC&HOgK9B z0s3okKqHrg9VmTA-5PC^{Z&Q@mjg#M;lATTYa%u>!t>yazQv!#|9zZtlU%Tvp7m`zr z3f!0<{^00bnsVyz7v#$USPmyS5>UAvZA)Cgj1|1Nz3%)KcoFgNY&@M@Taw|jk>13A zh6fRvwFRbirfRF5U zlA1sez=lwCsspH1s@(l{*M^!&U@VWQTiWSl{^S}XIm~?4B@(gV){4J3R4JNb%!f$t z^6jFE+ufiZAMIIyiZ0U@(WYP>6O&uxVncJ}l&J)cj+?CDx!B>or9{Fh%p~k^3uQn; z@abXAhKR+s{z3#NL7(U3Ri`j|)kfA>VH&v3TM+(bLyiw?XyU9uA$@{~TL|~!2DUN>*p0Ql?l;hwq4g( zwQ;g#Z|u)p?Qf#-A~gxb_s#xza3)dNqTKOw4ICfe>X9K89;#&YZ~(p`G#Zq?kRe{3 zXu^|&2w$`K({==Go%FaO0z;3YziejS|GT__zijcFWS)<4{>mb`^Y)=VY+E;-8adC# zkQ9))JN5Naa;qao2{#onAD-4~#Xht+*OX5=VT@}y8FdAK93zB0x@ z`}Z8cTOssAsgHsPY!u|6A$c{~CqZ0^R?eUek%CBP>#+h;O{;+8wWEjKc4PR1uN86j z5)8#{B@+G0?YgWZd2jphs!y&>nl2~)D3PD#6JdU<(4{r3j?+7ZN5Op+KEvsh$j^>* zu7G|@#(x@TliB6|tC|pwg<$mwNy>nGgaUMws}BDH&i>@Qd>3S(;2*W| z0w|R)4OIJ)7zL>m$PgK4AQKAr*8)=U-ZEezpF>8APJbS{3Suu0wFW?epf}P}j7#^| zYvY}okN?`tDir07`4>xGqMr2~c^n<)xFCa)850Y)jH8EvYwF9S(_Z1lCsuYZzePywR}gwo-Xqg|`t$N<{sADTpPG&*;`2T?RKS+)x1Vi2!{v|L%& zMom3tNpwN^ThZnz&Ip@i_(VrW$&SN(q=Qn#4mn@iyd4#ez;V)11GS z9IiC0Mk7VI)2kShs=T;;!uw@8wYBS^z;kYNCr5JPYsc@v>;PKYZ|p&&v^an&IZ2{| zYRmqrs2>$DDg7Zye&g;so>3ItvMf!!~|JVx_CoPwcNci^bW%DE?# zB@+ynh9V;qUJynA(^+}i1(&*Xz?)P2dHAq<*pgtFLs9INT-M`U-06ySd7>YNX7>)1 zJmum^rB~%;@LE;{J+nzA$^HFwPZM*Z)IysWJ!j^=*knD~{zYMOkUFbp406F7Q$4w^ zryA5o9}4f?jHU!M0|{NFp@8>1*(Dl*HNQt;XGs*!GSrsq{^ENY@c;3&}0LwD$ zVkukL$`EL4b-^VD4!|&lXdVcCy0{BJce-f_@1gPe&$RC74=feJ8obaXVq$XL^YWLW zo;!OUn)+y?JUNa1r7RNmK9!5(TpR`V?Lif|pQj26JD>aT>gt)ujNM+zTmHh3YWng@ z;W`Eb3K2-af{6fh)V;B0rfA#JURQ^*&z9IIyL#fXAg#XJ!#n?qj}~9VFO5f219)Ck*)#{9CSWyfaFm!Lk-XDv;2tb<0=hHGqDL4Tnu<-Ax76WLR%LD zqS17poliX!wF|L7;kUFvZFRp7AIkT79>686O0LlQJj60&nXbr5wVV?B+hqEO|FYiV z*zBZ!W9wszSW~dU(6}J%qW||g?jz1sC3SGYiF#8z@BrOc@FBb0sg?}$4rHG124x)I zH*)0)`(oggTO7VO&QNW5-33?AJ;}{rLKp#3lHwgQL|x7TRG62q(@jL_YjFuYds7bQ z?9hY`MBj;ew0O5yGnM?<-44Zm{t(K52zeS1h(>n4HXW!}J^Q%5-STWI!-6}mYh^9| zN`saL@~8qiE@i?ErXdms%)(BI>iQlTnU)=;6LyLANzq1KgbIA%7Un(N_y}ysXcq+d z^V^Ky#m}hH#5Ba6!tBZRUm-Co4n2Eu$mn)8#e6gn*lN_jZPVZ})AfQc%=!jKHYIa* zrWV2VOb)Hxwc_vRmw8f?vf%lsk`K-TjnPYr|UzbCn>wu1NSgap_0;2{^QEv ztsd?d$sm>u0=b;cFZ6mUW*(1>U_=`01LH?*wL$DR-CXRL3I$0sp+>{BWt{o4IK(M{ zw`x+ zS(}eWrsw~Jy?A|NK$RYW)}OB9?;kWP(DU+h_u@Jd7V@Jhugh@Nw|sgXzR-Jgsfxf* zMuI$3J!mm^l;=N!zs?zgcoKU_kN-?H%Rp|1&=cn7F^L4K%lWBYe%XA%+zT)OC65x& z1;gY=uwK&S0jX5~A|9`aK&+oF45R2<%0V&DB9|%g=&em(>>P-`j!P$?EBMBzk8I0i zt|D9gxdndwe(<|BESseE1#prT&HscH_xdFOr2RzoYKzhcpz`}G7dfF^YZYml#ra+Y) zeFGhHaF>;F=vG)PHoHGTy1vxYOHb}2{~KrJoT_kc-vEDU(cX+Q`nJe@4DoPs-amydoDJtnR=#s6rAWL ze%uZEiFi+hoX@b4GmlBe%>QnQ+o9Q>vt@1E@={BR4hOT zTu0E7Edy`nq*|1g9yS|-PgC3%brqSQ*vpx;Is%;l$mmW1fJSM^qp+)S70?jwKW$bB zEjm1mrGJ=`BaNA=F5eb{IPVB?-$Ms994#t&sotN4^MyTD_%de1%Q35r?mFT6J=wV` z54I%z7-@S5th%b~dVe~f&E&!jY(qWLam#sd<*!mdKr7ziKN7tiW^{lPlflKT)>$a% z0ZNFR-3T)i{I{1H8k}%h0?T^C*za4Jp1$J(nbZ3V8gXFImf|N7AcvEup1LMxPV#4` zL?3^P{hBZ(G|H!cQP}fEjXxfx@~x!Y$ZG9%d=4`Zfe_O0JlTm^$#5=tC9wMA(Qk6Nk+|_Neqsn zOnezAkQkq$27p^_@t6IjGJ!zh9W^gCbU2pGIg*B;p>aNlW8q-M_bRdNubJShBkIDp z%a0CE{ii?o#M`=P3{Bl^shB{7svkDOA8Dq^%#3AjO!o^N`V$JToFYKy1fAyPv^4v2 zO=|Eg1$|otIN4Y z)q+GVf%(1m^1Rt&mt_OEf(Be-;UwX6&83Z)oHvSvU^0C;j-c~M3wd%JxRBg zpBkL_91PqLor*Q(&A@hLeoAj=TcfL)|AN;ViL^^zrn_8RIMI}{Fd4DIdEx%`z{C$A z5!dN@8o>)_(;q!tkBGN@Mh{+6;hZ{d!@F%`EzRz8KjzEK?dj5W_XHFBeFeb9`K0fH zAc|fEk**NeiBfe&DG(^bp8-JPKp#26)II~tNF!lmKU}@t^rGam*CnRX-+8AburglK zCx16UPW=G3OB0II{DmFaUFl{FT`PM?DJ`t+g8*;qJc$f1xFuA5BVy?S)L}10z`79*dyGtd2~}Ljn3DufEBdW6fJbbizRiy_KhX) zo&DHqqmOjNV;d@we0et;SzfSC74mZy*ndxEoe8)0J{|VoGH%?x6V~WIBb60Jm||Bs zuzJhy#YYkoeKK(+7Lr|Hz|9HtXzRv-y+s)Hf#P;##TCE12cKasWtUu7mpm4eTHW{G ztCPT!66#HiAaj3OFZ#uG=YyQrYo|1y?uW{kFiIPc696ayW1)XaWBE*o%Zac!{7Cm& zOq0u%+S0PS$T-XU_P9reo%?@UwkhvFW{RF|ek|`MJx|a)t4~?`Dj>hIuoMjMvnHfd zIIvi#%)+4=9xz_p8|^{YZ<-DZk^r)5fn~4*YVI{Sy&^xBg92c&W%`d^THMOeERG2%zH^xbg|C z(kXDhf?X#+Fq$Tt^yS9WU^qaXCns)2#{bYE4K24-vLB^dDOXkJjiFbjOi*AyMN=0m z@2T}YRYJIAk|V+J#Q~Q_s@ha{+zZhhoXBXI&Xrs86+PX3r^XUaGL3S)$R>*+rjs*5 zdYa02=~1*VBb;om<%~7`1lT5Cg_JCe6ER$fM0eKx>F7Le4CG`-E|ALdARkzPj+^7L zZ5eAWd`$G?Df*j91Js6m8ybpGhIM~>bteJ$HW~FG`JY3d4O9%&ffKqdYOhZpTbcDs zuJJhqUA>`8bs${c10z!scNr5bDPC`63LnrIx~K+w7(F9be%p@Kcr|u^XZ%E2erIpn zQhaf!X!g=EB~>TPi@T#p)+5_Rfu5=xgow$ylt>f6fhx8O z!EHT$S1JSItMEh)C2jlNC^nvQ~1cY>W5*0F%m^I`EPUdUN{CBI=yeeQ#=_42t23fH`* z>1PcAir0?!sd6(gBmvpyT=d%V#8?;Po|*)%L7Y+y8~}16wb_rEWZ%pSqf1#1zh}a< zBH^NgGlGvrWViF};T&sOaEKeOcWqUsEH~?{clL$(zubhVbwFE585e;wZ6X83` zgak%R87*sR_{WCTPSI==sz%(m?cigJACl;o;wQ5d#`>D!4G&$hsb2&zZVLB=XglUA ziaZ+oW`o0u{qa8-((LXLMj`Mrp4AJE{~)A{dtyhKNNv*-=4w>n5}Jl%R-BA79x)kc znLBxi-c&_LK>k-7QbO0d-!PtvhKfIT#XN+(Ay?gm$x|bhK-l->b z|HG2CL-X@e2Q^c51oUAzeM4QZNvB+KgoJG5j~llJZ?SxSNJyw7fJ!X+atVk&^BlBn4hTy1Tm@=HvSh=h}Ony`Ja3=f0&` ziROr!AZjaSWkuZ?38AUOwH6J3=@bhC;XLj|#d)lawg5_A4YwBrjNpgk=s^23Cj&*v zQwS>DenG1~2-qnALpV6=Yli-NT5TW8)&nUD&ddAyKX<$a0=9G9%$_y}Y^U2-O+Wsv zfYA7=at>l%LTkab3*QSXrA;ufzwR>uVw~Y;hc$mutDAD?<&ZNjN5v=eL%fB|^>P>VW)Rw<~44Kpxim@g0?&aY?EvV>*Veu9yz*O5mkNWnfSh-waFJ?}7{IqoIOk*F&B}p;xtW`1+R_?pfD_$m zCu957Ki^d9t>H}~LS*p)tAea^*wnue+H)%hleEs_FYt?^C-(9=Pr3`Y~H5!Eltrl7yyJ@Gt5Xm$`Vc<&o_tP5N zqZ&Rh`!fCYP8Vzvc>O zL_)+2<$AeW@h$u)NL~I#RAvVr-TG{w<`D%5s8vP}lWPp6rxF;pjl>_eqf$(eM!d|q(#?T)>t&cN^J{UpA&FymO zf0&na76ayyP`j8aAfpmPuu5iB2M_cS88+=#DX@LLMCojAZ-?K2o44*y(?0IIb;G+n zo~c1ze&x5rM1u7AajtLnD+Thsw4RjAoz{jB4zU7{g51p&JuKg2iBQ%M)O82r&tleA z50Cl2NgFo2Q-GuWs;ztFHsw4fDiqIFa@X?fxgQxDl>4 z_HL;=H-jhq0d$MI8`eQ7`O{xC|33+Sw^)#~t4u_=Gb+7n%~plzRgX!$f@R$+vKYhNnuXVqf7>&E-_k#60{uD*r;ir2qM@E!Smx}~Gz!k86?pxRYz zJvTD3THnTq&8Na#HMjO9(s)z{&~}&o;!mxOK&{!bF9axO zrf24lU?Gzyba!Zb#Z1LtTMzoc)0v~GAGtg5Rc}xORYYy;*(bXZ{;%O?gtlslVJ#Sk z?$5o51t{yfQCvkgq%z_#;>ihPmx@0@P?&%|2t`q&<3vXI3QsdEB-LKSW3L+|R5C9v z(MCk4i5Z1+0yxcKgZje(`2NRncg%kolk7ZsrRS-NNsM*G;dT+}ScM={E6JG_EU0-X zwhc2mEHylwi0u%1Ze_BZUZd0;g&mz|*YhGsJ6m35V{FXu`vSgFrpTu`tXlqA2a$Tz zRF6|z=`kxS6d>|Z^)=8B)&S+b`ev=%kMi!g{StS8U`j7eBsRW*zB7{5j{R%tbzdsz zkvzeed|1)mB?Yff$i3t${0mH{BG0nwFk5Ug@vdr85EIex>@SjQpRkSXYgWZ#uN!gT zNfoO~HqWJ2T+p`j*;Z{szE4p_-%}pZoH=a7aQz;58R%=zEu4ql^2lJ4@+g&;4>oh5 z#A+{;&>HVws5i3l4j1o592_v6a2E99(FE9yF?uF%Qgow+mmxjfhI^8+5cYDRA^V=6 zAJ^VS<(zl5S1Pa9j>O_0YJ~EQ-Dnq4Ky}}>kOK+-W zX^_xDjN9c-wExd1i)oZ^05J;2yD3=!r~*XsieP`u;!gdgWM(nRG%!VxQslseuN^Va z1XC_TcVHU5L89b6Z`oepoAEaXk|P!La#TgI=u3OZn3%GZ-I`v2R}O|4$OYk>7KrHc zQ@ZjE=b+uAH)c)#*ZbEpO9}pdR0=D8`#G0gErk3Cr$wu5T2W~M-)-q02#W|d42>Rt zUh9-u;X3hDMyLn1G}QU0D<9$wYN1pl#|*$%Br2?5RmMA*ttFy+^`~K~_x9s(8WyY> zt#RGn^`*a4_BK@oKhi1;AAz6eI7(V7y9Fkz1}YWJOkmjKYxzVG0SV~!uqdEkHo|<8 z_-h%bG@u`&-!{2YMi;Gg!7ODRD24kYt4rS!iA>Na(o0P05mz zF=@OR@YpWYPKZaJkRDIrwDC<3|M+^uLO$EBHlfYvi6T0Y-4UxHqI@0oeX!1LqBw?g9;_}CIHdY0dT)C~pfC9GVl710$O_8st+hDXtn1kd)*rje4 zmQV5HK=d7oNh>Z76gb>CBRK0bU;yo=RdOwi^|)=;gHEU%8|W;h$;(APnki#-cS7SibxP|5!|e_g{7sV@5MY+TDXPR*trUWLj=Y~h)g5FpzNF@M2F z0b^n9X9pH@@CZ-<*pogHId~KkgwGmz`rit|SS<7L@vjFNcRM!JT+=gPH2m|wNc7{@ z!WN#9h^0?yG>q;zBcH)1Ea)fgr;u0w-Ax8GT)x$bJ^YRSR^K2SHeB}ya@S*s_FOi2 zF)-xnN*pB!HT#f7z8HSsbyQIZ|BPBmCK!?pu3RDoc+k0lba@P1>e8bH&-h)9GHaM%5e62Fo zY8zz(R|#V>+1aqFhM8VIZ5iC~eyq;weB&bHNV#~GV0>C{ob1MX^VX85oYqjMSLn|S zBJ|rgW30ZR9$i=2=vSECA*S^B*uBR=K156!9y(LLfJ5 zv1egz>ydY)`sm-Sr{_M?tdPSW_SA#$lns3!jVa zxZhg9Dl)QRc|Jsk9s}45Dc3iF84m}zYysQkeerOrZ^Rlrbczsi^j9zTdpSP*JN}#x zx-t8kTh2#74nC*?8es#_Gl~cm(1ln19BJ~_gr7-@wg_D#x%N>Ig6?U; zgzgId4J)EH=QVih4xy9s7>k-!Y4GJ9|4kGZ?-;-gqzK$Sw>Q62Mi5qg+t3sjlSXT>WHY3e=Q{`bmGWX4 z--jG=;ar~|a69T~$a2|nJw#(Mm}fW!m;z1*&FxJ0m>)T2A+&-<`ZMf_vbQoNOMzq_^F zg;VR>Hu=N0E?P3nCSDhbKtI8or5cMBhF2&j!yEnmm-~$KgkIG02H(4p02W@fPycwr zmt_=%59h(xglU(_rEU)Em49}rb5jDoF#uQ8wjS6_U+Wz2vq3j>e>Dj}MTUvCW70j% z3E9ABvM1YpfE!op4HjFnlsC0LMk8xvW;>!a$Cg$7fh+bScfr0i^Dk-&aV#qcCf3+kWxF8bzjco6 zQ|h|%p$q;{a4I{m9w2sj{CH9ud_?Bj5 z9S#UXLoFfKMWEoT8hFM3=UHBxA5?hp_7e?7yU(b>zUuc2-VxdfCclP~D*B!O#FrOw zFFY}kF%)nL#FZqV1aQGiry|Ibk_K?yA;1b7;xIBazvqiJ#Z!UrlV>mSU0*Mvn1Raw z^|8k86sZHKFoGNE*_cX=d+=OXTMe*$H#pno^`JhpZUSFz>dR=%H8P4h8~J7SDZiu6 zYy3_%6WuVLjt)`J=`(Wm#dkCR@USg%WstqFz=yK*=T=U;sK8%BzJJXY@R9>*k!s~K zMJzNbbd7N2KGY&mN2fB(2Kn?kh`89FYJW*Hi=ARKhcQn^xwQ(Uu#&fLEmN{6cSP5j z=Ca)Gb^px6e>PrdlA^oJV&V6b9O9_zYWZdF9zGUlF{7Nz=c^i0(Wzq4*t7`oB>_i2%Q& zr|C(QSZ8OI`PthlzeJW?_0xSxS<`-&svHyANMg;nC$w+eKdSOyImV;le>s&Y{|FEJ zrTGstq&0MNLzCrN1`(HA1h0Z5RZNDfvHe~zNCk335OCAviJpE z#BFI_zT?%TsSyYta@7z8OgQ)QkOG*95UVh8&NRvoAdHEQk39AJ?jrdl;dYZ^$ee^C znZ0EyC6TVB@K=W3>Xm$y01XNvx)UpbmIMcC?jq~zOHW^Aad3kEx7yh=-Lw5g*#@u1 zp3IAuP;m<%%YpSJug0-N+nYf|mKG3H7W4=tq_<+HO^{r#A!9yxjSp&{g~V zSkTMlt!t9DZg9<{G@81tdVXx6kUS{rOlun@<)Z(hB$c5!q zEq{FKeAq3sxIj-bp_b(jseZF$#)dp@$607b1%3RO{KjGWV+nh89u|zOkO>l%qm~u? z!T-vwhge+^WJ8!)FXo_(L5L3PLWte0WC^|l4txR5=BwAm0RFegm(w;^tw53WNn}{?!+IF{W%ke$3o)W#*cM(sv5)5|{*Sru=;-Dp(y|3Rcg*^K05 zc|&<}K4PReVE4HE(R2!GeKWRtoESlq&%OeEjR&8%?&^5MaZQb!FvWKS@J<; zbUa!;f60CkKhs8^>Mk5lcm19xqaom!;qB>PDhb`N9|Y-r&G9dFe5HCOsm@V$ql!Ge z-o>Zll5}Nj`|NbO@|X^nIf_p#el=4nf85&>pMf;-Hki6BreaX#N&gG`vtK0D#>8n2 z`iVg@>B%!Fy7hODgO}IQaOJbqvkHed?3}KjbVOZz%3qOjF}|KbdaJl@Thc{!6Pe!t zLpC@c!-0#@409aP_dS}&eU0$Ljg$(+$dXHk_?5TLD#SrtlLbC5M79+7(y*U z!n6OlM7kRci_TL*iNJ;dxbi^gint&FwlP&Y9?VgEe_>#TS6?PNlgeWDmE#M(QFcPG zaH}&H#_yf@!Q#usw>}4OLn1Ebhfkq&RGgN6?=-xSGaV1C4i665=>8iO(Rp|G#mHr~ z+l?}0o9L__H3}lq7}n0{fAAqsn+qVeqjHRky>w#5ClK0bcPRTJAoQ|qzP|aYi>ixm zsR(^D_-2*vc(LW0^-r5WUim`l@oSljy7+_XL^3BL0B>Sd5Ql|aV{r!`KzkNI!+DRK z_Ah_QRA}Jp74hw)%ghblDs))-r6FIA<(P|0uKozW+@A`&({1$<$ot^^xj3UDd*`Q(qG_a|ua^>O4<>l+wuf+JvBfTv(E7($zL_ z)enMzO4XzKIz5 z4B18lja;#Y5+WyN-)EDd!|oMBEJlT%R#z)0rDoQTjvu8n+Im$7Lmvk3p0wF+{w~pl zoCHha?nW)L?TnCqJLf!Z=|;}*@1S22-H&5l`}Kp1JiKB6jLERvp51&Rgb<8AKj@$z zL*VuaSXz1AH0wCbTK31P_8*@xTp{dyo&gMZ$}|KDiNpUJ)pp+o4WW1#mF8x!GCx&S z0d@myi)gWAiu?D2s&(Me2~3lN@w8#dr+9usdSd6iWAeW%g_2Pd)ME*J2#%Gq>l&ru zn1gvP$6WIuC#+PNSKz$U=T|}nz&jdn^u~Yn zw<7-|=)n95edZ~O5ejt6F@g%{(HKxOh?rW$IP?J_49AnhjZpeo;Gg;kl4AQ5Ba6%B zc#UrD(`!h|HqQ9|B+*+SlI7u7;?<4DNie~kM{pb?VGgO`a{L>ZH{M`n=%jN95PKnfQeZRCx;z%Sa$}?-B<`JDkkgSor1mq(srr%gX?_zMtcDL;_ zA&gT;*Zi+0*k281^Zxq{{||@D^?Z5mcV4WrPcSbFS+u8Ak2(K)!Wy=qm8zmF$d}vn z+nTGFLfN~t^Hh^NV(E(}o5j5Lr9MXKdV>Sf(BqDg_vK@WK^X6jG!#?mC2kej=RF^S zQ;Lu~zj-Y>oFFUk{Q%`S_ERfXAYHlr({=Q!+aFz}F?dKYt`ym4^x1W}{b+DNvX2;8 zY(kg8$gWr$1n2^O82I&rBUXxmo2dbj;NzL) z0+K-2jiE#hDxq(X9hd4G^2CPbPyv0s{F!U2p}rXmH6zr%b?c0H=voV{`*ck9cUV3~A zq|8?_>jz}QxeWh9hbm#^y-$Zf3_QBJSHzbtV~CxB*udT5LGSUv8v_|UIHVhoX$xxV z2OuGWw`m4+hA&hStZc&ENbw=`9kX{zp@__SM-~u1Spv?y39qL;2gCV)1}DfNLeQv6 z?%s{2`WJG1aY0!51Twy!>!lT0(oF$aG$~{Pcg5LLD$@u)^h)^jgIHpyeF>h)iDDN( zIQ1I6#+gs*4~ff!mL0~7vD*s_>Ow4FKvofwhX)?DO@fQtNw9}67g|$?7o8h92ap)6 zJJV4UdsZxcCws_%C57q$o*Cp6KnB%&varlz>I(`wl5>n+oo1Q>VX6Ak>Cd}EZ6|If zNW6t7`{CZ?$m2QdjQ{fVW{}F5w_-G8%_d$?V0I5Q!-h1o%E0?tH6B(Z(Wty6Qo%Pb zi25%Zf&?{0l{v3~|1bEbN#B`YrHiPs!W`Di>+sr-qu%*|T8a%#*GcYZuK}!E-1SMD z{+qNb7!^c^2^*u9xiMpg`;Y*u^Vb69LK*bU+H@aTybbOXLJS}+*x(`%kZCrgH`<`=nKYdscm9~bx z8##Zbw`uKjr4uip>n7&V|JL_@N8^5HYU$tAzdsPIm;%vYp=fvEsTcjPn~;>8x;C_7 zNKOwz;jP=GEsfst^wwStg7ubA$6y^d?we(E^eDQA5e5Jik!6kr&9MNDTFAs-MD!Yf zqqdnv9a94)A}q1&x5aAZK*K%@_nsP>@steWTfpDbO(^5ECFgx!h93Ge0)zq{4|c4;1(0PKSk);QnDj^ zeHqo6Mlo*uDtqh%m5I+5)(Yh7b2#L zW5(Q^i}^}?i8bnd5&Uh;Lx%Jo#FD~}GeZ^-*9X9;oK`pz?ArSnX01=6-;&pNCl)m7 z(11SK7V%TF6tqpZ|B%REgl|dgkw2)L7R!GW(X%Dn=R8R64Tt#WCO?M#L24Dg1StlL zdXyMNorfs_Yr{g}o{beYfC-xpPS!cf2d?9`1a4rplUYg)^<(#a^uKN!3|O$gk9yiD zHd`r^`VHN8bAV(XAH`K3SX*1RlH|P@na5=bt^x^f@Te;iKz{~);vtKcq!*V(<#4Ze ziNvXNzOO1VIi{AHoL@-TqsZ5)f4dK0*}dP0Pt|1_Rk`H7a$eSlr@bk86Z!g$#&4Z) zh!k|L1RAz0mhF_EKO_8O2mb}5m1j{ypj~a7ttW-b$*M?#8`CR`lMkyUNhms@bKE4- zds9xvk?))W2%fWM59BB9=4m+^#;R(-eMxq(!9fWay-fV>)?Z&(1H z_KIHh7AaTj)Td5M51s6f!76@A2*Y9RLf)F(_Lar6)H}^`fdL7G(WI9?nUqdLv=#@Q z4k$e7rN%zj5o6!%+XHuNk1Dy>PXcpk`h1rnD-|9W!iwuc0t{9MxIcpDL*i>*czd9| zSE>AyeqE6qOj8I5Z>uD{23^rUQMu?*5l?!3@<1U2%`R4AK*%7divy7)7;nqe*dV1Q^%xQ{Mqd=RfUj(26Z=;8$&4Lo3f=-S-#p>`p``JTk1KA zAW!jew>h;Z&%4%Rf4oSlyt%2X*?YI6wrx{~49O8+nPGpe`m%Q1Gd)r`Ay5Envv?IU zIZ*-q+@0Qfxv?}yr1z{XERTRuT6;rbB2fnsD{iDkybeZi?>&KJV02m9qCtCRarjAl zwUS7t<9nvC-CBdi-haqo&-(i(qB@~MiqpYenaNz|$7@Cq-!zvc=;Umo!gVmMD~Fzz zfN0adO3=ol59j2w9f}++p3@K+-yne48yBBTodOsTb-|3r^*fi12u`wMYt62# zL+&4X`!0DAX4(zxWTB+U0p=*J&CXrVT$z)rc~&!1pG`+`=2#V9$tuRFY<6o4Nndl)m} zNrul_y`GESHZw^JC;4*XtBTCJsW8^_Geu#C|GFt@b7g52+2kdF;fRvcM|&b3M(NXKz;jX!B@hz>GG%?nq&XG?Cz5~@Dpe!~%FrU^;bbz% za}{D~)>GKMqFl_G8{oZJ!)X@iKC;(D_i#tmU@B^0iWz~g*y6|RzbIZppBQ-#n~y;gQH04a}%`d^idNpwp@S z6-9-4j1>w)lm?#mrss?)VAIRcvAz$h2J}t7f?WeOT5#ZSRpwvF{?7l%7Z&`w@w+YNyr9uAanI)2mpFAjo$_5Mi3lO!sua+yh9x@6CmsJ+;+Ym;(6JeTM70gXk=U% zmGG%bmf`cj29OXXfHvjBVtqaL$$f5e>+3BYm7plKA7#b2QsX@swL(K&EcH*6cn9HH z;1%N`FpOHA`t_?N1_1^N`j@!pYxPNR&D%~tvuUUR9(qE64M3#l3DCh3h}uUdHB;99j`Gku z-W0zB=?LsdKzaP_?!u};2ZS(1W1>!787OK$ubFO!=Sj55>}D}1?;^*4y@8)>7WSyY z<>&MOqInFGUdTK?h>!Zj@D!f(hVyzOjWyAMEuK%a3kRJPs5smFHN_LT>t?e(o6p(T ztCqvJedOoMMZzD+WEZK1By$3l{gXKa4^}1}O_yku9|@MYm&nCOe`%kNIy9vj`f;mtjmep5mZJ z1UNxwn}KJVCx_xiwxh;+pwTJab(ova)?=et`%OY3^Lz(M|9^MO1#I8dF4h(aIM851 zxkCQSQx}k};DOVI-jt7@qzPOvV}O+q5{#1)c3~!Tias{X{$XT^+svn`7S>?Erd?S0 zFLuI*Q5BF{`9!=BA70r326M1ieX*zk&`P?`=b(Q9m_V;iV*vw}T zTkF+-N*tEI+K_vappConShyD-pi+~Vob2Z6D{O#MtmrmewK>nJQi!+lao6Z}QrWc0 z_G?yZqtb-FMSe@Wpt(pUt`L)K_WDzVRA$>6nC5pP`p&9Jv8v ztS=;f8A9QD6dW@f+)g+tAJMj+Qg-MhVi2rZkMStINDNQ{{=)_vCgO#M!}C_l{7DaW z!SxTtdvzcD>&+S^_{7ylBVc^10hsK`CVX~nn1T$G45V#qktt1~%p|LW0VFCHKnky@ z0cMP0LiU_Le#}xVFAxzCpq_k=qmiq2``705Wd6;R1a>1+=xT1q_w5IbxVr=wgLD0V zJfj}KsDq(GNe`8m7`n-wfm4za77_fI^FIw*M(;Af-%ILhF5<gJ_g*QeM zYg9_M_%V&W`QIaUo{&XGlK&|nV8oiIHQ^TbZ93E}G7z25W-l(I}^wW%!6|~YP zM(Ov(@!~B<;MF?~B*j~b`TORktMt7wl{Ms_CsEM1=8Wy1G&DUF0CJU@Gd30#f6{;K2CZv<&Dm-lt zs$KmwZ>Lf2T+O}vlGd7)I3vxpyvcQW@L$AwKWs1vGjnnDuBSiVeyf_ZriG=nMO|!I z;&)#J=;jAHL3h@}v-8y5qsE&mETKDJ&2=(=AaVo<;5ajSPB3iqe2uGxfL_c4s!r44io0yqK{UBO zRsOq}opZj{{=bJuf1RjrzacHCdCvve=W$Rl_P9kHr4qOL)7+?hC+V8k{OesE!eExW zTn^%F`D^xKmRxV2FY)5*bfiLI`}M!Y7PIfwX)ENEr zqmBDQfLx|-Dc*#9UsI*h#ENna!0QQmIXV@^DeRw5q%HaRrs}Wy7+h!tBK|7a z56$VRJji{ZG!etFxtcf)LYu$c&tN3}_*#UCiHq7<)-K!IuJbdmoOTs1FX!yUBWCG!yS8p!|} zF}X+EF$+LS(vYJQz#q5T3$$MN$DWfc4!(Z-g=*~2aDI@37Mb7Rqs}hS@6f3EhA4uy zGQd~5z1&!4FMuyt#t1NbO@qMsT2}2GUBq%56Q6EPo+8|R|j4xgmRcoEoO#hgTdj{aYHF+8CBq5l4_uE z(~-E;{;E!5J^Ql##j~!naJ^&ifRYAo{x)K~w!@}bNg^X73SaP)NbM_%a0|ie+5O$= zQ!r-cV4pN%T7vGS&NFlejI76a|JUWiS@Ga*PVnl)ky7C)hKuq>71=WK;$qkn3w9Pv zUc!QpVLBDK;ZRB8PVAOM4#>p(xTL?%uDm~dN10HLC#AhQ!tWuC^T59MCMF~iIPi0v zw=h7{muB9Tb(o@qgJ3KOTH1?sjpCVLRFl)K-~4_tmRgoS@rzD6K72UqcpYi>7~oT1 z`I`rFD#ofIw>h}Esh(QS8Nr>GTUmjQj`{|M7$s%;oVPM52hXb}8;>b{9(OE1o?2;v zq)|$!34W`+brou7^JD8hX37+f5xCtjjf?|-Ux%yy@ zO24cULZdR5= zUx|TWA%wdwdAi${b(D&Ec=lghha2sl768$&oK7FPpPtt%pEr)6D|-~=7u`CZl=8Iu zsj)>?pG~&fiAU>y4~30Zmp~{51=cVbnG_>PCaHR!?rwW+SiDSx<(mAtx~XGgqB9@_ z#BFbOHQOX5kFN^o0@YRFg` z0&wKKH9HFpfB1Q!{t;6&7pk~Irq=eJTwx!i_*#>hUgF}^t`fg>_oIRBpcPvHh3MpQ#z2f2&uX(@{OTeu64}2Hda# z8M}~fpgtphJFN8QQs(BE=HJZba8?wb6bpFHfO5gFr?qw<2wW6#x##}!m-!#G%4)b8 z4CqG<|N2&!9W?57%8m6Ub|3eGL_#t1zf`&6aCI*7@r`3z@-KL?;&=Hk0Icru8r!~} zcePYuOxS;xl(=!I_~xJsZi${1k%3YlUpuPMvU)dYY^mG)$EcZy+}Ow|+JOt|Z(>fx z%+lvpal;r_x1jdiRmqHf3!1+k%%_8?Ld&liRqfe}E`nj)U|w!hqF5L9N>Cmuzz-k@ z;n~EHPe7h*!q27a;}`U(sv&##FjU;HCNA`U(9ey^g14K&kQI&{(;M1>QNHwJs*uBZ zv)L!C?-qS=G&7hY6^`Za7UFS4^(DUm#&7@uQU&KRmS;3(@#ytO{%aQ7IHwveo{)Lz zGqHPII&GO${Ce`pEq6Kl=TiE%tI(q0`U~JUt|O;D@+TC!y3)_<-!Vd@cOIU~ z?mxzOVyt$ua7PsuFY7pTZw_Y#uObo$&0S)*^$LtXo{nF?0HXdAM6yN*KGp=Al_=1& zV>Fybb;cHE!zjEisV62!1p_JE>z6e;7b&M}m;hE%E|Y_|^RK{_|IK(c5;PXKcb|uO zG{^!o5IBI}f9j$N8zWqyd5OkBC`H06cxzRI+sq=wjrY((r5|&Rcg(GyoA|5Rhl>jK z0zECNks-gMwv{-}KhtYvGe$DoZ=`QvY$EF~$l71`2_makF=+IR(0({ne<9}m>d(Ix z+lZ9Mw@Tt-y6Nka7rdip`0Slzmgw6-2>IX;A!bzKZr*5^9XqI>Daa0u}EQ$1*(3}>bivf2B{r1a1HBaREV$-8J zj=rWS(s7>ZQ2QS>S8PUhVvbyqUG(v_?V*go+@b*qZED$!o-rSLMA+6JaoRb)6B6~7 zfAjX(EU)2rF|=Q}IWkaCqb@&Ms;K+x^?W zr)ow@@x&;<-N+SL@$5i1$M4NQu%pi&NELRJE^A61A<+{HX0zXV zHzbUMKnSE*pUpT1dvW=}qxy0%**6op%3iU=YRo^KkH(02yVpr!I#PKj7lZl;A1oaj zQJCS(SAo&;v-{@}0k`{ijqP%y*BQFim&oZ1s|Vg3r^}l+0(ftm;E>vke*#pkhrLR# zEFE9nhNCED0&|!OSvL|~vvyM~s)ovX@;4+kUIf0coQp+_A_LOx+XAeaojB^t6&8h@ z`A>l*8}w3e3Scb)kn>oo2h7oOx}Y1XVsv(lsbUJv7tVKf%KU8SR? ziTO(+IK(_~?zc-=n;pXp2QwfSO6kTfA9p2!Jo%eho(enacMZ1nIA4Jk`S3l}{ELuz zJn6j#=fBh3sIKZo3XO0$4{Ftjl9T-EAQs_VtGdfE9XG@H5meie_fhCM17kAL)ELAk z#$Z{5{I{CA`e!39NYC7^h*cAcI4A*ml zbtXkX`A1C}-~UYt4OO+aeyM+MonLky852%uB~>L&#=s=S=l=|4R#|Z%*GM^4q?v zHFV92(Js5YEUY@?E>hWf*fwP%30>cd&4sCUYKq*na!+{Tp;=b`CEKIvcgH}E>>oRQ zI6SoTSZrx1kRksNNx`?&{(i$Wv()@ysqMucACv|5a$b@eH z_OQ1x7!x!6&#i;87z!l}`e%MO7jIEbQTGT{YgZK8R%L9u<6`$7AAW7toF)6J`S)!b z1CYWMZSxT`At6LrEJoBP1^0u%x9vZ#{jv2wE^UFUGq5uuSwn$Y(CD+fV=F7GNL~s( zJcm2q5&E+bEtV51}w125Ml~}+Zm%cy>Mqo z`+ihh_kwvOs5LRCSQ20BjT^ENt=qpW(HdGV~5m3dw_+Lff z+0}me5zy|~BxY{ji*KE_XIcmAx^a3qGp+C0?Wy{lZJjyp1u!6&No z4`#E`^eFmO!AxAw#v-IePKizODUua^-G*6_iHxLIH({HNS)5Wg;$5R*+NUk3H6CB0 z113Unl1yWRcZIV__WFj&DtHs31?KAki`HIL-8Q`;<6VMv&PuFx{-gE((o4563xMe7 zwi&(V-A<=^jGm#F^CB^T9V!rhZHc(s^=zZeuQfBYvo z{W3(;%q35vba0vy!}C{_IQ3klV6Zfa-VO_4X;#+=|6jCNuSi#tQZw z`4hzA?)jJ6yFH`cmo*-mxOL8Aqs(HkB`T_D^r;vY@3I0qup}ipjhmN2d3M zQB;4gueXD2TT4f#g$Nz}M8SOLAD3up8_g81+g;;6vDqZ;UR>igy#S72>KF`s?~Ley zbS6M?m~ozd{VXO|4p8g3f7<=c&Y5skf%ZR&&VntfuZ!Y$hM|$}9HgbYnE|9l_yY<^ zqlALAbPOpVEg?z_3eugDLkLKB2-4l%%)Go`;XeEBv(7qu{}yJ(81&fgb(OQKucPn$ zy8G@Tziirn){06XvaHfbQ0VYJ=~(?Vc1OD{ID?%6W$M3uvHY2<2Ux=m;GaGh*914d z9R|x+ZbKVb7>t3!C%9s;b9aIz`H$;k6q}SB&*<)hysE$nL5$=_sji5hF0(vF>fazp z^3$>!0pNnU2Un7?yR}~^HJ1(c{woVI`^Wy5F2V^x7L=B$`3rPA4_}w`+t{wDEN*a1 zYmrs;ZyK@PZ@x50iQ`D)47m}zns6m6VX=((lZhr7**Rt}alh{$2Lt2nMjCV}MO3jO z5+z7q8dWqb3JtW<4j+x_{$3gj&Y2HEt9|y1781@WuZBIl^-u?06^I6jrBpju&=L4P z0*VQxeLYBx_;6bvaf>1ah`{h4L3`v_Mpz)|G$u#fupFzcAx}t=XnnkG7B&S`N!Ed_Udm;poPAj?OlPh+=AKS&aTL9K;U#iIc$&! z>fUqML?e`T=9RM%Cb%H9EFpq@g#_gtp4Zk%-;X3-`L!M&_;~w7S$V7!|HyRer0v;k z^l#@Imp)2>K>Iy~C+B|9{~K?LWv(GEm?H@em0mllzwr-Px*9OYTW&5)#p6RfBSVCK zIucxLI!pq&+?Yke|MA_~{b<2Ze;1MC`Ovv3krjBHm-Zav(l$Ysmno`bm$#=(bR}f< zQsm0*klj@z_BNjR~sMIF-d zKTQUl-y%9{9iCxOhiF8%WZ=c%4-l@ry-de=>cCKChd_2eJoK<>-pS?sR;8IRUs z+$PW5mlWr`Nb$Un{;{@X_H2m28tt3!@~y}k=#R3Q^OnQ5i>dO!%jH;xzd%IF1VOR7=$NesNxqj-+hk%LuQMK%V36<+*mVk@=Vf5K_Ti_Ixg-ag^2zp3}kJyTk zlq)Mcs;ZPwO{Lx_g!Gthyna3|c0W2gSf(3o*Tq;=q_NBRPW10L-|-{CXa61F#YHDP zY?r>(=27SSDGuAcef`54caTiSu}%2O9;i zm6197lD;c;k=M&-i+ZI9Cpos6PFz%Rm3fZLF~q9qQX0nl{KxV;_!W><1+hG*;RDV1 zI*pGnVHVLVQ=Y&7#i%N*XcD3_d1GahckOyEB0dQL-X9;VXlXz9j$37CtF-z8{w6t^ zn;tJHFimqfUw=H`&i0a&wiZ$Mj0;#wXi!-={W-KzKbkdr;2f3~BWIw)D;}wfOgR2Uuvu;VlDGh<(h}oozG22DV4X@>3yXXn{5(h*PG>?Uk3RS~t6pw{TG2cB} z8>+kMPL!Y94&5a@ge)mKWDYfTfl)WjvI4eS9JD}omV&mGWv*R{i&Kpr!g#g)eM^2cN6j!)mFeu?yx;Y=C+0>g z8LNl`?y)v(IL$0qWDO)^*a&o^vT=Tf_y~U6e-R!pr$hmoh`=($_FWD?45T54)5C>< zDo~mHe=qT|EoEMW4Yp~{7Y9nW7c<*;)V?4|(UwsZN(SEa3~i`g(BZ6bjj>wDFQhpa z?C$MoK4nQ|eTHPit?HkCT8N7Q zo%zLQ@#r6j=_-@R2G{cQxmkr)Gh7Fh5PwARimtj&B;_T2wYiSXbUfpc+Fmf=7zRMW z0B8n*MF?~=``%epfgKHJCMS*O|NYx{W@cgBbD(?g;BdIN>qz3^g(>{i($rL3EFPV+ z@QsQ_qFCU#`9GG36%vm}3h_?DbKgw9nuwUSP@xC1jH#4ugWP`hi<#caY@E?+Z9MHN z$z*ue?C8-gaH2V+Gov)E`>D9)iHKa|BBOfDzG%ejVTKL3{>kg#;Y`M=Ci*FhcrR+d zv{2o1He1bvO8i%>SuLUvX*g>aoM5A0x9@qSLd=gx2b*!c@Om)dQ_H z!PM9J6VX+~H0eCH?VpLp7wOJ2K-V%z-jkaKXL%{mCAN^a-hWkMl zMyN!(L;JF#cV0SCPZO|(NWd-xtJ|k!O*(BD9smn509@UaO2j-)7rP=Q{PS*McgIX-aeO~PgCR`e4r@6mwo`=U;b6l- z)B&VH25dnYzqrpt?s(Tf0t1T7+~aNPz&Hk1X8^M!*><*JqO%Cylxl{1`2X{+mg`X{ z+AJP(lQNrH#|tjre4=}Fjb3tedf@)%4jUIWZNlbvSYua!phy<;( z-CNpD-$&KzDs*1Z^rOfwN}BO=lLwTt#J8qm`pA!(LkY0P9TUbz6E zh%>{AQc>rRWO_jDyIMUEwJrv9cE9Y;+J!y&YYf~`jWP{+TI5UX8Hj;FMwF}xZyjz* zIN+HspI3mZL{W9v`rH(K#|Kid|9{nOsla=Xqfs6oB9;-W0fAcqlu`WQ6+02oC1%6} z_<-jtkmJ{Qg0F+5=UYya<3I)VYvSh$SlSgZi6jCofVgPg5*-fM6C!fNfj`+XoC-Ll zA5(!Ex~_g@cHlW4T)&y^GiPiia3(S7B^V>A?Nz}QXwI1qY^7>Y!;yqWD=v15G6}~N zjvOF+f<&NUb+erqG56=~mkQR1RH+^f04Ye^Nn#U~4gO~S4>KFe=`<9HTaMW>YZNK9xf`fGns7pF z3;z0tPyjbS8hAEzF+q1jpfP~CQ(mywlc2bD)X9yb%C}?RF2_68Icf_TV(6er@Ofn( zD{(mzz&~OV=oY+{CnRQn)PN3SmV$;GAK&BFqIhzBZ zE0JNy4-QsC!2;>-Q)DuTv>t3AnJ=x-DnId;f(XYiw526Sw;Vb{#(3!^8I(-5M%VG~ z!f2)v+ZM^+Z{*A8HZx$>CE6B!V;VXtFTX6zboz<6dvJ6lRm{D?`qQX|Sbm!&bjbcw z+^J*wBH1I*QG4=|ugCJ~#)mG{2`-zMKW;EN4^H@E?B}tp=uyUg)lwV*a4dp(HzN;j}IDlrlz`#kq z9;rng#FCc~#YB#Xz&WapFazPmPPfgwxPoQtBs*|`>(!6NCJbegW4LQQBV9UbOpksZ zJmLeqQp+sb+%bxkfJ=r3puYGy8GEUM$&7#XUR!&WZB_YbP70rgjCDM?zS{4*Q~-?s zO#IL_F0!loSBNEWlC%ZfG%mq_q+U4?hFO|5lI|28N-y zkdhSZG>hyOfrIxTHCgH|z46<$yAvn;*Ski;bYg9qyf)kIu)Pf4{W{}$!Jhrji)7m_ z_mMRFNQx0jKI59;gialTn*1pqF|%>2PZyLz)&=nO(vfQwDj02=@&e9jgS$Qe|9G+_Tr*5!ldqoDE;lU(Xq?P-8BIx#^Gp z5h}Y8eIfZ!{mB@RBI|Vn?aygaqg<&GyFvcg1V|jr)oAdL$VV+x_~HWbhHAO$60Ttx z^2yUsOSR6@M_%+%YxyDDi`0*7b4I|Z@i=Thi6KjnJ!m8$5;5*_t#tOhpf8sB2811a z3(jl>(R^uHpgJUiZW+UfSBJ29;JwIN`4Mv}ktUM>ezW}-SkJMZ=Z=!*Dz(!v?lXCv z+k^0u0O&9K3E07|SKW#`n?eGtd2=wZ=4Jqw)vGC5euUvK{#il-N6+mbUxHTJT+3NU z;c8DjGi>pstfEQ0b+MO6p=-}Wy>PDHc6BivlgO#bjI?@29r^*jT!(iS!m;$L!sT>` zLQOC>^XZfPBv2Cr?nqR&)Z6(rtV_F?` zy>xveS`&duWGw?8`_=p~$@NCqsFIKd_wHRsX?CFdfnZs{)#;(ZrV2p-R}NZf=d;g1 z;SB=!1}+(n79Z1(#DEl*Gu3t2pEpN6j7veBxcgFnQUg92iHKQhNDAK@caVIsyW;yP ztVX|~^m6yWaa?EgN#kyf_ml6yTF z3N+RD!lJ$%&-uYrITJkVnsmSx z&a5})bvb1-pyd6*OK@;TX(x!=Xh!*0=AWGQB{q4Qp${JPo&POw)(?D}rsmr-8#kn+ zYASDF8y#N}VPT0>ydZ6u6eqFSNC1hovH3(vV=C!8#f3s>6~pqOrEK<&JkbUA!8R4c zj0Ibw%HhI_)lBA5b!2Ud)QAenIgY>gKO`8}zUI6&r{6o;%$%E|YUNgIOcD}e+WAfZ z1&+|CA(jlSciQ9tB=`@Qn~iYk0pLl)_2@2Y-R84H70~L|@}Dgr+Bap|b~5pzJ>rpr zgt$b}gwCU;+a8%G#`gAiQZ!(f3sn2V97NdZ&rDud4l0I5VZEL$aoQR~a0n2VuteVH zVZF!VBJu(2-U*iw;g{U3+U{E~OZxOjegF1B>qj|SO?L8zZr8TKNlNxMU421(0F$1n z04R-y=#T4Cv`)sne4(`)`BvKSz@*|;9L7`Z62u=|5+qnAG`U~?w~7ZO75L7G2>2fK zA3|k;T-AJm!1<<7dw1Bm^ZiCpAlgYI_gNUO5&L5y=cSV+-P3}_Hk;G#%rArI;_U8K z#CZ9hM=$r`E1f;UfP4M*wrfdfy*QUS;L`c8%@Mx6a29 zghiR>@a>a|LPvfy%WQ+|>3ofac~iHRQsBdJs=^bAm0aEey`=qI&!2`Xdxc5Q)fX5y zc9b)JneY-GAV#_0NxAg7IrNdkztiTGlXr+`0AiEY(oaX z^P-8*4eP0+@p#JTr0R%-y>EC|tl{TO1hKSGk+Nx;U{K~+`A4(0{)W~qu6iG8tRSDx?@DCrIt$-t>0_2vph2}5O z6LZCna$UOAUf&T10YAL#(iTsA?XvlW_iXY;0_3R<&1LYo8L5N%=A8>%Na+SNWWOcF<#*2W4b0U-84QvKmM(2EkYo zi2B+zODNN;S@eTD3FI}R(R9{ef46-T3;Qz7o`-P9fnvj~;L($X9j=AD9h2dguiOKC z_?J#TXo4sgMOFH#_9bX^B@i`1dXR`gMfPHXqqK(IH-=-I?-cN^lsbzVS!t{--;AuZ zvlB>T-W4~xHT*_o--WxF8TF({Y!G#75*n)nY4ft zA7Rke9s4Yx6SymT%k$PSU%!@vrB{Mdz!DbZ=;@>vGi_2G81OnSKPQR)j}zkSnC<{# z9SQ70(@vg%Ql)LZT?PU zximU3v`7S3_b!S>U@O*?0|)5}KEJ`c)mIB@Ozt6={-)5z8l(kC;lOu6DDW425ODvn zYaoWu3DTB>&)^T-*SvkUbW>a0>3(OrIAD`5iRgX_fkSPq0_0uXV7=gxDDDb;@F!q? z0j&E7UU4z0Y>o%?{@pS36DGi${~fkRq4Z;qME|Es6SZQ^YVfdn)~#t-b!US@5|%K2 z^I1quR{J^dxSdS&d*PmnP&zZ_(+IMg+x*6bjvjpq91-2y&)8QhtWx|`&8-G%rq7B% zD1oeiKYYLvW_rVGfAs+7d4REaOhO7l;Nn_o*?>?uOB@8=wkxpxfF}B#>A_z#6lBF4 z<+6?FozPsYAYC3jvSyX@8`K03<}&DTtHkqhkm_*~m{0)o34jd0=A7xeUHSa>C_jaS zm=mCcI_<7RAu)0>_k8v;Rr?)@S-H6l=f(Y^;h*|lLl$Kxbz75KlpNq&JbVEDvx7d& zj?j19fn$R)>b~=znJ?!aBNYLB{RHq)h!|!5^A8vWKBv!gO=A1!NrJ-}7ERl|iENYW z#*{huIQc!<1-Bh45HLHKuebw+f3|;kC2OxJG<*k^0CF)bUyEP7Vs`v%NO4&!nK@76 zoBHCnlzRgmQ#}D9j_xF_zoUJ&Ch?K(8fQA3Dii<2{4*J_5o8EVQ9twQ%t=zWa+??* zJeRwSXA4Wfy=TEGRnAWO4)jr)eJpwgxS&Ip@Ditpxn{g&+Ib z2DB0kw3u*tI5};XpaU3AQpH&Y)DeL>MaOPVs{NsF>$7efBD0^-~x&04YFTWscS1`2lvK#`Jx!t?#_?J76jBW zIe?1CiASPTeV*gqhhi+AA6jecVo2)B{!nNDy^y6CI5-j1Y+$5G=O?wkR~JAUvM+?E z;Nd)Zmlq%}Xd0?=BoF=rZ$m_yUziBe+J#BP`cnJm_&Yl-&%tJ+Bm~=?! zn^C~uJ!;ZQFHa4J(o*+MicmSCNgmNnx`6a(`Hu}dG`NB&HXL$)ie0&l_f3`cJQ4YJ zVL$@`^?jfV^4ySQZcmonz9zhX{SlyK?BO248EyopS5C72xH}DA`M#8D=CHDoloWWk z%I^FMnn1bD9xbAdYu*QGRumU;o>>dGMByCiV=UkVOMSSl*%l>WfB}vTu;-_r=~EwO zxe?=_K5d%&-W~T`9{khF|12o{Jo_OeI>p^?I7`|?>b1=ieNkS7t!?W0U;ZhTi($VD zTd9Top7`_3QZb6NTfy`1p6qWWAuf3~t^jTA`;_3~+n{sl zC~{gJ-MX|-9EA7ZtUwc!lFsgxYDw*HNl|MM8%hrdF<%yCw(fCE|IItlk=#Y=`JGb_X5L`T8W^-aK^Pgx6>BCfp_uuUk11JuMWuNtSs;T~h?f*^ zt@sJ}YgI^s%b5F79@fyotr!b86FK3=ARdC%p9*Lt*lvQwCABEX33!y-ks-n@SV0Nm zAa0xlgHH#N5KAU}R7&i?Rp0F^c9Aq?MV*Rx9G_h00(>Yo3J?B%^~Op7=|YVn?!$g# z0@UF&jI1w)Z<9F5GsfZlu=xF5@fliGVg5Y?eLEkxDI8KUbR!$Idh!17?h%&~1o^8q z} z(l<}?0{Z%Bg+m4xSi@|IX5S8-I{TwPTm0VA$(Nv;m5NGE#FuTm=}p9K6Fcn)*Zgx} zoEy43c*dWW5k6l@wo90)wV5sO2JFIQ7PduI`StO0s;8EL$`aTIHON}u?39n&F2)

BA~_71S@&KD@8NIfw9|sPGCnNhR|1zI)wtQ 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 z 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

Z6$(Fu?k1gYdE%D%yifA9C9lLsx1+%mL8 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 z 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

|#B_tT$lq^PPwP7!mqiwx)i#Ibm}`N`NFF2S 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≈NZ>e)kCMUE+C=t$k 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 literal 0 HcmV?d00001 diff --git a/package.json b/package.json index af1752c..d740da2 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "license": "MIT", "files": [ "dist", + "assets", ".env.example", "README.md", "LICENSE"

u)T?lo7wGX66Du>3 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