From 2c96bee0779961b463c18ecf61f930a52aa0747b Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Mon, 2 Nov 2026 14:00:56 -0800 Subject: [PATCH 1/7] Add read-only agent monitor TUI Add an independent terminal viewer for invocation relationships, explicit waits, and retained agent results from local CLI event logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/.gitignore | 2 + tools/agent-monitor/README.md | 94 ++ tools/agent-monitor/package-lock.json | 1207 +++++++++++++++++++++++ tools/agent-monitor/package.json | 31 + tools/agent-monitor/src/App.tsx | 193 ++++ tools/agent-monitor/src/cli.tsx | 49 + tools/agent-monitor/src/events.ts | 190 ++++ tools/agent-monitor/src/model.ts | 343 +++++++ tools/agent-monitor/test/App.test.tsx | 104 ++ tools/agent-monitor/test/events.test.ts | 68 ++ tools/agent-monitor/test/fixture.ts | 44 + tools/agent-monitor/test/model.test.ts | 96 ++ tools/agent-monitor/tsconfig.build.json | 10 + tools/agent-monitor/tsconfig.json | 14 + 14 files changed, 2445 insertions(+) create mode 100644 tools/agent-monitor/.gitignore create mode 100644 tools/agent-monitor/README.md create mode 100644 tools/agent-monitor/package-lock.json create mode 100644 tools/agent-monitor/package.json create mode 100644 tools/agent-monitor/src/App.tsx create mode 100644 tools/agent-monitor/src/cli.tsx create mode 100644 tools/agent-monitor/src/events.ts create mode 100644 tools/agent-monitor/src/model.ts create mode 100644 tools/agent-monitor/test/App.test.tsx create mode 100644 tools/agent-monitor/test/events.test.ts create mode 100644 tools/agent-monitor/test/fixture.ts create mode 100644 tools/agent-monitor/test/model.test.ts create mode 100644 tools/agent-monitor/tsconfig.build.json create mode 100644 tools/agent-monitor/tsconfig.json diff --git a/tools/agent-monitor/.gitignore b/tools/agent-monitor/.gitignore new file mode 100644 index 00000000..b9470778 --- /dev/null +++ b/tools/agent-monitor/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/tools/agent-monitor/README.md b/tools/agent-monitor/README.md new file mode 100644 index 00000000..301f97ed --- /dev/null +++ b/tools/agent-monitor/README.md @@ -0,0 +1,94 @@ +# Agent monitor + +Independent, read-only terminal viewer for **one explicitly selected local Copilot +CLI session**. It does not change the built-in `/tasks` interface, load the Copilot +SDK, resume the session, install hooks, call a model, or write to the source log. + +## Run on Windows + +Requires Node.js 22 or later. In PowerShell: + +```powershell +Set-Location "\tools\agent-monitor" +npm ci +npm run build +$session = Join-Path $env:USERPROFILE ".copilot\session-state\" +node .\dist\cli.js --session $session +``` + +Run this in a **separate interactive terminal tab** alongside Copilot. The viewer +replays the selected directory's `events.jsonl`, then follows newly appended +records. The directory can also belong to a historical session. There is no +automatic scan of other sessions. + +For development, use `npm start -- --session ""`. +Use `node .\dist\cli.js --help` for command-line help. + +## Views and keys + +The invocation tree retains finished executions. The result pane shows the +selected execution's task, original response/error, recorded duration, and last +activity. The tool pane can display each recorded tool result. Long text is +loaded from the selected log on demand rather than copied to a new database. + +| Key | Action | +| --- | --- | +| Up/Down or `k`/`j` | Select an execution | +| Left/Right or `h`/`l` | Collapse/expand its children | +| Enter | Toggle collapse | +| `f` | Cycle all, active, finished views; ancestors remain as context | +| Tab | Switch between the recorded result and tool log | +| `[` / `]` | Select previous/next tool | +| Page Up / Page Down | Scroll details | +| Home / End | First/last detail page | +| `q` or Escape | Quit and restore the previous terminal screen | + +`DONE` means an invocation has a recorded `subagent.completed` or agent-attributed +`session.task_complete` event. `REPLIED` +means a response and turn end were recorded, but lifecycle completion was not. +Follow-up executions on a reused agent remain separate from its earlier work. +The finished filter includes replied executions without claiming the agent was +terminated. Running/unknown states are **last observed**, not a process-health +check; silence or end-of-file does not prove that an agent is dead or finished. + +## Relationships and compatibility + +Initial compatibility was verified against Copilot CLI **1.0.104**, using a local +parallel/nested-agent trace and a same-agent follow-up. The on-disk event format +is version-sensitive, not a supported stable public attachment API. Other CLI +versions display a compatibility warning; malformed or unsupported relevant +records produce explicit diagnostics rather than fabricated results. + +- Invocation parents use verified launch-call ownership or + `subagent.started.data.parentId`. Envelope `parentId` is the event chain and + is never interpreted as an agent edge. +- Temporary waits use synchronous invocation or explicit `read_agent` + `wait: true` data. A nonblocking status read is not a dependency. +- The verified format has no general prerequisite/`depends_on` DAG. Its absence + is displayed explicitly. No graph edges are inferred from timing or prose. +- Background launch acknowledgement is not task completion. +- Missing outputs stay unavailable. Original text may already have been + truncated by Copilot; the viewer cannot reconstruct it. +- Ephemeral events and unrecorded cancellation/liveness signals cannot be + recovered from this file. Unknown agent-event types are diagnosed, not + interpreted as cancellation or success. +- Detected source replacement/truncation rebuilds the view. A trailing record + without a newline remains buffered until it is complete. Reading stops on a source + error, with a visible diagnostic and a nonzero exit status on quit. + +Text from the log is rendered without terminal control sequences. Session +content stays local: do not copy private transcripts into this repository. +This private package is a standalone local tool, not a modified distribution +of Copilot CLI or a claim that upstream accepts core CLI changes here. + +## Validate + +```powershell +npm run typecheck +npm test +npm run build +``` + +Tests use synthetic records and temporary files, covering replay/tailing, +UTF-8 boundaries, source reset, agent/result attribution, explicit waits, +reused agents, completed-history navigation, filtering, resizing, and scrolling. diff --git a/tools/agent-monitor/package-lock.json b/tools/agent-monitor/package-lock.json new file mode 100644 index 00000000..b6f652e8 --- /dev/null +++ b/tools/agent-monitor/package-lock.json @@ -0,0 +1,1207 @@ +{ + "name": "copilot-agent-monitor", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "copilot-agent-monitor", + "version": "0.1.0", + "dependencies": { + "ink": "^8.0.0", + "react": "^19.3.1", + "wrap-ansi": "^9.0.0" + }, + "bin": { + "agent-monitor": "dist/cli.js" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^19.3.0", + "ink-testing-library": "^4.0.0", + "tsx": "^4.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.3.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.1.tgz", + "integrity": "sha512-cUD0PT0YX+rImRhfn7BfMRduP/tX/Zqt/N0dHlAyn2RlcJssCb5KsCJkhln61eh9vuJDkH8wKn3L8zJzzWXPFg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha1-wETV3MUhoHZBNHJZehrLHxA8QEE=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha1-v24QMDvPLnxoaXX6Uvk37Cco2Lw=", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha1-LYTs5qTiaE2SvibuE9QnV9gxw4E=", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha1-DGJGvI0sTRcqrC2z+xGQ1yvWVQQ=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha1-/DjU1jWNjcHPU/CfdYn+Q262SAE=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha1-+Dr+6sHX2sAcei/QErPkUaBZH8w=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha1-UQFHwFWnlViNu+FP1rG4rQovMN4=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha1-CTuSAOzwsRW6Tl4kinSFycX4vV4=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha1-C+Irbfkl0hPoQeqHEjr134Cw+vc=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha1-vrEq1yuE9y0oSIzBuO6ffrFB11M=", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha1-G9vGUc2pupmVxT7ZxxzqplCUdi0=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha1-uB+dVVKbRcIGpGoTghSxqmh5aWs=", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha1-WYZnJBoEyZt27W75QKxQA4xBn5g=", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha1-HFHrnOqQP1PZe1rzsYQdtw9Vlso=", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha1-Y91h8XzrMagSJ/QT/qyKcbwsUfI=", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha1-N2Owj95c8lqx+suOd1Lt/kX7/Cc=", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha1-GhN/8pOoKQbrMXY4W9fo4OXPt8s=", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha1-Jos2IRwUbKVPj+EsV4qNbviXlIU=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha1-Ilca2VHWK7aszILY0frVyMGsC6E=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha1-QvzFcpfrCgyj9fxHUpH0waP3wN4=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha1-nrMq8QSsPaz07coB9ZZmSqsMc+8=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha1-/r7SQC1giCJekfIPtM4lIq0KTv0=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha1-hWQcPUZkKL+8zqXyHCaDZmP+9c4=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha1-pzb52JYkgQRfxMPlT1R58iyHD7Q=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha1-7lq0D60YYgG2UqM/il6xSenkJTI=", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha1-xA0optmaEn2mcR8q/XSxHLY7Bqc=", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha1-shr/uATMFnwTPZX0WzodwTI7moc=", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@types/react/-/react-19.3.0.tgz", + "integrity": "sha1-Ozzo2MFif7XM1NrdV+9EXFoVO3c=", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha1-U5W7dLIVCkodbjwlZfSuynjShic=", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.4.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-regex/-/ansi-regex-6.4.0.tgz", + "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "7.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-styles/-/ansi-styles-7.0.0.tgz", + "integrity": "sha1-hZCJpyVWDiNaQf7N6JmRlt9seMA=", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha1-UNjmPqWh3dy15eNkUcGoJm/7sq4=", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "6.0.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/chalk/-/chalk-6.0.1.tgz", + "integrity": "sha512-/Ce6KNm3vIbWdMlNna6RVIZ/ICQxnJxCicet5LBKK9ZffBkqzDw0xh9EiKSljdRtiIQ1S1z4YgcscUUGzNCWrA==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "4.0.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/cli-boxes/-/cli-boxes-4.0.1.tgz", + "integrity": "sha1-cmf0rjLsu1K1r3f+9IviGfZmxXw=", + "license": "MIT", + "engines": { + "node": ">=18.20 <19 || >=20.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha1-POz+NzS/T+Aqg2HL3A9v4oxqV+o=", + "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "6.1.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/cli-truncate/-/cli-truncate-6.1.1.tgz", + "integrity": "sha1-Na5moEDu81eu3P/9YUnYR796LrE=", + "license": "MIT", + "dependencies": { + "slice-ansi": "^9.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha1-LefUbphRQ4XLAfezt0EyARX0yV4=", + "license": "MIT", + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha1-YabJj4qmJsFrKWuGKpFBKjO862s=", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha1-7EjA8+mT5QZIyG2lWeJhCZXPmJo=", + "devOptional": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha1-vz1uj3+P0ipl2XA0dbwBRzV6aw0=", + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/environment/-/environment-1.1.0.tgz", + "integrity": "sha1-jobGaxgPNjx6sxF4fgJZZl9FqfE=", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-toolkit": { + "version": "1.53.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/es-toolkit/-/es-toolkit-1.53.0.tgz", + "integrity": "sha512-3+AYXp9fmJ/UhHBMYj2/U+RaUouMD90SzoItyDOB2Za7W6dB7fQApFGnZPl2MF6K8/Tv1koh1JyirSx/kju6Pw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types", + "tests/browser-compat" + ] + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha1-D0O9G62VW3LSTiJh46vllXzPCBY=", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha1-owME6Z2qMuI7L9IPUbq9B8/8o0Q=", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha1-ysZAd4XQNnWipeGlMFxpezR9kNY=", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha1-T9KYD8yvhiLRTGTWlPTPM8gZUaU=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ink": { + "version": "8.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ink/-/ink-8.0.0.tgz", + "integrity": "sha512-M2aFwZOqmkFxCwedy6xrP4E+DGoK57iahAFnfTqNyOyJ54O7DZzu027z/NI8EGy44Sw9O9BcbTF4QEUS2GHAtg==", + "license": "MIT", + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.3.1", + "ansi-escapes": "^7.3.0", + "ansi-styles": "^7.0.0", + "auto-bind": "^5.0.1", + "chalk": "^6.0.1", + "cli-boxes": "^4.0.1", + "cli-cursor": "^4.0.0", + "cli-truncate": "^6.0.0", + "code-excerpt": "^4.0.0", + "es-toolkit": "^1.52.0", + "indent-string": "^5.0.0", + "is-in-ci": "^2.0.0", + "patch-console": "^2.0.0", + "quick-lru": "^7.3.0", + "react-reconciler": "^0.34.0", + "signal-exit": "^3.0.7", + "stack-utils": "^2.0.6", + "string-width": "^8.3.0", + "terminal-size": "^4.0.1", + "type-fest": "^5.10.0", + "widest-line": "^6.0.0", + "wrap-ansi": "^10.0.2", + "yoga-layout": "~3.2.1" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@types/react": ">=19.3.0", + "react": ">=19.3.0", + "react-devtools-core": ">=6.1.2" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink-testing-library": { + "version": "4.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ink-testing-library/-/ink-testing-library-4.0.0.tgz", + "integrity": "sha1-cHHayaPXg+e6su4F/fHQGizTuw0=", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": ">=18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/ink/node_modules/wrap-ansi": { + "version": "10.0.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/wrap-ansi/-/wrap-ansi-10.0.2.tgz", + "integrity": "sha512-6OLRNZqRntVGBm26ghZ5eZhCuWWyOkRCSn8XsOIuq5stpCE/tjLbdozd93naILZig0lmD8ANfRwnhn0DLeXGlg==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ink/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha1-wETV3MUhoHZBNHJZehrLHxA8QEE=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha1-BGsqbU9rFWsiM9MgfUtal4OZm5g=", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-in-ci": { + "version": "2.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/is-in-ci/-/is-in-ci-2.0.0.tgz", + "integrity": "sha1-5LNHHFVbR1CagxGGnDd8I0lnB58=", + "license": "MIT", + "bin": { + "is-in-ci": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha1-ftLCzMyvhNP/y3pptXcR/CCDQBs=", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha1-0Oluu1awdHbfHdnEgG5SN5hcpF4=", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha1-kCP0ZlhA5m9A6c53T5BKYxZ0M7s=", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "7.3.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/quick-lru/-/quick-lru-7.3.0.tgz", + "integrity": "sha1-Kvfg/HK2a3SWJR9iJsxyNmLFBmU=", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-reconciler": { + "version": "0.34.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/react-reconciler/-/react-reconciler-0.34.0.tgz", + "integrity": "sha512-eVibvB1fc9j5Qf1KRkiM3GzRPBdjg2P6G6QV2f3P6Eb3OGMxRBhdg2rrUwjbV5JrQaJj8nOa8uajmmJA5tJWjQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha1-UZVgpDGJdQlt725gnUQQDtqkzLk=", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha1-A9PKNIrq+LdQL2RGjyF7J1I/lpU=", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha1-qaF2f4r4QVURTqq9c/mSc8j1mtk=", + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "9.0.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/slice-ansi/-/slice-ansi-9.0.1.tgz", + "integrity": "sha512-aBY19bn/XA+hKOKX0qL7Z0UfdBsbQvc9hn993U8ALUjzxCvDcuZqLoRXjGJrUARWAlwMnsRVD9sw2AFNOYvalA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha1-wETV3MUhoHZBNHJZehrLHxA8QEE=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha1-qvB0gWnAL8M8gjKrzPkz9Uocw08=", + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha1-0iomlSKDamJ6+NBLXD/Sx/o+MuM=", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha1-oLWRfChky6VIQUlav6P2sT7c9NY=", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/terminal-size": { + "version": "4.0.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/terminal-size/-/terminal-size-4.0.1.tgz", + "integrity": "sha1-T+jLdIKq4lPwQrzQxnth8t3peQE=", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.23.15", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/tsx/-/tsx-4.23.15.tgz", + "integrity": "sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==", + "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/type-fest": { + "version": "5.10.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/type-fest/-/type-fest-5.10.0.tgz", + "integrity": "sha512-NoSdpq/WEiAg5sjmBkmV/hfxv6HJH4NqPNrqjtSO5CwRmpsDfaf4begxW34KdJykH/l1yHtwBWQkCRdoXO8mPA==", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha1-W09Z4VMQqxeiFvXWz1PuR27eZw8=", + "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://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha1-aR0ArzkJvpOn+qE75hs6W1DvEss=", + "dev": true, + "license": "MIT" + }, + "node_modules/widest-line": { + "version": "6.0.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/widest-line/-/widest-line-6.0.0.tgz", + "integrity": "sha1-aoY4r4NwWVAdYcXTvfcsuTT2x5I=", + "license": "MIT", + "dependencies": { + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha1-lWgy3qlJQwbm0gnrhxZDu4c9fJg=", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha1-wETV3MUhoHZBNHJZehrLHxA8QEE=", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha1-tbuOIWXOJ11NQ0dt0nAK2Qkdttw=", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha1-0tG6BvDoHC62UMPlrYsLSt3h6EM=", + "license": "MIT" + } + } +} diff --git a/tools/agent-monitor/package.json b/tools/agent-monitor/package.json new file mode 100644 index 00000000..c053007f --- /dev/null +++ b/tools/agent-monitor/package.json @@ -0,0 +1,31 @@ +{ + "name": "copilot-agent-monitor", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Read-only terminal viewer for a selected local Copilot CLI session", + "bin": { + "agent-monitor": "dist/cli.js" + }, + "engines": { + "node": ">=22" + }, + "scripts": { + "start": "tsx src/cli.tsx", + "build": "tsc -p tsconfig.build.json", + "typecheck": "tsc --noEmit", + "test": "tsx --test test/*.test.ts test/*.test.tsx" + }, + "dependencies": { + "ink": "^8.0.0", + "react": "^19.3.1", + "wrap-ansi": "^9.0.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^19.3.0", + "ink-testing-library": "^4.0.0", + "tsx": "^4.0.0", + "typescript": "^5.0.0" + } +} diff --git a/tools/agent-monitor/src/App.tsx b/tools/agent-monitor/src/App.tsx new file mode 100644 index 00000000..0bbf2328 --- /dev/null +++ b/tools/agent-monitor/src/App.tsx @@ -0,0 +1,193 @@ +import {useEffect, useMemo, useState} from 'react'; +import {Box, Text, useApp, useInput, useWindowSize} from 'ink'; +import {stripVTControlCharacters} from 'node:util'; +import wrapAnsi from 'wrap-ansi'; +import {errorMessage, JsonlReader} from './events.js'; +import type {ContentRef} from './events.js'; +import {AgentModel, isFinished} from './model.js'; +import type {AgentRun, Filter} from './model.js'; + +interface AppProps { + model: AgentModel; + revision?: number; + loadContent: (ref: ContentRef) => Promise; + source: string; + diagnostic?: string; + width?: number; + height?: number; +} + +function safeText(value: string): string { + return stripVTControlCharacters(value).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, ''); +} + +function singleLine(value: string): string { + return safeText(value).replace(/\s+/g, ' '); +} + +function label(status: string): string { + return ({completed: 'DONE', replied: 'REPLIED'}[status] ?? status.toUpperCase()); +} + +function duration(run: AgentRun): string { + const ms = run.durationMs ?? (run.startedAt && run.endedAt + ? Date.parse(run.endedAt) - Date.parse(run.startedAt) : undefined); + return ms === undefined ? 'not recorded' : `${(Math.max(0, ms) / 1000).toFixed(1)}s`; +} + +export function App({model, revision = 0, loadContent, source, diagnostic, width, height}: AppProps) { + const size = useWindowSize(); + const {exit} = useApp(); + const [filter, setFilter] = useState('all'); + const [collapsed, setCollapsed] = useState>(new Set()); + const [selectedId, setSelectedId] = useState(); + const [mode, setMode] = useState<'result' | 'tools'>('result'); + const [toolIndex, setToolIndex] = useState(0); + const [scroll, setScroll] = useState(0); + const [loaded, setLoaded] = useState<{ref?: ContentRef; text: string; error?: string}>({text: ''}); + const columns = width ?? size.columns; + const rows = height ?? size.rows; + const treeHeight = Math.max(2, Math.floor((rows - 7) * 0.45)); + const detailHeight = Math.max(1, rows - treeHeight - 7); + const tree = useMemo(() => model.tree(filter, collapsed), [model, revision, filter, collapsed]); + const selectedIndex = Math.max(0, tree.findIndex(row => row.run.id === selectedId)); + const selected = tree[selectedIndex]?.run; + const tool = selected?.tools[Math.min(toolIndex, Math.max(0, selected.tools.length - 1))]; + const ref = mode === 'tools' ? tool?.output : selected?.error ?? selected?.result; + + useEffect(() => { + setToolIndex(0); + setScroll(0); + }, [selected?.id, mode]); + + useEffect(() => { + let cancelled = false; + if (ref) { + void loadContent(ref).then( + text => { if (!cancelled) setLoaded({ref, text}); }, + error => { if (!cancelled) setLoaded({ref, text: '', error: errorMessage(error)}); }, + ); + } + return () => { cancelled = true; }; + }, [ref, loadContent]); + + let content = mode === 'tools' ? 'No textual tool output recorded.' : 'Final output unavailable in the recorded source.'; + if (ref) { + content = loaded.ref !== ref ? 'Loading recorded text...' + : loaded.error ? `Cannot read recorded text: ${loaded.error}` : loaded.text; + } + const details = selected ? [ + `Task: ${selected.task}`, + `Agent: ${selected.agentId}`, + `Execution: ${selected.id}`, + `Elapsed: ${duration(selected)} | Last observed: ${selected.lastActivity}`, + ...(selected.status === 'replied' ? ['Reply recorded; lifecycle completion was not recorded.'] : []), + ...(selected.status === 'running' ? ['Last observed running; a quiet log does not prove current liveness.'] : []), + ...([...model.waits.values()].filter(edge => edge.fromRunId === selected.id).map(edge => + `waits-for [${edge.kind}]: ${edge.toAgentId}`)), + 'Prerequisite DAG: unavailable (not recorded by this verified format).', + '', + mode === 'tools' ? `Tool ${Math.min(toolIndex + 1, selected.tools.length)}/${selected.tools.length}: ${tool?.name ?? 'none'} [${tool?.status ?? 'not recorded'}]` : 'Recorded result:', + content, + ].join('\n') : 'No agents match this filter. Waiting for recorded subagent events.'; + const lines = wrapAnsi(safeText(details), Math.max(1, columns), {hard: true, trim: false}).split('\n'); + const visibleScroll = Math.min(scroll, Math.max(0, lines.length - detailHeight)); + const treeStart = Math.max(0, selectedIndex - treeHeight + 1); + const visibleTree = tree.slice(treeStart, treeStart + treeHeight); + + useInput((input, key) => { + if (input === 'q' || key.escape) exit(); + else if (input === 'f') { + setFilter(current => current === 'all' ? 'active' : current === 'active' ? 'finished' : 'all'); + setScroll(0); + } else if (key.tab) setMode(current => current === 'result' ? 'tools' : 'result'); + else if (key.pageDown) setScroll(visibleScroll + detailHeight); + else if (key.pageUp) setScroll(Math.max(0, visibleScroll - detailHeight)); + else if (key.home) setScroll(0); + else if (key.end) setScroll(Math.max(0, lines.length - detailHeight)); + else if ((input === '[' || input === ']') && selected) { + setToolIndex(current => Math.max(0, Math.min(selected.tools.length - 1, current + (input === ']' ? 1 : -1)))); + setScroll(0); + } else if ((key.upArrow || input === 'k' || key.downArrow || input === 'j') && tree.length) { + const index = Math.max(0, Math.min(tree.length - 1, selectedIndex + (key.upArrow || input === 'k' ? -1 : 1))); + setSelectedId(tree[index]?.run.id); + } else if (selected && (key.leftArrow || input === 'h' || key.rightArrow || input === 'l' || key.return)) { + setCollapsed(current => { + const next = new Set(current); + const close = key.leftArrow || input === 'h' || (key.return && !current.has(selected.id)); + if (close) next.add(selected.id); + else next.delete(selected.id); + return next; + }); + } + }); + + if (rows < 12 || columns < 28) { + return Terminal too small. Resize to 28x12 or larger. q: quit; + } + + const all = [...model.runs.values()]; + const finished = all.filter(isFinished).length; + const warning = diagnostic ?? [...model.warnings].join('; '); + const mainWaits = [...model.waits.values()].filter(edge => edge.fromRunId === null); + return ( + + Agent monitor | {all.length - finished} active/unknown | {finished} finished/replied + Source: {singleLine(source)} | CLI {model.version ?? 'not recorded'} + + {warning ? singleLine(warning) : `Observed events only | filter: ${filter} | main${mainWaits.length ? ` waits-for: ${mainWaits.map(edge => edge.toAgentId).join(', ')}` : ''}`} + + + {visibleTree.length ? visibleTree.map(({run, depth, hasChildren, unresolvedParent}) => { + const status = model.status(run); + const marker = hasChildren ? collapsed.has(run.id) ? '+' : '-' : '.'; + const context = filter !== 'all' && (filter === 'finished' ? !isFinished(run) : isFinished(run)) ? ' [context]' : ''; + const waits = [...model.waits.values()].filter(edge => edge.fromRunId === run.id) + .map(edge => edge.toAgentId).join(', '); + return + {`${run.id === selected?.id ? '>' : ' '} ${' '.repeat(Math.min(depth, Math.floor(columns / 8)))}${marker} [${label(status)}] ${singleLine(run.name)}${unresolvedParent ? ' [? parent]' : ''}${context}${waits ? ` -> waits-for: ${singleLine(waits)}` : ''}: ${singleLine(run.task)}`} + ; + }) : No matching agents.} + + {mode === 'result' ? 'Result' : 'Tool log'} | {singleLine(selected?.name ?? 'none')} | lines {visibleScroll + 1}-{Math.min(lines.length, visibleScroll + detailHeight)}/{lines.length} + + {lines.slice(visibleScroll, visibleScroll + detailHeight).map((line, index) => + {line || ' '})} + + q quit | j/k select | h/l fold | f filter | Tab result/tools | [] tool | PgUp/Dn scroll + + ); +} + +export function LiveApp({file, onError}: {file: string; onError: (message: string) => void}) { + const reader = useMemo(() => new JsonlReader(file), [file]); + const model = useMemo(() => new AgentModel(), [file]); + const [revision, setRevision] = useState(0); + const [diagnostic, setDiagnostic] = useState(); + const loadContent = useMemo(() => (ref: ContentRef) => reader.readContent(ref), [reader]); + useEffect(() => { + let stopped = false; + let timer: ReturnType; + const poll = async (): Promise => { + try { + const changed = await reader.poll(record => model.apply(record), () => { + model.reset(); + if (!stopped) setDiagnostic('Session log was replaced/truncated; view rebuilt from the new source.'); + }); + if (!stopped) { + if (changed) setRevision(current => current + 1); + timer = setTimeout(() => { void poll(); }, 400); + } + } catch (error) { + if (!stopped) { + const message = `Source stopped: ${errorMessage(error)}`; + setDiagnostic(message); + onError(message); + } + } + }; + void poll(); + return () => { stopped = true; clearTimeout(timer); }; + }, [reader, model, onError]); + return ; +} diff --git a/tools/agent-monitor/src/cli.tsx b/tools/agent-monitor/src/cli.tsx new file mode 100644 index 00000000..99b0ba91 --- /dev/null +++ b/tools/agent-monitor/src/cli.tsx @@ -0,0 +1,49 @@ +#!/usr/bin/env node +import {join, resolve} from 'node:path'; +import {parseArgs} from 'node:util'; +import {render} from 'ink'; +import {LiveApp} from './App.js'; +import {errorMessage} from './events.js'; + +async function main(): Promise { + const {values} = parseArgs({ + options: { + session: {type: 'string'}, + help: {type: 'boolean', short: 'h'}, + }, + strict: true, + allowPositionals: false, + }); + if (values.help) { + console.log('Usage: agent-monitor --session \n' + + 'Read-only terminal viewer for that directory\'s events.jsonl.\n' + + 'Keys: q quit, j/k select, h/l fold, f filter, Tab result/tools,\n' + + ' [/] tool selection, PgUp/PgDn or Home/End scroll details.\n' + + 'No CLI configuration changes, network calls, or session writes.'); + return; + } + if (!values.session) throw new Error('--session is required (see --help)'); + if (!process.stdin.isTTY || !process.stdout.isTTY) { + throw new Error('An interactive terminal is required. Run this command in a separate terminal tab.'); + } + let sourceError: string | undefined; + const onError = (message: string): void => { sourceError = message; }; + const instance = render( + , + {alternateScreen: true, patchConsole: false}, + ); + const terminate = (): void => { instance.unmount(); }; + process.once('SIGTERM', terminate); + try { + await instance.waitUntilExit(); + } finally { + process.off('SIGTERM', terminate); + instance.cleanup(); + } + if (sourceError) throw new Error(sourceError); +} + +void main().catch(error => { + console.error(`agent-monitor: ${errorMessage(error)}`); + process.exitCode = 1; +}); diff --git a/tools/agent-monitor/src/events.ts b/tools/agent-monitor/src/events.ts new file mode 100644 index 00000000..3bb82ac4 --- /dev/null +++ b/tools/agent-monitor/src/events.ts @@ -0,0 +1,190 @@ +import {open} from 'node:fs/promises'; +import type {FileHandle} from 'node:fs/promises'; + +export interface SessionEvent { + id: string; + type: string; + timestamp: string; + agentId?: string; + data: Record; +} + +export interface RecordLocation { + offset: number; + length: number; + generation: number; +} + +export interface EventRecord { + event: SessionEvent; + location?: RecordLocation; +} + +type TextField = 'content' | 'summary' | 'result' | 'error'; + +export interface ContentRef { + eventId: string; + field: TextField; + preview: string; + location?: RecordLocation; + inline?: string; +} + +export function isObject(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +export function optionalString(data: Record, key: string): string | undefined { + const value = data[key]; + if (value === undefined || value === null) return undefined; + if (typeof value !== 'string') throw new Error(`Expected string field "${key}"`); + return value; +} + +export function requiredString(data: Record, key: string): string { + const value = optionalString(data, key); + if (!value) throw new Error(`Missing string field "${key}"`); + return value; +} + +function decode(line: Buffer): SessionEvent { + const parsed: unknown = JSON.parse(new TextDecoder('utf-8', {fatal: true}).decode(line)); + if (!isObject(parsed) || !isObject(parsed.data)) throw new Error('Invalid event envelope'); + const timestamp = requiredString(parsed, 'timestamp'); + if (Number.isNaN(Date.parse(timestamp))) throw new Error('Invalid event timestamp'); + return { + id: requiredString(parsed, 'id'), + type: requiredString(parsed, 'type'), + timestamp, + agentId: optionalString(parsed, 'agentId'), + data: parsed.data, + }; +} + +function textAt(event: SessionEvent, field: TextField): string | undefined { + if (field === 'result') { + if (!isObject(event.data.result)) return undefined; + return optionalString(event.data.result, 'detailedContent') + ?? optionalString(event.data.result, 'content'); + } + if (field === 'error' && isObject(event.data.error)) { + return optionalString(event.data.error, 'message'); + } + return optionalString(event.data, field); +} + +export function capture(record: EventRecord, field: TextField): ContentRef | undefined { + const text = textAt(record.event, field); + if (!text) return undefined; + return { + eventId: record.event.id, + field, + preview: text.slice(0, 240), + location: record.location, + inline: record.location ? undefined : text, + }; +} + +export class JsonlReader { + private identity?: string; + private offset = 0; + private pending: Buffer = Buffer.alloc(0); + private pendingOffset = 0; + private line = 0; + private anchor: Buffer = Buffer.alloc(0); + private generation = 0; + + constructor(readonly file: string) {} + + async poll(accept: (record: EventRecord) => void, reset: () => void): Promise { + const handle = await open(this.file, 'r'); + try { + const stat = await handle.stat(); + if (!stat.isFile()) throw new Error('Session log is not a regular file'); + const identity = `${stat.dev}:${stat.ino}`; + let replaced = this.identity !== undefined && this.identity !== identity; + if (stat.size < this.offset) replaced = true; + if (!replaced && this.anchor.length) { + const previous = await this.readBytes(handle, this.offset - this.anchor.length, this.anchor.length); + replaced = !previous.equals(this.anchor); + } + if (replaced) { + this.offset = 0; + this.pending = Buffer.alloc(0); + this.pendingOffset = 0; + this.line = 0; + this.anchor = Buffer.alloc(0); + this.generation++; + reset(); + } + this.identity = identity; + const changed = replaced || stat.size > this.offset; + while (this.offset < stat.size) { + const chunk = await this.readBytes(handle, this.offset, Math.min(64 * 1024, stat.size - this.offset)); + if (!chunk.length) throw new Error('Session log changed during reading; reopen the viewer'); + const buffer = Buffer.concat([this.pending, chunk]); + let cursor = 0; + let end: number; + while ((end = buffer.indexOf(10, cursor)) !== -1) { + const line = buffer.subarray(cursor, end); + this.line++; + if (line.toString('utf8').trim()) { + try { + accept({ + event: decode(line), + location: { + offset: this.pendingOffset + cursor, + length: line.length, + generation: this.generation, + }, + }); + } catch (error) { + throw new Error(`Session log line ${this.line}: ${errorMessage(error)}`, {cause: error}); + } + } + cursor = end + 1; + } + this.pending = buffer.subarray(cursor); + this.pendingOffset += cursor; + this.offset += chunk.length; + } + this.anchor = await this.readBytes(handle, Math.max(0, this.offset - 64), Math.min(64, this.offset)); + return changed; + } finally { + await handle.close(); + } + } + + async readContent(ref: ContentRef): Promise { + if (ref.inline !== undefined) return ref.inline; + if (!ref.location || ref.location.generation !== this.generation) { + throw new Error('Result belongs to an outdated session-log generation'); + } + const handle = await open(this.file, 'r'); + try { + const bytes = await this.readBytes(handle, ref.location.offset, ref.location.length); + const event = decode(bytes); + if (event.id !== ref.eventId) throw new Error('Session log changed; result is no longer at this location'); + const text = textAt(event, ref.field); + if (text === undefined) throw new Error('Recorded text is unavailable'); + return text; + } finally { + await handle.close(); + } + } + + private async readBytes(handle: FileHandle, position: number, length: number): Promise { + const buffer = Buffer.alloc(length); + let total = 0; + while (total < length) { + const {bytesRead} = await handle.read(buffer, total, length - total, position + total); + if (!bytesRead) break; + total += bytesRead; + } + return buffer.subarray(0, total); + } +} + +export function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/tools/agent-monitor/src/model.ts b/tools/agent-monitor/src/model.ts new file mode 100644 index 00000000..f5620497 --- /dev/null +++ b/tools/agent-monitor/src/model.ts @@ -0,0 +1,343 @@ +import {capture, isObject, optionalString, requiredString} from './events.js'; +import type {ContentRef, EventRecord, SessionEvent} from './events.js'; + +export type RunStatus = 'unknown' | 'running' | 'completed' | 'failed' | 'replied'; +export type Filter = 'all' | 'active' | 'finished'; + +export interface ToolActivity { + id: string; + name: string; + status: 'running' | 'succeeded' | 'failed'; + output?: ContentRef; +} + +export interface AgentRun { + id: string; + agentId: string; + name: string; + task: string; + parentRunId?: string | null; + parentAgentId?: string; + launchCallId?: string; + status: RunStatus; + startedAt?: string; + endedAt?: string; + lastActivity: string; + durationMs?: number; + model?: string; + result?: ContentRef; + error?: ContentRef; + tools: ToolActivity[]; + hasActivity: boolean; +} + +interface ToolCall { + ownerAgentId?: string; + ownerRunId: string | null; + name: string; + task?: string; + background: boolean; + targetAgentId?: string; + wait: boolean; + complete: boolean; + activity?: ToolActivity; +} + +export interface WaitEdge { + callId: string; + fromRunId: string | null; + toAgentId: string; + kind: 'sync-call' | 'wait'; +} + +export interface TreeRow { + run: AgentRun; + depth: number; + hasChildren: boolean; + unresolvedParent: boolean; +} + +export const isFinished = (run: AgentRun): boolean => + run.status === 'completed' || run.status === 'failed' || run.status === 'replied'; + +export class AgentModel { + readonly runs = new Map(); + readonly warnings = new Set(); + readonly waits = new Map(); + version?: string; + sessionId?: string; + private readonly latest = new Map(); + private readonly launches = new Map(); + private readonly calls = new Map(); + private readonly seen = new Set(); + + reset(): void { + this.runs.clear(); + this.warnings.clear(); + this.waits.clear(); + this.latest.clear(); + this.launches.clear(); + this.calls.clear(); + this.seen.clear(); + this.version = undefined; + this.sessionId = undefined; + } + + apply(record: EventRecord): void { + const e = record.event; + if (this.seen.has(e.id)) return; + this.seen.add(e.id); + const d = e.data; + switch (e.type) { + case 'session.start': + this.version = optionalString(d, 'copilotVersion'); + this.sessionId = optionalString(d, 'sessionId'); + if (this.version !== '1.0.104') this.warnings.add(`Untested CLI event format: ${this.version ?? 'unknown'}`); + break; + case 'subagent.started': + this.start(e); + break; + case 'subagent.completed': + case 'subagent.failed': { + const callId = requiredString(d, 'toolCallId'); + const run = this.forLaunch(e, callId); + run.status = e.type === 'subagent.completed' ? 'completed' : 'failed'; + run.endedAt = e.timestamp; + run.lastActivity = e.timestamp; + run.model = optionalString(d, 'model') ?? run.model; + if (typeof d.durationMs === 'number' && Number.isFinite(d.durationMs) && d.durationMs >= 0) { + run.durationMs = d.durationMs; + } + if (e.type === 'subagent.failed') run.error = capture(record, 'error'); + break; + } + case 'user.message': + if (e.agentId) { + const current = this.latest.get(e.agentId); + if (!current || isFinished(current) || (current.hasActivity && d.delivery === 'idle')) { + const run = this.create(e, `message:${e.id}`, e.agentId); + run.name = current?.name ?? run.name; + run.parentRunId = current?.parentRunId; + run.parentAgentId = current?.parentAgentId; + run.model = current?.model; + run.task = optionalString(d, 'content') ?? 'Follow-up input not recorded'; + run.startedAt = e.timestamp; + run.status = 'running'; + } + } + break; + case 'assistant.turn_start': + if (e.agentId) { + const run = this.current(e); + run.hasActivity = true; + run.lastActivity = e.timestamp; + if (!isFinished(run) || run.status === 'replied') run.status = 'running'; + } + break; + case 'assistant.message': + if (e.agentId) { + const run = this.current(e); + run.hasActivity = true; + run.lastActivity = e.timestamp; + const requests = d.toolRequests; + if (d.phase !== 'thinking' && (!Array.isArray(requests) || requests.length === 0)) { + run.result = capture(record, 'content') ?? run.result; + } + } + break; + case 'assistant.turn_end': + if (e.agentId) { + const run = this.current(e); + run.lastActivity = e.timestamp; + if (run.status === 'running' && run.result && !run.tools.some(tool => tool.status === 'running')) { + run.status = 'replied'; + run.endedAt = e.timestamp; + } + } + break; + case 'session.task_complete': + if (e.agentId) { + const run = this.current(e); + run.result = capture(record, 'summary') ?? run.result; + run.status = 'completed'; + run.endedAt = e.timestamp; + run.lastActivity = e.timestamp; + } + break; + case 'tool.execution_start': + this.toolStart(e); + break; + case 'tool.execution_complete': + this.toolComplete(record); + break; + case 'subagent.configured': + case 'subagent.selected': + case 'subagent.deselected': + break; + default: + if (e.type.startsWith('subagent.')) this.warnings.add(`Unsupported agent event: ${e.type}`); + } + } + + status(run: AgentRun): string { + if (run.status === 'running' && [...this.waits.values()].some(edge => edge.fromRunId === run.id)) return 'waiting'; + return run.status; + } + + tree(filter: Filter, collapsed: ReadonlySet): TreeRow[] { + const included = new Set(); + for (const run of this.runs.values()) { + if (filter === 'all' || (filter === 'finished' ? isFinished(run) : !isFinished(run))) { + let node: AgentRun | undefined = run; + const ancestors = new Set(); + while (node && !ancestors.has(node.id)) { + ancestors.add(node.id); + included.add(node.id); + node = node.parentRunId ? this.runs.get(node.parentRunId) : undefined; + } + } + } + const children = new Map(); + const roots: AgentRun[] = []; + for (const run of this.runs.values()) { + if (!included.has(run.id)) continue; + if (run.parentRunId && this.runs.has(run.parentRunId)) { + const list = children.get(run.parentRunId) ?? []; + list.push(run); + children.set(run.parentRunId, list); + } else roots.push(run); + } + const rows: TreeRow[] = []; + const visited = new Set(); + const visit = (run: AgentRun, depth: number, unresolved = false): void => { + if (visited.has(run.id)) return; + visited.add(run.id); + const descendants = children.get(run.id) ?? []; + rows.push({ + run, depth, hasChildren: descendants.length > 0, + unresolvedParent: unresolved || run.parentRunId === undefined + || Boolean(run.parentRunId && !this.runs.has(run.parentRunId)), + }); + if (!collapsed.has(run.id)) for (const child of descendants) visit(child, depth + 1); + }; + for (const root of roots) visit(root, 0); + const reachable = new Set(); + const mark = (id: string): void => { + if (reachable.has(id)) return; + reachable.add(id); + for (const child of children.get(id) ?? []) mark(child.id); + }; + for (const root of roots) mark(root.id); + for (const run of this.runs.values()) { + if (included.has(run.id) && !reachable.has(run.id) && !visited.has(run.id)) visit(run, 0, true); + } + return rows; + } + + private create(e: SessionEvent, id: string, agentId: string): AgentRun { + const run: AgentRun = { + id, agentId, name: agentId, task: 'Task not recorded', status: 'unknown', + lastActivity: e.timestamp, tools: [], hasActivity: false, + }; + this.runs.set(id, run); + this.latest.set(agentId, run); + return run; + } + + private current(e: SessionEvent): AgentRun { + const agentId = requiredString(eAsObject(e), 'agentId'); + return this.latest.get(agentId) ?? this.create(e, `unattributed:${agentId}`, agentId); + } + + private forLaunch(e: SessionEvent, callId: string): AgentRun { + let run = this.launches.get(callId); + if (run && e.agentId && run.agentId !== e.agentId) { + throw new Error(`Conflicting agent ID for launch call "${callId}"`); + } + if (!run) { + const agentId = requiredString(eAsObject(e), 'agentId'); + const orphan = this.latest.get(agentId); + run = orphan && !orphan.launchCallId && orphan.id.startsWith('unattributed:') + ? orphan : this.create(e, `launch:${callId}`, agentId); + this.launches.set(callId, run); + } + run.launchCallId = callId; + return run; + } + + private start(e: SessionEvent): void { + const callId = requiredString(e.data, 'toolCallId'); + requiredString(eAsObject(e), 'agentId'); + const run = this.forLaunch(e, callId); + run.name = requiredString(e.data, 'agentDisplayName'); + run.task = optionalString(e.data, 'agentDescription') ?? 'Task not recorded'; + run.model = optionalString(e.data, 'model') ?? run.model; + run.startedAt = e.timestamp; + if (run.status === 'unknown') run.status = 'running'; + run.parentAgentId = optionalString(e.data, 'parentId'); + const call = this.calls.get(callId); + if (call) this.link(run, call, e.data.executionMode === 'sync'); + else if (run.parentAgentId) run.parentRunId = this.latest.get(run.parentAgentId)?.id; + } + + private link(run: AgentRun, call: ToolCall, synchronous: boolean): void { + if (run.parentAgentId && run.parentAgentId !== call.ownerAgentId) { + run.parentRunId = undefined; + this.warnings.add(`Conflicting parent evidence for agent ${run.agentId}`); + return; + } + run.parentRunId = call.ownerRunId; + run.parentAgentId = call.ownerAgentId; + if (synchronous && !call.complete) { + this.waits.set(run.launchCallId!, { + callId: run.launchCallId!, fromRunId: call.ownerRunId, toAgentId: run.agentId, kind: 'sync-call', + }); + } + } + + private toolStart(e: SessionEvent): void { + const name = requiredString(e.data, 'toolName'); + const relevant = e.agentId || name === 'task' || name === 'read_agent'; + if (!relevant) return; + const id = requiredString(e.data, 'toolCallId'); + const args = isObject(e.data.arguments) ? e.data.arguments : {}; + const owner = e.agentId ? this.current(e) : undefined; + const call: ToolCall = { + ownerAgentId: e.agentId, ownerRunId: owner?.id ?? null, + name, task: optionalString(args, 'description'), background: args.mode === 'background', + targetAgentId: optionalString(args, 'agent_id'), wait: args.wait === true, complete: false, + }; + if (owner) { + owner.hasActivity = true; + owner.lastActivity = e.timestamp; + call.activity = {id, name, status: 'running'}; + owner.tools.push(call.activity); + } + this.calls.set(id, call); + const run = this.launches.get(id); + if (run && name === 'task') this.link(run, call, !call.background); + if (name === 'read_agent' && call.wait && call.targetAgentId) { + this.waits.set(id, { + callId: id, fromRunId: call.ownerRunId, toAgentId: call.targetAgentId, kind: 'wait', + }); + } + } + + private toolComplete(record: EventRecord): void { + const d = record.event.data; + const id = requiredString(d, 'toolCallId'); + const call = this.calls.get(id); + if (!call) return; + if (typeof d.success !== 'boolean') throw new Error('Tool completion lacks boolean success'); + call.complete = true; + this.waits.delete(id); + if (call.activity) { + call.activity.status = d.success ? 'succeeded' : 'failed'; + call.activity.output = capture(record, d.success ? 'result' : 'error') ?? capture(record, 'result'); + } + } +} + +function eAsObject(e: SessionEvent): Record { + return {agentId: e.agentId}; +} diff --git a/tools/agent-monitor/test/App.test.tsx b/tools/agent-monitor/test/App.test.tsx new file mode 100644 index 00000000..df741d23 --- /dev/null +++ b/tools/agent-monitor/test/App.test.tsx @@ -0,0 +1,104 @@ +import assert from 'node:assert/strict'; +import {setTimeout} from 'node:timers/promises'; +import {stripVTControlCharacters} from 'node:util'; +import {test} from 'node:test'; +import {render} from 'ink-testing-library'; +import {App} from '../src/App.js'; +import type {ContentRef} from '../src/events.js'; +import {AgentModel} from '../src/model.js'; +import {fixture, record, start} from './fixture.js'; + +const load = async (ref: ContentRef): Promise => { + if (ref.inline === undefined) throw new Error('Fixture text missing'); + return ref.inline; +}; + +async function frame(view: ReturnType, pattern: RegExp): Promise { + for (let attempt = 0; attempt < 100; attempt++) { + const output = view.lastFrame() ?? ''; + if (pattern.test(output)) return output; + await setTimeout(10); + } + assert.match(view.lastFrame() ?? '', pattern); + return view.lastFrame()!; +} + +test('completed results remain selectable and selected history survives new live agents', async t => { + const model = new AgentModel(); + for (const event of fixture()) model.apply(event); + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /Parent result/); + view.stdin.write('j'); + await frame(view, /Child result/); + view.stdin.write('\t'); + await frame(view, /Tool 0\/0: none/); + view.stdin.write('\t'); + await frame(view, /Child result/); + model.apply(record('tool.execution_start', {toolCallId: 'live', toolName: 'task', arguments: {mode: 'background'}})); + model.apply(start('live', 'live')); + view.rerender(); + const output = await frame(view, /1 active\/unknown/); + assert.match(output, /Child result/); + assert.match(output, />\s+\. \[DONE\] worker/); + view.stdin.write('f'); + await frame(view, /filter: active/); + assert.doesNotMatch(view.lastFrame()!, /\[DONE\]/); + view.stdin.write('f'); + await frame(view, /filter: finished/); + assert.match(view.lastFrame()!, /\[DONE\]/); +}); + +test('folding, tool-result inspection, and narrow resize do not lose completed work', async t => { + const model = new AgentModel(); + for (const event of fixture()) model.apply(event); + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /Parent result/); + view.stdin.write('h'); + await frame(view, /> \+ \[DONE\]/); + assert.doesNotMatch(view.lastFrame()!, /Task for child/); + view.stdin.write('\t'); + await frame(view, /Tool 1\/1: task/); + assert.match(view.lastFrame()!, /Child result/); + view.rerender(); + await setTimeout(50); + const output = stripVTControlCharacters(view.lastFrame()!); + assert.ok(output.split('\n').every(line => line.length <= 40)); + view.stdin.write('\t'); + await frame(view, /Result \|/); + view.stdin.write('\u001b[F'); + await frame(view, /Parent result/); +}); + +test('recorded control sequences are inert and long results are scrollable', async t => { + const model = new AgentModel(); + model.apply(start('agent', 'agent')); + model.apply(record('assistant.message', { + content: '\u001b[2J' + Array.from({length: 80}, (_, index) => `RESULT-LINE-${index}`).join('\n'), + }, 'agent')); + model.apply(record('subagent.completed', {toolCallId: 'agent'}, 'agent')); + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /RESULT-LINE-0/); + view.stdin.write('\u001b[F'); + const output = await frame(view, /RESULT-LINE-79/); + assert.doesNotMatch(output, /\u001b\[2J/); + assert.doesNotMatch(output, /RESULT-LINE-0\n/); + view.stdin.write('\u001b[H'); + await frame(view, /Task for agent/); +}); + +test('a live synchronous wait shows its target and disappears when the call returns', async t => { + const model = new AgentModel(); + const records = fixture(); + for (const event of records.slice(0, 10)) model.apply(event); + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /\[WAITING\] worker -> waits-for: child/); + for (const event of records.slice(10)) model.apply(event); + view.rerender(); + await frame(view, /Parent result/); + assert.doesNotMatch(view.lastFrame()!, /waits-for: child/); + assert.match(view.lastFrame()!, /\[DONE\]/); +}); diff --git a/tools/agent-monitor/test/events.test.ts b/tools/agent-monitor/test/events.test.ts new file mode 100644 index 00000000..2d29ed8d --- /dev/null +++ b/tools/agent-monitor/test/events.test.ts @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import {appendFile, mkdtemp, readFile, rm, writeFile} from 'node:fs/promises'; +import {tmpdir} from 'node:os'; +import {join} from 'node:path'; +import {test} from 'node:test'; +import {capture, JsonlReader} from '../src/events.js'; +import type {EventRecord} from '../src/events.js'; +import {record} from './fixture.js'; + +test('incremental replay buffers partial UTF-8/JSONL and reads full results lazily', async t => { + const directory = await mkdtemp(join(tmpdir(), 'agent-monitor-reader-')); + t.after(() => rm(directory, {recursive: true})); + const file = join(directory, 'events.jsonl'); + const text = '\u5df2\u5b8c\u6210'.repeat(200); + const bytes = Buffer.from(JSON.stringify(record('assistant.message', {content: text}, 'agent').event) + '\n'); + const split = bytes.indexOf(Buffer.from('\u5df2')) + 1; + await writeFile(file, bytes.subarray(0, split)); + const reader = new JsonlReader(file); + const records: EventRecord[] = []; + const accept = (event: EventRecord): void => { records.push(event); }; + const unexpectedReset = (): never => { throw new Error('Unexpected reset'); }; + await reader.poll(accept, unexpectedReset); + assert.equal(records.length, 0); + await appendFile(file, bytes.subarray(split)); + await reader.poll(accept, unexpectedReset); + assert.equal(records.length, 1); + const ref = capture(records[0]!, 'content')!; + assert.ok(ref.preview.length < text.length); + assert.equal(ref.inline, undefined); + assert.equal(await reader.readContent(ref), text); + assert.equal(await reader.poll(accept, unexpectedReset), false); + assert.equal(records.length, 1); + assert.deepEqual(await readFile(file), bytes); +}); + +test('rewriting a log past its old size resets state and invalidates old result references', async t => { + const directory = await mkdtemp(join(tmpdir(), 'agent-monitor-reset-')); + t.after(() => rm(directory, {recursive: true})); + const file = join(directory, 'events.jsonl'); + await writeFile(file, JSON.stringify(record('assistant.message', {content: 'old'}, 'agent').event) + '\n'); + const reader = new JsonlReader(file); + let records: EventRecord[] = []; + let resets = 0; + const accept = (event: EventRecord): void => { records.push(event); }; + const reset = (): void => { records = []; resets++; }; + await reader.poll(accept, reset); + const old = capture(records[0]!, 'content')!; + await writeFile(file, JSON.stringify(record('assistant.message', {content: 'new'.repeat(500)}, 'agent').event) + '\n'); + await reader.poll(accept, reset); + assert.equal(resets, 1); + assert.equal(records.length, 1); + assert.equal(await reader.readContent(capture(records[0]!, 'content')!), 'new'.repeat(500)); + await assert.rejects(reader.readContent(old), /outdated.*generation/); +}); + +test('an unfinished record waits, but a malformed completed record is a visible error', async t => { + const directory = await mkdtemp(join(tmpdir(), 'agent-monitor-invalid-')); + t.after(() => rm(directory, {recursive: true})); + const file = join(directory, 'events.jsonl'); + await writeFile(file, '{"id":'); + const reader = new JsonlReader(file); + let count = 0; + await reader.poll(() => { count++; }, () => {}); + assert.equal(count, 0); + await appendFile(file, '\n'); + await assert.rejects(reader.poll(() => { count++; }, () => {}), /Session log line 1/); + assert.equal(count, 0); +}); diff --git a/tools/agent-monitor/test/fixture.ts b/tools/agent-monitor/test/fixture.ts new file mode 100644 index 00000000..d18b8831 --- /dev/null +++ b/tools/agent-monitor/test/fixture.ts @@ -0,0 +1,44 @@ +import type {EventRecord} from '../src/events.js'; + +let sequence = 0; + +export function record(type: string, data: Record, agentId?: string): EventRecord { + sequence++; + return {event: { + id: `event-${sequence}`, type, data, agentId, + timestamp: new Date(Date.UTC(2026, 0, 1, 0, 0, sequence)).toISOString(), + }}; +} + +export function start(callId: string, agentId: string, parentId?: string): EventRecord { + return record('subagent.started', { + toolCallId: callId, agentName: 'general-purpose', agentDisplayName: 'worker', + agentDescription: `Task for ${agentId}`, parentId, + executionMode: parentId ? 'sync' : 'background', + }, agentId); +} + +export function fixture(): EventRecord[] { + return [ + record('session.start', {copilotVersion: '1.0.104', sessionId: 'synthetic-session'}), + record('tool.execution_start', {toolCallId: 'parent-call', toolName: 'task', arguments: {mode: 'background'}}), + start('parent-call', 'parent'), + record('tool.execution_complete', {toolCallId: 'parent-call', success: true, result: {content: 'Started in background'}}), + record('tool.execution_start', {toolCallId: 'control-call', toolName: 'task', arguments: {mode: 'background'}}), + start('control-call', 'control'), + record('assistant.message', {content: 'Control result', toolRequests: []}, 'control'), + record('subagent.completed', {toolCallId: 'control-call'}, 'control'), + record('tool.execution_start', {toolCallId: 'child-call', toolName: 'task', arguments: {mode: 'sync'}}, 'parent'), + start('child-call', 'child', 'parent'), + record('assistant.message', {content: 'Child result', toolRequests: []}, 'child'), + record('assistant.turn_end', {turnId: '0'}, 'child'), + record('subagent.completed', {toolCallId: 'child-call'}, 'child'), + record('tool.execution_complete', {toolCallId: 'child-call', success: true, result: {content: 'Child result'}}, 'parent'), + record('assistant.message', {content: 'Parent result', toolRequests: []}, 'parent'), + record('subagent.completed', {toolCallId: 'parent-call'}, 'parent'), + record('user.message', {content: 'Follow-up task', delivery: 'idle'}, 'control'), + record('assistant.turn_start', {turnId: '0'}, 'control'), + record('assistant.message', {content: 'Follow-up result', toolRequests: []}, 'control'), + record('assistant.turn_end', {turnId: '0'}, 'control'), + ]; +} diff --git a/tools/agent-monitor/test/model.test.ts b/tools/agent-monitor/test/model.test.ts new file mode 100644 index 00000000..fd01f761 --- /dev/null +++ b/tools/agent-monitor/test/model.test.ts @@ -0,0 +1,96 @@ +import assert from 'node:assert/strict'; +import {test} from 'node:test'; +import {AgentModel} from '../src/model.js'; +import {fixture, record, start} from './fixture.js'; + +test('parallel/nested agents retain their own results and same-agent follow-ups remain separate', () => { + const model = new AgentModel(); + const records = fixture(); + for (const event of records.slice(0, 4)) model.apply(event); + const parent = model.runs.get('launch:parent-call')!; + assert.equal(parent.status, 'running'); + assert.equal(model.runs.get('launch:parent-call')?.result, undefined); + for (const event of records.slice(4)) model.apply(event); + assert.equal(model.runs.size, 4); + assert.equal(parent.result?.inline, 'Parent result'); + assert.equal(parent.status, 'completed'); + assert.equal(model.runs.get('launch:child-call')?.parentRunId, parent.id); + assert.equal(model.runs.get('launch:child-call')?.result?.inline, 'Child result'); + const control = model.runs.get('launch:control-call')!; + assert.equal(control.result?.inline, 'Control result'); + assert.equal(control.status, 'completed'); + const followup = [...model.runs.values()].find(run => run.id.startsWith('message:'))!; + assert.equal(followup.agentId, control.agentId); + assert.equal(followup.status, 'replied'); + assert.equal(followup.result?.inline, 'Follow-up result'); + model.apply(records.at(-1)!); + assert.equal(model.runs.size, 4); +}); + +test('late start and launch records hydrate a completed invocation without regressing its outcome', () => { + const model = new AgentModel(); + model.apply(record('assistant.message', {content: 'Partial reply before lifecycle records'}, 'agent')); + model.apply(record('subagent.failed', {toolCallId: 'late', error: 'Recorded failure'}, 'agent')); + model.apply(start('late', 'agent')); + model.apply(record('tool.execution_start', {toolCallId: 'late', toolName: 'task', arguments: {mode: 'background'}})); + const run = [...model.runs.values()][0]!; + assert.equal(model.runs.size, 1); + assert.equal(run.status, 'failed'); + assert.equal(run.parentRunId, null); + assert.equal(run.error?.inline, 'Recorded failure'); + assert.equal(run.result?.inline, 'Partial reply before lifecycle records'); +}); + +test('event chain order is not dependency data; only explicit blocking waits create wait edges', () => { + const model = new AgentModel(); + const agent = start('a', 'a'); + Object.assign(agent.event, {parentId: 'previous-event-not-an-agent'}); + model.apply(agent); + const run = model.runs.get('launch:a')!; + assert.equal(run.parentRunId, undefined); + model.apply(record('tool.execution_start', {toolCallId: 'peek', toolName: 'read_agent', arguments: {agent_id: 'b', wait: false}}, 'a')); + assert.equal(model.waits.size, 0); + model.apply(record('tool.execution_start', {toolCallId: 'wait', toolName: 'read_agent', arguments: {agent_id: 'b', wait: true}}, 'a')); + assert.equal(model.status(run), 'waiting'); + assert.equal(model.waits.get('wait')?.toAgentId, 'b'); + model.apply(record('tool.execution_complete', {toolCallId: 'wait', success: true, result: {content: 'Still running'}}, 'a')); + assert.equal(model.waits.size, 0); + assert.equal(model.status(run), 'running'); + assert.equal(model.runs.size, 1); +}); + +test('filtering retains ancestor context, collapse hides descendants, and cyclic input cannot recurse forever', () => { + const model = new AgentModel(); + for (const event of fixture().slice(0, 12)) model.apply(event); + const parent = model.runs.get('launch:parent-call')!; + const child = model.runs.get('launch:child-call')!; + const finished = model.tree('finished', new Set()); + assert.ok(finished.some(row => row.run === parent)); + assert.ok(finished.some(row => row.run === child)); + assert.ok(!model.tree('all', new Set([parent.id])).some(row => row.run === child)); + parent.parentRunId = child.id; + assert.ok(model.tree('all', new Set()).some(row => row.unresolvedParent)); +}); + +test('unsupported agent signals are diagnosed, not fabricated as completion or cancellation', () => { + const model = new AgentModel(); + model.apply(start('a', 'a')); + model.apply(record('subagent.cancelled', {toolCallId: 'a'}, 'a')); + assert.equal(model.runs.get('launch:a')?.status, 'running'); + assert.ok([...model.warnings].some(warning => warning.includes('subagent.cancelled'))); + assert.throws(() => model.apply(start('a', 'different-agent')), /Conflicting agent ID/); + assert.throws(() => model.apply(record('subagent.started', {toolCallId: 'missing-agent'})), /agentId/); +}); + +test('main-session completion does not complete children; an attributed task summary completes only its own execution', () => { + const model = new AgentModel(); + model.apply(start('a', 'a')); + model.apply(start('b', 'b')); + model.apply(record('session.task_complete', {summary: 'Main finished'})); + assert.equal(model.runs.get('launch:a')?.status, 'running'); + assert.equal(model.runs.get('launch:b')?.status, 'running'); + model.apply(record('session.task_complete', {summary: 'Agent A finished'}, 'a')); + assert.equal(model.runs.get('launch:a')?.status, 'completed'); + assert.equal(model.runs.get('launch:a')?.result?.inline, 'Agent A finished'); + assert.equal(model.runs.get('launch:b')?.status, 'running'); +}); diff --git a/tools/agent-monitor/tsconfig.build.json b/tools/agent-monitor/tsconfig.build.json new file mode 100644 index 00000000..3607fe11 --- /dev/null +++ b/tools/agent-monitor/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["test"] +} diff --git a/tools/agent-monitor/tsconfig.json b/tools/agent-monitor/tsconfig.json new file mode 100644 index 00000000..e1dc8723 --- /dev/null +++ b/tools/agent-monitor/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "jsx": "react-jsx", + "strict": true, + "noUncheckedIndexedAccess": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src", "test"] +} From 2f6f8db6723d7f0461010677e9ccbb9112fa977b Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Mon, 2 Nov 2026 15:29:11 -0800 Subject: [PATCH 2/7] Redesign agent monitor in Copilot CLI style Replace the list view with Graph (default) and Timeline tabs that use Copilot CLI's tab bar, query chips, key-hint footer, detail dialog and Primer theme modes. Record wait history, main-session turns, launch mode, prompts and follow-up links; keep tool calls hidden until toggled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/README.md | 56 +++-- tools/agent-monitor/package-lock.json | 54 +---- tools/agent-monitor/package.json | 2 +- tools/agent-monitor/src/App.tsx | 282 ++++++++++++------------- tools/agent-monitor/src/chrome.ts | 227 ++++++++++++++++++++ tools/agent-monitor/src/cli.tsx | 12 +- tools/agent-monitor/src/events.ts | 6 +- tools/agent-monitor/src/graph.ts | 172 +++++++++++++++ tools/agent-monitor/src/model.ts | 172 +++++++++------ tools/agent-monitor/src/screen.ts | 209 ++++++++++++++++++ tools/agent-monitor/src/theme.ts | 53 +++++ tools/agent-monitor/src/timeline.ts | 177 ++++++++++++++++ tools/agent-monitor/src/tree.ts | 174 +++++++++++++++ tools/agent-monitor/test/App.test.tsx | 139 ++++++------ tools/agent-monitor/test/fixture.ts | 8 +- tools/agent-monitor/test/model.test.ts | 26 ++- tools/agent-monitor/test/tree.test.ts | 68 ++++++ 17 files changed, 1471 insertions(+), 366 deletions(-) create mode 100644 tools/agent-monitor/src/chrome.ts create mode 100644 tools/agent-monitor/src/graph.ts create mode 100644 tools/agent-monitor/src/screen.ts create mode 100644 tools/agent-monitor/src/theme.ts create mode 100644 tools/agent-monitor/src/timeline.ts create mode 100644 tools/agent-monitor/src/tree.ts create mode 100644 tools/agent-monitor/test/tree.test.ts diff --git a/tools/agent-monitor/README.md b/tools/agent-monitor/README.md index 301f97ed..8f2688e9 100644 --- a/tools/agent-monitor/README.md +++ b/tools/agent-monitor/README.md @@ -22,29 +22,47 @@ records. The directory can also belong to a historical session. There is no automatic scan of other sessions. For development, use `npm start -- --session ""`. -Use `node .\dist\cli.js --help` for command-line help. +Use `node .\dist\cli.js --help` for command-line help. Add `--theme ` to +match the Copilot CLI `/theme` color mode: `default`, `light`, `dim`, +`high-contrast`, or `colorblind` (Primer semantic colors; needs a truecolor +terminal such as Windows Terminal). Minimum size: 80×24. ## Views and keys -The invocation tree retains finished executions. The result pane shows the -selected execution's task, original response/error, recorded duration, and last -activity. The tool pane can display each recorded tool result. Long text is -loaded from the selected log on demand rather than copied to a new database. +The layout follows Copilot CLI's own interface: a tab bar, blue query chips, a +`·`-separated key-hint footer, and a bordered detail dialog. Two views share +one selection: + +- **Graph** (default): a spawn tree. The main session sits on top; agents sit + under the agent or session that launched them, and a reused agent's later + turn sits under its previous turn. Edge labels show `bg` / `sync` and any + recorded wait with its duration (yellow while waiting, dim once finished). + Wide trees scroll horizontally to keep the selection visible. +- **Timeline**: one swimlane per execution, with the main session's model + turns on the first lane. Bars are colored by outcome, `╍` marks time spent + waiting, `▾` connects a waiting lane to the execution it waited for, and the + `now` cursor follows the session. The default window covers the period in + which agents ran. + +The bottom dialog shows what the selected execution was asked (its task +prompt), its final message or error, and its recorded relationships. Tool calls +stay hidden unless you press `t`. Long text is loaded from the selected log on +demand rather than copied to a new database. | Key | Action | | --- | --- | -| Up/Down or `k`/`j` | Select an execution | -| Left/Right or `h`/`l` | Collapse/expand its children | -| Enter | Toggle collapse | -| `f` | Cycle all, active, finished views; ancestors remain as context | -| Tab | Switch between the recorded result and tool log | -| `[` / `]` | Select previous/next tool | -| Page Up / Page Down | Scroll details | -| Home / End | First/last detail page | +| Up/Down or `k`/`j` | Select the previous/next execution | +| `g` / `G` | Select the first/last execution | +| Left/Right or `h`/`l` | Graph: previous/next sibling. Timeline: pan earlier/later | +| `+` / `-` | Timeline: zoom in/out (beyond the agent window up to the whole session) | +| Tab | Switch between Graph and Timeline | +| `f` | Cycle all, active, finished; ancestors remain as dimmed context | +| `t` | Toggle tool calls / relationships in the detail dialog | +| Page Up / Page Down | Scroll the selected result | | `q` or Escape | Quit and restore the previous terminal screen | -`DONE` means an invocation has a recorded `subagent.completed` or agent-attributed -`session.task_complete` event. `REPLIED` +`done` means an invocation has a recorded `subagent.completed` or agent-attributed +`session.task_complete` event. `replied` means a response and turn end were recorded, but lifecycle completion was not. Follow-up executions on a reused agent remain separate from its earlier work. The finished filter includes replied executions without claiming the agent was @@ -75,6 +93,9 @@ records produce explicit diagnostics rather than fabricated results. - Detected source replacement/truncation rebuilds the view. A trailing record without a newline remains buffered until it is complete. Reading stops on a source error, with a visible diagnostic and a nonzero exit status on quit. +- While following a live log, open executions extend to the current time. A + recorded `session.shutdown` switches the header to `ended` and stops the clock. +- Below 100 columns the detail dialog shows only the task and result. Text from the log is rendered without terminal control sequences. Session content stays local: do not copy private transcripts into this repository. @@ -90,5 +111,6 @@ npm run build ``` Tests use synthetic records and temporary files, covering replay/tailing, -UTF-8 boundaries, source reset, agent/result attribution, explicit waits, -reused agents, completed-history navigation, filtering, resizing, and scrolling. +UTF-8 boundaries, source reset, agent/result attribution, recorded wait history, +reused agents and later turns, graph layout, the Graph/Timeline views, +filtering, hidden-by-default tool calls, resizing, and result scrolling. diff --git a/tools/agent-monitor/package-lock.json b/tools/agent-monitor/package-lock.json index b6f652e8..c38cf931 100644 --- a/tools/agent-monitor/package-lock.json +++ b/tools/agent-monitor/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "ink": "^8.0.0", "react": "^19.3.1", - "wrap-ansi": "^9.0.0" + "string-width": "^8.3.0" }, "bin": { "agent-monitor": "dist/cli.js" @@ -647,12 +647,6 @@ "devOptional": true, "license": "MIT" }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha1-vz1uj3+P0ipl2XA0dbwBRzV6aw0=", - "license": "MIT" - }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/environment/-/environment-1.1.0.tgz", @@ -1151,52 +1145,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha1-lWgy3qlJQwbm0gnrhxZDu4c9fJg=", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha1-wETV3MUhoHZBNHJZehrLHxA8QEE=", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha1-tbuOIWXOJ11NQ0dt0nAK2Qkdttw=", - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/yoga-layout/-/yoga-layout-3.2.1.tgz", diff --git a/tools/agent-monitor/package.json b/tools/agent-monitor/package.json index c053007f..b34eee6b 100644 --- a/tools/agent-monitor/package.json +++ b/tools/agent-monitor/package.json @@ -19,7 +19,7 @@ "dependencies": { "ink": "^8.0.0", "react": "^19.3.1", - "wrap-ansi": "^9.0.0" + "string-width": "^8.3.0" }, "devDependencies": { "@types/node": "^22.0.0", diff --git a/tools/agent-monitor/src/App.tsx b/tools/agent-monitor/src/App.tsx index 0bbf2328..34a5b1b4 100644 --- a/tools/agent-monitor/src/App.tsx +++ b/tools/agent-monitor/src/App.tsx @@ -1,193 +1,189 @@ -import {useEffect, useMemo, useState} from 'react'; +import {useEffect, useMemo, useRef, useState} from 'react'; import {Box, Text, useApp, useInput, useWindowSize} from 'ink'; -import {stripVTControlCharacters} from 'node:util'; -import wrapAnsi from 'wrap-ansi'; +import {drawChrome, drawInspector, frameLayout, promptRef, resultRef} from './chrome.js'; +import type {DetailTexts, Frame, ViewState} from './chrome.js'; import {errorMessage, JsonlReader} from './events.js'; import type {ContentRef} from './events.js'; -import {AgentModel, isFinished} from './model.js'; -import type {AgentRun, Filter} from './model.js'; +import {drawGraph} from './graph.js'; +import {AgentModel} from './model.js'; +import type {Filter} from './model.js'; +import {Screen} from './screen.js'; +import {PALETTES} from './theme.js'; +import type {ColorMode} from './theme.js'; +import {drawTimeline} from './timeline.js'; +import {buildTree, MAIN, nowSeconds, visualOf} from './tree.js'; + +export const MIN_COLS = 80; +export const MIN_ROWS = 24; +const NEXT_FILTER: Record = {all: 'active', active: 'finished', finished: 'all'}; interface AppProps { model: AgentModel; revision?: number; loadContent: (ref: ContentRef) => Promise; - source: string; + source?: Frame['source']; diagnostic?: string; + mode?: ColorMode; width?: number; height?: number; + clock?: () => number; } -function safeText(value: string): string { - return stripVTControlCharacters(value).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, ''); -} - -function singleLine(value: string): string { - return safeText(value).replace(/\s+/g, ' '); -} - -function label(status: string): string { - return ({completed: 'DONE', replied: 'REPLIED'}[status] ?? status.toUpperCase()); -} - -function duration(run: AgentRun): string { - const ms = run.durationMs ?? (run.startedAt && run.endedAt - ? Date.parse(run.endedAt) - Date.parse(run.startedAt) : undefined); - return ms === undefined ? 'not recorded' : `${(Math.max(0, ms) / 1000).toFixed(1)}s`; -} - -export function App({model, revision = 0, loadContent, source, diagnostic, width, height}: AppProps) { +export function App({model, loadContent, source = 'following', diagnostic, mode = 'default', width, height, clock = Date.now}: AppProps) { const size = useWindowSize(); const {exit} = useApp(); - const [filter, setFilter] = useState('all'); - const [collapsed, setCollapsed] = useState>(new Set()); - const [selectedId, setSelectedId] = useState(); - const [mode, setMode] = useState<'result' | 'tools'>('result'); - const [toolIndex, setToolIndex] = useState(0); - const [scroll, setScroll] = useState(0); - const [loaded, setLoaded] = useState<{ref?: ContentRef; text: string; error?: string}>({text: ''}); - const columns = width ?? size.columns; + const cols = width ?? size.columns; const rows = height ?? size.rows; - const treeHeight = Math.max(2, Math.floor((rows - 7) * 0.45)); - const detailHeight = Math.max(1, rows - treeHeight - 7); - const tree = useMemo(() => model.tree(filter, collapsed), [model, revision, filter, collapsed]); - const selectedIndex = Math.max(0, tree.findIndex(row => row.run.id === selectedId)); - const selected = tree[selectedIndex]?.run; - const tool = selected?.tools[Math.min(toolIndex, Math.max(0, selected.tools.length - 1))]; - const ref = mode === 'tools' ? tool?.output : selected?.error ?? selected?.result; - - useEffect(() => { - setToolIndex(0); - setScroll(0); - }, [selected?.id, mode]); + const state = useRef({ + view: 'graph', filter: 'all', selectedId: MAIN, showTools: false, zoom: 1, pan: 0, detailScroll: 0, graphX: 0, graphY: 0, laneTop: 0, span: 0, + }).current; + const [, setTick] = useState(0); + const [spin, setSpin] = useState(0); + const rerender = (): void => setTick(tick => tick + 1); + const tree = buildTree(model, state.filter); + if (!tree.byId.has(state.selectedId)) state.selectedId = (tree.order.find(node => !node.ctx) ?? tree.root).id; + const node = tree.byId.get(state.selectedId)!; + const result = resultRef(model, node); + const prompt = promptRef(model, node); + const textKey = `${node.id}|${result?.eventId ?? ''}|${prompt?.eventId ?? ''}`; + const [loaded, setLoaded] = useState<{key: string; texts: DetailTexts}>({key: '', texts: {pending: false}}); useEffect(() => { let cancelled = false; - if (ref) { - void loadContent(ref).then( - text => { if (!cancelled) setLoaded({ref, text}); }, - error => { if (!cancelled) setLoaded({ref, text: '', error: errorMessage(error)}); }, - ); - } + void Promise.allSettled([result ? loadContent(result) : undefined, prompt ? loadContent(prompt) : undefined]).then(([r, p]) => { + if (cancelled) return; + setLoaded({ + key: textKey, + texts: { + pending: false, + result: r.status === 'fulfilled' ? r.value : undefined, + error: r.status === 'rejected' ? errorMessage(r.reason) : undefined, + prompt: p.status === 'fulfilled' ? p.value : undefined, + }, + }); + }); return () => { cancelled = true; }; - }, [ref, loadContent]); + }, [textKey, loadContent]); - let content = mode === 'tools' ? 'No textual tool output recorded.' : 'Final output unavailable in the recorded source.'; - if (ref) { - content = loaded.ref !== ref ? 'Loading recorded text...' - : loaded.error ? `Cannot read recorded text: ${loaded.error}` : loaded.text; - } - const details = selected ? [ - `Task: ${selected.task}`, - `Agent: ${selected.agentId}`, - `Execution: ${selected.id}`, - `Elapsed: ${duration(selected)} | Last observed: ${selected.lastActivity}`, - ...(selected.status === 'replied' ? ['Reply recorded; lifecycle completion was not recorded.'] : []), - ...(selected.status === 'running' ? ['Last observed running; a quiet log does not prove current liveness.'] : []), - ...([...model.waits.values()].filter(edge => edge.fromRunId === selected.id).map(edge => - `waits-for [${edge.kind}]: ${edge.toAgentId}`)), - 'Prerequisite DAG: unavailable (not recorded by this verified format).', - '', - mode === 'tools' ? `Tool ${Math.min(toolIndex + 1, selected.tools.length)}/${selected.tools.length}: ${tool?.name ?? 'none'} [${tool?.status ?? 'not recorded'}]` : 'Recorded result:', - content, - ].join('\n') : 'No agents match this filter. Waiting for recorded subagent events.'; - const lines = wrapAnsi(safeText(details), Math.max(1, columns), {hard: true, trim: false}).split('\n'); - const visibleScroll = Math.min(scroll, Math.max(0, lines.length - detailHeight)); - const treeStart = Math.max(0, selectedIndex - treeHeight + 1); - const visibleTree = tree.slice(treeStart, treeStart + treeHeight); + const animating = tree.order.some(n => visualOf(model, n) === 'running'); + useEffect(() => { + if (!animating) return; + const timer = setInterval(() => setSpin(value => value + 1), 250); + return () => clearInterval(timer); + }, [animating]); + useEffect(() => { + if (source !== 'following') return; + const timer = setInterval(rerender, 1000); + return () => clearInterval(timer); + }, [source]); + const now = nowSeconds(model, clock()); useInput((input, key) => { - if (input === 'q' || key.escape) exit(); - else if (input === 'f') { - setFilter(current => current === 'all' ? 'active' : current === 'active' ? 'finished' : 'all'); - setScroll(0); - } else if (key.tab) setMode(current => current === 'result' ? 'tools' : 'result'); - else if (key.pageDown) setScroll(visibleScroll + detailHeight); - else if (key.pageUp) setScroll(Math.max(0, visibleScroll - detailHeight)); - else if (key.home) setScroll(0); - else if (key.end) setScroll(Math.max(0, lines.length - detailHeight)); - else if ((input === '[' || input === ']') && selected) { - setToolIndex(current => Math.max(0, Math.min(selected.tools.length - 1, current + (input === ']' ? 1 : -1)))); - setScroll(0); - } else if ((key.upArrow || input === 'k' || key.downArrow || input === 'j') && tree.length) { - const index = Math.max(0, Math.min(tree.length - 1, selectedIndex + (key.upArrow || input === 'k' ? -1 : 1))); - setSelectedId(tree[index]?.run.id); - } else if (selected && (key.leftArrow || input === 'h' || key.rightArrow || input === 'l' || key.return)) { - setCollapsed(current => { - const next = new Set(current); - const close = key.leftArrow || input === 'h' || (key.return && !current.has(selected.id)); - if (close) next.add(selected.id); - else next.delete(selected.id); - return next; - }); + const order = tree.order; + const index = order.findIndex(n => n.id === state.selectedId); + const select = (target: number): void => { + state.selectedId = order[clamp(target, 0, order.length - 1)]!.id; + state.detailScroll = 0; + }; + const siblings = node.parent?.children ?? [node]; + const sibling = siblings.indexOf(node); + const pageStep = Math.max(1, frameLayout(cols, rows).box.h - 6); + const panStep = Math.max(1, (state.span || now) / 4); + if (input === 'q' || key.escape) { + exit(); + return; } + if (key.tab) state.view = state.view === 'graph' ? 'timeline' : 'graph'; + else if (input === 'j' || key.downArrow) select(index + 1); + else if (input === 'k' || key.upArrow) select(index - 1); + else if (input === 'g' || key.home) select(0); + else if (input === 'G' || key.end) select(order.length - 1); + else if (input === 'f') { + state.filter = NEXT_FILTER[state.filter]; + state.laneTop = 0; + } else if (input === 't') state.showTools = !state.showTools; + else if (key.pageDown) state.detailScroll += pageStep; + else if (key.pageUp) state.detailScroll = Math.max(0, state.detailScroll - pageStep); + else if (state.view === 'timeline' && (input === '+' || input === '=')) state.zoom = Math.min(64, state.zoom * 2); + else if (state.view === 'timeline' && input === '-') { + state.zoom = Math.max(1 / 64, state.zoom / 2); + if (state.zoom === 1) state.pan = 0; + } else if (state.view === 'timeline' && (key.leftArrow || input === 'h')) state.pan += panStep; + else if (state.view === 'timeline' && (key.rightArrow || input === 'l')) state.pan -= panStep; + else if (state.view === 'graph' && (key.leftArrow || input === 'h') && sibling > 0) state.selectedId = siblings[sibling - 1]!.id; + else if (state.view === 'graph' && (key.rightArrow || input === 'l') && sibling < siblings.length - 1) state.selectedId = siblings[sibling + 1]!.id; + else return; + rerender(); }); - if (rows < 12 || columns < 28) { - return Terminal too small. Resize to 28x12 or larger. q: quit; + if (cols < MIN_COLS || rows < MIN_ROWS) { + return {`Terminal too small for the agent monitor (need ${MIN_COLS}x${MIN_ROWS}, have ${cols}x${rows}). q: quit`}; } - - const all = [...model.runs.values()]; - const finished = all.filter(isFinished).length; - const warning = diagnostic ?? [...model.warnings].join('; '); - const mainWaits = [...model.waits.values()].filter(edge => edge.fromRunId === null); + const texts: DetailTexts = loaded.key === textKey ? loaded.texts : {pending: Boolean(result || prompt)}; + const warnings = [...model.warnings].join('; '); + const frame: Frame = { + model, state, tree, now, spin, cols, rows, source, texts, + diagnostic: diagnostic ?? (warnings || undefined), + }; + const layout = frameLayout(cols, rows); + const screen = new Screen(cols, rows); + drawChrome(screen, frame, layout); + if (state.view === 'graph') drawGraph(screen, frame, layout.content, layout.legendY); + else drawTimeline(screen, frame, layout.content, layout.legendY); + drawInspector(screen, frame, layout.box); + const palette = PALETTES[mode]; return ( - - Agent monitor | {all.length - finished} active/unknown | {finished} finished/replied - Source: {singleLine(source)} | CLI {model.version ?? 'not recorded'} - - {warning ? singleLine(warning) : `Observed events only | filter: ${filter} | main${mainWaits.length ? ` waits-for: ${mainWaits.map(edge => edge.toAgentId).join(', ')}` : ''}`} - - - {visibleTree.length ? visibleTree.map(({run, depth, hasChildren, unresolvedParent}) => { - const status = model.status(run); - const marker = hasChildren ? collapsed.has(run.id) ? '+' : '-' : '.'; - const context = filter !== 'all' && (filter === 'finished' ? !isFinished(run) : isFinished(run)) ? ' [context]' : ''; - const waits = [...model.waits.values()].filter(edge => edge.fromRunId === run.id) - .map(edge => edge.toAgentId).join(', '); - return - {`${run.id === selected?.id ? '>' : ' '} ${' '.repeat(Math.min(depth, Math.floor(columns / 8)))}${marker} [${label(status)}] ${singleLine(run.name)}${unresolvedParent ? ' [? parent]' : ''}${context}${waits ? ` -> waits-for: ${singleLine(waits)}` : ''}: ${singleLine(run.task)}`} - ; - }) : No matching agents.} - - {mode === 'result' ? 'Result' : 'Tool log'} | {singleLine(selected?.name ?? 'none')} | lines {visibleScroll + 1}-{Math.min(lines.length, visibleScroll + detailHeight)}/{lines.length} - - {lines.slice(visibleScroll, visibleScroll + detailHeight).map((line, index) => - {line || ' '})} - - q quit | j/k select | h/l fold | f filter | Tab result/tools | [] tool | PgUp/Dn scroll + + {screen.segments().map((segments, y) => ( + + {segments.map((segment, i) => ( + + {segment.text} + + ))} + + ))} ); } -export function LiveApp({file, onError}: {file: string; onError: (message: string) => void}) { +function clamp(value: number, lo: number, hi: number): number { + return Math.max(lo, Math.min(hi, value)); +} + +export function LiveApp({file, mode, onError}: {file: string; mode: ColorMode; onError: (message: string) => void}) { const reader = useMemo(() => new JsonlReader(file), [file]); const model = useMemo(() => new AgentModel(), [file]); const [revision, setRevision] = useState(0); const [diagnostic, setDiagnostic] = useState(); + const [stopped, setStopped] = useState(false); const loadContent = useMemo(() => (ref: ContentRef) => reader.readContent(ref), [reader]); useEffect(() => { - let stopped = false; + let active = true; let timer: ReturnType; const poll = async (): Promise => { try { const changed = await reader.poll(record => model.apply(record), () => { model.reset(); - if (!stopped) setDiagnostic('Session log was replaced/truncated; view rebuilt from the new source.'); + if (active) setDiagnostic('Session log was replaced/truncated; view rebuilt from the new source.'); }); - if (!stopped) { - if (changed) setRevision(current => current + 1); - timer = setTimeout(() => { void poll(); }, 400); - } + if (!active) return; + if (changed) setRevision(value => value + 1); + timer = setTimeout(() => { void poll(); }, 400); } catch (error) { - if (!stopped) { - const message = `Source stopped: ${errorMessage(error)}`; - setDiagnostic(message); - onError(message); - } + if (!active) return; + const message = `Source stopped: ${errorMessage(error)}`; + setDiagnostic(message); + setStopped(true); + onError(message); } }; void poll(); - return () => { stopped = true; clearTimeout(timer); }; + return () => { + active = false; + clearTimeout(timer); + }; }, [reader, model, onError]); - return ; + const source = stopped ? 'stopped' : model.sessionEnded ? 'ended' : 'following'; + return ; } diff --git a/tools/agent-monitor/src/chrome.ts b/tools/agent-monitor/src/chrome.ts new file mode 100644 index 00000000..ee553a19 --- /dev/null +++ b/tools/agent-monitor/src/chrome.ts @@ -0,0 +1,227 @@ +import type {ContentRef} from './events.js'; +import type {AgentModel, Filter} from './model.js'; +import {fit, Screen, width, wrap} from './screen.js'; +import type {Part, Style} from './screen.js'; +import { + elapsed, formatDuration, glyphPart, isActiveVisual, nodeType, runLabel, sessionSeconds, + VISUALS, visualOf, waitLabel, waitSpan, +} from './tree.js'; +import type {Node, Tree} from './tree.js'; + +export type ViewName = 'graph' | 'timeline'; +export const VIEWS: ViewName[] = ['graph', 'timeline']; +const TITLES: Record = {graph: 'Graph', timeline: 'Timeline'}; + +export interface ViewState { + view: ViewName; + filter: Filter; + selectedId: string; + showTools: boolean; + zoom: number; + pan: number; + detailScroll: number; + graphX: number; + graphY: number; + laneTop: number; + span: number; +} + +export interface DetailTexts { + prompt?: string; + result?: string; + pending: boolean; + error?: string; +} + +export interface Frame { + model: AgentModel; + state: ViewState; + tree: Tree; + now: number; + spin: number; + cols: number; + rows: number; + source: 'following' | 'ended' | 'stopped'; + diagnostic?: string; + texts: DetailTexts; +} + +export interface Rect { + x: number; + y: number; + w: number; + h: number; +} + +export interface Layout { + content: Rect; + legendY: number; + box: Rect; + footerY: number; +} + +const MUTED: Style = {fg: 'muted'}; + +export function frameLayout(cols: number, rows: number): Layout { + const boxH = rows >= 34 ? 10 : rows >= 28 ? 9 : 7; + const box = {x: 1, y: rows - 2 - boxH, w: cols - 2, h: boxH}; + const legendY = box.y - 2; + return {content: {x: 1, y: 4, w: cols - 2, h: legendY - 4}, legendY, box, footerY: rows - 1}; +} + +export function selectedNode(frame: Frame): Node { + return frame.tree.byId.get(frame.state.selectedId) ?? frame.tree.root; +} + +export function resultRef(model: AgentModel, node: Node): ContentRef | undefined { + if (!node.run) return model.mainResult; + return node.run.status === 'failed' ? node.run.error ?? node.run.result : node.run.result; +} + +export function promptRef(model: AgentModel, node: Node): ContentRef | undefined { + return node.run ? node.run.prompt : model.mainPrompt; +} + +export function drawChrome(scr: Screen, frame: Frame, layout: Layout): void { + let x = 1; + for (const view of VIEWS) { + const on = view === frame.state.view; + x = scr.put(x, 0, ` ${TITLES[view]} `, on ? {fg: 'onEm', bg: 'accentEm', bold: true} : {fg: 'muted', bg: 'tab'}) + 1; + } + const zoom = frame.state.zoom; + const windowLabel = zoom === 1 && Math.abs(frame.state.pan) < 0.5 ? 'agent activity' : zoom >= 1 ? `${zoom}x` : `1/${Math.round(1 / zoom)}x`; + const chips: Array<[string, string]> = [ + ['status:', frame.state.filter], + frame.state.view === 'graph' ? ['edges:', 'spawn,wait'] : ['window:', windowLabel], + ['session:', (frame.model.sessionId ?? 'unknown').slice(0, 8)], + ]; + x = 1; + for (const [key, value] of chips) { + x = scr.put(x, 2, key, {fg: 'fg'}); + x = scr.put(x, 2, ` ${value} `, {fg: 'accent', bg: 'chip'}) + 2; + } + const index = frame.tree.order.findIndex(node => node.id === frame.state.selectedId); + const indicator: Part = frame.source === 'following' ? ['●', {fg: 'success'}] : frame.source === 'ended' ? ['○', MUTED] : ['✗', {fg: 'danger'}]; + const right: Part[] = [ + indicator, [` ${frame.source}`, {fg: 'fg'}], [` ${formatDuration(frame.now)}`, MUTED], + [` ${index + 1} / ${frame.tree.order.length}`, MUTED], + ]; + const rightWidth = right.reduce((sum, [text]) => sum + width(text), 0); + scr.seq(frame.cols - 1 - rightWidth, 2, right); + if (frame.diagnostic) scr.put(1, 3, fit(frame.diagnostic, frame.cols - 2), {fg: 'attention'}); + const hints: Array<[string, string]> = frame.state.view === 'graph' + ? [['j/k', 'node'], ['tab', 'timeline'], ['f', 'filter'], ['t', frame.state.showTools ? 'relationships' : 'tools'], ['pgup/pgdn', 'detail'], ['q', 'quit']] + : [['j/k', 'lane'], ['+/-', 'zoom'], ['←/→', 'pan'], ['tab', 'graph'], ['f', 'filter'], ['t', frame.state.showTools ? 'relationships' : 'tools'], ['q', 'quit']]; + const parts: Part[] = []; + hints.forEach(([key, label], i) => { + if (i) parts.push([' · ', MUTED]); + parts.push([key, {fg: 'fg', bold: true}], [` ${label}`, MUTED]); + }); + scr.seqFit(1, layout.footerY, parts, frame.cols - 2); +} + +function metaLine(frame: Frame, node: Node): string { + const {model, now} = frame; + if (!node.run) { + const agents = frame.model.runs.size; + return [model.sessionModel, 'interactive session', formatDuration(now), `${agents} agent run${agents === 1 ? '' : 's'}`].filter(Boolean).join(' · '); + } + const run = node.run; + return [run.name, nodeType(node), run.model, run.mode, formatDuration(elapsed(model, node, now))].filter(Boolean).join(' · '); +} + +function resultHeading(frame: Frame, node: Node): [string, string, Style] { + const visual = visualOf(frame.model, node); + if (visual === 'failed') return ['Error', 'from subagent.failed', {fg: 'danger', bold: true}]; + if (visual === 'replied') return ['Reply', 'turn ended · no completion event', {fg: 'fg', bold: true}]; + if (!node.run || isActiveVisual(visual)) return ['Latest message', isActiveVisual(visual) ? 'last observed · still running' : '', {fg: 'fg', bold: true}]; + return ['Result', 'final message', {fg: 'fg', bold: true}]; +} + +function relationships(frame: Frame, node: Node): Part[][] { + const {model, now} = frame; + const lines: Part[][] = []; + const key = node.run ? node.run.id : null; + if (!node.run) { + const launched = [...model.runs.values()].filter(run => run.parentRunId === null && !run.previousRunId); + const sync = launched.filter(run => run.mode === 'sync').length; + lines.push([['↳ ', MUTED], [`launched ${launched.length} agent${launched.length === 1 ? '' : 's'} · ${launched.length - sync} background, ${sync} sync`, {fg: 'fg'}]]); + } else if (node.edge === 'turn') lines.push([['┆ ', MUTED], [`later turn of ${node.parent?.run?.name ?? 'the same agent'}`, {fg: 'fg'}]]); + else if (node.edge === 'orphan') lines.push([['? ', MUTED], ['launcher not recorded', MUTED]]); + else lines.push([['↳ ', MUTED], [`spawned by ${runLabel(model, node.run.parentRunId ?? null)} · ${node.run.mode ?? 'unknown mode'} task`, {fg: 'fg'}]]); + const edges = [...model.waitLog.values()].sort((a, b) => Number(Boolean(a.end)) - Number(Boolean(b.end))); + for (const edge of edges) { + const [start, end] = waitSpan(model, edge, now); + const active = !edge.end; + const kind = edge.kind === 'sync-call' ? 'sync task' : 'read_agent'; + const tone: Style = {fg: 'attention', dim: !active}; + if (edge.fromRunId === key) lines.push([[active ? '◌ ' : '⇢ ', tone], [`${active ? 'waiting on' : 'waited on'} ${waitLabel(model, edge)} · ${kind} · ${formatDuration(end - start)}`, {fg: 'fg'}]]); + if (node.run && edge.toRunId === node.run.id) lines.push([[active ? '◌ ' : '⇠ ', tone], [`${active ? 'blocking' : 'waited on by'} ${runLabel(model, edge.fromRunId)} · ${kind} · ${formatDuration(end - start)}`, {fg: 'fg'}]]); + } + return lines; +} + +function toolLines(frame: Frame, node: Node, rows: number, w: number, scr: Screen, x: number, y: number): void { + const tools = node.run ? node.run.tools : frame.model.mainTools; + const shown = tools.slice(-rows); + if (!shown.length) scr.put(x, y, 'No tool calls recorded.', MUTED); + shown.forEach((tool, i) => { + const visual = tool.status === 'running' ? 'running' : tool.status === 'failed' ? 'failed' : 'done'; + const end = tool.endedAt ? sessionSeconds(frame.model, tool.endedAt) : frame.now; + const duration = formatDuration(end - sessionSeconds(frame.model, tool.startedAt)); + scr.seq(x, y + i, [glyphPart(visual, frame.spin)]); + scr.put(x + 2, y + i, fit(tool.name, 12), {fg: 'fg'}); + scr.rput(x + w - 1, y + i, duration, tool.status === 'running' ? {fg: 'fg'} : MUTED); + scr.put(x + 15, y + i, fit(tool.detail ?? '', w - 17 - width(duration)), MUTED); + }); +} + +/** Bottom dialog: what the selected execution was asked, what it produced, and how it relates to others. */ +export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): void { + const node = selectedNode(frame); + const visual = visualOf(frame.model, node); + scr.box(box.x, box.y, box.w, box.h, {fg: 'border'}); + const x = box.x + 2; + const top = box.y + 1; + const rows = box.h - 2; + const inner = box.w - 4; + const split = inner >= 96; + const rightW = split ? Math.min(52, Math.floor(inner * 0.42)) : 0; + const leftW = split ? inner - rightW - 3 : inner; + const title = node.run ? node.run.task : 'Main session'; + scr.seq(x, top, [glyphPart(visual, frame.spin)]); + scr.rput(x + leftW - 1, top, VISUALS[visual].label, {fg: VISUALS[visual].tone}); + scr.put(x + 2, top, fit(title, leftW - 4 - width(VISUALS[visual].label)), {fg: 'fg', bold: true}); + scr.put(x, top + 1, fit(metaLine(frame, node), leftW), MUTED); + const prompt = frame.texts.prompt ?? (node.run ? node.run.task : undefined) ?? 'Not recorded'; + scr.seqFit(x, top + 2, [['Task ', {fg: 'fg', bold: true}], [prompt.replace(/\s+/g, ' '), MUTED]], leftW); + const [heading, note, headingStyle] = resultHeading(frame, node); + const bodyTop = top + 4; + const bodyRows = Math.max(0, rows - 4); + let body: string[]; + if (frame.texts.pending) body = ['Loading recorded text…']; + else if (frame.texts.error) body = [`Cannot read recorded text: ${frame.texts.error}`]; + else body = frame.texts.result ? wrap(frame.texts.result, leftW) : ['No final message recorded.']; + const maxScroll = Math.max(0, body.length - bodyRows); + frame.state.detailScroll = Math.min(frame.state.detailScroll, maxScroll); + const scroll = frame.state.detailScroll; + const position = body.length > bodyRows ? `lines ${scroll + 1}-${Math.min(body.length, scroll + bodyRows)} of ${body.length}` : note; + if (rows > 3) { + scr.put(x, top + 3, heading, headingStyle); + if (position) scr.rput(x + leftW - 1, top + 3, fit(position, leftW - width(heading) - 2), MUTED); + } + body.slice(scroll, scroll + bodyRows).forEach((line, i) => scr.put(x, bodyTop + i, line, visual === 'failed' ? {fg: 'danger'} : {fg: 'fg'})); + if (!split) return; + const rx = x + leftW + 3; + scr.vline(x + leftW + 1, top, rows, '│', {fg: 'border'}); + if (frame.state.showTools) { + const tools = node.run ? node.run.tools : frame.model.mainTools; + scr.put(rx, top, `Tools · ${tools.length}`, {fg: 'fg', bold: true}); + scr.rput(rx + rightW - 1, top, tools.length > rows - 1 ? `latest ${rows - 1}` : 't to hide', MUTED); + toolLines(frame, node, rows - 1, rightW, scr, rx, top + 1); + return; + } + scr.put(rx, top, 'Relationships', {fg: 'fg', bold: true}); + scr.rput(rx + rightW - 1, top, 'other deps: not recorded', MUTED); + relationships(frame, node).slice(0, rows - 1).forEach((parts, i) => scr.seqFit(rx, top + 1 + i, parts, rightW)); +} diff --git a/tools/agent-monitor/src/cli.tsx b/tools/agent-monitor/src/cli.tsx index 99b0ba91..5df45ccc 100644 --- a/tools/agent-monitor/src/cli.tsx +++ b/tools/agent-monitor/src/cli.tsx @@ -4,32 +4,36 @@ import {parseArgs} from 'node:util'; import {render} from 'ink'; import {LiveApp} from './App.js'; import {errorMessage} from './events.js'; +import {COLOR_MODES, isColorMode} from './theme.js'; async function main(): Promise { const {values} = parseArgs({ options: { session: {type: 'string'}, + theme: {type: 'string', default: 'default'}, help: {type: 'boolean', short: 'h'}, }, strict: true, allowPositionals: false, }); if (values.help) { - console.log('Usage: agent-monitor --session \n' + console.log('Usage: agent-monitor --session [--theme ]\n' + 'Read-only terminal viewer for that directory\'s events.jsonl.\n' - + 'Keys: q quit, j/k select, h/l fold, f filter, Tab result/tools,\n' - + ' [/] tool selection, PgUp/PgDn or Home/End scroll details.\n' + + `Themes: ${COLOR_MODES.join(', ')} (default: default).\n` + + 'Views: Graph (default) and Timeline; Tab switches.\n' + + 'Keys: j/k select, f filter, t tools, PgUp/PgDn detail, +/- zoom and ←/→ pan (Timeline), q quit.\n' + 'No CLI configuration changes, network calls, or session writes.'); return; } if (!values.session) throw new Error('--session is required (see --help)'); + if (!isColorMode(values.theme)) throw new Error(`--theme must be one of: ${COLOR_MODES.join(', ')}`); if (!process.stdin.isTTY || !process.stdout.isTTY) { throw new Error('An interactive terminal is required. Run this command in a separate terminal tab.'); } let sourceError: string | undefined; const onError = (message: string): void => { sourceError = message; }; const instance = render( - , + , {alternateScreen: true, patchConsole: false}, ); const terminate = (): void => { instance.unmount(); }; diff --git a/tools/agent-monitor/src/events.ts b/tools/agent-monitor/src/events.ts index 3bb82ac4..6606ba15 100644 --- a/tools/agent-monitor/src/events.ts +++ b/tools/agent-monitor/src/events.ts @@ -20,7 +20,7 @@ export interface EventRecord { location?: RecordLocation; } -type TextField = 'content' | 'summary' | 'result' | 'error'; +type TextField = 'content' | 'summary' | 'result' | 'error' | 'prompt'; export interface ContentRef { eventId: string; @@ -62,6 +62,10 @@ function decode(line: Buffer): SessionEvent { } function textAt(event: SessionEvent, field: TextField): string | undefined { + if (field === 'prompt') { + const args = event.data.arguments; + return isObject(args) && typeof args.prompt === 'string' ? args.prompt : undefined; + } if (field === 'result') { if (!isObject(event.data.result)) return undefined; return optionalString(event.data.result, 'detailedContent') diff --git a/tools/agent-monitor/src/graph.ts b/tools/agent-monitor/src/graph.ts new file mode 100644 index 00000000..0c5b7d00 --- /dev/null +++ b/tools/agent-monitor/src/graph.ts @@ -0,0 +1,172 @@ +import type {Frame, Rect} from './chrome.js'; +import {fit, junction, Screen, width} from './screen.js'; +import type {Part, Style} from './screen.js'; +import {elapsed, formatDuration, glyphPart, MAIN, nodeType, runLabel, VISUALS, visualOf, waitSpan} from './tree.js'; +import type {Node, Visual} from './tree.js'; + +export const CARD_W = 26; +export const ROOT_W = 32; +export const CARD_H = 5; +export const ROOT_H = 4; +export const GAP = 2; +export const LEVEL = CARD_H + 2; + +export interface Placed { + node: Node; + x: number; + y: number; + w: number; + h: number; +} + +export interface GraphLayout { + placed: Map; + width: number; + height: number; +} + +const MUTED: Style = {fg: 'muted'}; +const cardWidth = (node: Node): number => node.id === MAIN ? ROOT_W : CARD_W; +const centerOf = (p: Placed): number => p.x + Math.floor(p.w / 2); +const clamp = (value: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, value)); + +/** Row of a tree level: the root card is shorter, then each level adds a card plus a bus row and an arrow row. */ +export const levelY = (depth: number): number => depth === 0 ? 0 : ROOT_H + 2 + (depth - 1) * LEVEL; + +/** Top-down tidy layout: each subtree gets its own column span and parents sit centred over their children. */ +export function layoutGraph(root: Node): GraphLayout { + const spans = new Map(); + const childSpan = (node: Node): number => + node.children.reduce((sum, child) => sum + spans.get(child.id)!, 0) + GAP * Math.max(0, node.children.length - 1); + const measure = (node: Node): void => { + node.children.forEach(measure); + spans.set(node.id, Math.max(cardWidth(node), childSpan(node))); + }; + measure(root); + const placed = new Map(); + let deepest = 0; + const place = (node: Node, left: number, depth: number): void => { + const total = spans.get(node.id)!; + const w = cardWidth(node); + deepest = Math.max(deepest, depth); + let cursor = left + Math.floor((total - childSpan(node)) / 2); + for (const child of node.children) { + place(child, cursor, depth + 1); + cursor += spans.get(child.id)! + GAP; + } + let x = left + Math.floor((total - w) / 2); + if (node.children.length) { + const mid = Math.floor((centerOf(placed.get(node.children[0]!.id)!) + centerOf(placed.get(node.children.at(-1)!.id)!)) / 2); + x = clamp(mid - Math.floor(w / 2), left, left + total - w); + } + placed.set(node.id, {node, x, y: levelY(depth), w, h: node.id === MAIN ? ROOT_H : CARD_H}); + }; + place(root, 0, 0); + return {placed, width: spans.get(root.id)!, height: deepest === 0 ? ROOT_H : levelY(deepest) + CARD_H}; +} + +function edgeLabel(frame: Frame, parent: Node, child: Node): [Style, Part[]] { + const {model, now} = frame; + const parentKey = parent.run ? parent.run.id : null; + const waits = [...model.waitLog.values()].filter(edge => edge.toRunId === child.id && (child.edge === 'turn' || edge.fromRunId === parentKey)); + const active = waits.some(edge => !edge.end); + const parts: Part[] = []; + if (child.edge === 'turn') parts.push(['later turn', MUTED]); + else if (child.edge === 'orphan') parts.push(['? launcher', MUTED]); + else parts.push([child.run?.mode === 'sync' ? 'sync' : child.run?.mode === 'background' ? 'bg' : '?', MUTED]); + for (const edge of waits) { + const [start, end] = waitSpan(model, edge, now); + const who = edge.fromRunId !== parentKey ? `${runLabel(model, edge.fromRunId)} ` : ''; + parts.push([' · ', MUTED], [`${who}${edge.end ? 'waited' : 'waiting'} ${formatDuration(end - start)}`, {fg: 'attention', dim: Boolean(edge.end)}]); + } + const tone: Style = waits.length ? {fg: 'attention', dim: !active} : {fg: child.edge === 'turn' ? 'muted' : 'border'}; + return [tone, parts]; +} + +function statusLine(frame: Frame, node: Node, visual: Visual): Part[] { + const duration = formatDuration(elapsed(frame.model, node, frame.now)); + if (visual === 'waiting') { + const edge = [...frame.model.waits.values()].find(wait => wait.fromRunId === node.run?.id); + const [start, end] = edge ? waitSpan(frame.model, edge, frame.now) : [0, 0]; + return [[`waiting · ${formatDuration(end - start)}`, {fg: 'attention'}]]; + } + if (visual === 'unknown') return [['status not recorded', MUTED]]; + return [[duration, visual === 'running' ? {fg: 'fg'} : MUTED], [' · ', MUTED], [VISUALS[visual].label, {fg: VISUALS[visual].tone}]]; +} + +function drawCard(view: Screen, frame: Frame, p: Placed, ox: number, oy: number): void { + const {node} = p; + const visual = visualOf(frame.model, node); + const selected = node.id === frame.state.selectedId; + const dim = node.ctx && !selected; + const x = p.x - ox; + const y = p.y - oy; + const inner = p.w - 6; + view.box(x, y, p.w, p.h, {fg: selected ? 'accent' : 'border', dim}); + if (selected) view.fill(x + 1, y + 1, p.w - 2, p.h - 2, 'sel'); + view.seq(x + 2, y + 1, [glyphPart(visual, frame.spin, dim)]); + const nameStyle: Style = {fg: selected ? 'accent' : 'fg', bold: true, dim}; + if (!node.run) { + const label = VISUALS[visual].label; + view.rput(x + p.w - 3, y + 1, label, {fg: VISUALS[visual].tone, dim}); + view.put(x + 4, y + 1, fit('main session', inner - width(label) - 1), nameStyle); + const model = frame.model.sessionModel ?? 'model not recorded'; + view.put(x + 4, y + 2, fit(`${model} · ${formatDuration(frame.now)}`, inner), {...MUTED, dim}); + return; + } + view.put(x + 4, y + 1, fit(node.run.name, inner), nameStyle); + view.put(x + 4, y + 2, fit(nodeType(node), inner), {...MUTED, dim}); + view.seqFit(x + 4, y + 3, statusLine(frame, node, visual), inner, dim ? {dim: true} : {}); +} + +export function drawGraph(scr: Screen, frame: Frame, area: Rect, legendY: number): void { + const layout = layoutGraph(frame.tree.root); + const selected = layout.placed.get(frame.state.selectedId) ?? layout.placed.get(MAIN)!; + let ox: number; + if (layout.width <= area.w) ox = -Math.floor((area.w - layout.width) / 2); + else { + ox = clamp(frame.state.graphX, 0, layout.width - area.w); + if (selected.x < ox) ox = selected.x; + if (selected.x + selected.w > ox + area.w) ox = selected.x + selected.w - area.w; + frame.state.graphX = ox; + } + let oy = 0; + if (layout.height > area.h) { + oy = clamp(frame.state.graphY, 0, layout.height - area.h); + if (selected.y < oy) oy = selected.y; + if (selected.y + selected.h > oy + area.h) oy = selected.y + selected.h - area.h; + frame.state.graphY = oy; + } + const view = new Screen(area.w, area.h); + const border: Style = {fg: 'border'}; + for (const p of layout.placed.values()) { + if (!p.node.children.length) continue; + const pc = centerOf(p); + const busY = p.y + p.h; + const centers = p.node.children.map(child => centerOf(layout.placed.get(child.id)!)); + const lo = Math.min(pc, ...centers); + const hi = Math.max(pc, ...centers); + for (let c = lo; c <= hi; c++) view.put(c - ox, busY - oy, junction(c === pc, centers.includes(c), c > lo, c < hi), border); + p.node.children.forEach((child, i) => { + const cc = centers[i]!; + const next = centers[i + 1] ?? cc + CARD_W; + const [tone, label] = edgeLabel(frame, p.node, child); + view.put(cc - ox, busY + 1 - oy, '▾', tone); + view.seqFit(cc + 2 - ox, busY + 1 - oy, label, Math.max(0, next - cc - 3)); + }); + } + for (const p of layout.placed.values()) drawCard(view, frame, p, ox, oy); + for (const p of layout.placed.values()) { + if (p.node.children.length) view.put(centerOf(p) - ox, p.y + p.h - 1 - oy, '┬', {fg: p.node.id === frame.state.selectedId ? 'accent' : 'border'}); + } + scr.blit(view, area.x, area.y); + if (ox > 0) scr.put(area.x, area.y + Math.floor(area.h / 2), '◂', {fg: 'accent'}); + if (layout.width - ox > area.w) scr.put(area.x + area.w - 1, area.y + Math.floor(area.h / 2), '▸', {fg: 'accent'}); + const legend: Part[] = [ + ['─▾', border], [' spawned (bg / sync) ', MUTED], ['▾', {fg: 'attention'}], [' waiting now ', MUTED], + ['▾', {fg: 'attention', dim: true}], [' waited (recorded) ', MUTED], ['▾', MUTED], [' later turn', MUTED], + ]; + const note = 'other deps: not recorded'; + scr.seqFit(3, legendY, legend, frame.cols - width(note) - 8); + scr.rput(frame.cols - 2, legendY, note, MUTED); +} diff --git a/tools/agent-monitor/src/model.ts b/tools/agent-monitor/src/model.ts index f5620497..2deb2fa6 100644 --- a/tools/agent-monitor/src/model.ts +++ b/tools/agent-monitor/src/model.ts @@ -8,6 +8,9 @@ export interface ToolActivity { id: string; name: string; status: 'running' | 'succeeded' | 'failed'; + startedAt: string; + endedAt?: string; + detail?: string; output?: ContentRef; } @@ -16,8 +19,11 @@ export interface AgentRun { agentId: string; name: string; task: string; + agentType?: string; + mode?: 'sync' | 'background'; parentRunId?: string | null; parentAgentId?: string; + previousRunId?: string; launchCallId?: string; status: RunStatus; startedAt?: string; @@ -25,6 +31,7 @@ export interface AgentRun { lastActivity: string; durationMs?: number; model?: string; + prompt?: ContentRef; result?: ContentRef; error?: ContentRef; tools: ToolActivity[]; @@ -40,6 +47,9 @@ interface ToolCall { targetAgentId?: string; wait: boolean; complete: boolean; + startedAt: string; + completedAt?: string; + prompt?: ContentRef; activity?: ToolActivity; } @@ -47,16 +57,20 @@ export interface WaitEdge { callId: string; fromRunId: string | null; toAgentId: string; + toRunId?: string; kind: 'sync-call' | 'wait'; + start: string; + end?: string; } -export interface TreeRow { - run: AgentRun; - depth: number; - hasChildren: boolean; - unresolvedParent: boolean; +export interface MainTurn { + start: string; + end?: string; } +const MAIN_TOOL_LIMIT = 200; +const DETAIL_KEYS = ['path', 'pattern', 'command', 'query', 'url', 'name', 'agent_id', 'description']; + export const isFinished = (run: AgentRun): boolean => run.status === 'completed' || run.status === 'failed' || run.status === 'replied'; @@ -64,8 +78,17 @@ export class AgentModel { readonly runs = new Map(); readonly warnings = new Set(); readonly waits = new Map(); + readonly waitLog = new Map(); + readonly mainTurns: MainTurn[] = []; + readonly mainTools: ToolActivity[] = []; version?: string; sessionId?: string; + sessionModel?: string; + sessionStart?: string; + sessionEnded?: string; + lastEventAt?: string; + mainPrompt?: ContentRef; + mainResult?: ContentRef; private readonly latest = new Map(); private readonly launches = new Map(); private readonly calls = new Map(); @@ -75,25 +98,41 @@ export class AgentModel { this.runs.clear(); this.warnings.clear(); this.waits.clear(); + this.waitLog.clear(); + this.mainTurns.length = 0; + this.mainTools.length = 0; this.latest.clear(); this.launches.clear(); this.calls.clear(); this.seen.clear(); this.version = undefined; this.sessionId = undefined; + this.sessionModel = undefined; + this.sessionStart = undefined; + this.sessionEnded = undefined; + this.lastEventAt = undefined; + this.mainPrompt = undefined; + this.mainResult = undefined; } apply(record: EventRecord): void { const e = record.event; if (this.seen.has(e.id)) return; this.seen.add(e.id); + if (!this.lastEventAt || Date.parse(e.timestamp) > Date.parse(this.lastEventAt)) this.lastEventAt = e.timestamp; + this.sessionStart ??= e.timestamp; const d = e.data; switch (e.type) { case 'session.start': this.version = optionalString(d, 'copilotVersion'); this.sessionId = optionalString(d, 'sessionId'); + this.sessionModel = optionalString(d, 'selectedModel'); + this.sessionStart = e.timestamp; if (this.version !== '1.0.104') this.warnings.add(`Untested CLI event format: ${this.version ?? 'unknown'}`); break; + case 'session.shutdown': + this.sessionEnded = e.timestamp; + break; case 'subagent.started': this.start(e); break; @@ -117,14 +156,17 @@ export class AgentModel { if (!current || isFinished(current) || (current.hasActivity && d.delivery === 'idle')) { const run = this.create(e, `message:${e.id}`, e.agentId); run.name = current?.name ?? run.name; + run.agentType = current?.agentType; run.parentRunId = current?.parentRunId; run.parentAgentId = current?.parentAgentId; + run.previousRunId = current?.id; run.model = current?.model; - run.task = optionalString(d, 'content') ?? 'Follow-up input not recorded'; + run.task = firstLine(optionalString(d, 'content')) ?? 'Follow-up input not recorded'; + run.prompt = capture(record, 'content'); run.startedAt = e.timestamp; run.status = 'running'; } - } + } else this.mainPrompt = capture(record, 'content') ?? this.mainPrompt; break; case 'assistant.turn_start': if (e.agentId) { @@ -132,6 +174,9 @@ export class AgentModel { run.hasActivity = true; run.lastActivity = e.timestamp; if (!isFinished(run) || run.status === 'replied') run.status = 'running'; + } else { + const last = this.mainTurns.at(-1); + if (!last || last.end) this.mainTurns.push({start: e.timestamp}); } break; case 'assistant.message': @@ -143,7 +188,10 @@ export class AgentModel { if (d.phase !== 'thinking' && (!Array.isArray(requests) || requests.length === 0)) { run.result = capture(record, 'content') ?? run.result; } + } else if (d.phase !== 'thinking' && (!Array.isArray(d.toolRequests) || d.toolRequests.length === 0)) { + this.mainResult = capture(record, 'content') ?? this.mainResult; } + if (!e.agentId) this.sessionModel = optionalString(d, 'model') ?? this.sessionModel; break; case 'assistant.turn_end': if (e.agentId) { @@ -153,6 +201,9 @@ export class AgentModel { run.status = 'replied'; run.endedAt = e.timestamp; } + } else { + const last = this.mainTurns.at(-1); + if (last && !last.end) last.end = e.timestamp; } break; case 'session.task_complete': @@ -165,7 +216,7 @@ export class AgentModel { } break; case 'tool.execution_start': - this.toolStart(e); + this.toolStart(record); break; case 'tool.execution_complete': this.toolComplete(record); @@ -184,54 +235,11 @@ export class AgentModel { return run.status; } - tree(filter: Filter, collapsed: ReadonlySet): TreeRow[] { - const included = new Set(); - for (const run of this.runs.values()) { - if (filter === 'all' || (filter === 'finished' ? isFinished(run) : !isFinished(run))) { - let node: AgentRun | undefined = run; - const ancestors = new Set(); - while (node && !ancestors.has(node.id)) { - ancestors.add(node.id); - included.add(node.id); - node = node.parentRunId ? this.runs.get(node.parentRunId) : undefined; - } - } - } - const children = new Map(); - const roots: AgentRun[] = []; - for (const run of this.runs.values()) { - if (!included.has(run.id)) continue; - if (run.parentRunId && this.runs.has(run.parentRunId)) { - const list = children.get(run.parentRunId) ?? []; - list.push(run); - children.set(run.parentRunId, list); - } else roots.push(run); - } - const rows: TreeRow[] = []; - const visited = new Set(); - const visit = (run: AgentRun, depth: number, unresolved = false): void => { - if (visited.has(run.id)) return; - visited.add(run.id); - const descendants = children.get(run.id) ?? []; - rows.push({ - run, depth, hasChildren: descendants.length > 0, - unresolvedParent: unresolved || run.parentRunId === undefined - || Boolean(run.parentRunId && !this.runs.has(run.parentRunId)), - }); - if (!collapsed.has(run.id)) for (const child of descendants) visit(child, depth + 1); - }; - for (const root of roots) visit(root, 0); - const reachable = new Set(); - const mark = (id: string): void => { - if (reachable.has(id)) return; - reachable.add(id); - for (const child of children.get(id) ?? []) mark(child.id); - }; - for (const root of roots) mark(root.id); - for (const run of this.runs.values()) { - if (included.has(run.id) && !reachable.has(run.id) && !visited.has(run.id)) visit(run, 0, true); - } - return rows; + mainStatus(): 'running' | 'waiting' | 'idle' | 'ended' { + if (this.sessionEnded) return 'ended'; + const last = this.mainTurns.at(-1); + if (!last || last.end) return 'idle'; + return [...this.waits.values()].some(edge => edge.fromRunId === null) ? 'waiting' : 'running'; } private create(e: SessionEvent, id: string, agentId: string): AgentRun { @@ -271,6 +279,8 @@ export class AgentModel { const run = this.forLaunch(e, callId); run.name = requiredString(e.data, 'agentDisplayName'); run.task = optionalString(e.data, 'agentDescription') ?? 'Task not recorded'; + run.agentType = optionalString(e.data, 'agentName') ?? run.agentType; + if (e.data.executionMode === 'sync' || e.data.executionMode === 'background') run.mode = e.data.executionMode; run.model = optionalString(e.data, 'model') ?? run.model; run.startedAt = e.timestamp; if (run.status === 'unknown') run.status = 'running'; @@ -288,17 +298,21 @@ export class AgentModel { } run.parentRunId = call.ownerRunId; run.parentAgentId = call.ownerAgentId; - if (synchronous && !call.complete) { - this.waits.set(run.launchCallId!, { - callId: run.launchCallId!, fromRunId: call.ownerRunId, toAgentId: run.agentId, kind: 'sync-call', - }); + run.mode ??= synchronous ? 'sync' : 'background'; + run.prompt ??= call.prompt; + if (synchronous) { + const edge: WaitEdge = { + callId: run.launchCallId!, fromRunId: call.ownerRunId, toAgentId: run.agentId, toRunId: run.id, + kind: 'sync-call', start: call.startedAt, end: call.completedAt, + }; + this.waitLog.set(edge.callId, edge); + if (!call.complete) this.waits.set(edge.callId, edge); } } - private toolStart(e: SessionEvent): void { + private toolStart(record: EventRecord): void { + const e = record.event; const name = requiredString(e.data, 'toolName'); - const relevant = e.agentId || name === 'task' || name === 'read_agent'; - if (!relevant) return; const id = requiredString(e.data, 'toolCallId'); const args = isObject(e.data.arguments) ? e.data.arguments : {}; const owner = e.agentId ? this.current(e) : undefined; @@ -306,20 +320,27 @@ export class AgentModel { ownerAgentId: e.agentId, ownerRunId: owner?.id ?? null, name, task: optionalString(args, 'description'), background: args.mode === 'background', targetAgentId: optionalString(args, 'agent_id'), wait: args.wait === true, complete: false, + startedAt: e.timestamp, prompt: name === 'task' ? capture(record, 'prompt') : undefined, }; + call.activity = {id, name, status: 'running', startedAt: e.timestamp, detail: toolDetail(args)}; if (owner) { owner.hasActivity = true; owner.lastActivity = e.timestamp; - call.activity = {id, name, status: 'running'}; owner.tools.push(call.activity); + } else { + this.mainTools.push(call.activity); + if (this.mainTools.length > MAIN_TOOL_LIMIT) this.mainTools.shift(); } this.calls.set(id, call); const run = this.launches.get(id); if (run && name === 'task') this.link(run, call, !call.background); if (name === 'read_agent' && call.wait && call.targetAgentId) { - this.waits.set(id, { - callId: id, fromRunId: call.ownerRunId, toAgentId: call.targetAgentId, kind: 'wait', - }); + const edge: WaitEdge = { + callId: id, fromRunId: call.ownerRunId, toAgentId: call.targetAgentId, + toRunId: this.latest.get(call.targetAgentId)?.id, kind: 'wait', start: e.timestamp, + }; + this.waits.set(id, edge); + this.waitLog.set(id, edge); } } @@ -330,9 +351,13 @@ export class AgentModel { if (!call) return; if (typeof d.success !== 'boolean') throw new Error('Tool completion lacks boolean success'); call.complete = true; + call.completedAt = record.event.timestamp; this.waits.delete(id); + const edge = this.waitLog.get(id); + if (edge) edge.end = record.event.timestamp; if (call.activity) { call.activity.status = d.success ? 'succeeded' : 'failed'; + call.activity.endedAt = record.event.timestamp; call.activity.output = capture(record, d.success ? 'result' : 'error') ?? capture(record, 'result'); } } @@ -341,3 +366,16 @@ export class AgentModel { function eAsObject(e: SessionEvent): Record { return {agentId: e.agentId}; } + +function firstLine(text: string | undefined): string | undefined { + const line = text?.split(/\r?\n/).find(part => part.trim()); + return line?.trim(); +} + +function toolDetail(args: Record): string | undefined { + for (const key of DETAIL_KEYS) { + const value = args[key]; + if (typeof value === 'string' && value.trim()) return firstLine(value); + } + return undefined; +} diff --git a/tools/agent-monitor/src/screen.ts b/tools/agent-monitor/src/screen.ts new file mode 100644 index 00000000..fd5a38a2 --- /dev/null +++ b/tools/agent-monitor/src/screen.ts @@ -0,0 +1,209 @@ +import stringWidth from 'string-width'; +import {stripVTControlCharacters} from 'node:util'; +import type {Tone} from './theme.js'; + +export interface Style { + fg?: Tone; + bg?: Tone; + bold?: boolean; + dim?: boolean; +} + +export type Part = readonly [string, Style]; + +export interface Segment { + text: string; + fg?: Tone; + bg?: Tone; + bold: boolean; + dim: boolean; +} + +interface Cell { + ch: string; + fg?: Tone; + bg?: Tone; + bold: boolean; + dim: boolean; +} + +const graphemes = new Intl.Segmenter(undefined, {granularity: 'grapheme'}); +const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g; +const JUNCTIONS: Record = { + '1100': '│', '0011': '─', '0101': '┌', '0110': '┐', '1001': '└', '1010': '┘', '1101': '├', '1110': '┤', + '0111': '┬', '1011': '┴', '1111': '┼', '0100': '╷', '1000': '╵', '0001': '╶', '0010': '╴', +}; + +export const width = (text: string): number => stringWidth(text); + +/** Removes terminal control sequences so recorded text cannot move the cursor or restyle the screen. */ +export function clean(text: string): string { + return stripVTControlCharacters(text).replace(/\t/g, ' ').replace(CONTROL, ''); +} + +export function fit(text: string, max: number): string { + if (max <= 0) return ''; + if (stringWidth(text) <= max) return text; + let out = ''; + let used = 0; + for (const {segment} of graphemes.segment(text)) { + const w = stringWidth(segment); + if (used + w > max - 1) break; + out += segment; + used += w; + } + return out + '…'; +} + +function split(word: string, max: number): string[] { + const pieces: string[] = []; + let cur = ''; + for (const {segment} of graphemes.segment(word)) { + if (stringWidth(cur + segment) > max) { + pieces.push(cur); + cur = ''; + } + cur += segment; + } + if (cur) pieces.push(cur); + return pieces; +} + +/** Word-wraps recorded text to display columns, keeping paragraph breaks and splitting over-long tokens. */ +export function wrap(text: string, max: number): string[] { + const lines: string[] = []; + for (const paragraph of clean(text).split(/\r?\n/)) { + const words = paragraph.split(' ').filter(Boolean); + if (!words.length) { + if (lines.length && lines.at(-1) !== '') lines.push(''); + continue; + } + let cur = ''; + for (const word of words) { + const candidate = cur ? `${cur} ${word}` : word; + if (stringWidth(candidate) <= max) { + cur = candidate; + continue; + } + if (cur) lines.push(cur); + const pieces = stringWidth(word) > max ? split(word, max) : [word]; + lines.push(...pieces.slice(0, -1)); + cur = pieces.at(-1) ?? ''; + } + if (cur) lines.push(cur); + } + while (lines.at(-1) === '') lines.pop(); + return lines; +} + +export function junction(up: boolean, down: boolean, left: boolean, right: boolean): string { + return JUNCTIONS[`${+up}${+down}${+left}${+right}`] ?? ' '; +} + +/** Fixed-size grid of styled terminal cells; every write is clipped to the grid. */ +export class Screen { + private readonly cells: Cell[][]; + + constructor(readonly cols: number, readonly rows: number) { + this.cells = Array.from({length: rows}, () => Array.from({length: cols}, () => ({ch: ' ', bold: false, dim: false}))); + } + + charAt(x: number, y: number): string { + return this.cells[y]?.[x]?.ch ?? ''; + } + + put(x: number, y: number, text: string, style: Style = {}): number { + const row = this.cells[y]; + for (const {segment} of graphemes.segment(text)) { + const w = stringWidth(segment); + if (w === 0) continue; + if (row && x >= 0 && x + w <= this.cols) { + if (row[x]!.ch === '' && x > 0) row[x - 1]!.ch = ' '; + const after = row[x + w]; + if (after && after.ch === '') after.ch = ' '; + row[x] = {ch: segment, fg: style.fg, bg: style.bg ?? row[x]!.bg, bold: Boolean(style.bold), dim: Boolean(style.dim)}; + for (let i = 1; i < w; i++) row[x + i] = {ch: '', fg: style.fg, bg: style.bg ?? row[x + i]!.bg, bold: false, dim: false}; + } else if (row && x >= 0 && x < this.cols) { + row[x] = {...row[x]!, ch: ' '}; + } + x += w; + } + return x; + } + + rput(right: number, y: number, text: string, style: Style = {}): number { + return this.put(right - stringWidth(text) + 1, y, text, style); + } + + seq(x: number, y: number, parts: readonly Part[]): number { + for (const [text, style] of parts) x = this.put(x, y, text, style); + return x; + } + + /** Writes parts left to right within `max` columns, ending with an ellipsis when truncated. */ + seqFit(x: number, y: number, parts: readonly Part[], max: number, extra: Style = {}): number { + let used = 0; + for (const [text, style] of parts) { + const room = max - used; + if (room <= 0) break; + const shown = stringWidth(text) > room ? fit(text, room) : text; + this.put(x + used, y, shown, {...style, ...extra}); + used += stringWidth(shown); + } + return x + used; + } + + fill(x: number, y: number, w: number, h: number, bg: Tone): void { + for (let j = y; j < y + h; j++) { + for (let i = x; i < x + w; i++) { + const cell = this.cells[j]?.[i]; + if (cell) cell.bg = bg; + } + } + } + + hline(x: number, y: number, w: number, ch: string, style: Style = {}): void { + if (w > 0) this.put(x, y, ch.repeat(w), style); + } + + vline(x: number, y: number, h: number, ch: string, style: Style = {}): void { + for (let j = 0; j < h; j++) this.put(x, y + j, ch, style); + } + + box(x: number, y: number, w: number, h: number, style: Style = {}): void { + if (w < 2 || h < 2) return; + this.put(x, y, `╭${'─'.repeat(w - 2)}╮`, style); + for (let j = 1; j < h - 1; j++) { + this.put(x, y + j, '│', style); + this.put(x + w - 1, y + j, '│', style); + } + this.put(x, y + h - 1, `╰${'─'.repeat(w - 2)}╯`, style); + } + + /** Copies another screen onto this one at the given offset. */ + blit(src: Screen, x: number, y: number): void { + for (let j = 0; j < src.rows; j++) { + const target = this.cells[y + j]; + if (!target) continue; + for (let i = 0; i < src.cols; i++) { + if (x + i >= 0 && x + i < this.cols) target[x + i] = {...src.cells[j]![i]!}; + } + } + } + + segments(): Segment[][] { + return this.cells.map(row => { + const out: Segment[] = []; + for (const cell of row) { + const last = out.at(-1); + if (last && last.fg === cell.fg && last.bg === cell.bg && last.bold === cell.bold && last.dim === cell.dim) last.text += cell.ch; + else out.push({text: cell.ch, fg: cell.fg, bg: cell.bg, bold: cell.bold, dim: cell.dim}); + } + return out; + }); + } + + text(): string { + return this.cells.map(row => row.map(cell => cell.ch).join('').trimEnd()).join('\n'); + } +} diff --git a/tools/agent-monitor/src/theme.ts b/tools/agent-monitor/src/theme.ts new file mode 100644 index 00000000..5fe005d4 --- /dev/null +++ b/tools/agent-monitor/src/theme.ts @@ -0,0 +1,53 @@ +export const COLOR_MODES = ['default', 'light', 'dim', 'high-contrast', 'colorblind'] as const; +export type ColorMode = typeof COLOR_MODES[number]; + +export interface Palette { + fg: string; + muted: string; + border: string; + faint: string; + sel: string; + selbar: string; + tab: string; + accent: string; + accentEm: string; + chip: string; + onEm: string; + success: string; + attention: string; + danger: string; + done: string; +} + +export type Tone = keyof Palette; + +// Primer semantic tokens, matching the Copilot CLI color modes on dark and light terminals. +const DEFAULT: Palette = { + fg: '#f0f6fc', muted: '#9198a1', border: '#3d444d', faint: '#262c36', sel: '#151b23', selbar: '#656c76', + tab: '#212830', accent: '#4493f8', accentEm: '#1f6feb', chip: '#162945', onEm: '#ffffff', + success: '#3fb950', attention: '#d29922', danger: '#f85149', done: '#ab7df8', +}; + +export const PALETTES: Record = { + default: DEFAULT, + light: { + fg: '#1f2328', muted: '#59636e', border: '#d1d9e0', faint: '#eaeef2', sel: '#f6f8fa', selbar: '#818b98', + tab: '#eff2f5', accent: '#0969da', accentEm: '#0969da', chip: '#ddf4ff', onEm: '#ffffff', + success: '#1a7f37', attention: '#9a6700', danger: '#d1242f', done: '#8250df', + }, + dim: { + fg: '#d1d7e0', muted: '#9198a1', border: '#3d444d', faint: '#2f3742', sel: '#262c36', selbar: '#656c76', + tab: '#2a313c', accent: '#478be6', accentEm: '#316dca', chip: '#283c58', onEm: '#f0f6fc', + success: '#57ab5a', attention: '#c69026', danger: '#e5534b', done: '#986ee2', + }, + 'high-contrast': { + fg: '#ffffff', muted: '#b7bdc8', border: '#9ea7b3', faint: '#30363d', sel: '#1b2330', selbar: '#ffffff', + tab: '#262c36', accent: '#74b9ff', accentEm: '#409eff', chip: '#11294a', onEm: '#010409', + success: '#2bd853', attention: '#f0b72f', danger: '#ff6a69', done: '#b687f7', + }, + colorblind: {...DEFAULT, success: '#58a6ff', danger: '#f0883e'}, +}; + +export function isColorMode(value: string): value is ColorMode { + return (COLOR_MODES as readonly string[]).includes(value); +} diff --git a/tools/agent-monitor/src/timeline.ts b/tools/agent-monitor/src/timeline.ts new file mode 100644 index 00000000..b9072a8f --- /dev/null +++ b/tools/agent-monitor/src/timeline.ts @@ -0,0 +1,177 @@ +import type {Frame, Rect} from './chrome.js'; +import {fit, Screen, width} from './screen.js'; +import type {Part, Style} from './screen.js'; +import {elapsed, formatDuration, glyphPart, isActiveVisual, mainSpans, MAIN, runSpan, visualOf, waitSpan} from './tree.js'; +import type {Node, Visual} from './tree.js'; +import type {Tone} from './theme.js'; + +const MUTED: Style = {fg: 'muted'}; +const STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400]; +const BAR: Record = { + running: 'accent', waiting: 'accent', done: 'success', failed: 'danger', replied: 'done', idle: 'muted', ended: 'muted', unknown: 'muted', +}; +const clamp = (value: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, value)); + +export function niceStep(raw: number): number { + return STEPS.find(step => step >= raw) ?? STEPS.at(-1)!; +} + +const pad2 = (n: number): string => String(n).padStart(2, '0'); + +/** Session-relative tick label whose precision follows the tick spacing. */ +export function tickLabel(seconds: number, step = 60): string { + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + if (step < 60) { + if (seconds < 60) return `${s}s`; + return h ? `${h}:${pad2(m)}:${pad2(s)}` : `${m}:${pad2(s)}`; + } + if (step < 3600) return h ? `${h}h${pad2(m)}` : `${m}m`; + return `${h}h`; +} + +const TYPE_SHORT: Record = {'general-purpose': 'general'}; + +/** Default window: the span where agents actually ran, padded slightly; falls back to the whole session. */ +function activityWindow(frame: Frame, total: number): [number, number] { + const runs = frame.tree.order.flatMap(node => node.run ? [runSpan(frame.model, node.run, frame.now)] : []); + if (!runs.length) return [0, total]; + let lo = Math.min(...runs.map(([a]) => a)); + let hi = Math.max(...runs.map(([, b]) => b)); + const pad = Math.max(2, (hi - lo) * 0.05); + lo = Math.max(0, lo - pad); + hi = Math.min(total, hi + pad); + return hi - lo < 1 ? [Math.max(0, hi - 1), hi] : [lo, hi]; +} + +function treePrefix(scr: Screen, node: Node, y: number, span: number, base: number): void { + const line: Style = {fg: 'border'}; + const isLast = (n: Node): boolean => n.parent?.children.at(-1) === n; + const chain: Node[] = []; + for (let p = node.parent; p?.parent; p = p.parent) chain.unshift(p); + chain.forEach((ancestor, i) => { + if (!isLast(ancestor)) for (let r = 0; r < span; r++) scr.put(base + 3 * i, y + r, '│', line); + }); + if (node.depth > 0) { + const cx = base + 3 * (node.depth - 1); + const turn = node.edge === 'turn'; + scr.put(cx, y, isLast(node) ? (turn ? '╰' : '└') : '├', line); + scr.put(cx + 1, y, turn ? '┄' : '─', line); + if (!isLast(node)) for (let r = 1; r < span; r++) scr.put(cx, y + r, '│', line); + } + if (node.children.length) for (let r = 1; r < span; r++) scr.put(base + 3 * node.depth, y + r, node.children[0]!.edge === 'turn' ? '┆' : '│', line); +} + +export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: number): void { + const {model, state, now, cols} = frame; + const labelEnd = Math.min(43, Math.max(24, Math.floor(cols * 0.36))); + const cx = labelEnd + 3; + const durationRight = cols - 2; + const cw = Math.max(10, cols - cx - 10); + const total = Math.max(now, 1); + const [base0, base1] = activityWindow(frame, total); + const span = Math.min(total, Math.max(1, (base1 - base0) / state.zoom)); + const end = clamp(base1 - state.pan, Math.min(total, span), total); + const start = Math.max(0, end - span); + state.pan = base1 - end; + state.span = span; + const spc = Math.max(1e-6, (end - start) / (cw - 1)); + const col = (t: number): number => cx + clamp(Math.floor((t - start) / spc), 0, cw - 1); + const colEnd = (t: number): number => cx + clamp(Math.ceil((t - start) / spc) - 1, 0, cw - 1); + const axisY = area.y; + const laneY0 = area.y + 2; + scr.put(3, axisY, 'Agent', MUTED); + scr.rput(labelEnd, axisY, 'Type', MUTED); + scr.rput(durationRight, axisY, 'Time', MUTED); + scr.hline(cx, axisY + 1, cw, '─', {fg: 'border'}); + const nowC = now >= start && now <= end + 1e-9 ? cx + clamp(Math.round((now - start) / spc), 0, cw - 1) : -1; + const grid: number[] = []; + const step = niceStep((end - start) / 6); + for (let t = Math.ceil(start / step) * step; t <= end; t += step) { + const c = col(t); + if (nowC >= 0 && t > start && Math.abs(nowC - c) < 6) continue; + grid.push(c); + scr.put(c, axisY + 1, '┬', {fg: 'border'}); + const label = tickLabel(t, step); + scr.put(c === cx ? c : c - Math.floor(width(label) / 2), axisY, label, MUTED); + } + if (nowC >= 0) { + scr.put(nowC, axisY + 1, '┬', {fg: 'accent'}); + scr.put(nowC - 1, axisY, 'now', {fg: 'accent', bold: true}); + } else if (now > end) scr.rput(cx + cw - 1, axisY, 'now ▸', {fg: 'accent', bold: true}); + const lanes = frame.tree.order; + const capacity = Math.max(1, Math.floor((area.h - 2) / 2)); + const selectedIndex = Math.max(0, lanes.findIndex(node => node.id === state.selectedId)); + let top = clamp(state.laneTop, 0, Math.max(0, lanes.length - capacity)); + if (selectedIndex < top) top = selectedIndex; + if (selectedIndex >= top + capacity) top = selectedIndex - capacity + 1; + state.laneTop = top; + const shown = lanes.slice(top, top + capacity); + const laneY = new Map(shown.map((node, i) => [node.id, laneY0 + 2 * i])); + const bottom = laneY0 + 2 * shown.length - 1; + for (const c of grid) for (let y = laneY0; y <= bottom; y++) scr.put(c, y, '┊', {fg: 'faint'}); + if (nowC >= 0) for (let y = laneY0; y <= bottom; y++) scr.put(nowC, y, '│', {fg: 'accent', dim: true}); + for (const node of shown) { + const y = laneY.get(node.id)!; + const visual = visualOf(model, node); + const selected = node.id === state.selectedId; + if (selected) { + scr.put(1, y, '▎', {fg: 'selbar'}); + scr.fill(2, y, cols - 3, 1, 'sel'); + } + treePrefix(scr, node, y, 2, 3); + const gx = 3 + 3 * node.depth; + const type = node.run ? (node.edge === 'turn' ? 'turn' : TYPE_SHORT[node.run.agentType ?? ''] ?? node.run.agentType ?? 'agent') : 'session'; + scr.seq(gx, y, [glyphPart(visual, frame.spin, node.ctx)]); + scr.rput(labelEnd, y, type, {...MUTED, dim: node.ctx}); + scr.put(gx + 2, y, fit(node.run ? node.run.name : 'main', labelEnd - width(type) - gx - 3), {fg: selected ? 'accent' : 'fg', bold: true, dim: node.ctx}); + const segments: Array<[number, number, Tone]> = node.run + ? [[...runSpan(model, node.run, now), BAR[visual]]] + : mainSpans(model, now).map(([a, b]) => [a, b, 'muted' as Tone]); + for (const [a, b, tone] of segments) { + if (b < start || a > end) continue; + const c0 = col(Math.max(a, start)); + const c1 = Math.max(c0, colEnd(Math.min(b, end))); + for (let c = c0; c <= c1; c++) scr.put(c, y, c1 > c0 && c === c0 && a >= start ? '╺' : '━', {fg: tone, dim: node.ctx}); + if (c1 > c0 && b <= end && !(node.run && isActiveVisual(visual))) scr.put(c1, y, '╸', {fg: tone, dim: node.ctx}); + } + const key = node.run ? node.run.id : null; + for (const edge of model.waitLog.values()) { + if (edge.fromRunId !== key) continue; + const [a, b] = waitSpan(model, edge, now); + if (b < start || a > end) continue; + const c0 = col(Math.max(a, start)); + for (let c = c0; c <= Math.max(c0, colEnd(Math.min(b, end))); c++) scr.put(c, y, '╍', {fg: 'attention', dim: node.ctx}); + } + if (node.run) { + const [, b] = runSpan(model, node.run, now); + if (visual === 'running' && nowC >= 0) scr.put(nowC, y, '●', {fg: 'accent', dim: node.ctx}); + if ((visual === 'failed' || visual === 'replied') && b >= start && b <= end && colEnd(b) + 1 < cx + cw) { + scr.put(colEnd(b) + 1, y, visual === 'failed' ? '✗' : '↩', {fg: visual === 'failed' ? 'danger' : 'done', dim: node.ctx}); + } + } + scr.rput(durationRight, y, formatDuration(elapsed(model, node, now)), {fg: isActiveVisual(visual) ? 'fg' : 'muted', dim: node.ctx}); + } + for (const edge of model.waitLog.values()) { + const fromY = laneY.get(edge.fromRunId ?? MAIN); + const toY = edge.toRunId ? laneY.get(edge.toRunId) : undefined; + const [a] = waitSpan(model, edge, now); + if (fromY === undefined || toY === undefined || fromY === toY || a < start || a > end) continue; + const c = col(a); + const dir = toY > fromY ? 1 : -1; + const style: Style = {fg: 'attention', dim: Boolean(edge.end)}; + for (let y = fromY + dir; y !== toY - dir; y += dir) if ([' ', '┊'].includes(scr.charAt(c, y))) scr.put(c, y, '╎', style); + scr.put(c, toY - dir, dir > 0 ? '▾' : '▴', style); + } + const legend: Part[] = []; + const items: Array<[string, Tone, string, boolean?]> = [ + ['━━', 'accent', 'running'], ['━━', 'success', 'done'], ['━━', 'danger', 'failed'], ['━━', 'done', 'replied'], + ['━━', 'muted', 'main turn'], ['╍╍', 'attention', 'waiting'], ['▾', 'attention', 'waits for'], ['│', 'accent', 'now'], + ]; + for (const [glyph, tone, label] of items) legend.push([glyph, {fg: tone}], [` ${label} `, MUTED]); + const scale = `${spc < 10 ? spc.toFixed(1) : Math.round(spc)}s / col`; + const range = lanes.length > capacity ? `lanes ${top + 1}-${top + shown.length} of ${lanes.length} ` : ''; + scr.seqFit(3, legendY, legend, cols - width(range + scale) - 6); + scr.rput(cols - 2, legendY, range + scale, MUTED); +} diff --git a/tools/agent-monitor/src/tree.ts b/tools/agent-monitor/src/tree.ts new file mode 100644 index 00000000..91cc5cba --- /dev/null +++ b/tools/agent-monitor/src/tree.ts @@ -0,0 +1,174 @@ +import {isFinished} from './model.js'; +import type {AgentModel, AgentRun, Filter, WaitEdge} from './model.js'; +import type {Part} from './screen.js'; +import type {Tone} from './theme.js'; + +export const MAIN = 'main'; + +export interface Node { + id: string; + run?: AgentRun; + parent?: Node; + children: Node[]; + edge: 'root' | 'spawn' | 'turn' | 'orphan'; + depth: number; + ctx: boolean; +} + +export interface Tree { + root: Node; + order: Node[]; + byId: Map; +} + +export type Visual = 'running' | 'waiting' | 'done' | 'failed' | 'replied' | 'idle' | 'ended' | 'unknown'; + +export const VISUALS: Record = { + running: {glyph: '◐', tone: 'accent', label: 'running'}, + waiting: {glyph: '◌', tone: 'attention', label: 'waiting'}, + done: {glyph: '✓', tone: 'success', label: 'done'}, + failed: {glyph: '✗', tone: 'danger', label: 'failed'}, + replied: {glyph: '↩', tone: 'done', label: 'replied'}, + idle: {glyph: '○', tone: 'muted', label: 'idle'}, + ended: {glyph: '○', tone: 'muted', label: 'ended'}, + unknown: {glyph: '?', tone: 'muted', label: 'unknown'}, +}; + +const SPINNER = ['◐', '◓', '◑', '◒']; + +export function visualOf(model: AgentModel, node: Node): Visual { + if (!node.run) return model.mainStatus(); + const status = model.status(node.run); + if (status === 'completed') return 'done'; + return status === 'running' || status === 'waiting' || status === 'failed' || status === 'replied' ? status : 'unknown'; +} + +export function glyphPart(visual: Visual, spin: number, dim = false): Part { + const glyph = visual === 'running' ? SPINNER[spin % SPINNER.length]! : VISUALS[visual].glyph; + return [glyph, {fg: VISUALS[visual].tone, dim}]; +} + +export const isActiveVisual = (visual: Visual): boolean => visual === 'running' || visual === 'waiting'; + +function matches(run: AgentRun, filter: Filter): boolean { + if (filter === 'all') return true; + return filter === 'finished' ? isFinished(run) : !isFinished(run); +} + +const startOf = (run: AgentRun): number => Date.parse(run.startedAt ?? run.lastActivity); + +/** Builds the display tree: spawned agents under their launcher, later turns under the previous turn. */ +export function buildTree(model: AgentModel, filter: Filter): Tree { + const root: Node = {id: MAIN, children: [], edge: 'root', depth: 0, ctx: filter !== 'all'}; + const parentOf = new Map(); + for (const run of model.runs.values()) { + if (run.previousRunId && model.runs.has(run.previousRunId)) parentOf.set(run.id, {id: run.previousRunId, edge: 'turn'}); + else if (run.parentRunId === null) parentOf.set(run.id, {id: MAIN, edge: 'spawn'}); + else if (run.parentRunId && model.runs.has(run.parentRunId)) parentOf.set(run.id, {id: run.parentRunId, edge: 'spawn'}); + else parentOf.set(run.id, {id: MAIN, edge: 'orphan'}); + } + // Re-home any cyclic chain so rendering always terminates. + for (const run of model.runs.values()) { + const seen = new Set([run.id]); + let cursor = parentOf.get(run.id)!.id; + while (cursor !== MAIN) { + if (seen.has(cursor)) { + parentOf.set(run.id, {id: MAIN, edge: 'orphan'}); + break; + } + seen.add(cursor); + cursor = parentOf.get(cursor)?.id ?? MAIN; + } + } + const included = new Set(); + for (const run of model.runs.values()) { + if (!matches(run, filter)) continue; + for (let id: string = run.id; id !== MAIN && !included.has(id); id = parentOf.get(id)!.id) included.add(id); + } + const byId = new Map([[MAIN, root]]); + const runs = [...model.runs.values()].filter(run => included.has(run.id)).sort((a, b) => startOf(a) - startOf(b)); + for (const run of runs) byId.set(run.id, {id: run.id, run, children: [], edge: parentOf.get(run.id)!.edge, depth: 0, ctx: !matches(run, filter)}); + for (const run of runs) { + const node = byId.get(run.id)!; + const parent = byId.get(parentOf.get(run.id)!.id) ?? root; + node.parent = parent; + parent.children.push(node); + } + const order: Node[] = []; + const visit = (node: Node, depth: number): void => { + node.depth = depth; + order.push(node); + for (const child of node.children) visit(child, depth + 1); + }; + visit(root, 0); + return {root, order, byId}; +} + +/* ---------- Time ---------- */ + +export function sessionSeconds(model: AgentModel, iso: string | undefined): number { + if (!iso || !model.sessionStart) return 0; + return Math.max(0, (Date.parse(iso) - Date.parse(model.sessionStart)) / 1000); +} + +export function nowSeconds(model: AgentModel, nowMs: number): number { + if (model.sessionEnded) return sessionSeconds(model, model.sessionEnded); + const observed = sessionSeconds(model, model.lastEventAt); + const wall = model.sessionStart ? (nowMs - Date.parse(model.sessionStart)) / 1000 : 0; + return Math.max(observed, wall); +} + +export function runSpan(model: AgentModel, run: AgentRun, now: number): [number, number] { + const start = sessionSeconds(model, run.startedAt ?? run.lastActivity); + const end = run.endedAt ? sessionSeconds(model, run.endedAt) : isFinished(run) ? sessionSeconds(model, run.lastActivity) : now; + return [start, Math.max(start, end)]; +} + +export function elapsed(model: AgentModel, node: Node, now: number): number { + if (!node.run) return now; + if (node.run.durationMs !== undefined && node.run.status === 'completed') return node.run.durationMs / 1000; + const [start, end] = runSpan(model, node.run, now); + return end - start; +} + +/** Main-session activity: consecutive model turns separated by short tool gaps form one span. */ +export function mainSpans(model: AgentModel, now: number): Array<[number, number]> { + const spans: Array<[number, number]> = []; + for (const turn of model.mainTurns) { + const start = sessionSeconds(model, turn.start); + const end = turn.end ? sessionSeconds(model, turn.end) : now; + const last = spans.at(-1); + if (last && start - last[1] < 2) last[1] = Math.max(last[1], end); + else spans.push([start, end]); + } + return spans; +} + +export function waitSpan(model: AgentModel, edge: WaitEdge, now: number): [number, number] { + const start = sessionSeconds(model, edge.start); + return [start, edge.end ? sessionSeconds(model, edge.end) : now]; +} + +export function formatDuration(seconds: number): string { + const s = Math.max(0, seconds); + if (s < 60) return `${s.toFixed(1)}s`; + if (s < 3600) return `${Math.floor(s / 60)}m ${String(Math.floor(s % 60)).padStart(2, '0')}s`; + return `${Math.floor(s / 3600)}h ${String(Math.floor((s % 3600) / 60)).padStart(2, '0')}m`; +} + +/* ---------- Labels ---------- */ + +export function nodeType(node: Node): string { + if (!node.run) return 'session'; + if (node.edge === 'turn') return `${node.run.agentType ?? 'agent'} · turn`; + return node.run.agentType ?? 'agent'; +} + +export function waitLabel(model: AgentModel, edge: WaitEdge): string { + const target = edge.toRunId ? model.runs.get(edge.toRunId)?.name : undefined; + return target ?? edge.toAgentId; +} + +export function runLabel(model: AgentModel, runId: string | null): string { + return runId === null ? 'main' : model.runs.get(runId)?.name ?? runId; +} diff --git a/tools/agent-monitor/test/App.test.tsx b/tools/agent-monitor/test/App.test.tsx index df741d23..2e2715ff 100644 --- a/tools/agent-monitor/test/App.test.tsx +++ b/tools/agent-monitor/test/App.test.tsx @@ -13,92 +13,93 @@ const load = async (ref: ContentRef): Promise => { return ref.inline; }; +function modelOf(events = fixture()): AgentModel { + const model = new AgentModel(); + for (const event of events) model.apply(event); + return model; +} + +function mount(model: AgentModel, width = 120, height = 36) { + const clock = (): number => Date.parse(model.lastEventAt!) + 5000; + return render(); +} + async function frame(view: ReturnType, pattern: RegExp): Promise { for (let attempt = 0; attempt < 100; attempt++) { - const output = view.lastFrame() ?? ''; + const output = stripVTControlCharacters(view.lastFrame() ?? ''); if (pattern.test(output)) return output; await setTimeout(10); } - assert.match(view.lastFrame() ?? '', pattern); - return view.lastFrame()!; + assert.match(stripVTControlCharacters(view.lastFrame() ?? ''), pattern); + return ''; } -test('completed results remain selectable and selected history survives new live agents', async t => { - const model = new AgentModel(); - for (const event of fixture()) model.apply(event); - const view = render(); +test('opens on the Graph tab, shows what the selected agent did, and keeps tool calls hidden until t', async t => { + const view = mount(modelOf()); t.after(() => view.cleanup()); - await frame(view, /Parent result/); + let output = await frame(view, /Main session/); + assert.match(output, /Graph\s+Timeline/); + assert.match(output, /main session/); + for (const name of ['parent', 'child', 'control']) assert.match(output, new RegExp(name)); + assert.match(output, /sync · waited/); view.stdin.write('j'); - await frame(view, /Child result/); - view.stdin.write('\t'); - await frame(view, /Tool 0\/0: none/); - view.stdin.write('\t'); - await frame(view, /Child result/); - model.apply(record('tool.execution_start', {toolCallId: 'live', toolName: 'task', arguments: {mode: 'background'}})); - model.apply(start('live', 'live')); - view.rerender(); - const output = await frame(view, /1 active\/unknown/); - assert.match(output, /Child result/); - assert.match(output, />\s+\. \[DONE\] worker/); - view.stdin.write('f'); - await frame(view, /filter: active/); - assert.doesNotMatch(view.lastFrame()!, /\[DONE\]/); - view.stdin.write('f'); - await frame(view, /filter: finished/); - assert.match(view.lastFrame()!, /\[DONE\]/); + output = await frame(view, /Parent result/); + assert.match(output, /Task for parent/); + assert.match(output, /Parent prompt/); + assert.match(output, /Relationships/); + assert.doesNotMatch(output, /Tools ·/); + view.stdin.write('t'); + output = await frame(view, /Tools · 1/); + assert.match(output, /task/); + assert.doesNotMatch(output, /Relationships/); }); -test('folding, tool-result inspection, and narrow resize do not lose completed work', async t => { - const model = new AgentModel(); - for (const event of fixture()) model.apply(event); - const view = render(); +test('Tab switches to Timeline swimlanes with an axis, lanes, recorded waits and the selected history', async t => { + const view = mount(modelOf()); t.after(() => view.cleanup()); - await frame(view, /Parent result/); - view.stdin.write('h'); - await frame(view, /> \+ \[DONE\]/); - assert.doesNotMatch(view.lastFrame()!, /Task for child/); - view.stdin.write('\t'); - await frame(view, /Tool 1\/1: task/); - assert.match(view.lastFrame()!, /Child result/); - view.rerender(); - await setTimeout(50); - const output = stripVTControlCharacters(view.lastFrame()!); - assert.ok(output.split('\n').every(line => line.length <= 40)); + await frame(view, /Main session/); + view.stdin.write('j'); + view.stdin.write('j'); + await frame(view, /Child result/); view.stdin.write('\t'); - await frame(view, /Result \|/); - view.stdin.write('\u001b[F'); - await frame(view, /Parent result/); + const output = await frame(view, /waits for/); + assert.match(output, /Agent\s+Type/); + assert.match(output, /now/); + assert.match(output, /▾/); + assert.match(output, /Child result/); + for (const name of ['main', 'parent', 'child', 'control']) assert.match(output, new RegExp(`${name}\\s`)); + view.stdin.write('+'); + await frame(view, / 2x /); }); -test('recorded control sequences are inert and long results are scrollable', async t => { - const model = new AgentModel(); - model.apply(start('agent', 'agent')); - model.apply(record('assistant.message', { - content: '\u001b[2J' + Array.from({length: 80}, (_, index) => `RESULT-LINE-${index}`).join('\n'), - }, 'agent')); - model.apply(record('subagent.completed', {toolCallId: 'agent'}, 'agent')); - const view = render(); +test('filtering keeps ancestors as context and narrow terminals show a resize message', async t => { + const view = mount(modelOf(fixture().slice(0, 15))); t.after(() => view.cleanup()); - await frame(view, /RESULT-LINE-0/); - view.stdin.write('\u001b[F'); - const output = await frame(view, /RESULT-LINE-79/); - assert.doesNotMatch(output, /\u001b\[2J/); - assert.doesNotMatch(output, /RESULT-LINE-0\n/); - view.stdin.write('\u001b[H'); - await frame(view, /Task for agent/); + await frame(view, /status: all/); + view.stdin.write('f'); + const output = await frame(view, /status: active/); + assert.match(output, /parent/); + assert.doesNotMatch(output, /control/); + const small = mount(modelOf(), 70, 20); + t.after(() => small.cleanup()); + await frame(small, /Terminal too small/); }); -test('a live synchronous wait shows its target and disappears when the call returns', async t => { - const model = new AgentModel(); - const records = fixture(); - for (const event of records.slice(0, 10)) model.apply(event); - const view = render(); +test('recorded control sequences are inert and long results scroll inside the inspector', async t => { + const model = modelOf([ + record('session.start', {copilotVersion: '1.0.104', sessionId: 'scroll'}), + start('agent', 'agent'), + record('assistant.message', {content: '\u001b[2J' + Array.from({length: 40}, (_, i) => `RESULT-LINE-${i}`).join('\n')}, 'agent'), + record('subagent.completed', {toolCallId: 'agent'}, 'agent'), + ]); + const view = mount(model); t.after(() => view.cleanup()); - await frame(view, /\[WAITING\] worker -> waits-for: child/); - for (const event of records.slice(10)) model.apply(event); - view.rerender(); - await frame(view, /Parent result/); - assert.doesNotMatch(view.lastFrame()!, /waits-for: child/); - assert.match(view.lastFrame()!, /\[DONE\]/); + await frame(view, /Main session/); + view.stdin.write('j'); + let output = await frame(view, /RESULT-LINE-0/); + assert.doesNotMatch(view.lastFrame() ?? '', /\u001b\[2J/); + assert.match(output, /lines 1-/); + view.stdin.write('\u001b[6~'); + output = await frame(view, /RESULT-LINE-6\b/); + assert.doesNotMatch(output, /RESULT-LINE-0\b/); }); diff --git a/tools/agent-monitor/test/fixture.ts b/tools/agent-monitor/test/fixture.ts index d18b8831..f74a93fd 100644 --- a/tools/agent-monitor/test/fixture.ts +++ b/tools/agent-monitor/test/fixture.ts @@ -12,7 +12,7 @@ export function record(type: string, data: Record, agentId?: st export function start(callId: string, agentId: string, parentId?: string): EventRecord { return record('subagent.started', { - toolCallId: callId, agentName: 'general-purpose', agentDisplayName: 'worker', + toolCallId: callId, agentName: 'general-purpose', agentDisplayName: agentId, agentDescription: `Task for ${agentId}`, parentId, executionMode: parentId ? 'sync' : 'background', }, agentId); @@ -20,8 +20,9 @@ export function start(callId: string, agentId: string, parentId?: string): Event export function fixture(): EventRecord[] { return [ - record('session.start', {copilotVersion: '1.0.104', sessionId: 'synthetic-session'}), - record('tool.execution_start', {toolCallId: 'parent-call', toolName: 'task', arguments: {mode: 'background'}}), + record('session.start', {copilotVersion: '1.0.104', sessionId: 'synthetic-session', selectedModel: 'synthetic-model'}), + record('assistant.turn_start', {turnId: '0'}), + record('tool.execution_start', {toolCallId: 'parent-call', toolName: 'task', arguments: {mode: 'background', prompt: 'Parent prompt'}}), start('parent-call', 'parent'), record('tool.execution_complete', {toolCallId: 'parent-call', success: true, result: {content: 'Started in background'}}), record('tool.execution_start', {toolCallId: 'control-call', toolName: 'task', arguments: {mode: 'background'}}), @@ -36,6 +37,7 @@ export function fixture(): EventRecord[] { record('tool.execution_complete', {toolCallId: 'child-call', success: true, result: {content: 'Child result'}}, 'parent'), record('assistant.message', {content: 'Parent result', toolRequests: []}, 'parent'), record('subagent.completed', {toolCallId: 'parent-call'}, 'parent'), + record('assistant.turn_end', {turnId: '0'}), record('user.message', {content: 'Follow-up task', delivery: 'idle'}, 'control'), record('assistant.turn_start', {turnId: '0'}, 'control'), record('assistant.message', {content: 'Follow-up result', toolRequests: []}, 'control'), diff --git a/tools/agent-monitor/test/model.test.ts b/tools/agent-monitor/test/model.test.ts index fd01f761..ac17ba1c 100644 --- a/tools/agent-monitor/test/model.test.ts +++ b/tools/agent-monitor/test/model.test.ts @@ -59,17 +59,27 @@ test('event chain order is not dependency data; only explicit blocking waits cre assert.equal(model.runs.size, 1); }); -test('filtering retains ancestor context, collapse hides descendants, and cyclic input cannot recurse forever', () => { +test('waits keep recorded history; launch mode, agent type, prompts, follow-up links and main turns are tracked', () => { const model = new AgentModel(); - for (const event of fixture().slice(0, 12)) model.apply(event); + for (const event of fixture()) model.apply(event); const parent = model.runs.get('launch:parent-call')!; const child = model.runs.get('launch:child-call')!; - const finished = model.tree('finished', new Set()); - assert.ok(finished.some(row => row.run === parent)); - assert.ok(finished.some(row => row.run === child)); - assert.ok(!model.tree('all', new Set([parent.id])).some(row => row.run === child)); - parent.parentRunId = child.id; - assert.ok(model.tree('all', new Set()).some(row => row.unresolvedParent)); + const followup = [...model.runs.values()].find(run => run.id.startsWith('message:'))!; + assert.equal(parent.mode, 'background'); + assert.equal(child.mode, 'sync'); + assert.equal(child.agentType, 'general-purpose'); + assert.equal(parent.prompt?.inline, 'Parent prompt'); + assert.equal(followup.previousRunId, 'launch:control-call'); + assert.equal(followup.prompt?.inline, 'Follow-up task'); + const wait = model.waitLog.get('child-call')!; + assert.equal(wait.fromRunId, parent.id); + assert.equal(wait.toRunId, child.id); + assert.ok(wait.end && Date.parse(wait.end) > Date.parse(wait.start)); + assert.equal(model.waits.size, 0); + assert.equal(model.mainTurns.length, 1); + assert.ok(model.mainTurns[0]!.end); + assert.equal(model.mainStatus(), 'idle'); + assert.equal(model.sessionModel, 'synthetic-model'); }); test('unsupported agent signals are diagnosed, not fabricated as completion or cancellation', () => { diff --git a/tools/agent-monitor/test/tree.test.ts b/tools/agent-monitor/test/tree.test.ts new file mode 100644 index 00000000..f5119fac --- /dev/null +++ b/tools/agent-monitor/test/tree.test.ts @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import {test} from 'node:test'; +import {CARD_H, layoutGraph, levelY} from '../src/graph.js'; +import type {Placed} from '../src/graph.js'; +import {AgentModel} from '../src/model.js'; +import {buildTree, MAIN, mainSpans, nowSeconds} from '../src/tree.js'; +import {fixture, record} from './fixture.js'; + +test('display tree nests spawned agents under launchers, later turns under the previous turn, and filters keep ancestors', () => { + const model = new AgentModel(); + const events = fixture(); + for (const event of events.slice(0, 15)) model.apply(event); + const partial = buildTree(model, 'finished'); + const child = partial.byId.get('launch:child-call')!; + assert.equal(child.parent?.id, 'launch:parent-call'); + assert.equal(child.parent?.ctx, true); + assert.equal(child.ctx, false); + assert.equal(buildTree(model, 'active').byId.has('launch:control-call'), false); + for (const event of events.slice(15)) model.apply(event); + const tree = buildTree(model, 'all'); + const followup = tree.order.find(node => node.id.startsWith('message:'))!; + assert.equal(tree.byId.get('launch:parent-call')!.parent, tree.root); + assert.equal(followup.parent?.id, 'launch:control-call'); + assert.equal(followup.edge, 'turn'); + assert.deepEqual(tree.order.map(node => node.depth), [0, 1, 2, 1, 2]); +}); + +test('cyclic parent data is re-homed instead of recursing forever', () => { + const model = new AgentModel(); + for (const event of fixture()) model.apply(event); + const parent = model.runs.get('launch:parent-call')!; + parent.parentRunId = 'launch:child-call'; + const tree = buildTree(model, 'all'); + assert.equal(tree.order.length, model.runs.size + 1); + assert.ok(tree.order.some(node => node.edge === 'orphan')); +}); + +test('graph layout centres parents over their children without overlapping cards', () => { + const model = new AgentModel(); + for (const event of fixture()) model.apply(event); + const tree = buildTree(model, 'all'); + const layout = layoutGraph(tree.root); + const cards = [...layout.placed.values()]; + for (const a of cards) for (const b of cards) if (a !== b && a.y === b.y) assert.ok(a.x + a.w <= b.x || b.x + b.w <= a.x); + const center = (p: Placed): number => p.x + Math.floor(p.w / 2); + const root = layout.placed.get(MAIN)!; + const kids = tree.root.children.map(node => center(layout.placed.get(node.id)!)); + assert.ok(center(root) >= Math.min(...kids) && center(root) <= Math.max(...kids)); + assert.equal(layout.placed.get('launch:child-call')!.y, levelY(2)); + assert.equal(layout.height, levelY(2) + CARD_H); + assert.ok(layout.height <= 18, 'a three-level graph fits the 120x36 content area'); +}); + +test('main-session spans merge model rounds split by short tool gaps but keep idle periods separate', () => { + const model = new AgentModel(); + model.apply(record('session.start', {copilotVersion: '1.0.104', sessionId: 'spans'})); + model.apply(record('assistant.turn_start', {turnId: '0'})); + model.apply(record('assistant.turn_end', {turnId: '0'})); + model.apply(record('assistant.turn_start', {turnId: '1'})); + model.apply(record('assistant.turn_end', {turnId: '1'})); + for (let i = 0; i < 3; i++) model.apply(record('session.usage_record', {})); + model.apply(record('assistant.turn_start', {turnId: '2'})); + const now = nowSeconds(model, Date.parse(model.lastEventAt!) + 2000); + const spans = mainSpans(model, now); + assert.equal(spans.length, 2); + assert.equal(spans[1]![1], now); + assert.equal(model.mainStatus(), 'running'); +}); From e40bc242a728a721a392b603d61b3e972eca5fd9 Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Mon, 2 Nov 2026 16:45:11 -0800 Subject: [PATCH 3/7] Address review findings in agent monitor Lifecycle and attribution: - session.resume after session.shutdown makes the session live again. - subagent.completed/failed settle the agent's open execution instead of rewriting its first one; recorded outcomes are never overwritten. - cancelled: true is shown as its own "cancelled" status, not done/failed. - Only phase-less or final_answer messages count as results; "replied" requires a final answer in the current model turn. - Rejected or blocked session.task_complete requests no longer complete an agent. - Tool arguments of unexpected types no longer stop the viewer. Rendering: - Later turns of a reused agent sit under its first turn (bounded depth); Timeline indentation is capped. - Graph cards narrow to fit wide fan-outs before scrolling; off-screen content is skipped; edge labels use the free space on their row. - Timeline tick labels keep a gap from the now marker; header chips stay readable at 80 columns; the inspector splits at 100 columns as documented and shows tool calls in single-column mode; relationship overflow is reported; wrapped text is cached; wide characters at the right edge no longer widen a row. - Main tool count reports the total, not the 200-entry buffer. Tests cover each fix plus reader offsets across polls/64 KB chunks, the lazy-load race, reset, display names and graph scrolling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/README.md | 43 +++++++----- tools/agent-monitor/src/App.tsx | 14 ++-- tools/agent-monitor/src/chrome.ts | 87 +++++++++++++++++------- tools/agent-monitor/src/events.ts | 2 - tools/agent-monitor/src/graph.ts | 45 ++++++++++--- tools/agent-monitor/src/model.ts | 78 ++++++++++++++-------- tools/agent-monitor/src/screen.ts | 15 +++-- tools/agent-monitor/src/timeline.ts | 59 +++++++++------- tools/agent-monitor/src/tree.ts | 29 ++++++-- tools/agent-monitor/test/App.test.tsx | 89 +++++++++++++++++++++++-- tools/agent-monitor/test/events.test.ts | 20 +++++- tools/agent-monitor/test/model.test.ts | 82 +++++++++++++++++++++-- tools/agent-monitor/test/tree.test.ts | 34 +++++++--- 13 files changed, 451 insertions(+), 146 deletions(-) diff --git a/tools/agent-monitor/README.md b/tools/agent-monitor/README.md index 8f2688e9..e8b84feb 100644 --- a/tools/agent-monitor/README.md +++ b/tools/agent-monitor/README.md @@ -35,9 +35,11 @@ one selection: - **Graph** (default): a spawn tree. The main session sits on top; agents sit under the agent or session that launched them, and a reused agent's later - turn sits under its previous turn. Edge labels show `bg` / `sync` and any - recorded wait with its duration (yellow while waiting, dim once finished). - Wide trees scroll horizontally to keep the selection visible. + turns sit side by side under its first turn. Edge labels show `bg` / `sync` + and any recorded wait with its duration (yellow while waiting, dim once + finished), naming the waiter when it is not the edge's parent. Cards narrow + to fit a wide fan-out; beyond that the graph scrolls horizontally to keep + the selection visible. - **Timeline**: one swimlane per execution, with the main session's model turns on the first lane. Bars are colored by outcome, `╍` marks time spent waiting, `▾` connects a waiting lane to the execution it waited for, and the @@ -45,9 +47,9 @@ one selection: which agents ran. The bottom dialog shows what the selected execution was asked (its task -prompt), its final message or error, and its recorded relationships. Tool calls -stay hidden unless you press `t`. Long text is loaded from the selected log on -demand rather than copied to a new database. +prompt), its final message or error (the latest message while it runs), and its +recorded relationships. Tool calls stay hidden unless you press `t`. Long text +is loaded from the selected log on demand rather than copied to a new database. | Key | Action | | --- | --- | @@ -57,13 +59,17 @@ demand rather than copied to a new database. | `+` / `-` | Timeline: zoom in/out (beyond the agent window up to the whole session) | | Tab | Switch between Graph and Timeline | | `f` | Cycle all, active, finished; ancestors remain as dimmed context | -| `t` | Toggle tool calls / relationships in the detail dialog | +| `t` | Show tool calls in the detail dialog instead of relationships (below 100 columns: instead of the message) | | Page Up / Page Down | Scroll the selected result | | `q` or Escape | Quit and restore the previous terminal screen | -`done` means an invocation has a recorded `subagent.completed` or agent-attributed -`session.task_complete` event. `replied` -means a response and turn end were recorded, but lifecycle completion was not. +`done` means an invocation has a recorded `subagent.completed` event or an +accepted agent-attributed `session.task_complete`. `cancelled` means +`subagent.completed` recorded `cancelled: true` (for example, a superseded run); +it is not a failure. `replied` means the agent's latest model turn ended with a +final answer, not progress commentary, but lifecycle completion was not +recorded. Lifecycle events name an agent's original launch call; they settle +the agent's open execution and never overwrite an earlier recorded outcome. Follow-up executions on a reused agent remain separate from its earlier work. The finished filter includes replied executions without claiming the agent was terminated. Running/unknown states are **last observed**, not a process-health @@ -89,13 +95,15 @@ records produce explicit diagnostics rather than fabricated results. truncated by Copilot; the viewer cannot reconstruct it. - Ephemeral events and unrecorded cancellation/liveness signals cannot be recovered from this file. Unknown agent-event types are diagnosed, not - interpreted as cancellation or success. + interpreted as an outcome. - Detected source replacement/truncation rebuilds the view. A trailing record without a newline remains buffered until it is complete. Reading stops on a source - error, with a visible diagnostic and a nonzero exit status on quit. + error, with a visible diagnostic and a nonzero exit status on quit; the clock + then stays at the last recorded event. - While following a live log, open executions extend to the current time. A - recorded `session.shutdown` switches the header to `ended` and stops the clock. -- Below 100 columns the detail dialog shows only the task and result. + recorded `session.shutdown` switches the header to `ended` and stops the clock; + a later `session.resume` makes the session live again. +- Below 100 columns the detail dialog uses one column and omits relationships. Text from the log is rendered without terminal control sequences. Session content stays local: do not copy private transcripts into this repository. @@ -111,6 +119,7 @@ npm run build ``` Tests use synthetic records and temporary files, covering replay/tailing, -UTF-8 boundaries, source reset, agent/result attribution, recorded wait history, -reused agents and later turns, graph layout, the Graph/Timeline views, -filtering, hidden-by-default tool calls, resizing, and result scrolling. +UTF-8 boundaries, source reset, agent/result attribution, lifecycle routing and +cancellation, session resume, recorded wait history, reused agents and later +turns, graph layout and scrolling, the Graph/Timeline views, filtering, +hidden-by-default tool calls, narrow terminals, and result scrolling. diff --git a/tools/agent-monitor/src/App.tsx b/tools/agent-monitor/src/App.tsx index 34a5b1b4..b568a626 100644 --- a/tools/agent-monitor/src/App.tsx +++ b/tools/agent-monitor/src/App.tsx @@ -11,7 +11,7 @@ import {Screen} from './screen.js'; import {PALETTES} from './theme.js'; import type {ColorMode} from './theme.js'; import {drawTimeline} from './timeline.js'; -import {buildTree, MAIN, nowSeconds, visualOf} from './tree.js'; +import {buildTree, MAIN, nowSeconds, sessionSeconds, visualOf} from './tree.js'; export const MIN_COLS = 80; export const MIN_ROWS = 24; @@ -45,8 +45,9 @@ export function App({model, loadContent, source = 'following', diagnostic, mode const node = tree.byId.get(state.selectedId)!; const result = resultRef(model, node); const prompt = promptRef(model, node); - const textKey = `${node.id}|${result?.eventId ?? ''}|${prompt?.eventId ?? ''}`; - const [loaded, setLoaded] = useState<{key: string; texts: DetailTexts}>({key: '', texts: {pending: false}}); + const refKey = (ref: ContentRef | undefined): string => ref ? `${ref.eventId}:${ref.field}:${ref.location?.generation ?? ''}` : ''; + const textKey = `${node.id}|${refKey(result)}|${refKey(prompt)}`; + const [loaded, setLoaded] = useState<{key: string; nodeId: string; texts: DetailTexts}>({key: '', nodeId: '', texts: {pending: false}}); useEffect(() => { let cancelled = false; @@ -54,6 +55,7 @@ export function App({model, loadContent, source = 'following', diagnostic, mode if (cancelled) return; setLoaded({ key: textKey, + nodeId: node.id, texts: { pending: false, result: r.status === 'fulfilled' ? r.value : undefined, @@ -77,7 +79,8 @@ export function App({model, loadContent, source = 'following', diagnostic, mode return () => clearInterval(timer); }, [source]); - const now = nowSeconds(model, clock()); + // A stopped source records nothing new, so time stays at the last observed event instead of following the wall clock. + const now = source === 'stopped' ? sessionSeconds(model, model.lastEventAt) : nowSeconds(model, clock()); useInput((input, key) => { const order = tree.order; const index = order.findIndex(n => n.id === state.selectedId); @@ -119,7 +122,8 @@ export function App({model, loadContent, source = 'following', diagnostic, mode if (cols < MIN_COLS || rows < MIN_ROWS) { return {`Terminal too small for the agent monitor (need ${MIN_COLS}x${MIN_ROWS}, have ${cols}x${rows}). q: quit`}; } - const texts: DetailTexts = loaded.key === textKey ? loaded.texts : {pending: Boolean(result || prompt)}; + // A running agent's latest message changes often; keep showing its previous text until the new one is read. + const texts: DetailTexts = loaded.key === textKey || loaded.nodeId === node.id ? loaded.texts : {pending: Boolean(result || prompt)}; const warnings = [...model.warnings].join('; '); const frame: Frame = { model, state, tree, now, spin, cols, rows, source, texts, diff --git a/tools/agent-monitor/src/chrome.ts b/tools/agent-monitor/src/chrome.ts index ee553a19..9ec7e7e1 100644 --- a/tools/agent-monitor/src/chrome.ts +++ b/tools/agent-monitor/src/chrome.ts @@ -74,14 +74,21 @@ export function selectedNode(frame: Frame): Node { } export function resultRef(model: AgentModel, node: Node): ContentRef | undefined { - if (!node.run) return model.mainResult; - return node.run.status === 'failed' ? node.run.error ?? node.run.result : node.run.result; + const run = node.run; + if (!run) return model.mainLatest; + if (run.status === 'failed') return run.error ?? run.result; + return run.status === 'completed' || run.status === 'replied' ? run.result : run.latest; } export function promptRef(model: AgentModel, node: Node): ContentRef | undefined { return node.run ? node.run.prompt : model.mainPrompt; } +/** The inspector shows relationships or tools beside the message once the dialog interior is this wide (100 columns). */ +const SPLIT_INNER = 94; + +export const isSplit = (box: Rect): boolean => box.w - 4 >= SPLIT_INNER; + export function drawChrome(scr: Screen, frame: Frame, layout: Layout): void { let x = 1; for (const view of VIEWS) { @@ -95,23 +102,28 @@ export function drawChrome(scr: Screen, frame: Frame, layout: Layout): void { frame.state.view === 'graph' ? ['edges:', 'spawn,wait'] : ['window:', windowLabel], ['session:', (frame.model.sessionId ?? 'unknown').slice(0, 8)], ]; + const index = frame.tree.order.findIndex(node => node.id === frame.state.selectedId); + const indicator: Part = frame.source === 'following' ? ['●', {fg: 'success'}] : frame.source === 'ended' ? ['○', MUTED] : ['✗', {fg: 'danger'}]; + const position: Part = [` ${index + 1} / ${frame.tree.order.length}`, MUTED]; + const clock: Part = [` ${formatDuration(frame.now)}`, MUTED]; + const source: Part = [` ${frame.source}`, {fg: 'fg'}]; + // Narrow terminals keep the chips readable: drop the position, then the clock, then trailing chips. + const chipsEnd = (list: typeof chips): number => list.reduce((end, [key, value]) => end + width(key) + width(value) + 4, 1); + const rightOptions: Part[][] = [[indicator, source, clock, position], [indicator, source, clock], [indicator, source]]; + const partsWidth = (parts: Part[]): number => parts.reduce((sum, [text]) => sum + width(text), 0); + const right = rightOptions.find(parts => frame.cols - 1 - partsWidth(parts) >= chipsEnd(chips)) ?? rightOptions.at(-1)!; + while (chips.length > 1 && chipsEnd(chips) > frame.cols - 1 - partsWidth(right)) chips.pop(); x = 1; for (const [key, value] of chips) { x = scr.put(x, 2, key, {fg: 'fg'}); x = scr.put(x, 2, ` ${value} `, {fg: 'accent', bg: 'chip'}) + 2; } - const index = frame.tree.order.findIndex(node => node.id === frame.state.selectedId); - const indicator: Part = frame.source === 'following' ? ['●', {fg: 'success'}] : frame.source === 'ended' ? ['○', MUTED] : ['✗', {fg: 'danger'}]; - const right: Part[] = [ - indicator, [` ${frame.source}`, {fg: 'fg'}], [` ${formatDuration(frame.now)}`, MUTED], - [` ${index + 1} / ${frame.tree.order.length}`, MUTED], - ]; - const rightWidth = right.reduce((sum, [text]) => sum + width(text), 0); - scr.seq(frame.cols - 1 - rightWidth, 2, right); + scr.seq(frame.cols - 1 - partsWidth(right), 2, right); if (frame.diagnostic) scr.put(1, 3, fit(frame.diagnostic, frame.cols - 2), {fg: 'attention'}); + const toolsHint = frame.state.showTools ? isSplit(layout.box) ? 'relationships' : 'message' : 'tools'; const hints: Array<[string, string]> = frame.state.view === 'graph' - ? [['j/k', 'node'], ['tab', 'timeline'], ['f', 'filter'], ['t', frame.state.showTools ? 'relationships' : 'tools'], ['pgup/pgdn', 'detail'], ['q', 'quit']] - : [['j/k', 'lane'], ['+/-', 'zoom'], ['←/→', 'pan'], ['tab', 'graph'], ['f', 'filter'], ['t', frame.state.showTools ? 'relationships' : 'tools'], ['q', 'quit']]; + ? [['j/k', 'node'], ['tab', 'timeline'], ['f', 'filter'], ['t', toolsHint], ['pgup/pgdn', 'detail'], ['q', 'quit']] + : [['j/k', 'lane'], ['+/-', 'zoom'], ['←/→', 'pan'], ['tab', 'graph'], ['f', 'filter'], ['t', toolsHint], ['q', 'quit']]; const parts: Part[] = []; hints.forEach(([key, label], i) => { if (i) parts.push([' · ', MUTED]); @@ -130,12 +142,17 @@ function metaLine(frame: Frame, node: Node): string { return [run.name, nodeType(node), run.model, run.mode, formatDuration(elapsed(model, node, now))].filter(Boolean).join(' · '); } -function resultHeading(frame: Frame, node: Node): [string, string, Style] { +/** Heading, note, style and empty-state text for the inspector's message section. */ +function resultHeading(frame: Frame, node: Node): [string, string, Style, string] { const visual = visualOf(frame.model, node); - if (visual === 'failed') return ['Error', 'from subagent.failed', {fg: 'danger', bold: true}]; - if (visual === 'replied') return ['Reply', 'turn ended · no completion event', {fg: 'fg', bold: true}]; - if (!node.run || isActiveVisual(visual)) return ['Latest message', isActiveVisual(visual) ? 'last observed · still running' : '', {fg: 'fg', bold: true}]; - return ['Result', 'final message', {fg: 'fg', bold: true}]; + const style: Style = {fg: 'fg', bold: true}; + if (visual === 'failed') return ['Error', 'from subagent.failed', {fg: 'danger', bold: true}, 'No error message recorded.']; + if (visual === 'replied') return ['Reply', 'turn ended · no completion event', style, 'No final message recorded.']; + if (visual === 'done') return ['Result', 'final message', style, 'No final message recorded.']; + if (isActiveVisual(visual)) return ['Latest message', 'last observed · still running', style, 'No message recorded yet.']; + if (visual === 'cancelled') return ['Latest message', 'cancelled · no final reply', style, 'No message recorded.']; + if (visual === 'unknown') return ['Latest message', 'status not recorded', style, 'No message recorded.']; + return ['Latest message', '', style, 'No message recorded.']; } function relationships(frame: Frame, node: Node): Part[][] { @@ -176,6 +193,21 @@ function toolLines(frame: Frame, node: Node, rows: number, w: number, scr: Scree }); } +function toolsHeader(frame: Frame, node: Node, scr: Screen, x: number, y: number, w: number, rows: number): void { + const shown = node.run ? node.run.tools.length : frame.model.mainTools.length; + const total = node.run ? shown : frame.model.mainToolCount; + scr.put(x, y, `Tools · ${total}`, {fg: 'fg', bold: true}); + scr.rput(x + w - 1, y, shown > rows ? `latest ${rows}` : 't to hide', MUTED); +} + +let wrapped: {text: string; w: number; lines: string[]} | undefined; + +/** Recorded messages can be long; the inspector redraws on every spinner tick, so keep the last wrap. */ +function wrapCached(text: string, w: number): string[] { + if (wrapped?.text !== text || wrapped.w !== w) wrapped = {text, w, lines: wrap(text, w)}; + return wrapped.lines; +} + /** Bottom dialog: what the selected execution was asked, what it produced, and how it relates to others. */ export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): void { const node = selectedNode(frame); @@ -185,7 +217,7 @@ export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): vo const top = box.y + 1; const rows = box.h - 2; const inner = box.w - 4; - const split = inner >= 96; + const split = isSplit(box); const rightW = split ? Math.min(52, Math.floor(inner * 0.42)) : 0; const leftW = split ? inner - rightW - 3 : inner; const title = node.run ? node.run.task : 'Main session'; @@ -195,13 +227,18 @@ export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): vo scr.put(x, top + 1, fit(metaLine(frame, node), leftW), MUTED); const prompt = frame.texts.prompt ?? (node.run ? node.run.task : undefined) ?? 'Not recorded'; scr.seqFit(x, top + 2, [['Task ', {fg: 'fg', bold: true}], [prompt.replace(/\s+/g, ' '), MUTED]], leftW); - const [heading, note, headingStyle] = resultHeading(frame, node); const bodyTop = top + 4; const bodyRows = Math.max(0, rows - 4); + if (!split && frame.state.showTools) { + if (rows > 3) toolsHeader(frame, node, scr, x, top + 3, leftW, bodyRows); + toolLines(frame, node, bodyRows, leftW, scr, x, bodyTop); + return; + } + const [heading, note, headingStyle, empty] = resultHeading(frame, node); let body: string[]; if (frame.texts.pending) body = ['Loading recorded text…']; else if (frame.texts.error) body = [`Cannot read recorded text: ${frame.texts.error}`]; - else body = frame.texts.result ? wrap(frame.texts.result, leftW) : ['No final message recorded.']; + else body = frame.texts.result ? wrapCached(frame.texts.result, leftW) : [empty]; const maxScroll = Math.max(0, body.length - bodyRows); frame.state.detailScroll = Math.min(frame.state.detailScroll, maxScroll); const scroll = frame.state.detailScroll; @@ -215,13 +252,15 @@ export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): vo const rx = x + leftW + 3; scr.vline(x + leftW + 1, top, rows, '│', {fg: 'border'}); if (frame.state.showTools) { - const tools = node.run ? node.run.tools : frame.model.mainTools; - scr.put(rx, top, `Tools · ${tools.length}`, {fg: 'fg', bold: true}); - scr.rput(rx + rightW - 1, top, tools.length > rows - 1 ? `latest ${rows - 1}` : 't to hide', MUTED); + toolsHeader(frame, node, scr, rx, top, rightW, rows - 1); toolLines(frame, node, rows - 1, rightW, scr, rx, top + 1); return; } scr.put(rx, top, 'Relationships', {fg: 'fg', bold: true}); scr.rput(rx + rightW - 1, top, 'other deps: not recorded', MUTED); - relationships(frame, node).slice(0, rows - 1).forEach((parts, i) => scr.seqFit(rx, top + 1 + i, parts, rightW)); + const lines = relationships(frame, node); + const room = rows - 1; + const shown = lines.length > room ? lines.slice(0, room - 1) : lines; + shown.forEach((parts, i) => scr.seqFit(rx, top + 1 + i, parts, rightW)); + if (shown.length < lines.length) scr.put(rx, top + 1 + shown.length, fit(`+${lines.length - shown.length} more · see Timeline`, rightW), MUTED); } diff --git a/tools/agent-monitor/src/events.ts b/tools/agent-monitor/src/events.ts index 6606ba15..818919c7 100644 --- a/tools/agent-monitor/src/events.ts +++ b/tools/agent-monitor/src/events.ts @@ -25,7 +25,6 @@ type TextField = 'content' | 'summary' | 'result' | 'error' | 'prompt'; export interface ContentRef { eventId: string; field: TextField; - preview: string; location?: RecordLocation; inline?: string; } @@ -83,7 +82,6 @@ export function capture(record: EventRecord, field: TextField): ContentRef | und return { eventId: record.event.id, field, - preview: text.slice(0, 240), location: record.location, inline: record.location ? undefined : text, }; diff --git a/tools/agent-monitor/src/graph.ts b/tools/agent-monitor/src/graph.ts index 0c5b7d00..ecadc024 100644 --- a/tools/agent-monitor/src/graph.ts +++ b/tools/agent-monitor/src/graph.ts @@ -5,6 +5,7 @@ import {elapsed, formatDuration, glyphPart, MAIN, nodeType, runLabel, VISUALS, v import type {Node, Visual} from './tree.js'; export const CARD_W = 26; +export const MIN_CARD_W = 18; export const ROOT_W = 32; export const CARD_H = 5; export const ROOT_H = 4; @@ -26,7 +27,6 @@ export interface GraphLayout { } const MUTED: Style = {fg: 'muted'}; -const cardWidth = (node: Node): number => node.id === MAIN ? ROOT_W : CARD_W; const centerOf = (p: Placed): number => p.x + Math.floor(p.w / 2); const clamp = (value: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, value)); @@ -34,7 +34,8 @@ const clamp = (value: number, lo: number, hi: number): number => Math.max(lo, Ma export const levelY = (depth: number): number => depth === 0 ? 0 : ROOT_H + 2 + (depth - 1) * LEVEL; /** Top-down tidy layout: each subtree gets its own column span and parents sit centred over their children. */ -export function layoutGraph(root: Node): GraphLayout { +export function layoutGraph(root: Node, cardW = CARD_W): GraphLayout { + const cardWidth = (node: Node): number => node.id === MAIN ? ROOT_W : cardW; const spans = new Map(); const childSpan = (node: Node): number => node.children.reduce((sum, child) => sum + spans.get(child.id)!, 0) + GAP * Math.max(0, node.children.length - 1); @@ -65,10 +66,17 @@ export function layoutGraph(root: Node): GraphLayout { return {placed, width: spans.get(root.id)!, height: deepest === 0 ? ROOT_H : levelY(deepest) + CARD_H}; } +/** Narrows the agent cards (down to MIN_CARD_W) before falling back to horizontal scrolling. */ +export function fitGraph(root: Node, areaW: number): GraphLayout { + let layout = layoutGraph(root); + for (let w = CARD_W - 2; layout.width > areaW && w >= MIN_CARD_W; w -= 2) layout = layoutGraph(root, w); + return layout; +} + function edgeLabel(frame: Frame, parent: Node, child: Node): [Style, Part[]] { const {model, now} = frame; const parentKey = parent.run ? parent.run.id : null; - const waits = [...model.waitLog.values()].filter(edge => edge.toRunId === child.id && (child.edge === 'turn' || edge.fromRunId === parentKey)); + const waits = [...model.waitLog.values()].filter(edge => edge.toRunId === child.id); const active = waits.some(edge => !edge.end); const parts: Part[] = []; if (child.edge === 'turn') parts.push(['later turn', MUTED]); @@ -120,7 +128,7 @@ function drawCard(view: Screen, frame: Frame, p: Placed, ox: number, oy: number) } export function drawGraph(scr: Screen, frame: Frame, area: Rect, legendY: number): void { - const layout = layoutGraph(frame.tree.root); + const layout = fitGraph(frame.tree.root, area.w); const selected = layout.placed.get(frame.state.selectedId) ?? layout.placed.get(MAIN)!; let ox: number; if (layout.width <= area.w) ox = -Math.floor((area.w - layout.width) / 2); @@ -139,24 +147,41 @@ export function drawGraph(scr: Screen, frame: Frame, area: Rect, legendY: number } const view = new Screen(area.w, area.h); const border: Style = {fg: 'border'}; + // Edge labels may run right until the next arrow on the same row (children of every parent at one depth share it). + const arrows = new Map(); + for (const p of layout.placed.values()) { + if (p.node.id === MAIN) continue; + const row = arrows.get(p.y - 1) ?? []; + row.push(centerOf(p)); + arrows.set(p.y - 1, row); + } + for (const row of arrows.values()) row.sort((a, b) => a - b); + const right = Math.max(layout.width, ox + area.w); for (const p of layout.placed.values()) { - if (!p.node.children.length) continue; - const pc = centerOf(p); const busY = p.y + p.h; + if (!p.node.children.length || busY + 1 < oy || busY >= oy + area.h) continue; + const pc = centerOf(p); const centers = p.node.children.map(child => centerOf(layout.placed.get(child.id)!)); + const drops = new Set(centers); const lo = Math.min(pc, ...centers); const hi = Math.max(pc, ...centers); - for (let c = lo; c <= hi; c++) view.put(c - ox, busY - oy, junction(c === pc, centers.includes(c), c > lo, c < hi), border); + for (let c = Math.max(lo, ox); c <= Math.min(hi, ox + area.w - 1); c++) { + view.put(c - ox, busY - oy, junction(c === pc, drops.has(c), c > lo, c < hi), border); + } + const row = arrows.get(busY + 1) ?? []; p.node.children.forEach((child, i) => { const cc = centers[i]!; - const next = centers[i + 1] ?? cc + CARD_W; + const next = row.find(x => x > cc); + const room = next === undefined ? right - cc - 2 : next - cc - 3; const [tone, label] = edgeLabel(frame, p.node, child); view.put(cc - ox, busY + 1 - oy, '▾', tone); - view.seqFit(cc + 2 - ox, busY + 1 - oy, label, Math.max(0, next - cc - 3)); + view.seqFit(cc + 2 - ox, busY + 1 - oy, label, Math.max(0, room)); }); } - for (const p of layout.placed.values()) drawCard(view, frame, p, ox, oy); + const visible = (p: Placed): boolean => p.x + p.w > ox && p.x < ox + area.w && p.y + p.h > oy && p.y < oy + area.h; for (const p of layout.placed.values()) { + if (!visible(p)) continue; + drawCard(view, frame, p, ox, oy); if (p.node.children.length) view.put(centerOf(p) - ox, p.y + p.h - 1 - oy, '┬', {fg: p.node.id === frame.state.selectedId ? 'accent' : 'border'}); } scr.blit(view, area.x, area.y); diff --git a/tools/agent-monitor/src/model.ts b/tools/agent-monitor/src/model.ts index 2deb2fa6..ebc35ced 100644 --- a/tools/agent-monitor/src/model.ts +++ b/tools/agent-monitor/src/model.ts @@ -1,7 +1,7 @@ import {capture, isObject, optionalString, requiredString} from './events.js'; import type {ContentRef, EventRecord, SessionEvent} from './events.js'; -export type RunStatus = 'unknown' | 'running' | 'completed' | 'failed' | 'replied'; +export type RunStatus = 'unknown' | 'running' | 'completed' | 'failed' | 'cancelled' | 'replied'; export type Filter = 'all' | 'active' | 'finished'; export interface ToolActivity { @@ -11,7 +11,6 @@ export interface ToolActivity { startedAt: string; endedAt?: string; detail?: string; - output?: ContentRef; } export interface AgentRun { @@ -29,10 +28,10 @@ export interface AgentRun { startedAt?: string; endedAt?: string; lastActivity: string; - durationMs?: number; model?: string; prompt?: ContentRef; result?: ContentRef; + latest?: ContentRef; error?: ContentRef; tools: ToolActivity[]; hasActivity: boolean; @@ -42,7 +41,6 @@ interface ToolCall { ownerAgentId?: string; ownerRunId: string | null; name: string; - task?: string; background: boolean; targetAgentId?: string; wait: boolean; @@ -72,7 +70,13 @@ const MAIN_TOOL_LIMIT = 200; const DETAIL_KEYS = ['path', 'pattern', 'command', 'query', 'url', 'name', 'agent_id', 'description']; export const isFinished = (run: AgentRun): boolean => - run.status === 'completed' || run.status === 'failed' || run.status === 'replied'; + run.status === 'completed' || run.status === 'failed' || run.status === 'cancelled' || run.status === 'replied'; + +const isOpen = (run: AgentRun): boolean => !isFinished(run) || run.status === 'replied'; + +/** Phased models also emit progress ("commentary") messages; only phase-less or final-answer replies count as results. */ +const isFinalAnswer = (d: Record): boolean => + (d.phase === undefined || d.phase === 'final_answer') && (!Array.isArray(d.toolRequests) || d.toolRequests.length === 0); export class AgentModel { readonly runs = new Map(); @@ -81,6 +85,7 @@ export class AgentModel { readonly waitLog = new Map(); readonly mainTurns: MainTurn[] = []; readonly mainTools: ToolActivity[] = []; + mainToolCount = 0; version?: string; sessionId?: string; sessionModel?: string; @@ -88,11 +93,12 @@ export class AgentModel { sessionEnded?: string; lastEventAt?: string; mainPrompt?: ContentRef; - mainResult?: ContentRef; + mainLatest?: ContentRef; private readonly latest = new Map(); private readonly launches = new Map(); private readonly calls = new Map(); private readonly seen = new Set(); + private readonly repliedThisTurn = new Set(); reset(): void { this.runs.clear(); @@ -101,10 +107,12 @@ export class AgentModel { this.waitLog.clear(); this.mainTurns.length = 0; this.mainTools.length = 0; + this.mainToolCount = 0; this.latest.clear(); this.launches.clear(); this.calls.clear(); this.seen.clear(); + this.repliedThisTurn.clear(); this.version = undefined; this.sessionId = undefined; this.sessionModel = undefined; @@ -112,7 +120,7 @@ export class AgentModel { this.sessionEnded = undefined; this.lastEventAt = undefined; this.mainPrompt = undefined; - this.mainResult = undefined; + this.mainLatest = undefined; } apply(record: EventRecord): void { @@ -133,20 +141,23 @@ export class AgentModel { case 'session.shutdown': this.sessionEnded = e.timestamp; break; + case 'session.resume': + this.sessionEnded = undefined; + break; case 'subagent.started': this.start(e); break; case 'subagent.completed': case 'subagent.failed': { - const callId = requiredString(d, 'toolCallId'); - const run = this.forLaunch(e, callId); - run.status = e.type === 'subagent.completed' ? 'completed' : 'failed'; + const launch = this.forLaunch(e, requiredString(d, 'toolCallId')); + // Lifecycle events name the original launch call even when a later turn of the same agent is the one + // still open. Recorded outcomes are kept; only the inferred 'replied' state can still be settled. + const run = [this.latest.get(launch.agentId), launch].find(candidate => candidate && isOpen(candidate)); + if (!run) break; + run.status = e.type === 'subagent.failed' ? 'failed' : d.cancelled === true ? 'cancelled' : 'completed'; run.endedAt = e.timestamp; run.lastActivity = e.timestamp; run.model = optionalString(d, 'model') ?? run.model; - if (typeof d.durationMs === 'number' && Number.isFinite(d.durationMs) && d.durationMs >= 0) { - run.durationMs = d.durationMs; - } if (e.type === 'subagent.failed') run.error = capture(record, 'error'); break; } @@ -173,31 +184,36 @@ export class AgentModel { const run = this.current(e); run.hasActivity = true; run.lastActivity = e.timestamp; - if (!isFinished(run) || run.status === 'replied') run.status = 'running'; + this.repliedThisTurn.delete(run.id); + if (run.status === 'replied') run.endedAt = undefined; + if (isOpen(run)) run.status = 'running'; } else { const last = this.mainTurns.at(-1); if (!last || last.end) this.mainTurns.push({start: e.timestamp}); } break; - case 'assistant.message': + case 'assistant.message': { + const content = capture(record, 'content'); if (e.agentId) { const run = this.current(e); run.hasActivity = true; run.lastActivity = e.timestamp; - const requests = d.toolRequests; - if (d.phase !== 'thinking' && (!Array.isArray(requests) || requests.length === 0)) { - run.result = capture(record, 'content') ?? run.result; + run.latest = content ?? run.latest; + if (content && isFinalAnswer(d)) { + run.result = content; + this.repliedThisTurn.add(run.id); } - } else if (d.phase !== 'thinking' && (!Array.isArray(d.toolRequests) || d.toolRequests.length === 0)) { - this.mainResult = capture(record, 'content') ?? this.mainResult; + } else { + this.mainLatest = content ?? this.mainLatest; + this.sessionModel = optionalString(d, 'model') ?? this.sessionModel; } - if (!e.agentId) this.sessionModel = optionalString(d, 'model') ?? this.sessionModel; break; + } case 'assistant.turn_end': if (e.agentId) { const run = this.current(e); run.lastActivity = e.timestamp; - if (run.status === 'running' && run.result && !run.tools.some(tool => tool.status === 'running')) { + if (run.status === 'running' && this.repliedThisTurn.has(run.id) && !run.tools.some(tool => tool.status === 'running')) { run.status = 'replied'; run.endedAt = e.timestamp; } @@ -209,10 +225,13 @@ export class AgentModel { case 'session.task_complete': if (e.agentId) { const run = this.current(e); - run.result = capture(record, 'summary') ?? run.result; - run.status = 'completed'; - run.endedAt = e.timestamp; run.lastActivity = e.timestamp; + // Rejected ("continue") or blocked completion requests leave the agent's work open. + if (d.outcome === 'completed' || (d.outcome === undefined && d.success !== false)) { + run.result = capture(record, 'summary') ?? run.result; + run.status = 'completed'; + run.endedAt = e.timestamp; + } } break; case 'tool.execution_start': @@ -318,8 +337,9 @@ export class AgentModel { const owner = e.agentId ? this.current(e) : undefined; const call: ToolCall = { ownerAgentId: e.agentId, ownerRunId: owner?.id ?? null, - name, task: optionalString(args, 'description'), background: args.mode === 'background', - targetAgentId: optionalString(args, 'agent_id'), wait: args.wait === true, complete: false, + name, background: args.mode === 'background', + targetAgentId: name === 'read_agent' && typeof args.agent_id === 'string' ? args.agent_id : undefined, + wait: args.wait === true, complete: false, startedAt: e.timestamp, prompt: name === 'task' ? capture(record, 'prompt') : undefined, }; call.activity = {id, name, status: 'running', startedAt: e.timestamp, detail: toolDetail(args)}; @@ -328,6 +348,7 @@ export class AgentModel { owner.lastActivity = e.timestamp; owner.tools.push(call.activity); } else { + this.mainToolCount++; this.mainTools.push(call.activity); if (this.mainTools.length > MAIN_TOOL_LIMIT) this.mainTools.shift(); } @@ -358,8 +379,9 @@ export class AgentModel { if (call.activity) { call.activity.status = d.success ? 'succeeded' : 'failed'; call.activity.endedAt = record.event.timestamp; - call.activity.output = capture(record, d.success ? 'result' : 'error') ?? capture(record, 'result'); } + // Only task calls are looked up again (by a late subagent.started); drop the rest. + if (call.name !== 'task') this.calls.delete(id); } } diff --git a/tools/agent-monitor/src/screen.ts b/tools/agent-monitor/src/screen.ts index fd5a38a2..f660c82d 100644 --- a/tools/agent-monitor/src/screen.ts +++ b/tools/agent-monitor/src/screen.ts @@ -117,14 +117,15 @@ export class Screen { for (const {segment} of graphemes.segment(text)) { const w = stringWidth(segment); if (w === 0) continue; - if (row && x >= 0 && x + w <= this.cols) { + if (row && x >= 0 && x < this.cols) { + // Never leave half of an existing wide character behind. if (row[x]!.ch === '' && x > 0) row[x - 1]!.ch = ' '; - const after = row[x + w]; - if (after && after.ch === '') after.ch = ' '; - row[x] = {ch: segment, fg: style.fg, bg: style.bg ?? row[x]!.bg, bold: Boolean(style.bold), dim: Boolean(style.dim)}; - for (let i = 1; i < w; i++) row[x + i] = {ch: '', fg: style.fg, bg: style.bg ?? row[x + i]!.bg, bold: false, dim: false}; - } else if (row && x >= 0 && x < this.cols) { - row[x] = {...row[x]!, ch: ' '}; + if (x + w <= this.cols) { + const after = row[x + w]; + if (after && after.ch === '') after.ch = ' '; + row[x] = {ch: segment, fg: style.fg, bg: style.bg ?? row[x]!.bg, bold: Boolean(style.bold), dim: Boolean(style.dim)}; + for (let i = 1; i < w; i++) row[x + i] = {ch: '', fg: style.fg, bg: style.bg ?? row[x + i]!.bg, bold: false, dim: false}; + } else row[x] = {...row[x]!, ch: ' '}; } x += w; } diff --git a/tools/agent-monitor/src/timeline.ts b/tools/agent-monitor/src/timeline.ts index b9072a8f..408432f1 100644 --- a/tools/agent-monitor/src/timeline.ts +++ b/tools/agent-monitor/src/timeline.ts @@ -1,15 +1,18 @@ import type {Frame, Rect} from './chrome.js'; +import {isFinished} from './model.js'; import {fit, Screen, width} from './screen.js'; import type {Part, Style} from './screen.js'; -import {elapsed, formatDuration, glyphPart, isActiveVisual, mainSpans, MAIN, runSpan, visualOf, waitSpan} from './tree.js'; +import {elapsed, formatDuration, glyphPart, isActiveVisual, mainSpans, MAIN, runSpan, VISUALS, visualOf, waitSpan} from './tree.js'; import type {Node, Visual} from './tree.js'; import type {Tone} from './theme.js'; const MUTED: Style = {fg: 'muted'}; const STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400]; const BAR: Record = { - running: 'accent', waiting: 'accent', done: 'success', failed: 'danger', replied: 'done', idle: 'muted', ended: 'muted', unknown: 'muted', + running: 'accent', waiting: 'accent', done: 'success', failed: 'danger', cancelled: 'muted', replied: 'done', + idle: 'muted', ended: 'muted', unknown: 'muted', }; +const END_MARKS: Visual[] = ['failed', 'cancelled', 'replied']; const clamp = (value: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, value)); export function niceStep(raw: number): number { @@ -45,22 +48,26 @@ function activityWindow(frame: Frame, total: number): [number, number] { return hi - lo < 1 ? [Math.max(0, hi - 1), hi] : [lo, hi]; } -function treePrefix(scr: Screen, node: Node, y: number, span: number, base: number): void { +/** Draws tree connectors left of a lane; deeper levels than `cap` share the last indentation column. */ +function treePrefix(scr: Screen, node: Node, y: number, span: number, base: number, cap: number): void { const line: Style = {fg: 'border'}; const isLast = (n: Node): boolean => n.parent?.children.at(-1) === n; const chain: Node[] = []; for (let p = node.parent; p?.parent; p = p.parent) chain.unshift(p); - chain.forEach((ancestor, i) => { + chain.slice(0, cap - 1).forEach((ancestor, i) => { if (!isLast(ancestor)) for (let r = 0; r < span; r++) scr.put(base + 3 * i, y + r, '│', line); }); - if (node.depth > 0) { - const cx = base + 3 * (node.depth - 1); + const depth = Math.min(node.depth, cap); + if (depth > 0) { + const cx = base + 3 * (depth - 1); const turn = node.edge === 'turn'; scr.put(cx, y, isLast(node) ? (turn ? '╰' : '└') : '├', line); scr.put(cx + 1, y, turn ? '┄' : '─', line); if (!isLast(node)) for (let r = 1; r < span; r++) scr.put(cx, y + r, '│', line); } - if (node.children.length) for (let r = 1; r < span; r++) scr.put(base + 3 * node.depth, y + r, node.children[0]!.edge === 'turn' ? '┆' : '│', line); + if (node.children.length && node.depth < cap) { + for (let r = 1; r < span; r++) scr.put(base + 3 * depth, y + r, node.children[0]!.edge === 'turn' ? '┆' : '│', line); + } } export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: number): void { @@ -86,15 +93,18 @@ export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: num scr.rput(durationRight, axisY, 'Time', MUTED); scr.hline(cx, axisY + 1, cw, '─', {fg: 'border'}); const nowC = now >= start && now <= end + 1e-9 ? cx + clamp(Math.round((now - start) / spc), 0, cw - 1) : -1; + const nowLabel: [number, number] | undefined = nowC >= 0 ? [nowC - 1, nowC + 1] : now > end ? [cx + cw - 5, cx + cw - 1] : undefined; const grid: number[] = []; const step = niceStep((end - start) / 6); for (let t = Math.ceil(start / step) * step; t <= end; t += step) { const c = col(t); - if (nowC >= 0 && t > start && Math.abs(nowC - c) < 6) continue; + const label = tickLabel(t, step); + const lx = c === cx ? c : c - Math.floor(width(label) / 2); + // Keep a blank column between a tick label and the "now" marker. + if (nowLabel && lx <= nowLabel[1] + 1 && lx + width(label) - 1 >= nowLabel[0] - 1) continue; grid.push(c); scr.put(c, axisY + 1, '┬', {fg: 'border'}); - const label = tickLabel(t, step); - scr.put(c === cx ? c : c - Math.floor(width(label) / 2), axisY, label, MUTED); + scr.put(lx, axisY, label, MUTED); } if (nowC >= 0) { scr.put(nowC, axisY + 1, '┬', {fg: 'accent'}); @@ -112,6 +122,8 @@ export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: num const bottom = laneY0 + 2 * shown.length - 1; for (const c of grid) for (let y = laneY0; y <= bottom; y++) scr.put(c, y, '┊', {fg: 'faint'}); if (nowC >= 0) for (let y = laneY0; y <= bottom; y++) scr.put(nowC, y, '│', {fg: 'accent', dim: true}); + const cap = Math.max(1, Math.floor((labelEnd - 19) / 3)); + const mainOpen = isActiveVisual(model.mainStatus()); for (const node of shown) { const y = laneY.get(node.id)!; const visual = visualOf(model, node); @@ -120,21 +132,22 @@ export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: num scr.put(1, y, '▎', {fg: 'selbar'}); scr.fill(2, y, cols - 3, 1, 'sel'); } - treePrefix(scr, node, y, 2, 3); - const gx = 3 + 3 * node.depth; + treePrefix(scr, node, y, 2, 3, cap); + const gx = 3 + 3 * Math.min(node.depth, cap); const type = node.run ? (node.edge === 'turn' ? 'turn' : TYPE_SHORT[node.run.agentType ?? ''] ?? node.run.agentType ?? 'agent') : 'session'; scr.seq(gx, y, [glyphPart(visual, frame.spin, node.ctx)]); scr.rput(labelEnd, y, type, {...MUTED, dim: node.ctx}); scr.put(gx + 2, y, fit(node.run ? node.run.name : 'main', labelEnd - width(type) - gx - 3), {fg: selected ? 'accent' : 'fg', bold: true, dim: node.ctx}); - const segments: Array<[number, number, Tone]> = node.run - ? [[...runSpan(model, node.run, now), BAR[visual]]] - : mainSpans(model, now).map(([a, b]) => [a, b, 'muted' as Tone]); - for (const [a, b, tone] of segments) { + const spans = node.run ? [] : mainSpans(model, now); + const segments: Array<[number, number, Tone, boolean]> = node.run + ? [[...runSpan(model, node.run, now), BAR[visual], !isFinished(node.run)]] + : spans.map(([a, b], i) => [a, b, 'muted', mainOpen && i === spans.length - 1]); + for (const [a, b, tone, open] of segments) { if (b < start || a > end) continue; const c0 = col(Math.max(a, start)); const c1 = Math.max(c0, colEnd(Math.min(b, end))); for (let c = c0; c <= c1; c++) scr.put(c, y, c1 > c0 && c === c0 && a >= start ? '╺' : '━', {fg: tone, dim: node.ctx}); - if (c1 > c0 && b <= end && !(node.run && isActiveVisual(visual))) scr.put(c1, y, '╸', {fg: tone, dim: node.ctx}); + if (c1 > c0 && b <= end && !open) scr.put(c1, y, '╸', {fg: tone, dim: node.ctx}); } const key = node.run ? node.run.id : null; for (const edge of model.waitLog.values()) { @@ -147,8 +160,8 @@ export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: num if (node.run) { const [, b] = runSpan(model, node.run, now); if (visual === 'running' && nowC >= 0) scr.put(nowC, y, '●', {fg: 'accent', dim: node.ctx}); - if ((visual === 'failed' || visual === 'replied') && b >= start && b <= end && colEnd(b) + 1 < cx + cw) { - scr.put(colEnd(b) + 1, y, visual === 'failed' ? '✗' : '↩', {fg: visual === 'failed' ? 'danger' : 'done', dim: node.ctx}); + if (END_MARKS.includes(visual) && b >= start && b <= end && colEnd(b) + 1 < cx + cw) { + scr.put(colEnd(b) + 1, y, VISUALS[visual].glyph, {fg: VISUALS[visual].tone, dim: node.ctx}); } } scr.rput(durationRight, y, formatDuration(elapsed(model, node, now)), {fg: isActiveVisual(visual) ? 'fg' : 'muted', dim: node.ctx}); @@ -165,11 +178,11 @@ export function drawTimeline(scr: Screen, frame: Frame, area: Rect, legendY: num scr.put(c, toY - dir, dir > 0 ? '▾' : '▴', style); } const legend: Part[] = []; - const items: Array<[string, Tone, string, boolean?]> = [ - ['━━', 'accent', 'running'], ['━━', 'success', 'done'], ['━━', 'danger', 'failed'], ['━━', 'done', 'replied'], - ['━━', 'muted', 'main turn'], ['╍╍', 'attention', 'waiting'], ['▾', 'attention', 'waits for'], ['│', 'accent', 'now'], + const items: Array<[string, Tone, string]> = [ + ['━━', 'accent', 'running'], ['━━', 'success', 'done'], ['━━', 'danger', 'failed'], ['━■', 'muted', 'cancelled'], + ['━━', 'done', 'replied'], ['━━', 'muted', 'main turn'], ['╍╍', 'attention', 'waiting'], ['▾', 'attention', 'waits for'], ]; - for (const [glyph, tone, label] of items) legend.push([glyph, {fg: tone}], [` ${label} `, MUTED]); + for (const [glyph, tone, label] of items) legend.push([glyph, {fg: tone}], [` ${label} `, MUTED]); const scale = `${spc < 10 ? spc.toFixed(1) : Math.round(spc)}s / col`; const range = lanes.length > capacity ? `lanes ${top + 1}-${top + shown.length} of ${lanes.length} ` : ''; scr.seqFit(3, legendY, legend, cols - width(range + scale) - 6); diff --git a/tools/agent-monitor/src/tree.ts b/tools/agent-monitor/src/tree.ts index 91cc5cba..47fb18f6 100644 --- a/tools/agent-monitor/src/tree.ts +++ b/tools/agent-monitor/src/tree.ts @@ -21,13 +21,14 @@ export interface Tree { byId: Map; } -export type Visual = 'running' | 'waiting' | 'done' | 'failed' | 'replied' | 'idle' | 'ended' | 'unknown'; +export type Visual = 'running' | 'waiting' | 'done' | 'failed' | 'cancelled' | 'replied' | 'idle' | 'ended' | 'unknown'; export const VISUALS: Record = { running: {glyph: '◐', tone: 'accent', label: 'running'}, waiting: {glyph: '◌', tone: 'attention', label: 'waiting'}, done: {glyph: '✓', tone: 'success', label: 'done'}, failed: {glyph: '✗', tone: 'danger', label: 'failed'}, + cancelled: {glyph: '■', tone: 'muted', label: 'cancelled'}, replied: {glyph: '↩', tone: 'done', label: 'replied'}, idle: {glyph: '○', tone: 'muted', label: 'idle'}, ended: {glyph: '○', tone: 'muted', label: 'ended'}, @@ -40,7 +41,8 @@ export function visualOf(model: AgentModel, node: Node): Visual { if (!node.run) return model.mainStatus(); const status = model.status(node.run); if (status === 'completed') return 'done'; - return status === 'running' || status === 'waiting' || status === 'failed' || status === 'replied' ? status : 'unknown'; + return status === 'running' || status === 'waiting' || status === 'failed' || status === 'cancelled' || status === 'replied' + ? status : 'unknown'; } export function glyphPart(visual: Visual, spin: number, dim = false): Part { @@ -57,12 +59,26 @@ function matches(run: AgentRun, filter: Filter): boolean { const startOf = (run: AgentRun): number => Date.parse(run.startedAt ?? run.lastActivity); -/** Builds the display tree: spawned agents under their launcher, later turns under the previous turn. */ +/** The agent's first execution; later turns hang off it as siblings so a long conversation does not nest deeper. */ +function firstTurn(model: AgentModel, run: AgentRun): AgentRun { + const seen = new Set(); + let first = run; + while (first.previousRunId && !seen.has(first.id)) { + seen.add(first.id); + const previous = model.runs.get(first.previousRunId); + if (!previous) break; + first = previous; + } + return first; +} + +/** Builds the display tree: spawned agents under their launcher, later turns under the agent's first turn. */ export function buildTree(model: AgentModel, filter: Filter): Tree { const root: Node = {id: MAIN, children: [], edge: 'root', depth: 0, ctx: filter !== 'all'}; const parentOf = new Map(); for (const run of model.runs.values()) { - if (run.previousRunId && model.runs.has(run.previousRunId)) parentOf.set(run.id, {id: run.previousRunId, edge: 'turn'}); + const first = firstTurn(model, run); + if (first !== run) parentOf.set(run.id, {id: first.id, edge: 'turn'}); else if (run.parentRunId === null) parentOf.set(run.id, {id: MAIN, edge: 'spawn'}); else if (run.parentRunId && model.runs.has(run.parentRunId)) parentOf.set(run.id, {id: run.parentRunId, edge: 'spawn'}); else parentOf.set(run.id, {id: MAIN, edge: 'orphan'}); @@ -120,13 +136,14 @@ export function nowSeconds(model: AgentModel, nowMs: number): number { export function runSpan(model: AgentModel, run: AgentRun, now: number): [number, number] { const start = sessionSeconds(model, run.startedAt ?? run.lastActivity); - const end = run.endedAt ? sessionSeconds(model, run.endedAt) : isFinished(run) ? sessionSeconds(model, run.lastActivity) : now; + // Without a lifecycle status the run is only known to have lasted until its last recorded event. + const end = isFinished(run) ? sessionSeconds(model, run.endedAt ?? run.lastActivity) + : run.status === 'unknown' ? sessionSeconds(model, run.lastActivity) : now; return [start, Math.max(start, end)]; } export function elapsed(model: AgentModel, node: Node, now: number): number { if (!node.run) return now; - if (node.run.durationMs !== undefined && node.run.status === 'completed') return node.run.durationMs / 1000; const [start, end] = runSpan(model, node.run, now); return end - start; } diff --git a/tools/agent-monitor/test/App.test.tsx b/tools/agent-monitor/test/App.test.tsx index 2e2715ff..c329d6a9 100644 --- a/tools/agent-monitor/test/App.test.tsx +++ b/tools/agent-monitor/test/App.test.tsx @@ -54,7 +54,7 @@ test('opens on the Graph tab, shows what the selected agent did, and keeps tool assert.doesNotMatch(output, /Relationships/); }); -test('Tab switches to Timeline swimlanes with an axis, lanes, recorded waits and the selected history', async t => { +test('Tab switches to Timeline swimlanes: a wait sits on the waiting lane and points at the awaited lane', async t => { const view = mount(modelOf()); t.after(() => view.cleanup()); await frame(view, /Main session/); @@ -63,13 +63,21 @@ test('Tab switches to Timeline swimlanes with an axis, lanes, recorded waits and await frame(view, /Child result/); view.stdin.write('\t'); const output = await frame(view, /waits for/); - assert.match(output, /Agent\s+Type/); - assert.match(output, /now/); - assert.match(output, /▾/); + const lines = output.split('\n'); + const lane = (pattern: RegExp): number => lines.findIndex(line => pattern.test(line)); + const parent = lane(/ parent\s+general/); + assert.match(lines[parent]!, /╍/); + assert.match(lines[parent + 1]!, /▾/); + assert.equal(lane(/ child\s+general/), parent + 2); + assert.match(lines[lane(/ main\s+session/)]!, /━/); assert.match(output, /Child result/); - for (const name of ['main', 'parent', 'child', 'control']) assert.match(output, new RegExp(`${name}\\s`)); + const axis = (text: string): string => text.split('\n').find(line => /Agent\s+Type/.test(line))!; + const scale = (text: string): number => Number(/([\d.]+)s \/ col/.exec(text)![1]); + assert.doesNotMatch(axis(output), /\Snow/); view.stdin.write('+'); - await frame(view, / 2x /); + const zoomed = await frame(view, / 2x /); + assert.ok(scale(zoomed) < scale(output)); + assert.doesNotMatch(axis(zoomed), /\Snow/); }); test('filtering keeps ancestors as context and narrow terminals show a resize message', async t => { @@ -97,9 +105,76 @@ test('recorded control sequences are inert and long results scroll inside the in await frame(view, /Main session/); view.stdin.write('j'); let output = await frame(view, /RESULT-LINE-0/); - assert.doesNotMatch(view.lastFrame() ?? '', /\u001b\[2J/); + assert.doesNotMatch(output, /\[2J/); assert.match(output, /lines 1-/); view.stdin.write('\u001b[6~'); output = await frame(view, /RESULT-LINE-6\b/); assert.doesNotMatch(output, /RESULT-LINE-0\b/); }); + +test('a main session blocked on a sync agent shows the agent by display name, never by agent ID', async t => { + const agentId = '0f8e2c1a-7b3d-4e5f-9a6b-1c2d3e4f5a6b'; + const model = modelOf([ + record('session.start', {copilotVersion: '1.0.104', sessionId: 'sync-wait'}), + record('assistant.turn_start', {turnId: '0'}), + record('tool.execution_start', {toolCallId: 'review-call', toolName: 'task', arguments: {mode: 'sync'}}), + record('subagent.started', { + toolCallId: 'review-call', agentName: 'code-review', agentDisplayName: 'Code Review', + agentDescription: 'Review the diff', executionMode: 'sync', + }, agentId), + ]); + const view = mount(model); + t.after(() => view.cleanup()); + const output = await frame(view, /waiting on Code Review/); + assert.match(output, /main session\s+waiting/); + assert.doesNotMatch(output, new RegExp(agentId)); +}); + +test('the graph narrows cards to fit a wide fan-out and scrolls to the selected card beyond that', async t => { + const fanOut = (count: number): AgentModel => modelOf([ + record('session.start', {copilotVersion: '1.0.104', sessionId: 'fan-out'}), + ...Array.from({length: count}, (_, i) => start(`call-${i + 1}`, `agent-${i + 1}`)), + ]); + const five = mount(fanOut(5)); + t.after(() => five.cleanup()); + assert.doesNotMatch(await frame(five, /agent-5/), /[◂▸]/); + const eight = mount(fanOut(8)); + t.after(() => eight.cleanup()); + await frame(eight, /▸/); + eight.stdin.write('G'); + assert.match(await frame(eight, /agent-8/), /◂/); +}); + +test('at 80 columns the header keeps its chips and t shows tool calls in the single-column inspector', async t => { + const view = mount(modelOf(), 80, 30); + t.after(() => view.cleanup()); + await frame(view, /Main session/); + view.stdin.write('\t'); + const output = await frame(view, /window:/); + assert.match(output, /session: syntheti /); + assert.match(output, /○ ended/); + view.stdin.write('j'); + view.stdin.write('t'); + await frame(view, /Tools · 1/); +}); + +test('a slow text load for an earlier selection does not replace the current one', async t => { + let release!: () => void; + const gate = new Promise(resolve => { release = resolve; }); + const slowLoad = async (ref: ContentRef): Promise => { + if (ref.inline === 'Parent result') await gate; + return load(ref); + }; + const model = modelOf(); + const clock = (): number => Date.parse(model.lastEventAt!) + 5000; + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /Main session/); + view.stdin.write('j'); + await frame(view, /Loading recorded text/); + view.stdin.write('j'); + await frame(view, /Child result/); + release(); + await setTimeout(50); + assert.match(stripVTControlCharacters(view.lastFrame() ?? ''), /Child result/); +}); diff --git a/tools/agent-monitor/test/events.test.ts b/tools/agent-monitor/test/events.test.ts index 2d29ed8d..6b35355a 100644 --- a/tools/agent-monitor/test/events.test.ts +++ b/tools/agent-monitor/test/events.test.ts @@ -25,7 +25,6 @@ test('incremental replay buffers partial UTF-8/JSONL and reads full results lazi await reader.poll(accept, unexpectedReset); assert.equal(records.length, 1); const ref = capture(records[0]!, 'content')!; - assert.ok(ref.preview.length < text.length); assert.equal(ref.inline, undefined); assert.equal(await reader.readContent(ref), text); assert.equal(await reader.poll(accept, unexpectedReset), false); @@ -33,6 +32,25 @@ test('incremental replay buffers partial UTF-8/JSONL and reads full results lazi assert.deepEqual(await readFile(file), bytes); }); +test('recorded text is read back from later records, across polls and 64 KB read chunks', async t => { + const directory = await mkdtemp(join(tmpdir(), 'agent-monitor-offsets-')); + t.after(() => rm(directory, {recursive: true})); + const file = join(directory, 'events.jsonl'); + const line = (content: string): Buffer => Buffer.from(JSON.stringify(record('assistant.message', {content}, 'agent').event) + '\n'); + const big = 'x'.repeat(100 * 1024); + const head = Buffer.concat([line('first'), line(big)]); + const split = head.length - 10; + await writeFile(file, head.subarray(0, split)); + const reader = new JsonlReader(file); + const records: EventRecord[] = []; + const accept = (event: EventRecord): void => { records.push(event); }; + await reader.poll(accept, () => {}); + await appendFile(file, Buffer.concat([head.subarray(split), line('last')])); + await reader.poll(accept, () => {}); + const texts = await Promise.all(records.map(item => reader.readContent(capture(item, 'content')!))); + assert.deepEqual(texts, ['first', big, 'last']); +}); + test('rewriting a log past its old size resets state and invalidates old result references', async t => { const directory = await mkdtemp(join(tmpdir(), 'agent-monitor-reset-')); t.after(() => rm(directory, {recursive: true})); diff --git a/tools/agent-monitor/test/model.test.ts b/tools/agent-monitor/test/model.test.ts index ac17ba1c..756f25c0 100644 --- a/tools/agent-monitor/test/model.test.ts +++ b/tools/agent-monitor/test/model.test.ts @@ -23,8 +23,6 @@ test('parallel/nested agents retain their own results and same-agent follow-ups assert.equal(followup.agentId, control.agentId); assert.equal(followup.status, 'replied'); assert.equal(followup.result?.inline, 'Follow-up result'); - model.apply(records.at(-1)!); - assert.equal(model.runs.size, 4); }); test('late start and launch records hydrate a completed invocation without regressing its outcome', () => { @@ -82,23 +80,95 @@ test('waits keep recorded history; launch mode, agent type, prompts, follow-up l assert.equal(model.sessionModel, 'synthetic-model'); }); -test('unsupported agent signals are diagnosed, not fabricated as completion or cancellation', () => { +test('unsupported agent signals are diagnosed, not fabricated as an outcome', () => { const model = new AgentModel(); model.apply(start('a', 'a')); - model.apply(record('subagent.cancelled', {toolCallId: 'a'}, 'a')); + model.apply(record('subagent.teleported', {toolCallId: 'a'}, 'a')); assert.equal(model.runs.get('launch:a')?.status, 'running'); - assert.ok([...model.warnings].some(warning => warning.includes('subagent.cancelled'))); + assert.ok([...model.warnings].some(warning => warning.includes('subagent.teleported'))); assert.throws(() => model.apply(start('a', 'different-agent')), /Conflicting agent ID/); assert.throws(() => model.apply(record('subagent.started', {toolCallId: 'missing-agent'})), /agentId/); }); -test('main-session completion does not complete children; an attributed task summary completes only its own execution', () => { +test('lifecycle events settle the open turn of an agent, keep earlier outcomes, and report cancellation as its own status', () => { + const model = new AgentModel(); + model.apply(start('call', 'a')); + model.apply(record('assistant.message', {content: 'First answer'}, 'a')); + model.apply(record('assistant.turn_end', {turnId: '0'}, 'a')); + model.apply(record('subagent.completed', {toolCallId: 'call'}, 'a')); + const first = model.runs.get('launch:call')!; + const firstEnd = first.endedAt; + assert.equal(first.status, 'completed'); + model.apply(record('user.message', {content: 'Keep going', delivery: 'idle'}, 'a')); + model.apply(record('assistant.turn_start', {turnId: '0'}, 'a')); + model.apply(record('subagent.completed', {toolCallId: 'call', cancelled: true}, 'a')); + const second = [...model.runs.values()].find(run => run.previousRunId === first.id)!; + assert.equal(second.status, 'cancelled'); + assert.equal(first.status, 'completed'); + assert.equal(first.endedAt, firstEnd); +}); + +test('commentary is progress, not a result; a reply counts only when the current turn ends with a final answer', () => { + const model = new AgentModel(); + model.apply(start('call', 'a')); + model.apply(record('assistant.turn_start', {turnId: '0'}, 'a')); + model.apply(record('assistant.message', {content: 'Checking the logs', phase: 'commentary'}, 'a')); + model.apply(record('assistant.turn_end', {turnId: '0'}, 'a')); + const run = model.runs.get('launch:call')!; + assert.equal(run.status, 'running'); + assert.equal(run.result?.inline, undefined); + assert.equal(run.latest?.inline, 'Checking the logs'); + model.apply(record('assistant.turn_start', {turnId: '1'}, 'a')); + model.apply(record('assistant.message', {content: 'Done', phase: 'final_answer'}, 'a')); + model.apply(record('assistant.turn_end', {turnId: '1'}, 'a')); + assert.equal(run.status, 'replied'); + assert.equal(run.result?.inline, 'Done'); + model.apply(record('assistant.turn_start', {turnId: '2'}, 'a')); + assert.equal(run.status, 'running'); + assert.equal(run.endedAt, undefined); + model.apply(record('assistant.turn_end', {turnId: '2'}, 'a')); + assert.equal(run.status, 'running'); +}); + +test('tool arguments of unexpected types are ignored instead of stopping the viewer', () => { + const model = new AgentModel(); + model.apply(start('call', 'a')); + model.apply(record('tool.execution_start', {toolCallId: 'v', toolName: 'view', arguments: {description: 42, agent_id: {}}}, 'a')); + model.apply(record('tool.execution_start', {toolCallId: 'r', toolName: 'read_agent', arguments: {agent_id: 7, wait: true}}, 'a')); + assert.equal(model.runs.get('launch:call')!.tools.length, 2); + assert.equal(model.waits.size, 0); +}); + +test('a resumed session is live again after a shutdown', () => { + const model = new AgentModel(); + model.apply(record('session.start', {copilotVersion: '1.0.104', sessionId: 'resume'})); + model.apply(record('session.shutdown', {})); + assert.equal(model.mainStatus(), 'ended'); + model.apply(record('session.resume', {})); + model.apply(record('assistant.turn_start', {turnId: '0'})); + assert.equal(model.mainStatus(), 'running'); +}); + +test('reset clears session state so a rewritten log replays from scratch', () => { + const model = new AgentModel(); + const events = fixture(); + for (const event of events) model.apply(event); + model.reset(); + for (const event of events) model.apply(event); + assert.equal(model.runs.size, 4); + assert.equal(model.mainTurns.length, 1); + assert.equal(model.waitLog.size, 1); +}); + +test('main-session completion does not complete children; only an accepted task summary completes its own execution', () => { const model = new AgentModel(); model.apply(start('a', 'a')); model.apply(start('b', 'b')); model.apply(record('session.task_complete', {summary: 'Main finished'})); assert.equal(model.runs.get('launch:a')?.status, 'running'); assert.equal(model.runs.get('launch:b')?.status, 'running'); + model.apply(record('session.task_complete', {summary: 'Not yet', success: false, outcome: 'continue'}, 'a')); + assert.equal(model.runs.get('launch:a')?.status, 'running'); model.apply(record('session.task_complete', {summary: 'Agent A finished'}, 'a')); assert.equal(model.runs.get('launch:a')?.status, 'completed'); assert.equal(model.runs.get('launch:a')?.result?.inline, 'Agent A finished'); diff --git a/tools/agent-monitor/test/tree.test.ts b/tools/agent-monitor/test/tree.test.ts index f5119fac..fc3e61e4 100644 --- a/tools/agent-monitor/test/tree.test.ts +++ b/tools/agent-monitor/test/tree.test.ts @@ -3,10 +3,11 @@ import {test} from 'node:test'; import {CARD_H, layoutGraph, levelY} from '../src/graph.js'; import type {Placed} from '../src/graph.js'; import {AgentModel} from '../src/model.js'; +import {Screen, width} from '../src/screen.js'; import {buildTree, MAIN, mainSpans, nowSeconds} from '../src/tree.js'; -import {fixture, record} from './fixture.js'; +import {fixture, record, start} from './fixture.js'; -test('display tree nests spawned agents under launchers, later turns under the previous turn, and filters keep ancestors', () => { +test('display tree nests spawned agents under launchers, later turns under the first turn, and filters keep ancestors', () => { const model = new AgentModel(); const events = fixture(); for (const event of events.slice(0, 15)) model.apply(event); @@ -17,24 +18,37 @@ test('display tree nests spawned agents under launchers, later turns under the p assert.equal(child.ctx, false); assert.equal(buildTree(model, 'active').byId.has('launch:control-call'), false); for (const event of events.slice(15)) model.apply(event); + model.apply(record('user.message', {content: 'Second follow-up', delivery: 'idle'}, 'control')); + model.apply(record('assistant.turn_start', {turnId: '0'}, 'control')); const tree = buildTree(model, 'all'); - const followup = tree.order.find(node => node.id.startsWith('message:'))!; + const followups = tree.order.filter(node => node.id.startsWith('message:')); assert.equal(tree.byId.get('launch:parent-call')!.parent, tree.root); - assert.equal(followup.parent?.id, 'launch:control-call'); - assert.equal(followup.edge, 'turn'); - assert.deepEqual(tree.order.map(node => node.depth), [0, 1, 2, 1, 2]); + assert.equal(followups.length, 2); + for (const followup of followups) { + assert.equal(followup.parent?.id, 'launch:control-call'); + assert.equal(followup.edge, 'turn'); + } + assert.deepEqual(tree.order.map(node => node.depth), [0, 1, 2, 1, 2, 2]); }); -test('cyclic parent data is re-homed instead of recursing forever', () => { +test('cyclic launch evidence is re-homed instead of recursing forever', () => { const model = new AgentModel(); - for (const event of fixture()) model.apply(event); - const parent = model.runs.get('launch:parent-call')!; - parent.parentRunId = 'launch:child-call'; + model.apply(record('tool.execution_start', {toolCallId: 'call-a', toolName: 'task', arguments: {mode: 'background'}}, 'b')); + model.apply(start('call-a', 'a')); + model.apply(record('tool.execution_start', {toolCallId: 'call-b', toolName: 'task', arguments: {mode: 'background'}}, 'a')); + model.apply(start('call-b', 'b')); const tree = buildTree(model, 'all'); assert.equal(tree.order.length, model.runs.size + 1); assert.ok(tree.order.some(node => node.edge === 'orphan')); }); +test('a wide character cut off by the right edge never widens a screen row', () => { + const scr = new Screen(10, 1); + scr.put(0, 0, '一二三四五'); + scr.put(9, 0, '汉'); + assert.equal(scr.segments()[0]!.reduce((sum, segment) => sum + width(segment.text), 0), 10); +}); + test('graph layout centres parents over their children without overlapping cards', () => { const model = new AgentModel(); for (const event of fixture()) model.apply(event); From f364bd9f00f06b7e68c883b0f920c0bf12715eaf Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Mon, 2 Nov 2026 16:48:29 -0800 Subject: [PATCH 4/7] Add agent monitor screenshots Graph and Timeline captures from a fictional demo session, linked from the README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/README.md | 10 ++++++++++ tools/agent-monitor/docs/graph-waiting.png | Bin 0 -> 99993 bytes tools/agent-monitor/docs/graph.png | Bin 0 -> 108466 bytes tools/agent-monitor/docs/timeline.png | Bin 0 -> 98602 bytes 4 files changed, 10 insertions(+) create mode 100644 tools/agent-monitor/docs/graph-waiting.png create mode 100644 tools/agent-monitor/docs/graph.png create mode 100644 tools/agent-monitor/docs/timeline.png diff --git a/tools/agent-monitor/README.md b/tools/agent-monitor/README.md index e8b84feb..47f4aba5 100644 --- a/tools/agent-monitor/README.md +++ b/tools/agent-monitor/README.md @@ -4,6 +4,16 @@ Independent, read-only terminal viewer for **one explicitly selected local Copil CLI session**. It does not change the built-in `/tasks` interface, load the Copilot SDK, resume the session, install hooks, call a model, or write to the source log. +## Screenshots + +Captured from a fictional demo session (synthetic events, no real transcript). + +![Graph view: the spawn tree with launch modes and recorded wait durations](docs/graph.png) + +![Graph view: an agent blocked on the sync agent it launched](docs/graph-waiting.png) + +![Timeline view: swimlanes with waits, a later turn and the now cursor](docs/timeline.png) + ## Run on Windows Requires Node.js 22 or later. In PowerShell: diff --git a/tools/agent-monitor/docs/graph-waiting.png b/tools/agent-monitor/docs/graph-waiting.png new file mode 100644 index 0000000000000000000000000000000000000000..9063154b50d66ca0d7a10c9dfc1bc226833c382d GIT binary patch literal 99993 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%0AwES4hu84elmlSwmM4?Rck>96+fsp<5_l-M`< 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�!dFl^M3Q5*|6 zqX^O>cBP4h&;lw}5D^M6(LvvM<5YtB9KT4B_u(m1Pl;*fB;b- zgb<1lLLi~sjWhGz@4NS|b?>_C{wee&FBcz{XL<{6I&x(FCu>d8kb&LVjU}n70cdM z5KiA#&w|q8mffOojt80IP8%V|(q?FR&2vahbjJ^41A=|J1KX;~X%3!S8KGdWYjL*S zoctMC&sI!~?s&|7bI@f2`n&h97CseAIe-I$+PZ_!4>-m`w`IY(W??_-Yssf(7PON9 zMeShJg??MktS2z~pf3Fo`MIphJ(h(yZ6*&N5)swM6%J*<4fiGVdZwV@)-GRn0eulnxaJ$bd7Y;lGGm|dd|*&74_mV%B)}o#$Nqj-y$Jm;593&N9xrjWi zOziV)pR|&d;x^2yyd6BL5Nst55WOduObqel9CS4-MZU+res_Y<_4c=jqlDP0eRM-z z?LKv>ba(g&&toq56;2=QGUIn;->@13#)k-77L8oxUlBJby-)m8aJjFwylgzhd)_$H z!&-sbL4==W-yp{ayEKH==u|GLinc%_c3J9ci-m zyuqzF=z|aGjF33g{^<1?V$VB*+Cu$kGG*%V=Z`*{!w|3Jn!Bv`^y>_%X-lpjW;2nG z#qfRRwga(tV>an*c9?v_AC*?B+?T@I2b4fdLq)PRG|n{w6RwPe#TSSBCu1M(&m^&j zJ-*@ZxS6rb)@=pMC?EU&WpZVVCL;#Okc21o6J=!s2(Y#vQZS#r9ixd5ZMv})^(&fg zPgA)ZWBzUWuobhTpqi~^P8_+Dr-rjE&>${P1)2BV4RV{{!A_Npdz&pzc&{bsW6hsM zB+aoz0dX@TYkpUkB?ajaLx*Jy%8!`aGKBHLw9TX(vs?C5Vx!g+)oapbVQCuujD)hf zxkT+DCL2ykhd-Ys+=X9x{3RP+ifc6=58d*F`~ER{@YrJs5Gk+9EuBC-s`k^^7d9a& z`15#(=;yxis}<~yGVQ+j7gI@8p#HCY)#VNHQ|jA7w_E<&N;gipjLR`E_6d8ri*ll+ z@x;dWck(NHt#fK4aPa=pIMii7Qjq*eK~gMA>W9;;oODFuPya$o99KI?o81Wsf6R_d zk6^RIVepFfdlgwEE;^nO*k%s0UnkMrk^UOc3?~PW>>h@+&h^BrufP zNB0&Wdv9|u)VkK{F(D7wL`y54rHgg(3RX1lRw;8jja#(IZJjq?QvA)0|7!HK3-R}b zka^VuG5jBwO&1nQ*m*9@hTK_`V+u>6Xqjl#udg_V4OKJ?n4=5VW7V|Tg52UsE4u65 zinTeqa-Tx-L=Mj(XUVcH&weJ&&+ME%ynSGRHvTC^ae@3WlyiE(EK0R-S5(s;j7H~| zRy`<`n8cKIK7km$dbL_WeO97Yc{;c}CVcZd{L<3CRElSZ&1=-UzT~en(0a>`5npx6 zuV~dHPRf6-&lXYXJ+q=P^{GKPX?*AJpQOc`1`jDd`jqKAbS?3rCr%d zSgQHe#Ra>(tF&C&&XN?>?(nHFAM=ZChQ<@?K|&HERLH=_K9BBDImrQwvn%)RMu+GN z>rURpD|`=2ftKE{-ZG3pIBiZGir?#YbhU~+K`zM`-Vsh3 zJj3Z~^xfzH*=J>m975PQmopQ)Ekn)$3kBOpO(uN7!yce8g1;9&4UTQz(>v}L`%Q7g z`WL?;Q32zxre1~0+kzgl_0PGz(=B2s;yx_Hk!_P4jP)OG&3(Cx;db{GE4_H~DIlL74?2AJe%wkvqbphQTv>@7172RtK>Itcw^G=}{AB;6sF{U;_pV4ztJBI1* z_u&3a8ptfazlokyfYoRml_Xy=qL~cCJGC7WIdS-R1A14`jtEgdSh)eqT7l zjKx@sRhdX;wn8(#So#SvqpOqy8oS)0=d!=5kBz?Bfp5dYekX02sfd1ZcSBIcudT!* z+V5klp7Aht)bKiFAm*QM{9R9Jk=51gacO=A6Md!l02^xQVt>w)+Y#NZCz0o9w(7fC z7=?XhuKE}*G`e10I2Wr^8{dC#CG76HtnhM?>osV94|q5{5iLNx7sKkmx2eq4Rzrj6 z^LCC4>oZaGY8W;wJyY#)M@Qphi*=6&FPN^HwfT!zGFN-nPyZgY=Yzl|=hUi*Yg&Er|X!`H@jxr|-_$#HO%~g3FpHD_A0#lg_T^esd^Q z*(ysJ(IST7gIwI$BiT){6nb+xH?nxN*8i3nE-}QO?;p7FDft4sO;`=Kk$yJF4+?tK zdf0oX(BcXve^Z8-I=3|KJtWySPriemevB|@k9xVXg?ovpi7E3@Y#U_v`2|MZlw5uf z`kbrbgYg!zgO<78NVbpxcD|Y6pNEXZr;&bFRrv<9DNShXpLI+(Xmma0Q_$b2>wfD- z)u`v7_62b0CNjm*$9jymm8YiE_bp-cJpE=2)GSMF`(nc+2}f<{>U7d4=tTy&mMkfd zDM(@Pb(GFVR%AdH;@p9*1F;Yx9tXUlW}m{gSz9qSJb@;WT^sRSa&Hwb;Z?{D(o4%5 zsmdC|qbZZ#DsB;FW&~yIL@dt5g3YvIZ@@SvR$X@Ue^nharTSStP$&t*wj~hUu7scl z0{Pw_Ul1b$&I>3{gvp`P;hveHPv>ry&QnhMVOMc>P0Emrr*o0T2degCkEka{uAKXv zt$@zp!T>+tp|FcJUZaK zYYDI4EHm)2t3_vB9j49%r9LE;o3TDI`w4BPrF(-#H543J+FJ6yrBK+p>vhj{+zAr#;#`rS- zj3R#<>u=2VV`SkP9BM3ND%stU8vX~Y$r2~Iw-FAq9u6Kik(#ObmKduwg?4zj(=H~)HL zY51=i-1 z>nKn`$MavFS+#BXB|78Mn~%Re@C{qld`+<2wG#0tf0}rTV4nS}CHZH<`|eE0Rh(yt z$?E-280)dGPdER%Lqz_SA-}4q+7%~F^}X@St7;V5+W`X067~L!Fafx<^6x>yzqsQ6 zPyf^346GhSrF$7}%W3ug+$qLh`|vTy1--BdRDUKzW+6*{$+Vabn$%A6c9V92!XNM#F26LA)?83)m8rhd=$rg)h zv3|5@Yw#xkI$hpXX^uP#$T_KOoU1ijba;j?l6KFc^TeHKjZhj7e5<-E;OQo3loz*N zak+h{;7H}2=})`Ps>((j%mRMY4b>I!e^Lwq^xZU+5JT!m&wkCXPgpG7%O~~IN${r3 z!Xrtu+2Gz;Xi~2ic)J=Q3Hp`gg3Y%JobQ0{19Zuj{ra@T(VTEVc;w9vjb$#@iaO%p z_LHWijw_(;M*V#jZYwuncK<2PVoy@~setteUNw-1PD%RC8)Ek)BCm{_Vn!E+rqcKA z15CnT1Vm#4*#_BOFpv-fRhvX~0I2qeUXY0G%ke!@+6|gzX~iND2SLmHMiaE#{7ahp zFc6Ixc}+7To8UY|Y{pi)(#FOKeqljmrDO ztwSs9MosM4UGT^)8rnZ$>qkkXqx~Y2PUj`(B4q!v69Cw}82~Wr4AIUX>{B!Wu@^op>^V5*zR48Oh?2C%Uvz7CclS|=Z>q-;jnE`oKnUEq_{bL0|U!iyQ z6MI8)?XIV9xuKU^?&LI)dYfiLRuUq?o$7}32Sa7w5*DG^X{dgCwhi(!XQD*Z5n9PA zq>8V9%;PEC5|uRm>7g66qoS}sV4~C5Y%*7vrPd>2PxK=BJbR1gyRv5C0CD9C?Ci2l zzhQLe9oqQUC2kFwJ+ZjdYpt9O*~(i)r_N|ex|y;dPOxAY^zX+wai=_Icv_blr#(q3 zi<7#V73%`;2Ga9RMzoQQBztI|=^CL(nt(drLK}&K3?7t;< z_I}h3$h&X&R=8;J2*eg@ThD3FT^`9KU^E|yF*DT(^^F9n{yr#udRe_D`cyO_EHLDY zF?x3NuHMC8)L(}m%1A!5#ssl0ykjl-G;+XF`-a&{U&8r*scoW#Yga<``h&3{yR0{F zdKP28qqV?Yhx_5uIimN5M>kfU&?4X8v~HS;24->b$DOp2LT-AE^_VGslETn~sV!kQ zGVs?Zp1W_Xu04A*&ZoV$S!9R25PS+^}7 zO(M0*ASOqVRrPJks-jiTs-|A*+e+FV;xxP)oHLX3dS6rhrf#RJe=<@xpKcghAag*R zj7K#%@4-IK3IqV*;E##TaMT4nb_$N@H~HB@>~xjIv2m?b@V3!W?JY(t;(xr^)R&nn zwzWvPTIRCD?j+KSTHJNDfQGu2XF4<)$?E!I2Rr*;`iV(<&>_ z@lPDuAkkm?+Q;>mqbp#s*9CsSPs~+r1*`rEHrM)HgtRqkKL9?6t6X9QDHm5VFgJGm z2LjiC7e!DoYnp)r&f8pr)VAL;pu6M41zRewDr7*+j4;dTwgI!vz*Gv@8)=%g?#~rm zk)+ANdhUO#uYV{{(UtX4`Vtqv6*u;c(wC1{iaWD?_=Yp^i2x0W6AC*Tc#jduqg9%7 zoCy?ciA~FrvV`I7w$M?(#9C+@4yT-CxCMKJyfB`g@dru8+V5cfWow?Z2$>rx-ehtc zOupKiI{X0g2}V9#;f8v^GnFdiMUYOpQm+0f_}NNpz!V9{kDe(t3%);Hhp^SJsd{wy z#>U!0q}OtcY!{$I2b@p@E&tie{a07L0o>?++xc5YZtWk^U%DMiY(U?LR^a*ni{AME z{wfcK8}`n0gV5rT@%@>aTC=gJf_uPKi8J`Mk@JsJ5`zE(Wa^Ev$3ud9B96@RgRpby z5jUp$Cd0h^00lc16Z5UCJPcu+jS~!`uiTAj3(sEEXQ}0)$J(4M=DYF(heGa_T1U=& zj#jjBHD9=uzTnMoy0FyC+wf9R9ZNAlDNz>{XeW=1(S&17I&^`aZ;Rat(0V|iVD;~=T7+Tz>b-^y)l~~&sOCnk7O3v z@2wQ0!tac(*e;GIsEk+N zSh@7M-k(4{iZx4Y)(2j=&pbH29l%2RWt`GJbKfij)Rsodx+-_7Px)u+9sAV+;L~FO zyPmQr&99}V8DF|K@^upn2CNG)K7Sbd5Ni)M30$7748i7eL0dv2{AhaPJ$&wE-9R0o;Ku%!E`J(|p6m#zbul(8-6R z#y&ZeG`awTU+njO0*BH}c(on8ks&oRncj-F|l57?+&^$EUUG?&;VsC^ES)fvwZb4KYF4c}KzI#_e zzS-c}bquk>sy`fR5TB?gTc)x%#F#5rsY+Am>%amc$gyNqat5aspcB2iLN%#IIEY-z zQOi|98nlJfHoz9ydN8-?<(*2bs*d)JS${v5C(L6oC_9-ws80_JD))-fk_*7U*X3Y9*RUlj8h&y(`Is{tK#^$K6;GS#Eyx+|p@}DU?IVMW2@) zOZ`PNeTGRM%5nm&5Hkw&nckVOsw~DoE>JauuQB*s+bMsIwrOhEVmm^Yjk6xFW3rgf zkjK*IKHzIz%&L_ryItw+ zEDN8OJ{F5a4J;fCKNA{u(R^=W9IX7}He9q5+cLk@<>Q!th`nV$_27f+o3S%>pX-Bq zL++`bGJV~Iu6QbYtQpE7rZS~t%dO%Ryrct&%%h0xlUO23Izq7x#rNd0#nEtAXnEfh zEePiG$2k@%32^vMa<~-qB3;YOYamb^B9xS+4*Ppimm2wZW7rigvjU>uj#QjnflrDu zQzUB=r!Vxr7amKj9?a@Q68P@rQD)itKZSf&gJ*CtVEn<9oSJpUpb zY%_cGB5Ap*(%%gWYZ8kNFZT9E(PvD2tOJKrm*?n|^-lg82WS?oE5XUaTpfLh6tbwV zD$XnJo$DVA2^z_p*K3pgn9QaR-kDe;1~NR7dJbalzM|5%9^~_@-yaZLCQHh7 zDpbxXwhXo?U#A?OSBnr$_R@@ghczkN`_cu%Tk~0GSWIS{_q^B-_o%h(QTaumhkC8k z{?m_~bK?4QBM6e67bZ#V!##fF@M63ltmU*{p0iic!blQ5#Emv*?v8O~OuZ|zf!FGF zKRWKjecN1clKZ|+M46t=8n@!O_lHj#9H;dEXrIlbT3DyBGs7X79&b1V$-aoPEzE$i z7-l2$57@rWdglImMs;fl6P-8yI`jKRKkQLmSxSG$B`Aq2>O@>Ahqrr6+aTdoyZ+O? zXSXg5jq6TklgAIUU$&Y%nP&5`VtII`cIs2xzKKNfrbRzRfeL*1TZ?FG z^}z>CVx^Q?sc$(Em|-qRj}@>3H&!W8hXc!^3s>`1GCp99M`Dq_<^6=wnn3G3wW_;p za<65Ncp!B0k8`qPGyTBBMb{E74$D_es@dEJ%YfO6i+fc9g~zVi3wu0(?F|LI&yuqQNpjpGM^Z_dU*sj* zbcmLQ7B5UXSLXVd1rB37%-kv&5+0(N3d^{o0Uh)O>4xz}$g?!hZvG50ih}8AR_D9w zNZhX~dMff-?9FkW+&bCivCLldvVN-doemsRx1*qye5v=2dtJL&n$>A(nwuX(rS2z2 zcj3$M$C z+^c;Hzr>A-X-ha{v%yXX*WGkl9PH!VUrL!+?&PWFPWo=ig|-`G*Zp*=AS|}K;n#+g zhdn;-gaBW)9pW5gTI*zGSE%9I&jvHGv>1d@a*_ugV(FM8Zf;WVsl``d9M@m0LB^mF zMrz!%Ik5(ham>e-K4m(RQ+fx;L4f-CwqG=8roStSH8uI&Prf0uWCS%_sA9!0;o-l> zsJL^~Pby@f!o-9NE2mB9Y}>Vv*_ruFmw1JiLA!xR*EZS>KC%kGn87b|-DnNprk^oz z+K#j4`uJ)hvOCjOiawFV%m_fJ3QOvd4a%SPWU{wNW*(oolqXDXFN+%tjYGzf*d3BwoYD)XY^2bFr)lNlljxqC%_Gf~W|Q4bYu9jN6Tbpj7g>#CLIs-62iDgvN}&@W8jpE~y3de&u2BK8_=W zPd-0`0cL9X{_{G%YH=LrUWGf@RhN5~E7Tu)$r|z&KLqG*B{>er?stQioO^?`8h?U3 zINlacWZUNPG1wlsH1U+D@klMiRaXcWh)gxq1>Uq)Le$vEVR&?~8rv{S@$`&?j6VC@ z6tGkbSc%-rV89qtwq$E{~xu`T%33p98;?VBPkzq2NpK zBkbM05d?R>6-!f#9+}!t-T)YUO8P?qE$?DfI!eQCUaQ!yXbw~e!n z00})wA?k;UCtUSf>z7w>5FLR(MnalMr|n%GGY@++Bo755jLdgMGM zS*>6q2(1}Ujh?XvgZdXMx9&4XJ*%wg8bKvG%*RYJAB|^+Pko!-d2A784N+KrJd@JH)cw+gW`PH85E{PI? z1DDw-yLH~Dh=9sOTtGtf$k1F^P^b58@@$~7kNdGGqin@T$3rYv3xKItbG$m`eEVh% zJ%9ZycqwWmiScI>Afk{TK&KZ}zBZ-n-UGeoJ+4b$8DWk}Yq`BIn>XByxxORSt#o%u zdS~K`9+lP*mS}Aopk{g0N6r{->Q2)#4uAt~te)CCMtZwm@W7g6{j1aVK1bXf$bBC> zTuHX6t%cUg9^{yhm;2}e2#q8N?>Li3mb7L12xvnwqj0GHEq%_SwpmRtFCik)k#uA-)RQx5%}|_K zS={Wrsy4e#UfbcKmS_n}zq6+E7?SgmNlTsWUiRkra)jvoE1Zahlr#`9-F0^LvCK>2 zcYGc2uFGQ|52`qPi{VBIoTvMy4-V?lG6oGN;=6$n9lnZ#Hsg1`Dw(G)SBL%$Fmo^2 zg#+L6$Ez}DZhI>#ks_pCn<20(zjR)!?Gj8gGw}1v&~toJ`shH-^rLGz z4StI6UN|M^6_2$(x;DNuFQCOpKVMB;*f8kHz$f{fP5KUlLnYS_(vqBZbQ(^i2>CZR zgYI9`x^ySor@Ei?oD%DXI;f9sO_DEl6wlBSB znrM5P`qY+mh9KNav{-@|mC@mAu(!Y>zBE9GJ?QakYbI#o>~bsS5+$Y08~K&lB*PJo zw2N&ZWW)3S_J4-8qJYRa)myY7vN+@fU$bR>roW?F1m_ZmcvrU7f;b)u7Lyt*2Q6B9 z89g?V8>s$NVx##ry;zYQrCD#qYNWvR+j}^?vM2vz05)deR!*Uf@4noPZc{yL!Ulbx zaIy*Rx<13>y+MZT>7cXs>*r<`Z-5CqD6DhU8KoC9Xv3HyfRxtYValvi;35X(k;I+X z2uT0GOJMPFKRbGl>-%bwAKxIu8|t5#Tol~4T|dK%*PR#haUrB!!^h3jth8kMbVBE& zYIgE5pWEu|Gk_|Pm~3HG$COYS5u@XU$PF1VIyFi><-1Gay8)?;R6jWFSn}vM)Hsd=^&UV{zBFqS}LU`@Z8?*Z`#0^1dH(;83Ty+FN zE_Ky0x^5q9Vl+F%u}?tdr9T&b!;TrKFQeaC8f3^MAGo9g9;`E;A(GC6I@O!MjxSy{<9PgHk4J@6~Rpg^>F&m2t8pNrJg7g6kjBU+c1Og5z^mci{<%S7^?LVrROu{VN89!KM2kHrK( zzg%Gj^u?4S_32^pfpQ)B+K_%AcA6v^CX%^l!xaBsE-C6!LyyO%0%NTtPmY}DRM$sK zaqz3)E!*Tavo27mU0Ht9h+2>{LX2IlBIKuhsgCWo@5&@ z#=T|D&2i)TVRHLz;Ej4HZy7jr6{-j%`;Tfb@RA{5+*k9U+o|YbX!q}$q(pwe|*Bgy8 z&t`dFfRgt3M(Aj0yT5M@TGsC%rMCIg{$T67Bv*S>V~bf}wVVgKwC-{jq{!V-U_uON z+-Y2QP*>T5qYez9THH)ayP6|0$@Y#5C6PlSFTv|S2q+95I1E9bCH5G*{DPt@eAT$T zTNBc!+0iI`ml1I9fX0?Z6C^r{;`3$TC$!Yxa$H*1apmP5^YEKi%Oi(aNtlxsYvL77 z^%b#k2>l+FbK0*uy6WGM_=YgQWMui(t8AH@AQm z{&cUkL5ZWtedLwCW<~2^Dg+aFkKdpPUMzXt5qDaz8QN{QwpDuO z5l+c+)!lAb6I5oEStZWwtJF|al22W;sR-O z!LCaFAC=dA>^WvrQag;9YZGPdQ}&~GXqYe<*Y{gS#mt{RTO=O77@b^=BhwfiZt2i` z2;Awq8^)qzsig!`Gw4i@^R2LA>ee%|=Tr+^&0Wu%`_u+*EO0$k`4e~zIvPLW{&$)i zFgZ56k?7%*j2#1U|Hhcv5y7thjM^I>9qyA+W{MSe)Z_#n%{Cikn*zK40q}JJ0Sa$X zYygr4e%LEpfILyLiZguN>8N?}p8~kT+kfs4!0g2BQc&IEcV@34kj&#*g`f`0`G}}dy09YFxmV%EiSMRR0UZTbtKE*1i%de z9fvJ}ZHkWj-<;bJV94Qk9RzGK*GO%%9VKZzU(NCZmS=YK0aZv~-@oq({83?1{!#}J zw>dCEdO%$9Ke8SaufNj>3>&>z(~S*<4ag$J)q?pUgU!*GRay5m5U*+shGt~VP{vBt zgT89lNQ~yk4gX{}yw960D`NX0RmNByuntz23}aL@SGe{6Lajtq>k;`|h;BomWoZDE zqvOi|VCPE5fu``^t3Z}anrXXm**HG@@p09PJ35@K*~&5tSJ?pR;%vk)!4MpBO_siB zgR{=_Y=U=#+#v-LoLWA6T-s7~Hv8w<6#NR@H+PeX4-X`f-Xf}1ngoDy`}bGgs#?TK zW=62-w+wGs2E@S~q$I|HCH5i+8&^@@MuS|=N=vSw4z98R3YX@NpRl!>=D8!cNm#b4y`GoSor6bEiemjq_1) zV(n@{*i`54K3`<;jkJNaak4k$e_Gb|mtW6m{l9Wwb;xcM9@ zX|?0H$V6cXvxnpNthtfY&p7x+Oc$zn{5yo>)8tLEZ$Jk&aVG)NoH%EG%Y}sNGyHFQ zI-IOD?>!<#a2J8iWog$+u1C;?(UEHUj~M?DM25^OmK)Ima@0x#rVG?(`%5@BZ2v(^BTi_MsWmRN7gsMqc=cnd+Qc|Go`%9!t{HQS*Rrb$$E~ z&|*!#hcn^hdFHGTZ_Y7$Ua_eF36OdIxg>Pt_=z2THoZwM z$Uk#{Q$60w$&h5j?WwqP;|pU*vrYnenPrmADq3cs0)Z^EArA>jiRO%n+XHn1`l$WZ zJS-`Bf%0Js8$nER3+nXkKdSGeoXAZV)>c}ZD-i#P41g|$8gK4oEtJNIMf5i$1FM;o zGMC3Cm1TV$t34rA({#Cw|6{~fc0Ur`H)h&GdEwjH%21~zd5ignLCme4l<AA)+BNZ|-aGQy^R2c^a74N4^sde}zkx8AzQtLRKx9>RNyTB`OZIZ?YS{OU`(ajLLkm2_#_GdPkt%~A4 zCuvOwzQ-06X}lhgJ9b#7;N$-~!B*1v!y7FFY0*WXjxn+s=M`CASS)7N817Jq@a460n@ z|8StJZS{GWq{dO>eAjGMAc>_;C10t}REUgOK~RVP`+RA+#t3kEDAxg-%M;}(3a0tA zJ@t75?UETwd0ruNIyMeAyz_ z=YqL?d`DmGto$UqCwuD-jK!4sfMO;*5j9}Nqkj$Ab-Y$34MjQ+P5ZHz5$ z#8k|fr{RbzO15(gm$Hz}1ar=YnR~a5Y<}oRxuptzzNy zv5?9%u-5`dV2oxKu)nTafxAgOw?JUVJo5M>cMcqn z1KzxwGzbJj+aBP#jANoh#Ub;?3JHULO2z!ZIbTpl7yXLaV#`JPJA!O4;Dz1t6M8RG z3kby4qHN#?^Jj4^>X6^$YPY%?SJpeVwQg^X8{&Jl&nbrjq;XOeUybvJMd^%YHS|+~&v;hZRZSdb9y>CX zr}<*uL6d+h1Z#Q6T8&b_d52fCr>B-6rIskhyxK)y*)!%3Lr7`2lkGB{Kcgp$$b)qB z#3!X%ey{ruS1Z*nIW{5m1x&VivNvy_GNIT7I^Q*fs7x@?o}2s~hanGM@^sFb^5!YZ zQIWio{68(IP7>oqAmPmNo9~*N)F_Q5da-m<5#CmO2bBF(mBx+j|HR}Vy7;2F_;p0i z)9qAn!!3W(V6Vtg-OyPRWzuP9I|#~?yd1p0y>(Cgte-V3uMZEOINn2}3>GddsRvZB z(L;Rx>^lOTGh8@yJY@M!gukCmxJ+3Y3K0qZ27fCK77Eh(I@AbWvL^RIf*!EOYYu=M zoKgR=#+|OEB)MCc2HcDFpp}7w)dc}34vh-f%cmcV4ZE2dfXpWU-I31b1^5|MY}SC_;jXVdJU?qvi< zo#QZB#WpNQ`gUgyreJ7f-(+v3l0=~iE%Ii6JGRf^wAajW6l~q!nS5?baRPeJ}}U5dT8g}J_rLFg_M2tk+zzKJ>oaPqZnO`Z8tFBX)=;sl1+9L4!*JJ7S{sW@9Dlnm&S!clp0~kFw8>@sWMyFFesc1)DdDi6cSYm}-O_wryYR{GJU-%x!L73v zMYDD}p*;q_govUD8ay7vtk)j9~s4Qn)kP^1T|<*afM= zWQST)X~Wa4OT6(i8~B-=yzBU+mpp8CJN=JGP6|wGzeOSw55Q(m%zW3STxV^4-Sn)0 zdxccC1vArZxN1^6L0zMaea56U}#d5gEu$?F8cEj#} zTBO5vh+o$`9{5ZAL#4AcIzV>^sGuw(^95R|Zu(CxBZts8G9^zYq&~^alRIFLNr+~| zA??0f)p+eT?8oNNPs&NyPW( zP_kPuy~HBnt4pUi#nsF0iJV?X3{zV?GEp%`q*5;fVy}`Wu-4_BZRH!8%xP^ zzUC86A-dv{&S`|o_GnvQMM0}W+l%OXj%&bsnT^>a)gGgFPqdLyW~X|FXBOth-v)<0 zkcoY4t6Zx03AAwGn#f{TOuQ4=da4YV|A$(Y-;p1=|4&f59*rA*t&0&y} z@&EK?NQu#fN0Q$y^d9|fu*#QoY&@diP8#YUc9*JTzGO;GB$U0@npfNIyRp7k?nvh~ zwX4Hw9bJhF{nDqf!mtZZggfbHa^zp*(hjy2y3cLphE8a_<`PDS5IF<_^S?uk^+4d5 zX0)$G9oS<{I}KAw(uQzzU*5wG(ke4e9uBE@GTd9ZLXrIqX6y)Hxc z1VXL#CoeTE$pi(Z$5{3*lR-VMRE=DE2`$X^8RJbb^80w%Y#M6m_;!`npXbD1Td#@! z3YXrAFps}ofS2Q`G<`@a6dvpiEC&UGPA0D&==q@>?z&Alf>u9#wmc~aj{=O`X=$eG zw~;&l2D8ejKMF6?dLMpP_M57$QjRmwF?6|*cv9yHE&N@tTpejUuiqfki81$T!cf2I}7ojrVH3w_lbOVJ#sTBqfpxFZ^i!Wh2>!Dw2qtb37&vU zEr~;@MFvD~!d>n#;Tl!cV zXW_dU*S_nJTz8ojgA~@#z!ZBYfD^_+4NKq3e^!Q`z4&x-==j6mnVf=f7F&(HSa{tB z$%w^(LK(TyP2cw z(rFK`bPY#hbwE|!uxTy~#hQX^@@p!~nZ>$8B6noykHaErnN3p3l^g5++gW1w%)|gN zD!T7zBJ$<&5O}~8s9(jBt6ecd_!oE~D@y*v2fbe33wS8#9I;Lx`hS6e0GttdwD2eH zFnDrDtj&to!oNINr$0iKtkg*ke)^|C&2^a1|Kq>AtR}pcHj}5N+`K7rrtE(r%lKN+2ZaZZ;SZgd`PjRGW>_Nn&|cgBe}3iv8w6%0 zb(>^9Wz9P-1tzIuc+k9_<$;+oRs-rEI{}G8%!8;n`HM&lNc5esn7iKG*s{b#p zVhdX2=+gGS?exkAOXN*@!%7HBqv-#d3xw8bHSLuEKEop!e8Q5_I zQh6wWX@FmOGUCTv>dTia1cRZ^>458VGi6+CBmOUQWhe!DZvkbV zZD9mnb-Bd&=xV`tv(V9op`Lu`z7?OvVKEvYF@nOCYNrQ4rSp<{YqxShEZwY*Q~*(l zrpf(VA@`b>;HGDS!__pDr$y4{f@&rJ*iIpq4DD3%2cH65%^~%l&zo`)5Kz<4_K%;= zk2hxJYQ_7!A!~Ld!$mG9*g%-zM@Qc4o-Km=G=CIXh4*z(sYwav%sKn2jr-1I^? ziL}sTN35uKzke@6*eO0kqXU<<`D4qiWFEeXs9{Wb!QTGmzd^uSTCAxhNXKv&=u3@m z_vQe8Qejt#YvU7-B_3G+-oFodVuuSmhyw?RV9kj8MP(OX!qN(U_nw{bWhcx6vhQb= z)H93VIvQ}Vs%WuEpY79QJno@eRQn!>65K%cs;^WB`#4dE1XgOkKW8bFBy9Jni}Tml ze6yCxox4!^$%`F5)-1jJZIVG2zems5S4!LaR#|w-Dgu7Dxc|64DsA;72*M^V@+ZTv zH|AA$akEdw1P0eLWBmGTGF__-{t85SY$87wHl-u$6e>H}O{tFOFI@mp?F!lT13it) zpvx5dV2=S=({@M5im`%i zp@hh)Ajx{x%#Ol<-?vxgAveW?+VF}1p_S=HuVV@M zc~0MprqpNrRo(0j~x zM-Jz@CQ-^DpbshApFyLM&t)0h1-LLjKsSp-?6g-olK$4NEsQ>?dGTUW2k* zi*AfKGcNNIt<_hX4FzVlsn>#(|6LJY7!_M;j%rzKdz7khMCoXWI4>4GxcPT-h=PGq z+%P{!mpB;+YOwoudu8MKT6=mzu7;l5(9z+)5aM*5SmEQ9-su>&oO#@-SYcWgcLEJ( zfULE6!8T%UO7>ObDj#S$p$Zz8rKY6ykpV#_P@!4>o;jcuQfyIrI0~=S)-4@>leJq> z!@V4fcz6H07BXMCD0s}{3UJ#lJ0@Wt>rn zz0)ZC^4VV#Z~3|O>+(a$;#VwhG?S^{sX%{1(w|=FU9?V5!sR9j$)Od%1FE18S>~5n`e_U-k`g^_7MC71N8@?^fw}*W9ha zlTZIW$NV8zrG885yHQoc{LK1z>(2o>J_k~CI+*S%WB&|Fh+;WCWIcnj{`|E>p|L~$ zqkiGg=E~x5D1vPhjgVFCv~Ruqxx&oYA^!kxS-!Mtkus&ETq~(0VOollaJZ6 zwC(QsoFh#RM&Ri7x5m)z%m@XC++<+_{)qN|AZ$(2b_*+?|4fbkrCv;-HmUs7maJ5F z-#2rGh0xesIrGwj{W^PxqiA?jhLVwRe*N1;#VvgsIP&gn^f}%2g-IQjPPCHfb)_A< zx}h#f%L~bEKOBDf7#@VlgIEr(@bmH^!diT*LI!Vz_n)uEY-jeYNUyIk#_#_fio~7% zcZdeRom0N_Zi7j}0i&k0sdqhJ_CvI{{=w^COa7RnPpfodjnUoTr~hy`VYe~L9T@xa zCzh5I)Y>Rh^94jXPrE~z42AUF@?8p~nVyOIk-+yZ<$ZT|#^Z6b?4HKt<(!)n2Zm6H zjp%#8s8JZ4HNQR+-TQ=*kQQ=IM$lH6igfK=CUZf7PlXK85(KBTa!+`qBcGeHVPfaV~}m~%C3x%iySWI z8rU#I7_*4rJW&ums_rPTkt`Z5$%#d{NQd8tUa5$k$9p^vJVkNazOy$DmJ~4_yXbi? zcNH`-H@is4z}K!vAL}f(?02vQBE1_8t zLCQ>5$f%LGTJw2SNq2pu(&gN+{^LE&j>-RxyZ4T2YU|p?QADhupomEGSP(%Fm0lvE z!T|vlr4v9@1f=&GQ4ytc5Tdk*G!aPXJ%A!bY0?r55Tu3>AVLTMLXx|J9?$!J-}{X_ z?ijy2#vQ-?2ZLfw1s>@mDxqXfYbBXjNLGSarbc zLbRE*Nm0Q$t`-@i9rM@C+dZqB?76V+hXc+elH@IO#vr=h10?gQfDzMrmtWO((I{rZ z=Ry>N(h^dm+nuffiP+t%fQJQXzB(f$tUS50q4qt)2pf|)SE(-E- z68$=QNiSpcktu;%^jC@PzU-q>j!$ANs)MA5RgD{@_IZ(SvyoQkJw+|0_A{k78{4TQ8zHiKlh^CswIzE}5#*b5;+x@Axp&dEhpbf7m zjQH0W>Z|?*NDw*>GB;os+&Cw0P7|y)ur9xd8%M_t4H4FJWqrT1$Dg~@85eLZvgtmf zgQAN{Y|jJ;xJ>1@v!_gBrgpX8W)fsZYh687Qv(fZJDpMI1^$lv1&@V?+yn9Oz)SW# zFAm-UFivArGrqi|W9ftL>7WE@)~pblPD4my_8is;REicno8sD~dk?^#J1g^K+5NDT z^8Gpw#m0Z3rdCCHv*QUn&G;C;jXwy12dffDuvz57Dg=1V>pS!{3W+U zp8z3K03Mu-Wkcx-hy~}d4;Hq3X^pmoPN1ri>W|8y0HxZ-*$&n_@1B%eX&Fd2sU3T; zx~_~>D@}8eIg{&Eke8@B6Ej*<)Vt^m|GjZ&{itq1t-W+9JYpfNVAS(#0Tfwm;^Z*+ zyg*L}mu>KZ^Q)yy2(R)rnM7=o(R)?YQBDUGYL$1doj;s9Q=}gLBlsP68Ju0EIA#f! zI?dE|$i-N*^|@SXA8M{a9z=a*wtvcYRj5()*bq~S^m+674w>X9dw?vlQP_o(Z>xI- z_-j|<1k{aBr3OM!r{j#jTRPfH-bL=!>Yeg#x$}3TyjA0FI59yWtYtQ%0H*Nn(V(4* zR9h#t_PLhN4C0`pGSB1S?@`o)7hBW&bKF#91EE@M^k2pBmhqHYvwQB!fCkY0R4m_C zVu>(FqiZXIa^$1THPf6&-?rS~38Mzcl_@0dJ$Q{*{%*Kds<4orWRYZI^Hj$Xo!SC@ z7qMi)5<3kXgubnpsp6u6k-h_{JngjeXo)!W{!UVyme1rwX!$&A&2(()6QhAW)UEh_ z`8CrDDG<~!InuI3#s4L!B6X_+Vt4V#lh(f~Y>IyluQdd&cZNN<(ArzR``K-Fvv4*Z zlI9Km3t^8mnf*K*4o_g&W;YS)3??cLmX~|K22F8ij-D&knDh2ipJYS~EQBp5i!Pl# z?OpX6dtJ-ta`_L12raw2`Ma`4x{_~-r@?#v9!R!#%RUQW=a0vK;(>c|%-w^x6bgi4 z0}M7K!YR+j+sDPtw7@yb`khjVXY#|#T9doRuZ!EA{Zye0%3s&~U>#GX)1B>yVw}}c zBl);3#ug*qZZ?@H6v03H$2V(FYhzxASb3|zhQd#7x(0JXIZF4vI+cgu^A?04O=Ze~;>M5S~#K04Mh0}potj9R_ zQ|NsexcMu~)2K7Kw|w`En+`fJQ8pAYbyu1mZ5AM3;0(S&D|8M=y{+~0Abw;9&LAGI>v(p{O_VY}ieYK!|0`&&NX9nDA|TvPt` zmSyU0rsm_h5UMsQ*lPtyjI}28T47O2#*r>50Cq7w767eX>aaeP&t%F(WpNBNl-PlDc5u}G~-4rw27(E5CW+JZ_kQ&$TOQExy!>m+aOx`yjF z-B#X3ZmV)e14*!VP4Jc!&4UvDpw{JQjX{#qsTIRSf%4 zAvGq;p=B(?B-iTz^=4 z$p)Lx)o$)$nFKw?^Lc+DG_ZvdHbi{)BowXF+Xg$m9Lf)aTR_g8xu>8*iXT28NI4*;B@4!c^( z=U?p5f9^^33VpY`%G9Jm^@321$<#iztDeI4w;O8Jw(Bk;#80o@u;RW}?IP*tBIWx| zUEMfn?8`{S3__gmq_-9ju!d?IJd07~QxJ!2^7cBcC|B_T{WfUsuT{0gR{u4nTeZqo zhk})+Km@NS_8@?IVpxyHJa5fd-7u{bP{V!^anxgOe}+pOLbG)QIF#-5=bi|W?eDSg zTfxb}2O=E-&v$So?>Z);lkXxN@%GH|M72*({LJ3xPQhoV#W5N7(vi-m!aip&4NeTA zM8cEW+6D8?SH%Dsh^t^CQ*@lrA7BLN*<+FpwvP*9MUltqU7%$DHTK#_L-CGNFK?VQ zQp+V3FF-71@u;#9sVW2K)z~WG(a8RVFf~i zL&_>&Kyr1Ldr+gIGQ3`r-bMkbtq3-EL_c00OPH=4kv4~4RB3P#2vjHd->`U8FM~sf zoFhzJc0gd<+ibmGhA+Nje|=F-LTX^-;LpOM^2=!G!EL8GeB1;Bds_BkH0`JHh12WR z&?HWy7ih;!!CNhHRW06nfDu7$WhF-V)%3ln+h^j^%kdt;k))&D2^Gc7h#GEw_6p=A{kj5x73^_#UIaiiu@x znAxf8h3tJX=3R)qWY0Zj=F$-8rtNY*)C3i^ZF;!6=3hrNuakUf>5r>cwY0+9;XQW7Y z%jAy%ewv6xmNUyYSaO~g62jytFm_pdb+24u?(wf3;Y9j-_ckuLqQKr#M@MxJ^$NFwBSZ0lI`AqaK0%Z)cHD7l9E+hZ2O8N3!By|OctRUWF&0SI z(r0tesddgec%RG8?ekwfb-hi(F4i>oiM=7c==C?J2`3W*YM}2%!Y2uNAr7yz1E4-c zsLs9Oy}`I*mxZ37jf)HmPQWB%sAXT}GDn>&0kHAMWk(Mv)y6<2Vz*emmk8**Nw-s% zZ3p;G#fN|84*#cOyye_|7U-x`rCD0QMS^?um5F2BLddNE0O{{2FTain2+4E)jHh!<|4T4E z!pC3wA@!B988RH4qx?U^Mfimh0bnEQK&|9`o-9=GqKO0@LNR3f!6LA+MBoRv^>xcn zZxIYPk-=LAiZf=$GW!%}Ud0o@Eil)c6&e5yPs>B9Z^j1i+qwP9uEC_(B|1%Mhr+DzuNp^sI6E}mnmp5xm z6!-rCU`951E$lNfmPH1D-CjRlOeDa&tPyHH%imhmI40c2*Gd!m>`@9|Oo+j}5C!Gn z1>9twy@4WoQY6wtTmlSW%Rh=+>Tau0za)NU1Ezpl)v@n(ttVxztK^i~5orABG{3Qq z$!1Ryx_--712G|y;OXY8YiwK;aB6;PqEECJfD?n~r9i0G23@R1YbOBA!p7=n^iwu* z`T-nD`yr$upgj0?%38i$Gs8=U`z((NoRo_63{0=oRk?2!K;;B90uyeNgPwhM&1~Ms z>B8R6hq?`iGI-GxH4YLDkWc2)XBG)imktZ=%}QEu3MF*89kIF_j01)Y;IMM&GkK#d zG5P7jgDqFTE)(!p4gyouqg($9qm`k_7>m2M09QKguv zd?k*BMg0ju7mS+5ylG23r(0~-nB7`SUuY{L-77o|(2lj8yU#Vgt`JtB zD$MN$nNL`cqXPm<30^h7A~~1{h#H@Y3t64{29Qyh)BVL3fFzl_C?Gby>fGK;s%<=T z?Lm42kIDzYxi%|>l$~vkltlMWKWC}?uRlgP9HD#w94nWR3 z%Jg?-Qxiwk#2RObPwsJDJD0>^sxBM6(Hm~&ukmO=6)=7F@kW%nBI#l$v^Oz|+A|J8 z&Q`ByTc(QUt0gtO|<+2LadQ=zYvg?H}R8g5=rt7GQ z4j8VC4bG$V3M&O{~2G5&ErrIIAxr9&07}Qz@p-<7M!9> z+EMk&KiL6^<+lu}o@>=om4NYd9x$%@OxCbb_BR<<0h#I==((I#mq39cIs{9d2Ebw& zw6sbo)C3!TtN~N;FeQL@JlOr=2iHAcL=+qt1X}SdfTuqUuU#Wm%~!n!#jZ*4quB%# z?BcV+%2K%Nh;PBy`qi%=jztIiSlzk4P~^~uad#M+D`i!>q=c2!58ZcQ$*RN5Nb^gb zyw{KoE6xd?aI&nm7utQej#d5)r$8+XE8gC~^JPF|1UAE%qSYa(N!F3h6ugr&P6a8V zs7Ep-6_&*~v41GKqthCE=q~#TgKsv?P6DJYAb!$uN!+5!=ad&Qu?AYj?PO|)(_vzVMt5T_h|ZbTUy09at$4OSNbzD|;i4#}7{6`<=mNs9?o*6| zSa-3+shBGW@bnh$9QqrL>!R(Ir~Uw{WlvTy`hs-i7PKXz#@QK1w!C0Hi^F>|G_QeXuu&;r2Cs70Nl!FQ-qqH_&;ct z$9Eznfnmt%oosexT>>l!;F50IEo+^;y*E}=1NVxXqw(v{x6a1D*{w;v4gjsdWpkKK zEBk-+s{dz4V9~B`_XlDungaSGUAC+h zhXDdZIX20|0%cB;dE<{3!sMb>0-3(jnp+`PhIU`U)7vSSvCk6ty=_!B!2?I55B=Zr z{5{9G$ujWkn*Z39%KsA)2Y`L}iaQQVxIzjeMA_>5O+6;LENGF;6LsU>mpg2A7&_jG= zC1fDeaw=JNmMfjqK^l$>Uyv~&U7K(7e&qa<+~Gi=4h+x>TkDF?Pr2xJJNE9V>BjjaCBl{)JPLpJ)@ekw<`5amLRGk- zmr#XdoajXygD2UNZhDNGerxq@QTF}#N7|rbU-WTPlX?HMakn&C<|k}UI|H1T8Fw)o ziWAK3hSpnXHxSl#MNN3zS^+%L2~L&x0*&0=vL^Gl0tXgWRrp4of*9{^iNeQh@Tgp$ z)~T8ez&qWybY&D<;$L9v3=@*kY;X#6K5|KODo3>YJ%aQc9zPXOEHZ0bK*&=pha*TU z`$x4x4AKZED&q4G$uqF5 zs8H=RF?3XSIkHvD8+|Rjth~ZE8#@r#=V58QE9;7L`&bdlab)ryxY=`VDUcQr>wKR2 z-k2dn5*j?PIo{{FFP~VgmT4r*>CZO0=uTd55mD`5{bYWrpT2a8>c3 zoiTx@KDVEFfvy9k)kddd+gDdr5$VudgnM-L8WC66s$H}S0EMGsI)}0~&c0~Kx1S4# z20vU^;)eOt4-Ry<`?S4ck8ZU#@|{R^h)F=b?0wG}Vr%hyxdOpkJUh6?Ys~UJeCm7c zO6o9A*e9my6`qDeU`eFHp`JHfEP}e%TX5lB4|mxKtg>gw&?13mmrT(r)_v01qUuzx}|iX=D9 z)z)&Gd(YFu0X5@bpyt?nS&Uc4E4YLe;E0ZjQBPnD`;Eq-0Y z&>+?0$hed8gZ_!t*sY*GV@czXwbs1=%h|kqj{f=RjJiqDFMyAH$h<~*W=KnACw zsRmFm1bB|DD!w2W@^XI)V6CS(Sv{zJOHCrt#=M1M1Iq!oON?Jy-d^{#&RTt7Rh-5l zZzF5Ad&<}QV7)7m#0hWTy6M}e=A3|!IjZ&PyOPhF9;Z)%+@D7cI@uI)|3MD_rzG4ka}g zS*Y;oY- z)jL?S9{HFCRy~K#4A#BFJCBQ#vQqxK?4u+VCzBWU;&tU3$UvP5Z=M$IE5%(;7>$J! zTG!QS23<7;;WDH*dLr&SQaDYxXXN(zd`}NoSfsl+c^+vX+>G@$o3s!K2w|#XG11np zn9&^^9A#3}dTFkR;MIV#d5sUkYOPKt3}}tZ3PXG0?8LoKSkF`Gq%YS+H1GUKCs3#G zv+GuxnJk%*;VJTX0NB==!GDzNrSUa{-&7r`Q0osHwSQRpWOjp>Sr=75AEE0!LkaQL zH+LLT^N{@!gqG5(v$rYO6h^*c+8v&I0zH5;azagNc)8gMDFG4$+*$8QQ`7jJYsxOJ zOk7%PNQ&YEt1IFnoXctpR`cZRQi#?XP6z5ks$bvRQ9au`)AJheN| zF`?A1JQroBI8>m}56BO_KanKs>t8+_dso;e0ryrTEpE@YtZl&IFiXs%y#={L~GsV`NP`=h-DGSF!^T7x}tm(-(IBa^QKVVz8qt==0 z^A_4mr2EQoGG|=hQH^JJ&fBQbzAt=4*A3i9Kbso6W(k(br1u*_H~(|M58N&sqPwSQ zdMZPD$-4pBM5z>E6m|&Cs?2nHJF&YhgC#@?0e%UPwR_nCk(b5DQXf_duV1zLh?Viy zb<`|*`54GBk@2N&{v2=cTSDtT2=9%*5E3|9wT`Pqp<3SX9_4xrU+gRWVMZf24~)^aG+Z%E~{1%=;mTBwi?Iz9MpgBnS0?c(1TxA8;%^>#yQ*pI zoW@g**X^d&0nnltJKM_7-TYi%7sg1(Epb{*YM zsyYC&5)AY9xufb?>@?ig>s;CEDU**BLLPk=t&lTy-hI0EX$>VNLCs6N5dWdr1zu74 zS?g~9B%xUjVQ+=%9vAJ-rky1*98$hQ6IKqeY-KN-P# z{k#<>V|5|oN&m*}8XSOS5jMSlkyP<1L;hU&kHyCrbiNbmt#kSS-lQ;S#N)>xU^Dlk zLN1oc@qQaqpy$FR*9if|ogsy4qLk~QWqu-MjAKT;MmJ{DQvLnch6I?Z73J0XbDxFV z2V7z;(yY&GD5A6?wa}N#MSJB-alqmE;;~w}C!6$evAkkIYFWPlr+$^4KdOaO_p$2S z4hmmg2P6|0r1nG~&DOFEOK?A|#nn6BSZWx%1_<7Ht#9P3=FiL5giuwYaREBD*pf-o zs1U2^b8k%~OXmWBfUKfW&cROgWXqg2W?S$<54i)zrwYO1sjfC~s22^~L)jK#@x+%7v&%Gd^(YVWEHjBRrzt(ZL=T`KaXW1jsVM@DGwsW7|gyo~y@L8P0pzsFDLB z(PIPm8|vW}M_72xisiMhg#t4q#uR=P8r~}~$I0<%s+U?umg>Nc-JBJEOLBIg3*6sK z!3Dkvnnz*VXbL2OCwIc5cBP7RwG^X8I#ezm48trm=MzY{&!P4ftEbmg^y;JsBGE;5 zR^`evLs~axP5`G~m`fO@i#UT~mJFUTMr4vs(`8MOxS z_c4`mQcI22hUXO&ts*c(`4Py>~s{z zfGzT4_e~p8&FigmtAl6{Pc(pD5^I`1MN{bphaqT(IL$p#RDWzrf6H!cO@8z!SR!fG zstv7m!oYN^tl^a2dn>$PrCKZnNpf{NPZe|OD|}*E9{!7En|{fP)u$d7&*N`rlfv4c zE!h|oEAnbOz>Me43Fdk9sqJrg5{9bwvh!he7wVpczdu#(yT2UduR@ZBQV}M*vnC+& z9ZQ}#O!4FS;dY_HFR(}zkMF=v0<{)x+x0#~)Nt&obBqD9_ z{0H@{QXHvE&pDEG$%A+2fV)Mo?UA9}l0hw%c9LPM3ML%|bh~{d0=? z(YHmewcx+{n>lYvn6`_Fbo+<*TcMSzAnoNWlbG1Q)d+pnGCbSI6q(za4gG*5C`5|N zmf(*gc^g8I3FUjg-I!RJ_MUEl!M(vsG?v0^y+StV(RSpJ>}hXh_F|9Li*{piHb&izhY@Y?Zo8wXW3-s$&K><64 zTF}m>TO8mx*57|g>{aBGFSFi&oza4+@};z@C+RntemW|ntNvokLs52sh5Y~y)msE3pl^=`%k)C%AU;%4zX^2SZ@~X6>3cu7aG)HPtWEzfnf0czy0Be zkPc*hu;+7+Jv3E~zVZ6=T7IPj$})(G1R#v;_`STc?-V9@%3}1TI#M4%Ra)i( zNN!!m%5Txesn{b^1Y|wtRPDxWN`jsu{=rK)RF8TK0w)C{&}A^|*HCM)Tc*}~rclj6 zBo)L?vE3CYyECOEl@$QCI)t%+vI$RPgVA}x66uihY>tJ=P;Op@l^g6Ga;B506@5l% z27rYNVYSOiIlc0@9IHNhzZh?=%N+ywexRTA%{D@Tys~K~iyiYq4Uwq!VoSW#Bj2}}864=nk;t6h@Y+2o@d*c=Mfwd)=r zOwa*Q$q1+QY7opkgGBp~eeZF4#cU~hu^I8lyqKPwVh~>trgmeAuXh%_4mcqUTbTMh zfW%XY5bn$^CY z+7NcKBr~N%4;5kqj&hMK_kc}V;ajI8RuePk;o?Gqht6%~NQq#VJUUqIc(47qCF%>n z^B;}3>vop|{&GbOrz|*R#RmO999ptfrG@CbX4z%HrGP4{bJ*fVGe^ zo4s4{B!Ex(b_yhd!eiCWE5qhj*yJF90-EhWgXE&d;>K(61UAn549P>tP7c2Lwvi`* z-gxRNn?nF161?<@o&5P}ayi(L`k(^7Va4#fV% zzXzcmkZqd)0aP1)cHngrRG))m5O5qfvJVIiUfx`-|NXJ-0!|gvc4mgV+xhnxc`##1pIL~uodP}3}el|^lKCgq#20Mrf8mXQY>YfR3U;e*n z4?z8GS^J^QYMiVcUF!$3*(yYNtFLv~!~8s^rsrGKY(TJo%fml_ z+9F236<@Dpc9O3#IuNT@?U*bCx*@PyQI!F{)`Vsw#P24 zn)n?L7FSClC#)OSH;}4Z80QG;)(eESYpXvxlo#A%-J_lAhV6m~uO_xs9pAxLm2RUK zKVGJp{-L8+;GG&UFL<0obwn@XLxru=2ay;~ zO(Bkz9gmke1+p5}t4FKXOd!Kzfs1yK^+k&t=3BkdJ`PGHoXCUQ*vh}k^`WE=tC7EZ zIB0G(m_8j1*>F`P2N4=ZnF9pgjlov(C$vJ6@V8L$y>O1dAABI|#5oqinuStqSY3eC zd!AXFlVCAADcxrfKPC-7e>zmZF)+5U<;9ouuYZ~*HO>V(TE-L8$8+z<)^E%(yzWJ6 zV3jUa;C$pF2&B`n^?L_K6$W`7^x!T@X!=fTrsZ`7f_hgb|FU;OyW)~olJzNRQc-O# z?(S78+GHO)k?_5~I8mIT)g7{K{8Ti9L`^>t74!K=>TQ@v5@ov1gfi$d(%Wh6r12VS z%^a|XoWz|BFR)5&2su3KMb+r*TzY-Nb6p1UiL$)>+WES&(?)WRX-R$P1Q=WP955QU zIm|~e8(6WdkU(i|w6R&OmP$jl;RV~g@mvX~+MJ{=;LIaVw1Xk=i2?Qlv?G7 z2VKt3XW_0BT17zLuC7UVYp5p&VwD-UhOhaSwdCKeal$CNsR7|D(%zRP7AMXK!?Gp{ z11t{asWHv)Fe#~~fA-SE{Rd@3+_8KlpDaOYR~`lo$S2%?HXTK~X?_T)nNfhP2j z`1}vWRlZbDz7zEKUsEcSET@d>$}H{nZpL+)LA=YtI5FsUXK zY-$r`uVl5S5_ZSHGkMjl+L1Pq!@fiwqIrFQ>&TgfA2K6$J~}9~(Z)q_9nMBk%{crE zamGg}SK%`{yG1@6l{44&iRy~Q|26C%cgf8Fw6YVv;i|zyCcaQV#VZWu$>6ab3+o#H zi8R#kPIew>P|EyC-TF6Q@>A4@$IJZ?*?wv+ge69P+TGLXe5DdUGu>(AApP0iIW<+* z@CKPUhb(yACw9)zci7STUg!Gu7{Y4;v}!(AESN}D#d2@})j~|$(Q6_6?M9Ca@r>`E z*wQyrdfSMppQ2A%=t*4i^x`kwpILP*Q1ys5|L45(m|2&wzuSxrQtge2KO>S^O1w3l zx05V7*2W%epr+jKw(_bib`*Z!=+C7e+j7QKY$mJ4L$)|Rv?VtW};_-;(I_RZBO^k-pr8$f0kF4-rJ>r?RLXj^A%B=yBJW`Y$eCkW}yA=CF;ZFR1_ZTpfQ_v$4Gn>O^-XlGT{yF>|=rVdBBoVY#K0CJ(+>_RS)v zb?k*kT%Pl^Lb61!h776xGAV<)aTpng7hQbWkTnFey;GYx536V7ce`$k!D^4)lFjB1 zr6@&s?&^Kb#IWkRi3WuhJg@sqa;Axrez6L?(Sn7p-~E6>wTTtiIi}-!X;($^y9Xb~ zO0z_eJO7dpQ-1BfA*9%c@iwCD4>xd}m3h=5;r#V!`{GldeeZ(Sl>5B>BFZy&7JbR6 zOLbk~J5>EdoGY-oFW78*a2M>(DK7*hb{jF^nI$fPnndtYMUpax3Nv05A=@$6x@kVD zvMYPQIB;+bD^~DjMaRthEkZWdfP(UJDf=`lE8R^`5BXA_(Ay^8p8W4){B<&4RZGS9=x%+3 z1Ec(>8HF^WXUIf)0&2haxvB$G2_HR9OgQ!$qjrBY@!jF&^@Htwy+%uvh<=%VNJQ%8#Z8p$Q@amN98c9J&8=qWx=)7AcljojL{- zrNPdh9RhkFu(`5o0wi_tXZ$iZzFOR#aTDm9{$zR!AS972hG*qwWcPnpMKkg$IIx;{F>W{RB;_K z?%y4SyAwCY#@N%kIpzVL%jc^V@yzjggL{lZND94?-w@r9U`D1SpLCDbD6Fni3k{hY`Hdc3z--A^dqG{9t}Uti)Ie z)QgiPhr9VZ%24=(1jBDxB?C%doIYpKQU^_p-th>JQsZqif0T!`Z#b&$>>pThVtdbQ zfy2t^V3qB%k=!-@(+^@a%%ye|YMooqiCYoM=}OyD!Jbm!WP>riuXA>JTc;|2P*Q1| z6_W3v{0pP1u&cYPyi2D4%slzz62d4IIbLjfT4!i`ZpuoI2%bpVnYUzu+vO=FiOdZS zK>4L-VLp>G{3@?Vw4rS0c^q1g4D`u`crqi%3X_4;4XMS zg4DmXC}d?sbqw2*>|X(Mcu)t;qfPG*ipbRG9|<@?q47AQjB1q6HYTZ2hIdyomNRA1 zAp{|gMj)7Ie$4=0Z6Cjn@^AT1hrwDDYU(e~6eP6DKlXwnosnai8+-)y=6;6Wx7oIs zySEnZPFCKiG96cx`vN-<;_P;p?NJRYA{lKwrFZM|{7Wai`vV!>gHCGMH(-lOy1wn5 z$2dfUL3Pc~<#t|;_@0peTIB;j^Kc5$N7#>Va6V2{Uk$#ax2rbgI2Z(tFS4IAFTUr! zX42)wUZp*h~*N=t~>Y%0t=#+xoEOfFHkh3wgR$@+S5I z$Gt${4F((14@@-Ab2&s}b%*@D9d=t|FMp-=?|kpZV9TmTg#v28GIp`wN-fcZ+)x(`Gw7i3A5E} z?#BEot)1t;U0C!SPR=CA5nXM?bhb5#0iWD-!@fRDGoWv+FI15>{J(EI6bnn*P%}ywZ&%i zx60Po$tU3{5vmdR#|(_;kJ%eXk{cz}B>V(?M67FLMRM`hpIL7T>9Y_qIS1E6677HW z@3ggZJrmMpWoVItG zkpkYPv__~%2vj5gR3o5L?wy-o(0F9`kNKrOSh}xS1Ldqb36$&RwptO`=M47HZc69+ ztY+LZ^rN364SM>BwEef<8vtfH5LkUM3IArEfi=r-wXcls64_oQ@awVQfo>ze2Vnc7 z=wHnRvisZI2S_|#Zq-;+0k;w_a6Mu=3aYot`AYaHcJR0Oj|R4ocBel zU-+jdv{3f$$Teg+fwf|=8vH9N0^Enj!Z)cwD+w&7LF#@MR*^Z>34%Bqv=kNOI@-;e%lB;)Z^t$MG*CAab+ybj|R+t1ADWz z0Tioc-g|MpJ$B)f*^^#Rb0gstUSYQCXrzXMpc7hI%x^r;B7SvfltH1bQC)@WYvub6 zyGMQBJ31P=!P@vzX|lVQ0OCfQ6aM|&oMv3oS2`R<#a8}N+H{`W8@B%N7=Glp*LX~*gO3%$|bF1+M5R5UWd6_eY_(KseI*s{bngAnW zWB8)dj7JVK5>bqCI`;TuEZs)E8$#=H)La+dw1FI!&w%48fFNvh2se9#Asl$uu(G=$ zNMinkX&jRdTWwXXecM2{hq(vNyE0eE3&Y$+T)-h>hd}mcDPGxH7WO~{djlo*_zFQt zCf;IvbFx~8x)A~OECE()$npfawZ}Rb z>F&i-<|{+0p$KL>1Sak1HBjwA%R}D=!3X2+d8%zHgGcQ}+?u$zuA<#NJ9{s^E%(v2 ze>EXmqlbH3=Hq~M5~x!xwouJEn7-A=el)u${?KON;0pQRN~jopm2T_5=UJ18tBj>B z4qZUQeVFTxfRs#dr~6GSbjz@kt+QMB#Wn_2$+?;Imkk<*^Y4%qDv<2k6X`;~r6tjqTI1&X8L@&n45dSaRbY&WUZT8z8Kq;zZhgd)mhd!@z2z05iVOSALNTVm;;iqUou;) zBYYL@Uu`2ByffC|F=6K7juyiDtHP@0MS2DNPSoqh8NXuwX>;&Sx!s^GU*f@Tf=1-9 z*@frogY-bu|?4mZUoFmfB(nxN!7B#dRg3t{gSHua%9a8y27 zyr`n;M9G5=OUgzMjlmpEeKlcY?bc|06V{MpMVZ|`_sobi=Nwx1u!0F4o$M0`7 z)nor{yl8kJ&D|OGu&9&m%Y$veuP~P-THN_)6;n&AwE1qE&OYO0Y84M5xC3${waGuS zaNELgb879gGcAtrVhxKd5;7z z!u@^A#_tPNIPgARCL!prQ@ZOIQ>tS?hjIj7af&k$MF&f4nEzOq3>a<^(!{ko>Rd!( zV?JoVxRrsc&4ds7oMOu7F&hlgKBoj=Mz@lFq}(Cv_=$%3zD4;Q_1#)Rf$vKl6KKwj zX`ksmFYr2_V?m@(@|=ZVIu32s2a)G{J79+TDSuo-@BBL4S zv&tg}3A#=fjOZN`*Y8Me>5 zU(^efk$6aUi(T^zJ7R{9g77@TI%usY?Ly2Lc-KKY^lme<_D9O8pyLO8$GRGJb!Yyl zUXJ1-o<8Y2mg!YfMHv5&A+SKD61(HQvy`NSjOyj%7wM~US)z3JJr*M0Y;!&he)>4~ z+#qg&`R%*T_Yw2Bm2yePVfhv45w@OWdp1sxlBU`?QqLiLSV4n#cuD+@hqiuBnuD#6 z`qPH|N9?VS1mhjyD@ocuuTFg29QfF6c$pyJ5eLbrpXW=c{ej$*Sw(n!OqJtTgy&h< zsE~%Q^BFvVAAp*oP9#75^FkNQ<6WRros*4jzrJ*bqlEW}yY$Ttk@>hTFUM8P<1$|z zl3FN@u)oTzS5ba_<- zi2RTE>%(EOOJZT&+4fiOcpTEu&5aC{0n6*}CS4f~xcrQ3vZuOh9RnT3>j4o{39@WW zcBOkiZF{*Fejl_e=vi6J%scF9PkS9~X>wS15XN1IihC|Ol$QQOdGOd(dkZpgkHard z%(7P8igon+Fpq->Vv#<&fuXDZ1&?UpOClAEA6u%532@#O+3VRFWf^heqieckVd)zY zmk%rTJ9_11hpzW2asP|bVcC3rTZY%W+jaKEN2+K%t=}mzyVd)YYl1?@{Cf>Wjbpg+ z;%;*!tL$r5qg0QQudd11H;2>deN?l)w?i+agGOklV%3Js31$Qyf@5u48cv`m46ho~ z+u?C~M8*{%AHC^WxrHFD;zWDWjuwuzRePF$^g38_FgGVrJ1{DSr86BviYqD<>3@y# zy(RnYcCgFwl3gq<2*XG-82PrqT@8$Xvr$ku5ycG6XrA31?56}7OaDQa-R%NV(GxyE zYAx@%o3H1F*Gz9X4SW7}?gGx2HGQjvOBr4I!Cu_1QN0NqRuqL2XQIrUr>$J&P2Zgo zDvvW;$lEM(=S4d7`pDz5NwRTVo=&5!G_+ zo{smIVRrSdbH6b9SmNvI8+Cb3?@n>~IX>$X+p$#|CC4E4IQeLqI{tL+F-g8yw_yXI zH}rJiU!i>^*RB3-7FxC4b^xQz`fPizGhPIDudpif!odY4hoD3F*kzT4<&B@19Q;qj3@>L}oPfKM4zNBS;wrc+K;4^-akukpj>%Kf& zDpO{-Ha(bp%Wvf9$XM0sa1HEI)W_!7kfl*PG;p25T4a>`0U}<~-0U#cf~2Gl*2zui z*ARhM3VpYHfGvwX9)H~mh4{yCyBTW6X+YRo|7R3!RRb^Y4Bg;`F`308Y_kXXM>@yS zABzWv{CXsq)E~iRoHglUXT0v0vu;dO|90~C(heYM3(mhL$t&&s*C+ccRCL3U_U4HH zU7U-2IgU9f0o4W`wg)tLVkAY|HF(iL3AC{h+9NcXjm6i2gb4P75oFB>Y^A@&qP_mv zN+*M?50+Q;r#1b+19lM4z@4A|*cO{(|1|%9yFd7!!h!5%Ke*%9+6PXerQ+`e{GVUI zwx*MPV%7e{0YyLx5}pnsY8=QXvGvRAo!Ztxj9*>zXJ%_FiusQX0E6fz67)kGB!52T zBWl;a8j02aow11r^wsLrN+{(w zJW6--*M`EGVR`>~R3dDS>1$K3<#= z%B$hM86*7&kp>&6kQwce4WH1Q7jMA%OY~+c%XfnpBTwiV&V_P6q8!t2T<56G++yP@ zu0z&|6ToHs$Wz3l)Ar4NpnwWV;-yV^+|J+*NtYsgN~l zrRZ)WW=Uqt?dtr_FM94yLZ^MH-NCftUdvfKdSOf2l7)T~agvZv8?tZs5n_FSwZ3Mu zunbC8KrW_{cjG&^D?ufl_JF6%5#Qg@v!ako3o6ZVPch(ENKN6{z;x=@(rMF``G`J6 z|M?HC>jRJJZ!bK?*>B-Mihzt2@YVT0+G{M02lBor&v&SC#p0R0X+`@3Ofg0K5yakL znoFI&((Rff?&TU#e%e;c$kBdo*laWQ)wZo*Ej+pCDcTu)fjfZFiKsrWn4GLA(wx^j z@?74tubk4E$^K$-i)(IbpBT8E_Pw0;)Z1Ze?{-HB7sntc5R3!tHS+7)j4ZN4)IYCA zvx{+8*(s-Am(p%_$o=n6931vw8@{1U-E`^KZvM_|Xt z-YNgQ+`uk<|G&JvYbywA{$GAM@AF*9#zrT5Pirw-?xd~Iu()!F{B+dB(y&{d^;s2b z6F97>@#7O%86r=5jcRlopqzur;a~9@%l+Z52(Ht)cr3}y+)sf$W{m>u{x&6+WV;ny zp`&0EzAp8Q5eN)XhYXvNmpu!i&J!U{$*6&Y1DZXyE(2VUyR>A7FZM*db%xAlxgmCrSg#PeZ$VKHH*|D<_4>w@83F%1*P4d`FyhYT@}lh6gr2%b_IIi zCo>=^lZVEXF&GV{+vxON-2)XXoL~bE-o0Q}ETPu@Zp=-Pmgj7l3|{W^%IVi6G&dgG zf+Jc+UMx;>59-EYt?Pvd`-omwj5Djz~Ww1=0-+97>UO3Wmwj6Yq!{YpxC z>U*eRbKV9A2i(>H0bME>+#QlW;HDHs?^lrN%)?E~cgVBFG<>6xTf>Xe1BW*Fk4-5) z+xX7Q8cd^O#6R>y^e&jYP0L*I-WTRnoax3oe{2Lhf4^gSImFSV2=?q2Ea}4f#>aL< zeP^G4)WfrDhTC$-PY1*G{7*fM3~jCbGVOCn${KRk67#>X<2uclzfCwS9kc5s~MYeRL2_Zx&2{qIN2qkWiE+93KPy{3pLP8BC zv~Qu$^PcA!4GVBh}1AEH$13ECz z!v|Z!hN7pbf!sR=iPtt^$?nuEbClVJ8t1|o#hnDcH{0LU1~w&lS|K6It#TVL?wYGW zV#h`>vjo8|&h>traI;4wkl2j~wJ^d@ubU8LOb!k7$bI6fLjkDUgnmD~RB zozpkhJDkThM6H2ut|2GnB|v}bAuhuOL;DqZLf^De86=-GPbO7u=9#bRF}0uDm|!ma z<0D(Y(QL-f{@BBB1m)XPy(kl{Z;pKaW7R8N%T-Mjxp?#G?EG+s+X;OZ#=#{32KL!i z?}lD%T&yBm_C6e$_8V=rIS;u|t8BZm9y7ePyJOc?z72aR1v>ckIZB>2D$gjG!Lt>zasK%3z)_B{;KK4Li_9h)kw8hs?cJ z*=;BLl}-taZFhqflRfW&*i`Mct#gemDvgRze_-9W=-Q`@p8f((33*7<|N}m(@a41sY8FVm~S5#q^br;7UbEJLc1j zeSA^bdja)l33)Tha;a2}gUnl{2}+RpY=?s44m2NA=((9F78Q1sR?1n~)b!NaO{UkQ`IV?k~#SM`VLZD93EpXvva2ytB=)cKcOS@%0bS zf;4Mf!4$*|bE7eG{Ovo+VH7(cn;OgxvNhDBAXWP8-uc>=u@!`yiim<6h}4p+DI#!^E^ND ze3^2GAWQ*tFllc!rxg`2Je&wYj)=VU!1^Gx)9*=uo?UQdx?+(kd47B- zfu*d91*h74qd;1FG{LlI=(4`WN@7I;InBdAZ9%CKPU~`ZA_VZ_AfnD83L_RKL1Uqt z!JFn&C0cGpo1;`ewa?m_(PuK9d#ReNogCk2P^vcGPq{D%E1!*TcGK20Rt)B#CXfyV zo|QeNh2nMvmZ&SZlUZn6r~rJpU>Pf8O|3!HRV5IXu}Z8$VuTgt64a6zA;GGoJ`JdF zL!DRYE$sRM`zfSjB-?IoIn)42;jdKD6yrDDNpKFCJzcT3)neK;`bQ=KM#9z}Mutlq zd_Pg?fW7x&Y^5*YeO$52g|sjboX+S>If&4FqgVY(xFQ%d74~<-wycHlO$GArb`kBj zR9doP6xEM-Z)%Dj01NgDgiWj@mB*w*N$spULu>v$%)~U7TK88|Wp~PI#4lKzM#^gcqXzlb1hZsk$+%vcL z3y-N?*=~H-8G)!o7*KpD@5hek_0GUp22HOm&-|%#25P{*YeJ1q_8PY2*uH*!WIx<& zn&G3zHzhaxI)Oa&VD?Rb>qlaU>$;$8usC)u$EB|eyWDug*UL=wcvvyE*6&+qSVF_> z!Dl}z zg_q(?jYX{Q?Vgq?vYcEq5v06ncgS1!zG@0Lwd}=J`xh3ZcnEHO>ddW;U_K$XbPsLc$*IlLtx+vzKRl^R{il1SCb(xXY7lBNoeSpG9r=8M>60|J8%hjBW&>_wxIN~krUo7lB#n8sRkbXS466>n{Lll!reGyRcci? z)fBCsm49LQLuDbcdm0ZEoYzx|`-!u1_fqG3s&ZP{gGoNDN#+#=(BrFb=# zo$IA0>ccQ1U{cMu-IBOL#$8vmMConiZzVX$dl>dCVWI|?vnF3iA;>JgK9>mLm$OL{ zOpgSx$AjvZvD|-h<_$;UBbyTB0$qiKrj$zOAG(<*r?VEO2@Z1!g-Xzp=TQISy71vp zB*%qR+A#wA9UUYLq0-Do9%I{-mKxu$822j+SCk>&fpGv2dfWd-SMW+UY7aO-FF15R z(>+*<4F7xU?KgB8n|_jT+V7!xC0miVE1;~GS#*Vu3J5Czudt= zbMD|g1(gC%>{OX02P+6#RD1ZaP+VE}=?LxfQzgFvLt=5VV$98WMD^y9wLl#mMTurl zn^M|P@*y)%5_ZNVqiMpjr7_kE4?_@I?dUlOHzYg1ES%P8meyuN&T$yLI?U+9>_>P} z6r=+L*z(RK`3U~>;MHyYra{P$A*Y{jdma#D_T^T}0J$I?aH`*B(Y}}4)Q$H?{shk=)YtPTS5PR##PW1+!YHOi86uKUi2i8PYm`)AX#CHMz`1(L*WHeu4Z4-urrN;&=J5p3&W$se zbFMg_6Jw|NzNmUu!3K2hBJPGUgGRFB7Zrxly&-78N<+0J>TsP&cn3}PJh$!YwSIYr~C5F@t6;*t49+tILH>w4UKbwSS`AUgf7bV67 zudjo%>+8&_JyGB6A}%sw@aekSr_DQ!{G1;kNc-ZSe4%0jAs&V1R)uLOq}&d3|kdBQtcVAwMTp)aDTNVOiiM1*ED z>+G>a{MQt_Ge?vAhO9(!-!<227;J%62Vnu}fBu$dQhWy(Mcd)NaYCdJQH}gg|kKKPP4ukLamIgR7se06u z(bRG`xR)TC?v%M$!Xw(DP1*=CbiQ9j`8Gr;!PXX9;zTEa6@T3f3UizvE?cwN+x^)c zf>&~(i2~$HWXqL}f#4}0Um&{^dVv+OSzT;-o0etqgBa7(g_a+fC?|O&^Env?D5{Drj0rmX z6!MsZvy54w&45|^_bkUek$G_fRN7!?wyqUlho0SCav>fu=1+^s;hUZ_9x8G&9?-`^ zE_gfJP}NgE`i#44r92_#rZzoFh~6%{JiJpbJhZFGPQ9^O=U^x2(oB%`S0|25Rd6G1 zsH0zc5NW3^vXepC%x923W#Ql#w3~992^Z5gsK#9NHV|h2M&L}-WUjM6N2k+xMN?~f z*#yDZ!FT2rK8uRMrK|X3IBDAyxC|(0w?$+z7GrpSposRclQXdDGA^Hgm)ND|ZbaxP zzYV3rQN%uC@b4RLEsK|yTU4_q9YKiAug>L@n=r|=DEefk=gY9B9%s|iEaU2!S&vcL z-ol|8$WRQDf>1{Fn=Qr{AG@fs0-NYU^jJc!r_hv1QK8dK&FK2%&Yw5Wqk&EoaP#C= zmR4w9+uM1`xHJPvijjB_c8zy_PF3Kil=Gj12tBl0e#2FD(KKE3RY`0#s`53r?1(wv z`v(m?58%4UuR;d82LU$BQC|S)2BYglh;b!B?vtYq?@C!$2Y7!oL{eFGv-Mng3ML=VOH{qD^kFArBeKr5AMySv3va3z| zxTnD(XH>lkDAJZO?kM=BZ=n=rDYH5`wxBKx&Myb@_I7l&vr=ptMGlltn$QZMU5k*K z2P*D;pBR91AKCRWpZ7(BLZIEs)phDhVd|1U192ODC&C+kAd`2H<==87 zRpdjR@nZFnoE}7~$Xs8m0>;4FQPSr_kJksJYhUQX)!`ORaJx4`*p^XgLmXP`uY}~r zhlE@}c`1Z^TC?1VUMU-m5wV3lw0hv+DljZo;NtO4l)GeCdu15!ijJ-~>S;^$A0gTUw<U|R6yl^%Jh+R{q2~S;9+_?oW8G>co#om<*;Fj;+tm@JtXtFfSKbG! zxRS2~LA4^O&({p*1I0)sS6jVuA59`Q2$ONHGcM)o|G zjxrTe@KAuUpwjMZ1}d<+97HM=@!qt&L(n)R`Kwsh{yqI?f2PqhKtuJ~>BZ;Vu`dI+ znkRm9-9GFkRoYF2M-;*`!@@(ix({w7ID&K8q%fhE)J7%Fg_ov}LZtc)E62+h+yL;n zvc2TO-F+gL_M&bMknxBgDhV9wqgnhJmQ_2ZK!|$+)yt~W(G+O2o@y1I;^0)K@6>(Z z>Yf!5;n3!{-ZQ?*zuLE|%N|f$@jDpSP<9N^a>)S*F>`O{UFUx?t0-c)HSJ!)=;ncb z=!rN!d>jdsfH=N~|9biX9L&IL01*!2d_~{>ZCVoP;)Qf7t6_n&(g|MC3Z;*ac=@S&s#LX$nn7@p{MtiiN z`4nFHntWrSOU`h=*4jw(16Xbht3nXj=Am=~7mrz+QK8b;st$|BP@hjvYEz#BlomSR z`_Et25>Kt|+lNcKfA_Z8f4#sRwmZ6|OWV?ho2a;MpPPYgXz9!u5p2w@lv9)-UY zRGv@-ljcXGDvseEAjRk6*GA#`o_Ot36bc~wU#;ze zcenRQ&Z?!f2r4 zJY+TRE|AF!AM5Gb>63K75Vtb``S;}8x34wcION9@)2kF8EL#Pi&#hy8Ca=L|rK(@cpSg`4uf zMo;lYMfllcqB{h}5v#B(86%h1_LQije7%m9qe$5-)<~AcRO_uoX#)x~kx(tOcipSJ zuU2ltmOHdVI{-&FKsQrh>PX^H0a3%t$++07pIqJ7ehj=C$i9_5v-U7feyX=hUg>G- zf1Ol)Hz3Ac;MneLrk2+Zd{+ofz2IJhVwcUl7ijbnmu}L4?Xi$$RQ^|!p@zXW( zI&`_{QBc~?*|PBM&)D>)Wy1~~4d<oW2E7EPipq@?Evx82(?v?v8gK zqG=smdDB^~9=*Mhk$XDGB!+d)u)J(|gdJT|H-&$Z2tro3goxYa$V96w4SmvN2V139 zH|lAPe_g4*4w!;{+Tz&8b5lb)GMBZX5PTS5&fC0}9b+gU0E`_w!;n{v*Jx%t?IL*bXzYd|{g*CXq*}9vAiQOv1nEE+Ir*1W~9S~FEcwN z-;@3F9In`l1J*&;+cMb`%2J0Sq(mXj0fq5W7kc=_0m}8gm*Us8mWqw|<^qAC1ikvp zLswj_O}Q?~57koKsSk5C_X=aH1Akm78ts%lEzCnX)^jQu;OQWlZ}3hw&GD5slgQ>y z_~MtOU5mxOS3aFiyeT{qV6{Xwqyju$Q^`(n%CrdS_4P$vZq;ndD7RhDBn-6w&7);Pa3TU5;3S9ifVD z!epIv!?kNVzixXt`Ob>yU;8MiCSlh67@hpvl(@l&m|i7t!Gx6)>-sK z1Hd3Cplno;edFRiJ9_9vjenTC^O!1h8s+}(Bku#)V+lJC3=9_KC4J#XZX3N|-o4(a zz5C`y=m7s|$|X=*blZ0yNUFH~<22LQ&ilk56Um%tlrr*?8+%|TFzt>fyXijWoqG7) zADz*0V~+yy_Ltfd;2Pl`7iFC{#Tm8f8iaG{+c1J9WH`OfBg*Vwm#usP5|j;lr@G>JXwKoPFpy zjoAg>vN3Q}Rj84>+H;o1k~daByY7{S6*OdcjFilcv^{nt{wX6yvror786TV|TWYyk zjv`^lcViwQwM94EKet_r869;@XIF*)W?`Zt;=6-vvAO57CS!loSRiv_RLGS0Lw#&x zss?&frK4OSN{&6O{={cv%HRGb8Do7gYdsCdO=E&&wylHs){d~U$`=wqlC{=l$D8Y4 z0p;M3cfE#jH-0OQgmf1le^1Oj{Q+%69Un=TIwjY5!0t@E=}n&x+gHZrx3OzymlaOX ztlG!DozDxYG`35)`8kHQ+c3xOxKo*Gc^NtF1IK=xb)h7AMf1aY`S7RbpCi&p)kDh8 z>t?C|ToWbl)@}yUd`$DvG`$q~R`fOfT53h#zp@o>Hbs-%de{6k*Iy1Ja3ZseoAeN< zSuCxAWf=W z3H=M*4TPk<_(j@8@l0nd;zMv@#bvad8^8N)+O)xit{_ew-S21-P)cY>XQ-MpTB?4C z!NhLBWi8c=ix3na?1$1u7@3=|6XtB2xq)z|r1}-ixCu=qe{IZ#S;yP`>C3i^IcHM) z6&-V8v*Fc1!ow#Gl;b`6^}^Vjr+K`@UAZOs`-UETH%d%#7Qa z(evpp)6uqxc211W)0z^QYV3Me#q4sY2Pb`&=};2%xRS#T-spY>Ws4}I!T1OXxaZ&U zX**_snnW^Qv4EGC{}S z`;VDxTq{&iNh$C{oE}nr6xb;)<%YVl#*4DLc&}W@@Y(d0Oo)Z)Xl^BM7x&zT?@dem z>B%_xfh_BWt*UC}$D-Jz07rct)+GVZ{WEJoR{CA;Y#3xc;UXAm=&aU!yhj|U@UH+w zzsP$?@{{&T)4rfP4E^nI>44VsA!v)tYZk0~%C7f#uT>=Da zD=quJO>TS}!R)r$;+MA5__RZ=-q`^Br?^uinGWr$-Jcrl2D6 z60AasbhNdOS=jU;-yf6Rjzcec@GHH}etnHsN8G(0FE35At+)jCal!tMmZU#YhXatY z^QZNLM*)mxA-3dJfrAQQ0D%d**(`;;2T8X^-j7Ia`s5~iFNXf2N_;i8Dz+tHR;dJ$ zuIV%>RHwH_fuwIjYqix4iwr4e4nU8X=kScG*WPk&Hg)UfJ#Sa}Ft zOM+`S0*qEcdWu`8*ayp%{0t9C?vRHc)>ERlxDX!ZlZ?1zosbNrmDZR>z8R4V^cik5 z8>(5Z&8Ilb%;o>Mp+x93V82BSOMtTY78~FDYdofvlV?p7p1lJ@er+MR8iDTVUtUD? z!v85wO7rQ5z`Zq6g`Tg!P)HeXP8S2m49|E5i=(DL>M`usXQ_6kDOLO$u)XSF&=Z4Lv%feq*F9 zXD#>w55ufjMo2Wyr?$%Brkx;QyU-cGWF7`H=qU6~ucQCY=oZYt+A-)Q8D(Axb_xQL z%P5ex|C>qK6dcgOt>t{r*?(rM$`a!~_JG?DGP784Y9gnS*4n4auRcp zs9LImjH3kb{`PAxv-T}(G-CgDzt&&5UIV4Nv7Kmvd9{ zI-Ny$J+#Qt)S^_P7u1g%n^^lz8!;SqQjk*#ugY8OAXCFmKqhb=+VV|&yGJ8zn#xx{D&dphY^GSKoAihdW&{7Y3?by z9z+fed}?QL6rTZ3=M=0l!lZqk52Yd4{zha5Y4RK;?%c6Xmw;O0*-i71>G~<;bfH3j zDf|Gbs}o-b>O)}uUa=b2B4tp$uO9um zBR;ZY{Nq`~?)5s|*nunp2!#(XjK;*bvbV~s(DlNk<~1Zp)eRGJWzg^iE=TzZ34KaG zH~4{fsf=z!v~*r{8|BzPrW+Afq69qiGSzRO26z}gC#}Z1&})Uk8fF2fuK;q!eeDZc zyap?L<)Apr-N`SnyfZfP@J>LvL;7)Kx^O>T8%WX7up%{zwSvF{Z{D<9BZOt2c*tKS zHMy!OtxHjz{`lr@!((4~kruLg*_{r;>4>PMd>8nw;7({hnWKs{H179`;@Q-FWp(_a zRA7JQmETJ5-pQPdJXC1n8=i2cxfP8{G+MXO4`0M?Hov}8?|D4r-J9A-)VTg6BJ$8c zjYr;S(lby#G;_fu;6rJ}GU41YwrSQ3s!vUyL^?O3*somH5g}1`D@z!;L(PC9Rv#n~ zjq`=Wfa(JP&hYuzN<#kK_ijgQ9V0Mszx3M|{qe_#`rmw8^R0NP<-Dbqvcf*Tt>JS3 z+xol%RMH`5ysfU6l$qHMlXdAeBUui_b?Hv&+kp)H?tLlpLr&lqEWe(|v11uad(X{F z0j~a45Uj1fT8LBi%?FtgJivfDlxq(y8;bJb)~L6^lLPCg-ZSB!0^k}kiyw}wJm_)( zZOhU>Kf89@|EBZV^mv~OQgr&AGaVh?a=1!(aHUVWZExXnPBu)zbUI#jzHiK3Gl>~n zGuq)n>2#=OW^TEr3P)pJ`3&fpd#EO>gxfX3Dz)wnFB!E}VYoaj{7R?UCorz6x_6ud z(8%{eZ-836ujuSVcV~v!`V5b~*39-jo5yw1)$`pz4L47azx+QXSj-Q2yym%*sW@Tc zr|`^7&rg9PBCgiw#8ACR((cL4GT|$kYKkspjJD^lw7y!f>U5yj3y`J`tzc26n6@Lh z_V5=7Gb)wpll@D%?J4Ufaj&7leub&E@0P68y@Tp^|NWX@_>pJf#d{4_001R-@YPLq zpZ`~X9NF*y#uvbj_Px4U;556Nx$iS$;e4GEz@)tV5Ae$Em+{Tq{ym5bA$u=m?sSo2 z_b+ll0YGYjTnTs+PtJ?F3_tx7EJ9)^C_X7BS1+Ou2P!jk)yA_g#2hs799zktO#DCzmvpN0p%I{@B+UO zxULqQNG*JsHNUeVYk(iGTVf{%vv2gVAwn+ENCqJ@fi%H zD3dGAF_Wjw&{4K=C8nODgs4?Yr;IIwRZK% z#0%Z?VxAA|O{?#K$LM_%YhrUH(Dc&LByZL;rjlm|U^vkbueF}3r3$b=KjrcF*D6Bh z`87llGNadk3nb-D76eOihIqhZRjDoct zKhg|Bu;0fp3i&(Li{&H18`W9-6*JeiBbUy4oO<3a1cy-17B%30V+<%-S9{{JA)R)9 zNH2($G$kjHAQu>fk36m~O4%%m+d2j}mkV0u_;>6WYauxov53v0Kem&~4eg9+JxTc8 zcLmi_0Y!h|yPQHi)x&bXhEF2wm}=V&pIR^-eHJQVJxCH&YN(%fwa4Mq>-zhjA)m6FS5%MB=-KuGKV9{M^KR zvRfQC(ZNk+QA)$>b%J$=MsVCRGV67B3c&>zs9q}7dr$XOYHx2qvN;B_LwPk<_GeXA z2Tp!&UJ&;lieKiKUGUi~+VBZ#AUcsOosXM}7Op*3fMySIm?qV;^?Zz8>orr_R9zmK zfm%Ve5HISoYDY~s!`o7KE*$HT^AL{TPyBaSi~lrXoPAJ74;?kzG9f8-Xn(6W(yA(S zK_X^gQN4}&pPG2)vgEGoF^;p6W_yIEri*RNVv&lKj~XW<<4*=H5pVV zO}Xm+z%j?^)QE8zn8+{vD63`Nod=E41C5hNtaF4H(6r&!4hBQ%sme%dc~wqcTtwN zdEeSy%5MH3kaC>JbteT+*C@E#Qf_74s2WX%YxvXqS5@-HOe%XBO%-aLFkpTc#N#kMp;j;i+Mn38*V|++$r}?Du7(6Vzfi$?8$It+}LI1{_74} za3`Br?8h4Ihtcx|T}#=}9zx_;f60VdiCJaf{WA+aymm!v2FGHP>k$a3Hr#~va5@5C zcq|mYh{jAteR(o6zkZ&LNXSV?Cr|dPJ=BED5f|@hYmpB3AsQ1 z5Ya++4V!E`9LDgEN{;B}znRAF>+%$KAD3bD6h?&04F{Kggjz3Kg$r7(V1r0W(?cXp z8$c81-LGKv_x9IU7Im!v@knM zEX6{l`M?!9pO!X6lEoY0VN!KWnfXGZ(5o#vI+mX=ocO)0r!iph0})~ZW_ zS_oazJ&(&JUe?O_{SMKr+xLW&0m)eMdNqUGN!lPXWy?E z_E9 zzuENZY#Soyn*@C;=~8?{-bym3l%!@kGH(aeV@@Z`|J9wxd?fqI-abcI7IZM>v;}0w#Qk$*z4?5uz zLVUe7Aw|ohVcQ-dW3i&|O?;nqha9KHaZs1gxgY{WEpg>juc}Ia{zfy(>b#jJT<`Q1A`+ucccih?JzSb|g7wCY zRNB!&UG|Vo#-}V_>pi_~W}D=unaYqeUx_O1v8HLF9fcm<%Cp-3FVs*ozYe#Xszz8zJ#r52 zrz=(d{y1UokWO;uaVD`M91Nrya`UHdlN>H5NvxWmp++v}+1bhv?2j4cb2m@wvO4Ut z{+1dKFjKBDG(ie5VQ=BvS7@HxQiy4aEO54}C0o=vs{&qB;QLD>sAn+=@F& z8xM7kqRDnv7w3A?rohIJf5%jl_aeSMLMRtw8d?QF zv7%)9&n~6bYj)nypNyN;wm*AHXWQF3Tqa5;?<;p* z&-opKanN`3sa{}@5Bq^P$6H3@g#C;)L0b}qn@RKYhgMRjTU0U^`~lkrAMy;B@dH6IY?5n`Ga0?vl%_(y64~3;i-xH5)()+vWs$11HHq&S*s+r1|js8fZl!?n_ ze=C{X>J{A#R6T61ps`e1(CNeIwe-g1xQpOMw48dVH|=TO#gSvTGfp3*C_Jz>BU{cG zbdT9TcC^u^mzl+Of^Td!G;iTY)knx%!IhXrfgJI9;5dvSFO<_xq`syd2n3ISN1F z#JPxapeBME>q%7#e239v7>$5Pl3t*cj8WLpJe3H1^YO8eDQDgKag8~F;PXV^Xt|KtEW;VMbnEU__CtToiF-LgMnNf+Z~Ps@+Q4M>dsA`f5i=^w->O6mS~CbFwbyt5NWB;|8bq)8yzPX z(iw_*^F1N1=Cg2?x~@;W}+D>U|YhvsdrjhA%;8hUS4ir zbf`E;4$vgX?G8$HDGRZR;lm_Fhw?!dyKo$0S*GpJUr|=UgF0U1_2XK^J-?cUnH`|> z-1FLn8T_%$@^pQdyg$E9sfNv3MWpiw5>H;LxQxtwEc(G+&x$?64;G@_u2@(O+6xM$ z+_3EKx>w4Q+wgGzd2L(T@~wnIh5?qNOxc5Vd4VT1l|Grxh1)tuEhyTI(1Q^izw zRwf>gb0aI;)CsanTBblNs!z5D#+9dpdsd;y-Imc@?zxfpO5>Qj3pF9Y!us=;$^!!* zjWB`e-hdrj)!ELDI~T>&-8SdAVBmFOoBup{RZg{@AZC)`zpEWS=6XuQl)sh9TXZSB zW!*3Bwua(k`^PsQ#I=DmB?i_lUJuKvpNb@T@>FRi6Emp~b=GCy;Emp#){L*fLT`*6 zD4#Eym=r7xrXO;d|ICTDQIyu}`4ybx=Vs|_^ZjRqrf@6PBT@3vLt<#S$$UlaNU}ix zsK2qSL*p6+F*u^-dJXK01S|~`w#_Q%A6t`dUFsS^l2b_Lb-mkF?HcXxIkP@`!AAxK ze{F6ZKG`%9t3m_MT&_LJyqn?gal$S_VER-_-gR5AVbalx+Nz7fvV(f#!d60k-p?(l zkTGY{;lZ6gS^ty2Qu9J2`o-WZxJkR06te zRUD&O02u?Ge5=;1USTi>X2HrN=C2wz16_wTl+@1?Dgw`{xpgc*m);fBEVb`5F8X7m z2`-QQQd5QA0jKopnYvt9>xHac5Ndkz%4cF^1I}Bgbhi7^tx^jGHErs%(#B8hqc4qa zmd<2s1{#LQ4q`v|zB9{Q$c6b|8`-E#aVPklKp&O7-3$7Z@B5;p*Vzu|!Pgl4ZBz9& zb(PLLSzZwydEcDxeg{Nut0%I};r5NqJLbkihxNu`(6ZCk!dD*Ue06Xo0ju#dv!(|W zJzeK}$O*Ub^|0eT=o9BFk^*E`O$(d9`1Q-vNeaf@jq^?6{J9Xpeb)bi#r{C9*xq}( zyb*3y^Pq3xKwpGx<686$d7{qoryR+J=y?17bcw9*m%OJ_4=wWBdwa=&@@uP33cVwM zE8V_L4@cqFi230O=kb-LL-t&VD9v~^3fjNvZ}{VY1tUuXo=jYZ59g{a$O$#K1~m2w z@FibDf+!>Zpldlb<3_`$@dr1)YTmbtJo~ne9pI=vMmLneLyok}#Oo_1+}NainT@9k!iu2g9t4UybBS&7lf^ zb0M6L`YTh}@8~JjSvk6T#Z8_2JmRN5uGo6PaG213esaz0Z+G7J3~Yx(#AF*UM<|lI z*0R3!zcVu(_tPbgR!2V2>g@o>tcLB-@e~8(38|}(F;Q=d`148Lg^e4G?MJtP#-(O9j)|j$ zDb9B(YVE;s>pU4U4p@|fPDO-;m$2O$O<=WT=Xo%AU1~G;UblLPj_V~387(44YY!9zT4*pG2B$R!^+IL&bv)WZ}TlJA12;uUJ1SM0#<;ix;1OAT4 zLeq#NLr+TOeO{R*BMg!6WY6Ou0vW^(|B*i8(;CmhMI5{8ved$PLYdL;<#wj~6izjf z8cAE-Q39NZOeG&>lvN1igP=T*)-lpU!`?ke~`b*5r1wmTFF9nC;u{S5nvav^YsyQOzySCf0t zTC>X~P4}bEU;9oPvWnIG?W~$w-gT#vFRu(9g;0p#@jk7Yo)TNH7F{v)txSUxQpD*vN7lfs4JnKX1 zjpJr<*k#P}Hx7Rxle0Cac7DS$M=cU8y3?6ta{lzZRX)!bIUC0P@|>v@tJrCLWn2u)6z72A)Y;~n z1*{D5OcvxnFl;6ca4&4EQd>yM3`H=~_YChGu7Js(RmzJ)45qkY#CK&~P?kw1RcZ-I zJ!xg*Ku@C?kK>w_Mqu*~JJQkZ(*dre`>Z4Sv!2m&NPV{t+MIYe-KQF1Uz(mwEzW}4 zrsm!#RG8KC3z=?6)#DI)3)bis524k4Uo(rxBIqT zJ6H8%yE?3lO>@{uk$gijx{J)p$x6uFU)v=|0Mn$fLzuhu4RoZ zM5NR42)UFbWE!yAJPXKwst926#?LBx`!=OBj)gu3K6v;tq~#`h;QjJbn7JRoLACi@P#_`bM!50g&H)Cf6l7xNu3Ooe2#-*h;@qE$r> z?^^|owhL~9EIGYf>a!nrvP7Ro55LP7S1#4)NB8a9clZ~OdGOEJw_!j_7sh$?Yn#Rw zfSVH~9PuYbS?1p!)GxE(1@@JE!{<23de{8E&z?Dw5EKXIh8 zD6%s)l+}XHR;z~3)=<<>69Hq_%N(~C7?9oM{?FUfbwD$NWkdT5msi@Gx$UYYo>%== zOK7H12lqYvh0yE)^ZWKGPXR9kzItly{r2DD)6?L+zrDOR3HUuZrti51naBL+UhRK$ zr$>I8Jw4?s*51Qtf7#9eEr9pF+4H&iKPpj`CmDQNAt^j!SD zeR79@em6f|OgnyRKF9az`%bhvyVosRlLMB2Dnfy7en114JwYNj{M%h^b^m2Fh_d?m zr`Jp1W_3vL@-@5N4#0->&cyqi@qK*4A!3euZZh*<{O;v!3C7%%>xWF=QO=RO97%y( zr|YVO5oiFY7ST~{{ zCNxx%taI*jRq=xK%IE~1>i}3C#C?`s$u`AW7n!R+iR{TYv*y)t8dU4h{_{KNcOXIi zGhl=-z-8=c&zN&-oba^Oo}m<9^xQ4^>DdTMQ#v<+`>Lrpk8(8VFVo2)G4fr5ogLAg zI8EP}UvVVO#ddmp{p%E=_~c=|HCT=QT95phAhKNBZWwxa(Mm z+JQgk;b||mVmg6DJm&X>#e>$e^$M)c!^dEzmr~ln^QYyksUsuTWCl0pjN)ElpmPYc z2YeLQV}x>qC1dW98R>HAT_KNENosQs)fg)AJHqH|w9snx2j&~qBXF;H^{}dIn0b}s zFcFxOP1`Ba3q1Eu#W4xm57XHWBfL!NC?)dDJEQ?+p|JS7#uAvgf!~cNaIO%b?9QaS zY58kOE_6hl2_shc4~O4Eq_vb|oBr13h6IVsY$v_)F(fxBS%#o%Qm62$3O}JTF6_i>bfut7U%uP4Wk3FM#=oNQ1Rf zQ+cF*k_08w_(TpJfUoG{lbhRqr%t_L6M>$B1jRG8iWy~C{2n|1JXnY z0jZ&53rH7f(udwdO&|$HK?s5nsey!mfV4n@lu#1NJ?QVb&p7va*L~M|*LwX6^>EJF z`|Q2Hdw;*5;;wUBX@H-~QlD9aCK^p6KBx>#tlJ~o{D+^~GZQtN7*ytccqBNS@O;HY_t@1WTG5up&Y+(@fv|YXXR@%}Bqk4> z7Gg7Ct)9|S{6eo@$mEIPT%hOkB0n^C(9dJpZ^#K#IX=z$eNeGMGJMCVQlenZi z>53n`B-~oSfr|<^myOx_-3BNix`lP+pv;^s^7t_&de#!b-#gMH_@RIV-?d?se1T%7_=0RAC## z8}^zrXGRS?l^3Ss?u-aWyB4c(#S(_Vb5Q_3H#p#UYVX^2`G}%u!jXfsF zwUlq}(Y4za*xdb;(6w(89EFQ?^_88m>{}DP_|Ucb(9MxsKKxPuAsHd^wqWHgM)J*3 zvlKeu2=P>h6N`RzJcJ1$BUrV7&gUJ zoBG$_7wt!iY@ofXJ|W$V`8(;l^1&H`wPo?;m3GxM!vE-(31lK0v8JCfh& z*3OF7jByYtsE2A~EBd4;SjU_)JqC7ad7OSGoJ|@+U0#)wb+g+a`Y!R52yVF_=y{$z zXY~G)+@z-hg7|!~eMQ(v)NMn!C>`1yTcm@FL{fWffqJOx3FsL$sMNr{>>=*~Rj*yu zX<~gUO2yPNdFtl3C}Q@y{Pgf%c6KdId4q(oxDtv$iRVDlowd&Pc^wHt!D&(N)}1%U zylgx`y{LC`-p*2vC2<=+H*^zpbd+B~uL9oR)NtXgemlPm?i>8r= z^OIM2T`y@b`kjdCn~KWb{vv_dAhN4^GOa`DqXd=JrA-z5q#|aPuDG9ae|j6n_AIS- zj#ot^L(F#woEB>e?es&R(DuA#=P`s@2w;1Hw!hx}tpP20J|bPGQ@M=AazIbj*-MWO zt|vV`wvqbkv0-q$@6`#4fUU>=7d=%;En2xxbEY5Q#JtE;!?;99N7PeDF1TS8{jt51 zXBiyJoe!Emscg4G2UR{PdAwUhkEc5WlvkQx&&IU31~TyYcHG9;-kn<$W`wo0#a^K5 z*%PxJji;tifa?8RLIU$WY+DDE+`9097CKr$P59fjF#_R z7qkq#4~(}?ocvBF!A>s8Hnwfg{+*o=VSIc%2~%u=`FlkT%86$$;JoblVdlCNqwHvU zu7O^lsC9$s>2`Ls)gue>il~p0F`Ti+8AE|m{ACi>kLK)@HI9Y*=cKbkaAl~94>R+3 z!YBf`9!yV-k05}tI&`OSvf;s-`E$rh$+2eS`*;3>kY>r!*9)G6r*on;&D1!a&|^Bm z8UAr*Q~0j=?zp9WL6U8ce93|c;w4XrxEH8{j9;XeG=E)nXd09%5P$YyvIH^_9(`7a zls@f1QaLMRLmC#|o!Bs~9-`)L&}B!sz3}PL=^@=x-D9)h$E1QyuDx9}!(P-a9eJD> zyYr&T-UKDC#g_sElhyO2i01HQJ#l*V(_K@WE%SHI-gx`ITLUKnVHNtmI>WmlP_iPE z9opxPI?%OrmoeDS5os2Y(|NP^C7foh%E^A4D_?dlx%kZ?dd8_~KcSp2ev&qAgpTU3 zMj8_A^P*GcW+hY6-M5_9I8n&RiWAGeHV@M!$}7y>g@FQ;eZiAuWCDE7r7dx*?vR^~Bl6B`JT?xBHL4gXZ)nNdK=McyQ9thX%-s(y3=au@dpPm+PVr6S1dmXq z2bRyf|NbWyIAj5dcbDnY^L%vvR-^1Zq^keDgXToxtp4HlzB$^`l}W#Ql_BbZrAO4m zxs~{U72uLx-8r5D!?vRS5PnLtzXZd0zSKF6E8klAp>)UQb}e@S zy2{t=?EB%bp5mjo)OG*&~&*J7!C9#G>f6ioNp@Wvw&%Id^>$ zL->&il4SJ`Hn8!QRbUy&(WePjq;9yKncZ#OeZ>_5{T5&Bv9^Q!?3ZX&m>)% zFmauda5U9Yfx1(jxMP%$iZaBY2Od(pUO+p}A0zXD12frdx_AWQS~VvTcuH;GjbI7= z(9(T@{-U8|rNCNO1QYQm>ROv;cpWi8g&=jQw+E0EhYk2MaM{eS)bst8d?ee++iSr+ zwX#|S-Z8;D^83R(Wb84xMuq;^U5u=n<6T~Msqwq1CLqlh^BE0_;mgafK1t#wUB!Y# zji=00e9tb)lv}1qF;P$uRabqdkG8CQ%VfFE+%Ql`e`3=E+QTm(L@4_?x$L^L)m9?X~5 z0M3Nj8c&L~1ji5hvjjp$z29w?{ ze*JWGpr0(&wPP)>ZEeuF?G9;hs$9!-=xX{;3Jk0?(nDHU+A(e z3{vR=k{hS$3X@`hd)p?D%mri#P6GjefYVE%@qEi0Gkf@{fiX4&Av6=V>;@lePUb1s ze;d^Dy$TNX@SLyY&xnnSKg|$)RM!S3-jAHrv@H@7$@n97sR20sV!qzC@IG10`?T?; znuA}HmYQ>#&X zJes!LTINn4hhnU|!9-;5=eM2$-8=&6qB(P?#2p6tKDPRrNYB4%1Rs&zIp=C4@Aj?_ z&n()R4=9<8=r-1vIGDhZC34r*P**ECTU;`P{r=?i*>sagKQ~H*$1PE&5RQqy$MH7W zgAG3A-*?$kpgd(u0tC7V6HwGM4uu4s8hs2jBYeSk+Pdw;0uq=Env)FiWR(HJ)H=kk zrqCcRWT-z$Z<1}1IG6!;NpZQ+YcRO7z?-4=W}E}REju?nW(guGo7P{qUy+c{A0&Uu z!V=aKN@QP@fOtWTu~jiiR;Ff4WrwB`FCq`Kqsd{Z`j3`c3tLlUIPit9gqV`cs+tX@ zj@m85kZpL_M@d2|ArQ&?$trWGakjM*F1+Dy`V^*F2`DJ3`0m2?^FBEbelKW{3e&zQ?5w`()>z*ciF;A9iXpf&YC(K#gky1W#dhfh9 zTZ*i{wIybLk?U_ED_;praF)~V_MU|iO9{YXNcSOSx{GU4A90TN@g4aBKgN_EsA%qC zQeGCd4Kh4=8ki)<4u%q47+}P7;KZU@hICd z1c=x;lze@Tu)UrzF6&PLIxfBf+^~C}N@1*9Xwrw_FkgpTGbwj!^g888MGE{W5c6aJ z&S-LhD>lV8Z*CxMLypHla}r{scbxH^bGzy>PT4; z-V=sH0fP~g9)(0+n>__klDV>A5dO+-^q0Wp8gFGhd9m_By>d+(BqAW__S+YOV>D0{ z;el1n7g-=mOBx`OW&sDR#Ztp9Bc%`eW(tw1w{yR+A(Igx)Z5CVEOh8fh4{auN# zM`mQXLnOqjrAQBUJqv*T@ZZts=Mx6NJUk*f2owr($T`|UxSmsZw58Uw!y>4fL?cY_}Y*~-BW?N+o# zue6$dlaz0-d{Ob#zj3ijviVx16sGpdN!u>&E+Ot=F3bM$@;EE2#kAIS`L)H?2Vt&K zy`+W0N`N-kf{}-@2h0VWl?-aaUa`XE(mZu8n8fuGoGEKuQJbXU)jb5jK<*KK2fnBuS_^%=Q()gt+$dDeZ1HG{ zKpgLhpGv_UB~{iv(4-46+kx}CZV`KBFxe3~;lcxXiqUCJf)V4hA!J8zLsRF;jk^Su zY@GniSm% zn+CasCjB`jR|%|Wc1vfevrqBxMLAe2sU>^G?p;08~KF0`ixfs{5}_T)>*ph7MlbG{^S{p-R24vwcZ*0evj>}P~F8Y?yLz0@5vL3^S*3_&m3LOb5^o(2Q15hD|<%Y27lOmn7p1M4D7i_)vlHnH^p^{mR`Ko z-ieT4c-TFEe3?s_JR)dxGmVGjf|WLg)-r4t%%n@h)3Qod-p0Rfdes}t8waP-7~5Bs zVPOv~fncRjAv{@=cg6%f+x{>?qoa_aiMHSBTd!mLaK{9dLZCt zR&*<&ssk5m$|PQGRQdg_3y$N&!oIOeFQB@Mjn#>@X={HXIk-)D66C3TcmY@auG}Bh zraXsup%-~ab4p!=-^b(HQ1Tcu-NaBG5)ULUASs;K=sRb3D`qTT!r!H#mP#Q>@mH1D zY$AieYcO=-$J%HTAE7GDB5WPflnlXqq}$3=h}P`6SbI&T6ehQutPFL+L_fyT%onFI zA>5-yWqdjd#6ni$6 zL220&9{}gB9~6w+FJoFJDSzEWd3WaLd_5S9L2V^r^`YWRePk zuMTmGxr@*oM>&huQo8z*ir7s97D&A|XN&Uux>```?H*}p$N&)cbrP+SA^;ig^V_|e zL}8WQ2Fp2jlOv$yGev&Rdup6*ACB5pXSOV7QU+vWaa(C~SN8_3iepFh zTZrX5-aWo({iSQ-oZjlUur17rEBfDK}4BXGoS;RZRi(c zFOaV%J1%T^3uZrKl|Ag!UO~O$fnk7>vJgHNcq>rY;!l*c^Rvu1*!Pq}X8@HeqAWlr zOmPHkrIp1!Va~e+V((w0Gph>6VOL#>_GUWfqxe}yxPBr=enXt2`u6wYm2Y^KW$LhO z{caf{`t($9L=9}RYt)HkmW zShEMpAd@`BSW97DAnp5lLsD#D z@!6E6a(UtmOo3J3FQ>+6M{FQ(ot#17jHRcT1 z{2g3iCGdmd_UzXC6F}zUKawktjDP&_=;Z&?Q+}4Fo*jAr!-FV;XEgS=h3)15)%w=1uOzKf1%&CLOp+QOL`8B)|RbaE^66a zE^aH+ulO<{>%wk>$i@L9Wn=OM`hr) zHv1ibe#(uhX14wVKRS8$#@%+y_>F^N)EwiX*`E(X=)@AkaU|`BXP$nedi=z*x8oo)fDd>fgupVO!vU0$hF+`(gKE?TOS`fK zE4oal`Chy-FUUMpzot|$PrmeB=+XmJpBxmCI8VN!Qt5x3oZNM zp+TxUke2V=FdFs`2ayAPoA+&Mx%0z^rpBw@m1a&5=}iR}p)2NsA;&@W7pb0Z<^-`6 z*k{Z6;S}DVAMT*?d9WMODDU|$k`*+&`~4uRUbku9xYwG}5^$lVLz*}wZiwY)f4iw5 z&N_|GlLHV|q%B-1BXe_lA(;5I!VUzy*}C}I{I$=v1IJ2<@G^*0e)ZfnExTTJ`=#BPT%Z z4c7@m-?z2!A_h=1Vr2=r#U4~;TAZP(J=%-%LK`r>FasuoV zz@L5T(mgt{IsEcQOpG9y}*A>TT1Uh=3t`@u$zm2Z4Sq$WohROOu6>pJj$Lq zuPC1RpysS-)%2zG~0vy#2AcGxL*D#~oXaEWTMkV_ui=WtieMr2fg7 zpGmlbdnyS3bNc^K7Qb?a%n1R70JN9BjtWo=U)Bpxnr`yd3Oq4>EK{CLRu#GY!wE%M zSzlP2uns`?%Eo8Xy>p@b)XC5c*gS3gH1O5VfL-YvZ%#A!2lr__?>?Ycpu-oHZRlaSmM)$kCZXKOuL%tdn~;Q+jx!}#dcr?uJI`zz)scs ziF$85olTHCdaQxIW1nqm)jle#{UA&ctgEelAoZ`p3KZhOWR2MP#!+F2>)Il>}bLN}hv5 zLhwqno3uY$)ZYhNc$K8k7|Ti#-0N&E7p>^S6LJ;a+juKY0FEE-LB`;re;tlRyRNEZP?43QJKz8)eTxU{H5wwvL zIZtr_L9nP}VM-|kfOs5q%whfIqr-@q9g*h%^FLfeA6Ic`dsf7w+85$&@yj0>L`Mh~a8(@x9ovpj3 zz|ErtX#r_q*Zd?-6%c+6w43vX;Q&UOCqC2SCKM_q_&e{5!__T2Wi270((ZMn@(%FJ z!*0iZ7dj32m5Vhy-JReb3bWsuR^9Eb5a_*hD)xS=zVyQ-xT`8Xtr+@{+Rz~lzEJ7W z8$*1ET=-#<`_VFzIcSw0JWr)qtSG>;oBtdDNi6q^&5JwltD%Y`5oi!%BT| z@qFR`>W$9!&#wdUvNiB?Ujb9*Ni*?C=%p9hcM=P%lG z#`Qc2uNx>`DZ3T!DG|J2vD9#Q{FZCAf7cNmxY_iEc^i-o@Ga@lF{6Zsk0B7L%&XcW zby#4R{gbBrb8E6Z*=}(>TM*yQ$9MZWM=j?2j)+sLZ+#&Cs!e;m-x663C)ss*eF(nD zwZJo~=rg8P^AQ)mr6*@{$w+QS_1%Ttk7#y-3yHDqJLo)ptI)jF?U20dbJ7^CK_Q8c zV+PceH$Fw{g?pE>SL19jiKHpryygRkgaO*^XxIC?^cb%4OswMbD|s+Pu0sNBWffJMvd?o z8e=oZeu<|a2c7@`(Uk;OE!0e%B5aL+G3qk2gjNQ{r;#Cx{j#YWPqfUrY1e1wWM%{7+07&V1n-Jw z*5xGM^~Z$h4oCD?5PA~lQLhZiF^3b7YU8{c1diWkELfdK5p_cbi?Y>&IDb8xi+3~1 zHXHaV_^tsQYQkH8277}N;vJGY;t1&9$nex+s=s*K59SO8&^m(vP)axWmd;1OVmP$J z64ZgX9pVk>W}Le?y~C-xym{+^_=RB@9LZE>hf0agIvsuw4YyG zvwpjuR{cmq9~)_9R}u&Bo{}lOwat%%g?3d5+qaJsXD!C$thnNi30;ts(3aw3AIL^j zDs}J)Mx(Q|cF~1p_}ICI=6Z(trlUEhe0K`Sju2Z7DOO45)2~%wUt{MdO`$57GKhgU z0#?luLGg$DHj0)Av5QJP$OuEMjT-H0%JFTD;Y-k-@|AQCY?;sfPc}~96o!tG7ettq z5PQ+jSd|(YrcQwEA5ou(ytvzX&D~Y5kACPX_r*fV^2W|icm&sDvf5``*%rw$xxw}* zZvWJ5ZY{bIdZSxUu}=kRy+g`MFT_?~jRbh8cRNd1wa&@j^S z==hG~Uwl|_b9sVNY@r05Xt9*d(1>R`95>j z7wbBe!6CH~2<{7fXqJ`MJW14zm}pgtdG8+#A6kgVd~W%8Ha4kVn-E3mbx-sP}NbaAq3f?^*Ys}%F`6nIUrfwt98XTZBIDfFa0gmR1Jb;}S9 zM~{_xMFVFp$0hsvoL-#QHn!Pit9nG>!8tjd;4yN-p+p+qQ%S$6UI!Kj^^(}j6>HOU zJTsb1LB__noV4vR8yR{)J{}m|lETP=nC3mLk8mlPer%0w^l!4ZR98taJ#M@})Ew9z z-Xy3@MEng2nr!g0HSZNtMg!tpFnp{4N7f4;uY-MtssD_mCpUkPv zK^ZVd{9(R52P8jfKW;Y>@iat`T^BAd&?N(L0^^NE8JoY=#{u4TJT?4dzOQgEiG+5* z>1%c1lI{$cw9^j>mvPjQaXAS{8}Y{b56R5X1E9wN0$ZLp+0DhgClRcV?ckH1i4x}Z z#Pj-#YdC&O5Y`<9q$2$Y&0;)Glv>r2l?2iBpWX1a^8k}O@;mRS2O!I`9_UwX*mlhr z6pUr_eADGb1y6!N$?<(;LM+B$MT=L#zT zY~gm$K9P2Jk@PnK56lx&*0^PRjeuw4+|9F)_LFZR;dauYy(jZFzQCF6_jfC(x(+s# zj`sljN7-*H7i2??nfaYIq}Kq8ywt{*ZDI}&3G(PX;jB7$V#QEvLpa-3A|q~`5C{~Z z+%>lWVz$%keuD|6#twr*;>eTeb8ywmzwFI+`5||%O1<{9eH%rzdglvTZqzQ>j^R=U z`#Gt1WLhNZ8FGK)~fkniD_I2Sf@2Gi6@lctJh2h4fGMYuQ{X*{- z;V7yo#<$@Q_KQQ@#)2zA7cl=`ORq!+^)gR|R-9)~egiE8k%oAu?29bhunI^Twt9+( zRJe4XXxwZW8Juw_&CpL|M?a#KZ^K69KEIl2XkPV~L20LJ8FMD!Xrel|iVv4(LGs;Y zE&C-4D+D6W$07YG8spLH8`G=SEA)C8&@xQgx3aPaVWq$)J}f-wYY22hIL1s+kO6q^WAECx>q&t>F9mf-{$!o=mwhavrjD>)s*+S;GS#La>AY2 z)QzjzoEq!nK8rW${+Ba43jw?+9>=NE9u>K-n9vQi(JjEz?UdU`De@_OD!q*Z+;^c; zk9*9j4%g=?UV2gzCu`mCXY(vRQvvN;-he`-ssIjdx!#(sfd6#RlM7awad&02s`kYn z618N8|bQ-j|7W4H!5Z zTHCthG*r4vZprMWDkU4ifOG$ z29b&#Qz-*uY*H$5dV~e<{N>@ThBf@vMxB8{xc{8JRP35p3Sm&TD6`e(tqT7r$l~j; z-(3--@=Ne4hq{Cf&M1##EJE2wfn~)&+NE2+B}^a^9<4uLF21s2GU_653Jl`PtDYhf z3#SM7&|K`EdER4{plZlXw;?|}6V<#B8C=@IR;QQC8-F7{mpX4Q&1RYgW|nUdWEOS@ z&ta1d`3M_JDrt@nZz+U`-YQ zN!|+m%@A<@en?p>B+C7tgR7BL8D@~fnnOEHcPfkEa^p}0@MP!a2=E7!E}W7(6H`qC zM6Ae3^TYZ%>!^FCd!4JKP;#zcM4Z;%LTggiV(V3FAZXxnQ-kAeWh+Y{5v~3FO$N8~ z7$viocS)Z+>%-q{bcL08lex4|{OT22Nz%KEjYa2>j?Bg|ffuY14EATDvq8!aP>7gR zqdo|Sd-hK1P|8eei{ru3$QuXF{(k|fs`^!^- zzx<-!eXzBvHfZdZ^0muv0V$t)u%~!Gl=n|W*pDP4@UL{hryIC%=>Iz%B*&<^M__ zg+;x**z_mWME^AL(DZ>0IqhSn5bW0e0TUv>v+;_gmDKu;|LM2xqyCI|znXAoGJtw6 zgT@i>uV>XDIqw8iNA&*{Tj&{Medp0^ct5s*)MjEqDc)ua&>nQA)V2oiRqj#7bycUY zRRn+=w^s8&{=)hb+ZDpy#h#)qJX#GwD6dj0e*^kZwOZ=hgRG61bE6XdvGGkCFZ&mk z_;VJ`!|9A1KiKNqn*%4kR?a$=0B`JuI<}2KY1IpC$}$7j6-#!Xa=MEmEBO;1db*O9 zcCG|96(-CSXOr=$^>pYUvpm5i1wc)>>3Z;I8YFK~@7EY~C#P)`SNKknx0e^vdtqvm zk~U2(GK75yxIuYng5Inf=*`FS{$G*f@;S4ryOb9;>?@53<^eZjrTTjNLVx1B?_^bG z6t0!xMo4Sf`XLyuz;Tij5d=|`iBV^LX`wmy0M3zO;8 z-(R_M1D~IaU+`JMdZqicv_h;#VNbx9C7sSTam)-1!IyYOzj|WG`qekxObLR_1XA2U zsWa1y%_YxU!<3*?5{#_fJT;8!k+gOS56XQ??=Cl;OA++YE0ZxzF?QgQCA+M3Ci55+ zOAYy{6lyL*1m;%-3Yz{HHN%{;I~+rV;Z93g;f+jckA_Qh>wThGcT+?n%MQ&VL18(r=rBGd)KRur-m1|My{1& zC&Wz_0wku5Jmvp(tv_eP`khnOyCV4-D|?c}G!ZXaX97xo8)AemTdvt#5+D^dIIBm> zaV5NJzfLG9_L-8KUUej9ZZ*7eJ*nUAn=Wp{Xc@+7Kk`%rY=+CkBNnA}Gn^_;MnPkp zu)H{G2UNLTu_)f@NxGO3!q$1;rn?$?gJg*m!krCHE`GPii9R#D;JP3WsMa{AYs~;> zZe4W`pSabh0@~>W22|ANa}A$!`ZTcxC?|MNj>9wTUosZ4^-Xt-u|@2pHrduH9}l>TrD1y3KQ29&EcvZm{{VR;wzKk9-8 z%p!wtXo!qFHlw{?`iL&^wKnIdKcfi`&ZZ}>tP`d(R2MPRZha4jtMbdec>Cob6G+uX z1w&Fr@D1XzmX9zlouYZ7C9-1Ci7zv5jXlP-QuLI-bbZ0r9i{O z)*f&x&+GD)tu&Yj54#fj`(|#-8T+#Lc^V;+YFufh4I)Z81GczGR&=_d+27ihV1$b$ zx5|tTyP#~4Xr`G88_fE_yM?7i0HG$@S~zjX)IevUe?{{Cbj`@}gTJ^lrDWnPvQFzv zDOHM{@}&x@GWkh1tj;)(^Fy1Z*Zuy4F!qf!LzY{?sLwVH%RavOe_y3YUE#n1NIGho zAoZzP#Y>uwX!S=N7*w*sw^%P6{fZWg;5D9Xbu96D%-mHB_0N;}l%*6Bq0VSoHeUt& zhs?Tvf>)J*CchW`@=qA3yT60ho-VpS;+V0Ty5qe^cRf?=3$O$^%j+xp4fe5%Z>nwz z-z)e_TFjK&E!5{w_yA&3=X`_&cME4_7No0$F~DR{$+|bP}*D_)$+h+IvF@AkgX^ zJDwyQwYO8^Oa2XnA#9pGJJ7RbXf}K%I;O~Zzvw8o0wBVRA%|$5gzp93C_r`1lx#aP z^!l|n;i*i zzqA`FD%1j-%shTqPyL(6pSd)YUhHUzYU}o})bB3f$`zm?6t8P761ITfSE7Lg&ZJ?! zOD3FRX&jUC|oO~$7Z{>L#jK!zex z9n!hZo9tZl_rW(%Q8FFz&_Fth3RM@dFZZGzY_E!2hsOa9q6W5AOsz+jUy_RKF39u_ zp08>3F~%#>%s5~s1oHX*je;egpj~?W$)Qa?G8zj}$lsT8G;sp^&1H+$*c5&RQTBUl zlh@8=IPKK#=7ZtvJx8Ff=*4xJ%7s^iSQ-Ef}pv92iA>ZF6ZNm0aDf?uz zTSuI}|2MXi>k|5C!Rb584&4 z>pVfe_B@}BjZ4icOtw``OCrV9aC`&aWe4cpJocUx>4yF zvlL_4ICd-@TAcL~%SoyNK1AT$xgm4ztmY&M^Io_5s>R`u3&E%FPqPy;O8^7w4W*uP zNT_1%Fb**goxQt$u0=w)rMOh3ELe%u$ZYQV#-xu=KbD9asDOT)52%o0>N#Moi2DTP zbg_lFfPV)X?@|Ahd_U2;*9O(X3wJI`&>+gj(~*+UV{GTgkNt7Jg=S)NiRD_#Gg;wc-_7Wc zjlv#S4A+Biz5)psXHn^P;AEdcIlYROg7SrZ`naN|j~rX2{}phfSSyI^#(Q7UxbvoL zjI2V8TY0w}5Q6AeIWB?X=p+jVt+^XH9+#&r#`H?Tk*3q{`py|n3nNt}sQcjH)U?p{ z72rERI`PZsZPB8I^k#OB%U80p6is%^wRH}EE~*WUHO!1@)srYvDUup%cER48 z7`my)my(Q%i{+HQLH@BhpGm^dNKOrS1zJrS5^JQ@3p|5U7tZ2oA4o@Iol z6qa~g(+?B_tY&`MrY>X(8Gbc!Dn7)bTkUO1<@$zj{&*jN037*cTMoL@s(Y&|Z}0L2 zmal^u_ug#uK1r|#-x341;Q(dl=YIPqnRNNo?ig_@`ifVOvO0J^Yn?2QrRrEIGH?Q6{oyn8Q=#?8)B~2qea_Ina27nEKKfma}{!dQ< zxV7*7feM-Y#8$;blmE($77PKtR`UZ018Wr!+&aYab#DbO6`EAd=L?{hfP4PeE5jn; z4gmRj`1^r>jr@;ez>mTj+BXJp@Cex+9#`EPSRO-fd};W@rt=SETT`8YpMvs7RLN>2 zPe_F;owrPZ{B4Z-*Y<-8$;rl>F0S0K`>ULOS%#X7>l%ja=;u}@c@Fm=m-PNp6*g}E zLQ{&m7+_QB-fpuuWHUjmGI@f2tS=m!nIL-lVatm*4Ku#gMK&6|t?I7nT+)y&B;Wv0 z-nMYXtC_?zV+u91=rumtZ*2ej`;uR4B>JOvm)t)*mTFDp-kR90ZE5hL=18x$pD|GJ zT020Mz+Rn-AQTnPF)uTqeMP+yhp%j_2CJ5y3|=rHy}BQSOikMee)KKsR!tp%wL5rh zofYI0U;o||ZH#&mrj3R5ZqD-rHF}QCJwU+n-4fD36FT7tq_SlmXZLP_FL*ZB z_25W%`08DVk3X$F7WHwhlhVwlTW?U@iPA2hE4UE^?QMzgee-^~lb5>9D~J4&N*Hf~ z7ouqV;oDJnLq(Hj$`=l~{yOyxUUTPIA0@!6Rp)XEnCB2=(k7IWn_J2(laMIqgegBGXg8$G#&-#{D2bn)XI>_Y$MH`BQ^FtQVIgYNLBh%EuH5lOY<2K6c79i@vM65RhCkyU_S* zQD{K7`lF+-)u@vVCz6ARu1L;lKUp&~ zBn=fGSHLSFJ_nz^318K2`NUPf%el#`N_w_C8(6mWj@o0d5jJC{P1*?wBK^HeddO4@ z1&oE~dW?+D{hb zREf#tf9*e}C;G&f0Ie-1Xy%(uRn@iG?pbw#E~lxoh2S!Ym#(CMM6U zax2A#Mo%PPpCRR6TjLbvN9!Uh9t3&+MbLsnD}tR%k1MzvFV5X}jnWPz({>BOrU&_o z_%~%5*-|@RyTV5@?TCO#kzohBiw!k;;WcWsB*>kC8DLf3$U$!F;#SmfGx z{6xED8vpx}9|f@=!TjHdxr*4Osx3JUvIdG%sy9h%*OI%~`cZ6WgE9M93A~84NA$KX zVYTRsrfueXnWXGgj@ZH~v$2R%kHtbs)&q;hC?DJ0wWhYh&YQywv(MXhT_?xy5|828 zmb=WS#i5WB7y4#qAhL~~uP2iGhalmr3lz(lG!7VfbGF~+#aj7v;@TTkE0GOTzBX|e zA9{6|aERB1v|NZfQiAZ5I6EEfN^LF6OhPwmj^KLp_VfhefY!onJLCvY`;adkpd{s2 zq9iTxYAghwr###Ly8`R4csLbl3l^74#TvQ3J?7`u1sgyw2XO%=o|hCYzQ+2pvd@j~ ziU+@<8S-;sFo7xJ-Y3bIsC7lJRB$Drd<}+R=(r6`TdX|NP)OCcdGo{qZO6=bKw2kA zd^Tt@8x2!MZu(ctLhB`Eq*?LLH-BE(ABw?Xh6 zAk7#2u0-n9Y^Sd>yj$9lFo-O;n*F&_2wgfz;q|QboB)hY~l;gmdvGe_sb0%0Xj4|NUcTZQqki_87 z#LL)b56=m_7yUIa7qOCS6xvjkSo~GPlh)Y9TF*s5dsReMSX+sKn@p|P4P?R6N;NsT z@aF!{jW?)`69nVK19N?clwc9VnIDlsty7GQH%|n*PcVf-mE2Dc=f*zWwmMm(wRsH@ zoZ_s|yvD)E@KunA2VM7ZXXAqexh6AOTfI=(uH0On-=fwN+WRf8Oy88r8i&~xt#&GV z$z#SXs|;*qgT-8!J{Sm4ARzlJJZ8kEP>ub+rTi(To_u#?dBJ`fvJp@E)JYHwHm%g_ zi!eib_4_)R5jKF)vD8kxd+G( zHb-9vsZ{XUvkd=wgg9eAsTHLo^$huD-LAF+uVF7d?R?3+lD&XT{X)0%xy+Ty2?Z&w z?A~iA}L8|MBVkTM-{<7)1+C!XsYltn|^qx5fUq(X_wtA%UO~BD!#eMiq%i#Bv z=cQ3J_Co|iJ{_&eRr`>fM}>^L8}<2n77orNylB@AbWBd!JA}Y zTLbITx}s4!HmX3>%b2Of_h-4ZOb2e(K?f$%Q18mpWmUK!LAElx6w7#OjnRONxjUl_ zukUnuy>u)UMe`a1MBh@zEn0+E$;G!!5@Dz#1Z*X&c`>LQ-)vo}7$f%R0<&UVKAcM1 zDY{t;j_rsl7*ui9bd&+_SNbT3;jRYZpDJWzvu$CH#-%vSj=$dbi+so zMG|KmMs7gmK{@ zzHUC)D+chTl@m<`hvuZq=?C5(?`3}-J4c?ujHnu4*4;1#FO~tYFveaYZdzeOcckEd zyxw`_RuMJ}phz@s z7lrPnq#qQ#dXbQC2Xu-0jlGrZkh$vX+f#2H?~m8qMh!xWa@X3`zi|M%wD^biOGwY- z(^pu=-2Zjk%a^sI2PXocqJf+@LBH)v?Nch<+ayJKMcqd)0$>W`zU`^BH(+8D6+Bi{ zv&PY^dX^xFa3V`=v%(P|S_x%)%vHL>`bB{nLpC~{w)7$?k&#ER*#?g?wMIN5TJU?>obq z%G!3((V0;!qo61X*p;qQ4G>0@-h_yBBGQCVB@kLbQA7n28z3b_X`u*_me6rf2!ap^ zkU&BaBZLqF0-+@Y&Iaduzjwa!`&`$#&i6dA=<6GT|ef<;jbLpaW3aa@E!jcmn&(8QKKDDBsB9S#wSPijz4mI zp#~XaqU3xKEw2}HgHt*^7^=0J(Il)Z_J0u{ICUseHieEHnG^-y=s%1n*gq6`r*|%& zom%+Z#UD_(Gc0mK=K9De>zX3b*V!>LF;j}pRT1;IIs)54UoHr3!n3~aA+ZEz5b zAr{XkN6&>#DBpjc7&BFUX%Y)7BtNrCSIcVFCfL*!Ll3C;;Z&q96#YWuwi%|$i2U@~ zfd59Ud~n+}eXIGZL3qQqzho~PBm}gWx}Gl!?0;sc)E}LrACUx= zSo|-F=%Yu^c4OMk&*@jWoqAGa280>cW>;J~ymHI^!#O8HAIQuH312G-br0tVn@L*z zv@{e-=Go2bJ!E+8;xzy57)1IYNvtJfGI-8b=iv$cGaGH=ryD;vaO7=Ip+&1}s?%R?Fu4V>R;IZJCay6Dd$ zminRQE{1qD&d=%0#rGiS{t~j1HDJoabR6U6w0aj|GR#DSbAuLz-tIKIEITRZ&wsnq z&a`Z|DZ&VMh}Ee2B;%-@5v}a)6`HtLkx=P02PAmJQ6?Q&cnpBgcRR=o#FVeiNs~gt z?<96E-so|m4_~!a4=z~OmabEnK55*FsJ}w$EM1q;TWdcia6EvdX$Mdj*@MSRt5;g| z6f-^)-<dog&SYPe(yNxiJyh}7$*O&_cQqBbfv_-$u=i`P-h1m@mU35&n;o;3VI_6=p$dEbgzpwYii|y~^ zrZ07V?ujj-|4EpC`@-yG&%SUpljb}WzcU{4d4&x3aEWbU)P4v<@%8oM{piig&N2X4 zZD``^#G(TuuD}qP*lH(=9=JiPB)aj}_ZuMJ-f640M?wYb2fwQnxrmu!HEwxh0drIL z0u(u_T6}l+kEh{d-HiV^*XJi`sfI+!$)a~<+Y-c0pb3IO4;)9$nU$ycxaGCeA@sqH zX3<59L*;F@`VAjL+kmuvJ@b|oo3i#rP6N`a1#>#=jc&Q?^M2!@Kawjhn+U00@C&3+bdx?KI@2uOYJI6W(DfD z_ukQO2p%FlYdd?LGizNKJ#XM1>^3RNu!p0t`~aa4MtUqH#z`qLtec94sq*U^Zg)A|itD$F?d*!`mY-X#IahaB_e`t~3@O)^Yrf5yUvQ8~xCDiVn#XCG z6L;j0ZKeuCZ%h#j>p$McmGl(OiqQ;r&&((n>`=eCnIuNh`I8;FbkU`_b-|A<9dEe~W z!dZQ-in>bV~N5AOc7l&S-ZVu-9!K^!5 zINRT$7Ye>_jjH^T#jmND|BP`Co{%1M?S5T{c>HJJ#2#;)&gq9qb2H7{47vS?wWHkp zsjFAw!{aLh*G_J^G=rHhdo{1`MY|q$BBmfy_PGamPX;Ul!nL$(EA)6(OG<1XNmfRC z77)_YqiMg6KUHk+Aao|X?m_Q`gxEYIpZAjF;bYzhU>yj%Ct)!xQ(6nM9+?f0p( zZ^HcRk6)o@*wfcjZ{3TQv+Pbn=L8DEO4so%9yhYmT!_Vk@WrHS4#ACW%Btm2kh&%$ zT9D?K4#SJ=-TlwQ_K@QB;IfkQicUhy>^oLEA^tudm0uQt6mvDI!gqPu)n1rs&w|jj zrQG87)S(1vsnMrr$j&ML@{px7m_K@w z0S*I}0~zQd(qPos7LhGKl8$Lm!=$fS4?gtKQZ2vYP3GR^P6o>6KQQ4f?X2pn*L+ocxQ(&#y0;pJ~L%TlHa7sPgNoz=f^4aqUiz z1in^^5A_dtT0iKCZ0W>;ZMXE^A`NESC&2R3!oBF>O?W{%;vDM1)`ZIz0G;HRSPoN4 z?&Q5}(Its3-T{0?{UY&4Q(idx9;qFzrL0|0rdvg?MA}12A97C*bVrC%&g7NFzKFn; zi+D9Z7335zm{kRq&sycE&ZQS{ZF669YdhwWqOhU;w3U|o!}8jKA}~6%Th5XdpwOJ~ z>`NMf1`s_LMO#Gn4*TMaWo++J`_G!xxrhynV=Tsxm-}BFTr(YkFH}D$T+HVw^2bxb zDCWg>bgeCSF!Xz>WgnHUB%ZIy&j5VlsRSahQY00gf=WK0Y?D>&gn_*P#q%epyK)GzLZo;CBD(%o%LxUubp^by6;Zt%}df z9VRDyeL|TIod};DRBU@89tOI|jp#H}{A?JSV6jVjhnMl$*Jg2b7IK&o>uu)15{`xM zj3T%tp;eMj1MjY6Wnhm0mm_ALN-)1r#1c0tb~Y|id68XQ=G?dBV=24;%z%L*pnRrD zDi?CI>+th_&%Z&RHy&BB4WoIQ+kW0bfrQ*D_n)k4FOW_>-#0wh@w{~Hd+{zZKP6^f z^g#r`f%Tx(YMHXA&SKKn_r5vu+f^3P!4M!E;1{eY=fOw^yM+V7cdaT_(pPC`E|#3y zzM7jtN$3_y z=%c}KUq=<0_`(MFBjrNpmFGT@C3Te_*a)r)_pRB$Yxq%Rgk@+WJ`_&qCEPY(dtlX1 z%A6uZDrHU3jp%PL+SI`Sh;e^&`LrUj7aOz9Dw*ca<6PEZHJRWT)^Z+&WHCpu)CAKct6Xy-5 zTk$jzk)OuwzwvMOiYVq+-KLI2a3zhm2oJqsg6}GCC}}&`!`{04Yqv;){-OUxi-fUg zF5Ap!C_Mj~_+5<~S*EIcIBgj1IgjbzrSHb?`iG{m5y9j61E2JH0vo^e{n9`DaT}`d z`&}TsLz5N-9GQpgq)x|L8`fIy!5mEy1>``@lxlN#z%6gj$wN z_~d{V@GAe~+V6J<$)idHxCjO%{G-4ArW?lMZ$wNzJ0`rh7aYR4`w<8+j#5!CjKxtJI&ZDOK zyM*%=-A2FlX$&K$<~z{CjY*C@)#kVd$hifgKa;BJ&r}bWorYnB&6ppy;Q1azGg!Ey zJY2t4gRK!89?J`R0{vh!oeGWNl${?_*79*muA{%L7boVwxnsLNh>(%*%mWifd8dmsVmiWF#*vze&8%x9#Vn29OlQEii(l}Dn@DJfuxB7 zzfiCXLtRT|raJ%|!Ne@Z-DbyI>dZqBs;#E6#~M^FJKTKQgoKBXyt;i$%_NDhF3a5OZ2xUw@UGR+ zh##};zXIYfGqiPJQHdjsWXYy(^}Wp7GT5RhoC)G2YLOvAA78peC*7Z`0=s-THUx7` z8gW@4Bo9wMT<5RY`lF``=-=`HR?;^=IcYcN!#l%2a`)wKEWMvs*Vb6o%~kpH=X?7< zpBijv7=K@yqQ6r%7t-Iqy#U|!--~af0y@qx^oRiV&O=aSd7EGN}fL?=I|@%MpgLyv=NI2_J{bKA4ol z7vToiuVSsOD;d1hED!}Z;FqQzv$EE|b3Z!9aMZ%S2_r&>puhY#L>R3D8%Qoh?kRh^ zCwO6^#4&QaX#Gr*jCA~i#vesNd-U}xIEJyoX9EkAoMkZQoH_Ojw#@e*zP(!8%hG2) zW+|kmV>ieQmPo`1ZszSNYeySF39a;Tp;v9k(_YPYiW`)^i)$+7n_GLe`X+iOu6lJN zL|codwYF^86ntcTBMW_GZ_lj`_5`xQ%kp+mj`0%G?z`Q0hP8I+&(L@b%rUSnHr!`6 zLhcke{GMkD;qW&0OwJ-(J-Igw_6$0t^!&;;c6uv5hKZQ|Sa_6znmLJEEuVW12IsGT z2=8{%cY89fv_P+E3o3QdIxAV%djl<;``)YfqgQ3f!^r1of6D1t)r?Dkguglf=O8Y& zGSo^ddYT7-lv$jT3{aG=%OaYoTyp;l;0Ie8-($?E&+i06ImL*EYt=f&s&oKsb^C6i zu$>k?OYr9rTU8*2q19Op=b$HyWHtvArS3!_krrb#_g-rC9a^IXj%;IJ3YxRa&tlhH z;rE8VU$VR7PC}+@0VmP>MquN5d`yvgbl_P+QDTfq_vqjt&UoRl>g`$lg3 zutW~0%B^jv81p*loQ1^Yh)X!&Aqi;qIxPWvPZBm+6%4?=OP03mxp{OOFmpIt0QQOP zY=B#^{JULzwWM#kpwxN+b26Zj*}5q}JbE zeHscfWt64C0{q1cT%T0r`Z;aIrg2VYtq!&Yd45vCxrxe92{~5;3l&)iA7v3CRynP$OE#vu1DPD6^7r-`al7 z!;E2`z;xlU@Rab!XxdZqHnc1x77m7K%M{UuJQcTjX&qD>^5sN8dyck9<=GCrI93vR z4Q&;yRmcriyyP=QR~u^vTVb+)|t~W zV)yxNZ&;cOEyV+9f+M6=55Z%*E+`c4wGy75%+|I)0f5ZNEez*zOHeW8d0EeNY=?uN zZykJ8Znj_=iRpOR(z$DPJL!E{Rc4r>+J`Xuac5*E7Uo>~ zIAd|57J%e15R%x}BXQG?_b1J~lNvQ0O;HPyOdGH0{NHD+fsj-^y^=`~6M!s|SM1c8 zoHq`)sncI;N>H*u=|-bG96Q4;p0uFkkCaEne>`xQ1`f4Ls(YB(5$5!%<{Bz^s9qMU zk>w^;eub`8s*i%0G|>l>MuU+b*Z@#|zmJ0W0HIvD#<@3~YCUJVRd?n)BkEUBDW`-F z7thkl#jV9`-E>KPr2{+Rt;@CFjOThEgiYe`*^Xx8v4L{jOWL1GL=VmQcW!?wp?zr! z^;WZJnYi518~OzQEt*d6nJL{tyL=#Ikl?d?4s$8fayOaRYoIdttv`#A&u9~uy{>qA`>TDB zg^JuE#eKQ7+_S!pezrM_ z>YKzclhWAJvXDYXXNoS%9LGIM56L_+SQcB^#_BZsswTUOHQ%97HUQ1Wm${ldjlSc} z_Tsx!oxNDJ%)s#7Ke->DTb5P5CeFo>Sjwaur*fOS3AJr7jwZ z9elzGb_pr`;kJ$K{;49s7zE5+5f*?Mm$idZV^mDLs&wW6am{_}`6*M>o2 zE91`2Xq)Fx6|ogud)M+QoaQB}dV`iaBY|%HQ0AfwypF@u5O()r(u$xJ?pBRw8UTA5 zr%!7wsUyZ$Se-@u!}X)l?ZMb*=>0VNvlOa;_R-wwEZ-5IHg#pPf*&=N`dcz&2W3I@ zgzl001Wo<^#8jwHDEX;cEF3xfJ?QX)<{sBA{n_Cjyn2(2hF=D;IN!kj^yJYyx5?J2mAl3LVnm1rr^3a}xvkdT@OUy!X; zKR?Gl7p2tD{v5s`vMxaV*4dvMj0?t?Wn>20?$K%fEAh;r^YZN=iup^!7aXP1dCBg) z;NKmk5^e&4i6%OXcR&V+T4#4`q@r^}S9jmQ#Kx2g?^e_IvNiYGVF4t8=F#G2s{D2m zQ@gA0oWo$p?I_bJ3hjH11D-^zANO$lkfIBbAU%NrIk!Q!UJGk#K`-(`vXytw^>2Ga zc3dciNDWvCybiqPc&QW#l!mMZyHymnkI_pFypyJ7hG2r#aDW-S<3my#QD&M_&@Ipbo^V#CW!@~I&^_5bO=&mWzb#^Fz# z9R#0Ml_(tAlb;6{EwHMVI2)3BQ^QJH(Y>~MIDA~?t^5M6loreRv#w=Q?S<~%l!CXl z;`TF`SJxOF0Eugu?TwNd*;>#M=Z@`84Jo>v_z+(o#w5kQxj<85m92E&=g z9-p!Y6}(CDmBcIUp~iCqU!<1cyI@g+T*=aq#xwrq1dHpq$y5T=_wzTR_lvPNV7qnVyP?1;qs6ZA61YjS!i+;3Pfc=yb=b7ZrCPYtGmmwxzB z+Q&=V*q~@Qx#*cCKxoOfi85=u{;$O%y@L|0i##y2RgO&3@=S#j?@GowW_IkDo!I5A z>~6sJlw7N+iuQ?^@=ptXo-p05n|u_o!fAb}U3`A4`9g?dy=IZ&ezOZzuZw8;+q_3Z z%M9A2Fih>Z`NGlc3Yd5!jUYSdIe9{82h#2APCA<``&W5+r4-&TApRwnQk{j%Iv)Ry zO0p;B1iztuREy`usDvmq=doM))4SN>j`Kj$@=Vr&ocVl1s4u$9-9o_2^KmgXAQOSx ztK}in>u26RmOn}W!0fqd*d4)MMtJWgX=PmK5+_1v_}<$w39BH*%IA+&o#)5M0f|Ck zs|9Ya*R4Euwhh%#vNPsy0H+8F!Z`}8gRg_fj7G#RN3ff3iz`dK$QkMjPJEE9R4)ja zU3t;%lsPu7I3*=ke`%+6+6ujeM&!!_n zUTxb#>U>x0=%;8OU&GOm=x~Sv4K9qgJpVALG}#<#@wBVLrRrUK)MPgK`w52Ml^BTo zXB89%Kd+8r45;lKfdS#C8mWKe_JJu&i>`dU*rh2iqFUACQfFYw-13eL78Lp+3!fs5 z?xtM7i@PO+}iT;9YXew(~>gQ`u9Z?Vh3?g zaLkR>#EkjaUA9NA4T}3N49ssQN)0gy5Uja(@Xk&MIXjUBha`3l^{v2;4|pE|Sw(H^ z(*Q`}=&V`H&@gX!G)LJ9u7fZ^)VxRBUV?oiAFims5L|a3| zmP`pQL?u@s^+r4OqUO!cr-0ESf3}c<-nWpj3Ez0-ttX~NB zn!Zc46#30+c+}^@r6lM`fpV?@Gw8AUI6G+fq^>)M+=1fKZcTEKy>W2p#U)nY5G`@Z zPZ%?u*h-d~&(d9}bHh#3`1s?KYcLFnH(ZZCf}KJ3-I3{+`0^~cR=0_Sw(sjj-aN3t zWPa!^WBNf!C{hy8c40LfF1h|;w@AO!PPU4j5jyYMdJewBGiJO$Dk@Ry4dfA3&?-Ou_5Dw7~T5>Yx1Vh*uJo zt0BK95?y1WFuyQapRgpxs#P0^=BGJ~V)_@!h6wsB`uV{a4af{JCLra%*mCj!UvXkWn`+(nX%yBvW2p6$7sxwcAoS}4lGMUB4Vm?M zwI)c?C*!EyBe>qC$F(CFh~WiRAVvULPiYU+1+6)z@e#c-W%w1Y_32e z(2ZR`k$qX@{rIFPdlp$dK@v}#aqI)dIdfmTjP;j2b+Of5UviD%i&8H&(aH({Yg|8k zCmZ5mMC;0U*(pRfjW03Fs>Hk%iniSCBv>4G){5Y)=<8O{l zq#*{=2xYSdepNoX(x4QlD)O#VoPw)Q8opMe-*zv*q7)Nj5xmwR9X3P41}@`0Yi z@l9~yA6NgE!c=hzzznx0V6$8_%K|rBy)``Ekoy#@t-Z?omSAh4uyIO=h!l6Uj^yOz zD2r1>cd~^-VN+w&!h%@B2IP>)`^l`B(PXAzX=WXAyy5lJ#>k~k#3Wu$yA7ZDm7jaX_}vQ*emWPz^hRu;L;p`ju4fsM|obn?td)A~wWf-=_b z&&?Vs9jg<+?Ui2Ttgg&AT{lve&Ba5T)&!2k5uLI}n>7@cm_r#PqW=)zjejGK_goL; zH2BBHotsDrX#Jh2z`!3%!K^N!-tmT8$1x?}fBWq>?6}EOX6&XTBaos(Zi3f0HLVZi znWjcNI~VIk#OZ-booeP7_wU~)Prcl<*4im(<;)D#EuncuNOG-W9FJD3$1kMPgG;JR z#o~Aj=i|cBch{iPFT0jepS6SDT-g}F-SuSALoRiV85g8t>Y`TW0$NcajGgR&-Eq8* zlByXZHPitX(6y6YS5g&OQkC<#g>U_9pcj$XNeqyIBK=q$`qC1ca=PhQKR0=V6fhLU zTK@6l$GjF|dQ~vq zF~W`L1gFX6Dif(LekBdv4t2v0bpsBO#3};u?N2lIO+U7?Ko4{zuEMo?C!0rL2lO!W z$>#XTGooxfk=i)mAn=EA)2Q?%2bgjdwUj`O{^B68f^iLCTniZYK9%kYLcZ zAFa-D?rF-8H0S5FLt;vwisyVHj)_LKR_KTnm zG;Uy`LHSgmGeJp}V@cHzm0o49n~|k{lO`%`JAg_iULUzBn%y)uuwJPrQA5O%` zt9xDxCEWM2FxEA!Z4J$>l9AZJkP^AOkjMn_MJ4l}T0k>~7uojc4DizDgOmX(W=sG4 zxDl-%MRb4>y%dpj{@$|(f41rKK){nYM(Ykt&_!L4aH?KZ(lci^ty7bbv)Q6@)_Y9g zOtC+evjNo%Y*!YgeTWz;aj`B(#UR|riIj$waZZzDJfrGxBOR7^kxQK^X|8j_zM|m= zJGSrHbSO|?JZn=cI!T*0ouNZNpJ%>Qxe%x(W!9H=kBTS&gWex z+>65wo=$k7Ut|r^%1fHr9IKszjt{?=S0tM=0+`^OGMszxSSid?#7EA^|UH_*=4~_oM}sZ;WTl+B>JE#qmvZ3`F8sa*rq)h`MRA|D-@h(MS!|6 zKXDkdP%11y@yeM~6G;P7&W91kx>||TZMWPvIs(>``lt8Y$`$Vgh?$pFapuo{ZWvZb zb+gTTu+ir^kct6fEBrej$#zx&)d0iGG%xFK8|qG`pT-0z)#bwAhK(x-h zTEpG%ZO#&cO$3FPw;`SB&-`Bc0SwR)$If607|I=BC{ZtLOlZrfHNE%&P!_dOIbVg9 z+s;<-J@|8PaLhYfGW5mfDwG?l-*leiSaDc0BNpZbZ=Z0Q36O7}FiEBe&Dbv~YRxA_ zlecpc*mAgvcd`Zl_GH;NFhe->Dy?*aR~oYAgJe&cjRzdt__FK9Y$ea_0!FIG#sZq9 z=<5*uQ})SOmCA2=a&GLyc#BubgBoDY?*ofFCQM4dy;A(9)|~T%tzh?r?JJAF1DdzU zHu3q{>bkRc@)x$%hOCf21o>J6cy6M_Uik{%N$v7KZOpv7%foCea06(C^BeWw6N*ZK zr&M$!6ta8jSYTCZq;Q+g!6X z{<(lkisa1$98Sae*Y$bp&l2Y*I@r%!HbTQi`fnhi4ySok=(`_QXA2`q#|VH{bV7)x0W-yq9;+J^MZP zoO5&MqOH}IO?x*yZ-OL z|I32^*R&woRjkpAmj-NR?UJ2+b=dE0hs7jnaJdl2@sRoP_{L2qgXx9K@)bwGT4sM@ zJ};wYR#{=f13$i%y11?DED{=Y>cp2wg7>Kgg(B@(u?L{ntGWz9SQ|&f!NEcB^HuY8 z4^mwWf}G2*+snUv_*~rQcGBkP=*5K0z^bG2McN7EzY+;PnWoWVa+NCt#UfXsB837D zDq*rWUm{3Wg1-0($8+jI>hDSqU7JFY%#RN>9Utc5R2^Kct8Ta~zmz5Ma<-n)v^>?o zzl~8x?6N>tR+?NJ|M)PkDRguNLdPM6sAdFzI9ws0t}FZk9DRm0yMF&K3a)B34hbTJyzw}vgG|PqFvoZGnDc1NjxI^c8-$Uf=5Ma6qAmF+*$O&eC4JR49Ac`a4S7?18|m zfryAY;+Q#gX&Z8B_DdB&mZ(t21Gfrkf4KQ4m}c(Cs|d+Fz!ymf5-IW;uh7YHkn?0| zS6%x3uYIo6l&OBFLoR#Bnl75tp8;&9IkYC!wV9Du)W6m|;@b78ys5#08PhhVN}%^T zKWqv*tiaNS`&>}keZC@~A$h>%7neOQN8|u;ZLI=G1*jpRSj5r`yt`|D7r1V0{bau~ z(UXU-ud93Z1?<4+bsuzfDYkFFh?wn2Vb&a7!{YqFN2ArL;!GTGjT5Kb?)?}mbz?4c zqQXBi1DU%;U~o&Fw!&AEm!j zfK(QN#TA81cM4B7*45R`MT=kVH}O7fIs`JlqFkl(S@CRecc4m{rKY9k_z;uy+@l!~ z9}48Ceq%h^*QegamGNS0mFl`0d1__@qyzb#vp1&7pBJp$(sZm&i1hOtUjjH+mU2G2 zs3HAGRhvT*?{nvUOn9-xdSmg4KT;8AgR?mI4MRtkSlK%6E8%W&QjhvNCkBEfZ}`(1 z0yXKQGQzEuJ8i$4&E9+n3fB_1FcJ293=;eRQ$YOa_ptBx=T|4cyh+#Kq)uO)7Qd$9 zX9B%=0}mfQOa<6(%V>*RkX#qAhp8WBYTQ1hMcD0lx-JKIt0J|c_(S5xHC`OfOW`yl zg#yWiS1ejlgR!h;=2e}F1P7xkP>at|Y{?+lYC*=l(BL+z+U^@NIP`8)v7>{7PM|F8OvghxPouz`t%$QS3*k!}-^F~F7bgBoveHX{ zs1wc0-CAbOlNzuOUf{E}c2gc=)y=t@-k@o~EYeYAwK3Co_gfvSp^FZeTU=*K(ec@taNj6c8=q5OdRt6 zeAri=3MGCFKSAvvDni@{_&I^DVGuje^iulOXGskJTbZi>#D}c)#Mkr2b+yj&1Swvy zX!b0ev3?Ea0~W`8r4wMzU`ckv+1iWHiV+9A=s82mZQ2X0@0MC{qu@5E+H&+wCf+of zhouhy1lw)5mgx$PN(bq!pWyUU?@@8TtbwwePs1;z4KkKgP(I#4pvhwjVQq6~qgHrK z7vx#aCo?j)otDHSozsP-LyH(oq%cv37x)27$FKH_G4J%(YpTUy0Tu!_Q~|?O2fWS@I1?i+{mORIuyiyy)&(p_(iERg_v6%9RzyLS%YnpZaWvf zwSqAZ=A>|LLqcDe#J~vm1Y=gLUzm6N-bKA=ZEi_tc)z7|VA$!As+%s#eA-5Er|Pc2~w8Jc$D4r!7V3 zZ7C@fE>BPOi3~@v!fe(8wG^GmuNYkDgLqUFrktUEXopbFxMD8f6$X85X5OcsQx}bN z*g1H_OHhNucBadYK?@f3jFl?XMeJ(9;^6c#2!3{1mpt9_N0^2B764Drsg{Wg?zBD^ z`@^PbM_uS!OA6)RMF%STZFyel_3%VnUHSoz46{_ls)Z@HWw*6(uDDUU;n#x4ur;5+F{60cjVqb*>gJwQJ0zUoE~UaOWZ%tMXUpAYwvA;YxX7ON`LIaOFxAhuY}U zdM>8m94#(9u5t6rG){CyS6v7H<|s@)|3nphrKDv8ae9yHtqC2S0=8NWjvrC@Qv3n3 ztkJmQJTQjjrDsy6Ct=&O%dfzT>dP8!{)38d7r-z^s`B~1-|s$Zl3{|wCeexeU?6K& zjD#mdt$4jVwVsIh)dmtc1$?O|UlKzFQ>9^BzHFZyHut*DURPx-TUlFmcr`yjy^26Y zHnb@~R%c(#04qWIx|PsKXG}A+Cf<4K$sp$n#T@Y*Tb!OUdwXG@l^0L4f=0C>1h15W zN&crY8w0;^f7P-7P}gr%k+&;JAK>wgBG%u!oD!sm)&tzNFK@fwuBk(!m=j*Y2PY0I zsH~W;WUhr0!CiZ;gWvGiC#Y9xywC`O1isk)ba&D2`PFV|W{kMh<^9N+Y;zYcUNgM| zbKr1fBlH6<)J^HJs=$RvS#s9hor`orvAv$(A;X8flX9P6+(nod;hXY za7ZU})UnG4zUzUImYB1}H04oO+3?t1*R6TvhCl9iQy$$>k2gq=!sZ=y*;uj;p!W18 zFtYb}29*$}^>?-NC5ixvn0OrUl8fZV7vsaJ4Q;B{_|MWXQH|H;5=X!r3V_gs9iGI) z?~q}f(RJ~ZFX5l&{mEIlVJ2Db3v;E1XHptQ#wJuK@?`-omXW8^)T~|mrf#taXTMC(-On7! zE8Di%sIYoxK=P6T2U!c5j%ZWU7C1>?H~DrhdM5Zyjj6xuZI+B@GAUhd4=OKKwRH~#gfyHSd$4R674)3%U`60}t85db&jH1}PD&VJy@Nx}Mr=kE$1>O$v458*wkU6gm0f%!s~{4G zvpVU2>$~rfI<)f#GM4-Fj5Cgy+N>waZXh0#_y27Jnl!b4G};3VkE4|}X_0mbzd27U=b>_&?SU*oW+DHq0lbSA z>B09EM9qok%!aQM42BcN5N~Gu)14MruepDM|?g4VObXiHslICzuB9@a3U=}1;a*QY34 zP66igE4YiwPj#T2@SFkGp3dX}e`=9_Qsla%hQlLQU%%V}<>4)$c{4HV`I|A(EvDzx ztC*W2wKr#&mS~q?nBmY%Zcx$TqPKoFqHIBm9Cc$Hu~qKcD_T2DOcE(2R0%E!CP=W5Ia(3kTD=^~ZJWzGXohNUEUggLaq zX~Pd^O!aCe|(nemtUjq=gpLC5G*s&e}= z?-Ib;?*>vU279VpkoU22nV-l36RIkiSLF&tX*-R0BaRh~g{!V>gV5r97x(*oN4#T2 zFtN1ikz+LZp=R)p@l9eDLbcNcC1IekWj_Wx zC9JWgr{&LXcWUppq{ux#MJ@GcCm?{xdY%0d*%-ak>h2qUisr`nX@LKul^5QRlyWw2 zx6V%bDH%_rH8nG2?KmC-i(m2Pk946O&~1|E6%pBCpUOC$ziuEpLL`!EGf9j@I6aEu zOYHLuD$HFnO9?#sQl7-U%6(^}s2(qn8$5veg0Hf1UH^}2blovW0PApRaMR0d`r?~Q zLz;Fb3l>#9?cdQZz&T<0%sxlP5;-QiDdCRudZOatOwz8Um1lAAJhNhRm#&>%VPYi- z-lg^K%|K0t*iKSofZYDmTS#eXNHdex6BQsUOV}*Kl{61g=iwI_i+d`*llZqRaT;!N z4z?))$<)jen3hO}A(C*`{F#4{%h@f>T(_RMsTi`bjE~g4As%uqRO4reeL&)&!*?B2 zlOml6s?y9MOP4p*56>a2`+VMS?RykMGCw;=IW+5%w4|JCl5H!vI#H;sgQSZ zx2Jan(vE`c7;%dtVL{8$7IYXxS-r63T9{j4Pah+UX2O_|foaz__B_I;0W{wOZ9QPC zH>}*dgNB_moWa;jt+KNv9aAXTp-ngWc#AKwpi8bc^+GZN3ckOA819^B5ee#NVN4yh z?MT`XUy6#=0k`}D)KLP9}jLR^bg96ntqGT zGBQg^SPBq#V+3&cU_Dr8u|)B=ECD>>4kvK|zclcIzSu(Eros?HR#Dth%x6lVa(bmc zzrR2~tM`0D$ed-)Vc(21Ua!K?H0Nb2$vA!_Tp{G&6b;}C_2!6EVXYcbaU-op?Ir0V zh&BPtjG=c0u~Ik8nWB;ERm)7&sMV>4#>M^7WO8N_7EV{yI7@ZxGaBqDp108NaBxCu67kvTyG{>Z2kkQmBhv!h5!|G90 zT%rc{Uv3MqK%Th_Og_(MbFB82jCVdmC;N+~gdB4h2~C+)rk$1Hb5?T}GhWkD209V) zvhY*g2BM;3rhG}26E~2w>xyB|j|o7>1N!s?7z495`iIzmx!X1IevpCP@j^RY8`(8m zR&+gb`t9Kw%(9hDQe=YGovxuznDWHSmRn1Z+eWScTZ!I)9S+ed)-<1{-T$!X`Zj9? z^(>5+K@dcL>gJ^KtnChK$Re>~@usgVN%17plpq^;u~OyX&BCDwb70(9kdW(nT?r7zoO z!@s{5p$@l%s2y(&()JZuu>F&f4zzO9qx5TA!mrabec zPK@p{F&wlD+Aa;_FY!%ph-!A@pzziWM6P5)f!lMT6fVPtWAi}K`lQ+TmXIAF9PK3iH8nAWJm+k(2IJSFJIr4BwM;|41!luK52Rv89!^Cs1Z2tN; zPpoh-%V2MvrRlvYtNOP-$%6)?2x}{61z2+Tv?ye8AOQ{)M5EDCnqP5g z-7E5Bq?yP6&BL!jcej|(fd||+Z`u5B6Cl>fFinM&vqd~#g9=fw)%}%`Tfu~&C>u35 z2ozY5%i-C6{SmJDtIJp)2?CDv7wn_%O(yO%65^)cbV2OVVb?y-*?pSMHHqch0jqyx zF85(!xxjQD=YpJga{&Z=ToJn<{Uym1D{tg9Tli9xo!A0pJnb^33;TPh!E5C=AT;U# z2*2^Kh-dTm|38O6|7fz*|K2F;Kbz*(T*fWJORUPIMqSuKZIGl^vvK9qL~@X1j=vGy(UyFXb@0+Z%TG+ga^0h$8sGU&py&!B*>-D&l{!bu zYYI@3CmTzYF8wdPV+vBz@j%m8z6HFrwEDQIabpQr#X=n$&Us#8B_6`tR~4Yb;4T{b zd8Ac+j~+Z3^Uk>7H^6Q{abr6N<++Gb2mHo=oBX2VH2;ui0csgJXy4@Va!?oio$?$% zb>w_0Dq`2~akc42g6_hXjiw39nS<2P##JRWe(@Fqow$?=smAd&@eX+g+Q#edIYI2! z6BoDa&q`eBl2o=V^nk8rL*`T(VfladbKgp)A<8|DL2z*8nDeh$z1S78LOPvxTFD}O zrOie!s~SX6^r5luXONyJ&TB4`lQYI^dtyh)t_e2lq-Jdkj3F!6Myivm8Ey8#dKoi5 zi-x&jAsR0;;W6_$Br<`2sOCjA6n_;t=E^9C#*2y@VO<&K2=k-{odVj{qhLR|+A}AW z8y^Ey@$XB9u@}pY+*&Ru)z!imk~#ApZG!Ndo<$cc3!h(>h6!(!#;HKt%v;o|(+&H~ zT~6gZas(v|(z?}>BCAWxTX5;oU<2)&>|0~jNfB*@PvS*kIV6$VAL ztr-5^Qq{DSD=p<|w%CCl0V^B>a+I9|Q3+4%NKx2nAKICzU7ra236m%I1o)?gtGkS6 zAPvsJq$c}Go%Bams4@Myu6@rRz9nBOgJC(HO1M@2C3P(B)zv(5vPUQ%L+7lPC9M(2 zTTB!gx+wfRi5%TXmeLGu83?zopGb}CLd{VLjxX4M9wztMA4zI}FRZwRMNpo}62}As z-6pB*e&MbEps@aO3i}0DZ#2UuOA# zr}FXJNeitWB2T#U*y?#N*5RELTIi9}-hK2c7!aD|;6)b%^U-A4!*&Q9yh zGwRrU_dh#V?z*ch7-h}KB=Jlh?6$VExiIBurJkODAdB9c(X=Tah_OHig;_>)G}F|U z;aMZBfUmxe6DHE&*bd4rshWNCS`0Klku^8{!2?;_vh0e!E1nkM##9n=e3X zwD!ENyL2+p>#2nWt2Ss|qqtMp{g-UT{q;ml_olNyQMR@W>~2YIFgx1s!|}(t*n-T1 zjrhhc?uDS6r`HoNY_kp+Gh4o8z1qGIjOX>h;rw4J3O)9zyky?+wY)KubOAMXeM#W# z?urd`W{2NJl$=1Wlp`8H;LNc1&8dt2WXHG#=v~^N?ns`tr8=B;3oaLa`%$dx*I>ox zgg6!{0UI3+5y27ZA}1JG{;75r#;zL~-}i`-Qg_Fa)=nF4tM?l{2ZYa5mSm!~gIfn} z^`@1L8e(=!qUNfS9IHelaU_JPOjuof-{8XaOj-iO!WW)G)e zx6!P|<~v14g@2(!#z2O74l1Ezya?vW`QhMb_TxfTn|d$PUDrBsGg)7gvf;$GNFXre36X{~#r-&mXjMFV-QO;vE~?egcwR)00M8|{{51>u+1w@Uujr$Htu9>(cPWv{EJV}a`>5Pe znu~rNG)4=Evf&$_Z0Nx=KNq33U@dPKPr@|-xAZ6HCUNRn;}PDy71_Z0GWl=93DjH= z?Uw3|u@9-E0*#Zy!P%;>;q)LN`BqTrOKSr`dYa(Wltf`nVhl~-(-o$1bnbL_XaMa= zk3DF<9R?((>d_A>bmT2ohu)q2a&MH-G$=rDA|#V8!VtQFHcf!Mov$72yC!5p+_`=A zgHy}SVeRj4LRo$zhc;k_Bksc6am2!OUARvl-Bm#9D`gqr{O8|;gu#u`_pmWT$9*lg z4nfEaK7BuoJHTE#IU6YlnP>6_wX zO&S-ALQLK_w-x9-s&r4lj{tg^`uu3(>Ng>$31#`~Jg8<#O`MyJRXXBJHoFgR8ou7X z96cD>HhBF7jw$L;1Cuj-l6Zo}q6DN2`wI{k2*St{Gpm-ly=z1^S&N<9!)FIQCAJV9PW!vbx%8tv@jbb{uQhf_5^Ln0W7!qlWcu5)W)>Dq^l#CBs-5>z{d^-hoaG;G!}e+ zWz|BNU1b$g(6W5Z3jG*k2ztNt)KSA0R|PLLi=1B&EZkXWob>LR&hDcQCEn|y`F6pg zcUZgXTGl>2Bp;m69AtW5B-r!LM*c>Ef@`pfrE(s+jOIwI=OjKdErGlFjotd9Y+YXE z;cr?P3u>gn6<~RVDCqF<_8ScHC|04?LIEc9+>i4omZmo7Yu}Rn%LX!J(5oc_qosDL z5)RIg#G>C)Fk&Z9xQ)p~lo7pMjR@x|2N$reB&w?HdWA;F{T>_6{)JWmNMbk76vV+?f4#|JO>8sVK z$3m!P(+tfJ=ig@H!^H&?UJIk**ab+LjFi8X<@I(cJH4GdX8@D*GT!GSg>@>YJb}Fu8gIp zmnE+K-zL2>CvPzlj8;@FjpeJ`5r&$rFqoA{*fFv@?I9`vj6hU{gAf6LEFg$AE;yiDa{|cAX0L zoL^tShs#d*rkeTIAwN)r3a@7m8Y!TT=GN)#eG!-`(B)_*& zs(9?TxrC43N>+UG$JqM)1tq?G_`)4p)+i(eykk)Ye2kQEHJ~ts_RD3rjB_0QECF_Y zcehjX7n(lYaO`cq@}~x%gz;*cU%#bf_qQdrkAmIevaH`zyy4S*er@4>dMd{ca2}hy zGdEWG_4Ty(`=O3XX;>{XW+c2@U{8Z!o9)s-R7+tUy}nLfcerQn5Ni%Nqy8W*p^jr1YB?;j!GN4x2RraKU1qiv^A`BN zZ_TE|(K{#a$r_klgnga7H#p9(UrPjKK2-jg!APB6uNWkqU&qoo)c7pRan2pqwldr?9a7^6mHni&gT*>ygOp&1@j{Fbv-DknXrvWoI{vO5$tRKr=dtc%z}icCBn z>Z1R_=sXaXjMrmUAi_LTB@#w!*yqGx%qu$kKHn`J`_RuJsR2~?Mxorxt6prtf2s;B zAD&;$Gkpg912al zJ~wT5h^_etm0U^j#$X@A3e{gMdOxunNl6I%IAj1mR!1QE1hzJXcZV3&5%vM&;csqZ ze!*k?usQJ7JCN$fvIdFPY096JA(>^ICMYU~)-Zdrm%svoy*VGao6F9^Ja$QHB*vCd zO9+cE5EK+aXA)mLfODd^S<{R^&_V*xsj~f;ss`E6vF_tp0&0ejShFnJ?XO&fISWTk zg)M*@A=Eo4It2ylIVElDV;P_E(yus<#uK$!=C`3a4e~N;Aul#=)&B z>B7K^sG0i}ct_pNweSu6oPlUlG>^~5hqp!-R#ST+C9KB&ga7EfHjVEO+BJen9{6fG z2`GH{>eyZBZ?TX)O4jk4p6*Uq@zU|m^LuvD5pB6SrnHn0 z+pKlZsl!(l}UDO!9|U=O}S776r<86`q%y|4E+txBm-Z-_XqDa zv{`5{r#`ZWvzJfgKY%3$6(SE51*_-F9#G6_KiZDTQ_k=&sw%YFT%$KUHqNKTb36SO zThW|nUvgmr0bjTCuHBh&8dmsY~Dl~yQQ+#vlLw=V+FD_A3-`d#(cdYO4ph*Jsa1EnDuGGW^tBsTPwb z9U9Esayl3CqOC)G?qArK`wn4k$EuCKX^uDrxD!uAF%O%=$Ge9Fe5t`cPTMLSjc1#G zCNMv>wbP$O-I!nZL~_EVaiMGMmKS48O9wqA1w^&g7({>3CU^a@=+ThoNYl}3xS9{evrB%<2rm;Z3IEDh z_S2BI;hUAz#dW~n_q|gvEtZHlORITaYUZ^-Nh}q0 zn$6BZdZ#E>q=lXG&%EPn4Z`;gFD0rEr(QPja4mDWk+AX#zR-ia6OMY!wf za!nzS6tpNE-jZ`weUyK4f`AYZ_O=#=RUj`XsDLx>KvICvYzo`kRBwRmrTTN71<(Ux z>4AxHF2jMFJvyPpcn-v7sk)+pcv_grjI&52h{4EKhqv;^atQsym@Z%iLS1ft8H&+%>ES`Jw>wprv>iAAwOIg7OLKjfsk>Gvk>94F1?@X>g2l+bj^ZZ{!YJqY(g`9(rrZrEidx1{iz+#q^K4a$w|j{2~N#cN;3~BHdB3W zTiDz1`ZVEHppJ!;2XFkZ-WB3H}$hcleIED-x5>JopBq#2~+| z9)cEKVGo381PU6D4l;3q)dbhRiO2B!zyc@_Gm($wU=-j9pS`pK(~}x7dZ6?1$0{3E zniKddLYn`G#$BW*>9;IJkl7wnLl17YtMHL5<`NsGEza9oppcO(@X@UX&!N*4#%GK~ zZg8^!5rr!@7JMnpHpLGa#B-V64oSS`#lP4m{gpH?C7^GUutO}3_`;*%jL_gxoNQzS~kuDxX?$3huo4d%#V_`In z8*}$x8yFOnjcTywLa?tB(0-DWb8j85OvYy}3s>T3KD4?Op_9AYS=Zi^c6_Q<4sEo&C6VS~Z)d>v< zR7h&HttO!7PgYep%M!t+8OuIbNXS()&Y~p5gLA#@PG;VnMVN7! ze;18s;*EVC=g)-$`*J=7Z#QJ-wI(ZCQ%|$|%2*C(C&rzY_!&-CUE@vtHB>$U6V9=oo&qR02kE{?7BZzya?(qGZMWW$Gprrk--HP9*2 z6b$>vS{sNQ(3FPE^#h_KL9+#OHTY}&(SHDvy=J@NzNi%`kBlh+`$PLf0YslWh zS?7uQcL3VqZDjGyK)rvBc52>RV2R>po`R^ZC$5!Lw@0~#&7A;_0@RNa`p6PEe(b*j z5v6X5x*wLep4hu1CGo!*A$5MOv&B?-J#h-C`~s=4|Ky!`0Z2_&bEh-d*|aA5CA1Ln z!jiUgkj~d@Y&qmkJ)h8<14=$M$A>jwuA0`gTt)Y3eZjn1oc#s|If0QeuY8c+=mOgG z2Nz_JKg)@u5!M>x-iL-3P7|XW=zp)Y2V!lSGsZQ5N(UxP3uhYd7>xGIE2SA&33(+# z$%hY%64^M$gS8~JlvL^eH!V2-S#fBspaxw32Q?J^4^T4^A}Q*bUe0DXsxpin^9+F2 zv46^#QWNY(3dCitgt6xiDdkV|79g`&h?%liGK!kH3}1Ox>zB?4N{l|-Vfb3Dqm_|W z-gK*=zCgHExFH)h1;J}_c+`BLpg_Y^v<&qwMQDxc+7iCb3z-d!;#C9vYKna}CdU+S zXSaZLrFa@J!U?h&`|Bq?CSm9?9bqK!r8b`NK|hnpmb~*v!}H1(Hh-@M$q~7U{9B6# z^9uMme*lvsUa9ME6xA%U4c1cnjlwMtb&S6*WS8x{q_x%kXgj6H_f%4Exp%7_QgESa zoxy~i(xO=UVi{kpG%i@>HZ55#%n*Jo9+4@2n~leD8gtHCsU^FF;|_s!u5Es-jm-{5 zWwK)SvYNZhMoYE*(aa@0&Bs`X+Vhq55N6!x`F<-{86be{S+L z#^Jzz!d9%@%4aZ6AsUu8Tu-rF_doX-yi3WwIO>~wp`z-zYxG}@5+PtU{zrq=! zV*PmLFDp--(S(K!fLDGs5ogC1;r1(uHtOzy>To3|#DPIaGaiaWvHRozZ+PdVB5bh^ zQK1 zvQ8F;6Uv-x$3hasK}(=b5>bg5BqqJ{tl)s^%;nICr1&GaX2?IYD*vRj-%bXHS4}*&(nGj)pF{~DAl)%*B zj@n5GOaq&Rg{Cnd+*tn|Dl+?$z!)(chZ4|zIIGLWm~wXRhrG$gi#w&67BaASpL#=Q z5!!Cj8w~G`_S6pwkeeK$+kSf1Y(FHb7bF_x;tIaP)WVIVq|(lCDK;8jWSPTin(Y}o z#Y3MNlCfh&aJ5V1Xhrt3{bmKKs6oq4N&o?RU$tTYB3~lP3J@UtmC7+&mJ}pWbD!gl z&e$l`O{GF_l`U%ZL22wLCGcdg#jPcm7+9Kkxb~p@-@nFV>nL^UvX^nytYhm*KIk|S z|Bw*%n9aY<4@U@GFwUPu9!YcgVdB8kZ|X#0Tc3>3YcH*v--!}~$W&8)DXOKaYS48f z9H75*+}QzvrS@vpu^v`p8v)A{;H$S+E+R*ub#Vclm9syz!#w&TNit{t5{4{()v2q*(h~;hsGj#I=`r2cAy)HIWU|@#95`##;Qo_c(oz!_xQyX*4;%8Y(C$3s zYI_r8RMXh8_f;)Rx@ut#s}VI&JwZZF%Sgg^?uotPCyKY293f)mP? zss^hG+y2PJ9pI3 z_Z3$#&=OC$7+6K8l*$rusSR)i=XkK9v&Qat)GTQ=?%S_!@+bCZ=^V|%JRSB}qHSR6 zE>)#EY~BcjjUEghGvEE%@R`R$wmTUmoGpgnA^*%@q@1=QB)zsT|4^JAK8I@SuOCZ- zRNq!jT|MWN?L(?u8p~Gb9n*&ycY5O@XTHT31h)QVI=Fh~21>Ot-A`#FMtjLvYb(|O z&UNm*c>uMQv#kCBBXdcpd^~DR8G?_02=;t)DWUK-Pwa`V%7}74>PbrygJL$niFDSe zETh~L;Y}XhYe4$I1BG~nv?{~;!yy?(wJFqDuZBTZqC=Qvq|-IHC1MIA9jvnBG-=lj z!kRu*9Wp|117~(q2@&UMI(4wRjT8%;?>+sTojdUNs45i<<@)qt->^i7=YoRd(L^&F zxxuoPGQxT(EjcVIK;!Bm@YWOgJldRk!4z$-2V(n!SVy+aLseR(Eu1&PZ~Gbn2#!{J z(FKLv_@yuJN*KBrGsb@MQD^;WT5_mi^4_UJXf|fC^4xNSt0Y%5_Vy&!o%#%g0og5_ zs9BT)8(6dV!}u$xzkN2Ay@PUNw-V1}BU>)GcBe+M>nXq4a)X=!iCeqoXqox;Qdh!@ z>o3xPMyjU{`Jrj)s#UNS)$9OgW@9d|%^TR6z%`=Pf>+uUgDlkDJ=NhV1rkJ{;NcC- z{c3hk-*oQM7A*5A^$LTRBXwhZTjsHQ`QL=}{^+l&mMvvtr=sT-GtXQCX?TfQj}Qed z{SESu*{=d~CBNriQhmeou7UT2p~K&AZLFmq9+tfs*usCU*P^OxJpjQf0+PKy@9~ZT z$4)p)4K`}8P@>`bTM*o)MHx_Zlxd3R;;LbCOvDJM&Th^AVc;6%F@b)5${D((bv@D9 zZt7&FvNa2hu&&7L#aT+m$VEs_RG%UQxt=VN_l)^&vfL` z#;3Fex&E?SLH5|8>jZ?<>#rnXO!VdPjxWVL)V}I>)1tYLz8Ycf88-Fa1dQ@=7hNjb zXs>qRBT`0^VzjrI9o5Jg#NbbUdaRFhCr*96Oj?!OK6gu*r-{pEKghQWz-nc9tds}`M82K+BAN_2p%jvCS6n-zNxya`F$ z-_JV1j5`x;;}ZfN@E8R;HVb}u;3tY1@HSh` z+GlnWV~kQ8quK5_r1jSE@Uh|eM|V{$$#}8ZNZq0==uqUEoh2tm=kH)PUIBUu%eYC* z%rMf)6i*W@gvKuu+s;#g#iy_e^;rL|8dc2rEoS0K&J;gQPMl(BTuaL7Tdf&!&Qj|^SB9L}$%h~VPP~mj%Dx_wyW8D&==jjwCtb9{kwFL2>h%?$FtSjw zzLZ}L?|>>W(u{Cw(FMODM!&vF3#&-`1L8qDRAmZ!z1W3I_hg&C3k{g;^+r3pI{6A) zd-}pG!*T9ZZ^5d87djok6}FO94#OMfW~(Y3g!*)h-5ksWr+5aijfc4Lm)bIGfb~|k zeaC{_hu%va3=P}+ezY}x^!BPtCK{}!Oj*f~iFTXQ+Bmh9GfVXlSJwhRKeSh+VIr*# z2>~k%NOJwT7ci#2B;W1T$+_FU^CmBdq>>GP5l!ptn_um?+J=YZSmE+H`g1mI<iQXQNb`lg}2l2O}i82eXLLU*Dx*D6asA0ZmYS#?I|vpqWv@G)cWe z@gczO0y;U89oz#zRH^8ZdKf&(TMR~wK7vcQd#&+6%y4Nogml+pLPmoNfuK-%sWU9r z>Xu)o;!vLE;YM9aswAecT8CvKu8S+003q$2qYhuabc z*-p<5!i`Fg@LscFt#yT}IP^S0r` zGJ_dFV5$)+cZnnp;;yqHD39vmD4Fqvh2NBK=I?2eT=*_l*O3j+%8=2(dXe&_W-gzP zAJo*sti*x_W>pMEGd1A5Y-y1Y1&>Kk*f6s(*f_g}31{O3iGH-ZaHZjf!=W)q4|$+P zJz17W^2Ac69&eV)G00bT{yY*{XF6kfX>EJ-`s?>b%5jM4CA$+4-?R3aAStPqn%YPa z-q05Jxm`q(K!tCq@|sFL~r)l{G(A(3pnU1&2&iYJ}qMCD*|USz@=kp zxF|j5vCfPOwQD~gB2}~Wzw;c%|G>8Ce{vJ(?<<-rpRUy?7@b(D*S%^7SuAi7jkB66 zG$~TGn{s}tL;8quh-{O^eIXnMh>pzSPe(_k!h6}8&|^jIeoUo z(b#nvu1!59)#0idEKd0(-PG8ieT6OV!qA5dMvrT~7VEl`Ysr?FjCx@(){hn2#DY)J z$^;UX4=wN2f*oiR4dl7>zEy4o0m26_jx4=ZcsYrigE-hpIwUjF(R!a>%f5xp#WGCkSW%*0{iwOzW+I_d>Iy zMw;pVbNd30^&Oe615k?br?6rj`obI*^8a>6H*K(F7;qC7tai384dC6uVP~Cx=#F`2 zuURxalBmWyrX_SV1Xx2pkR3$2r~_A^>NFtRVTUO@``V5Iv?4ak3n9m;lEK!KWQ8|h zEAY7sr?7Ow-X<*R8IyR$(|v}z5Q8d%l|=dV@a2}div(1UB}lG{X8XBDdu}i z*USbunDl?#gbT9=vMUG$ZC6d3qZr0Pq(>l9~p<1F(Aako?&SN5xCodwQ8N;69rriG9z$ZzR(S5h;cys(jx z(!x`ql0bfX7&74C_n(khb5R2+fDRza58oDl0f}S3tu@|Jfv$yl;FR6bE4-}r-*GW5 zQM!eDTpkwq@n30^l`_Bnc{glgg@MiLaoxA`(QC+JuK(`TTmX6eYUFz!LaS(C+nh+w7Qq@CyZwYvUK#b%uvfz zOunsT5nIpM_5G}?=br7>>&Cs;Gz@{$QnG(7oNBVEMYn)3bj)$f=9_7tu{BdJk_6uRVt`+usDvUdr*bKp{kx{`Uhj? zodvk@_S_OU*?&Z?R2&?HCVB;U&Jt{0>DoU zdYj+boG(IeDV?kGSZ)MMt&er4(eYXczkurdCOkv~(LL1r$Z}yWQyVV= zn(-DD+zxtW_v7s#S+vev+?|fBik+^V+=PiCY)fH0=RNQn1LJ%+yvcS%bgB%U7_tD(7dz&cEn`~H(X&>B;1(kLhI6ZwD7aKCqDkr!NePrjaT4q*U0DPaEre-Af(8{(6}A6rr%dh0I= zvkYuEH!k)9ubO;~)Pn&%g1p&vacv4xo71t`P5Eug;B$8@4V+)(r~4Ru2!D2g7C7=D zyXU{~_AcO1=l|Qdtz<(@M??&rv|EyL3Nw^KTi6ckd?@1>LsQ0K7_u$0Vw9G0Ohm;P zry1oi6q(A|#4zI!3S$g0#$g8Ye-E`=``zz8zvp?b|I>A~m(}?A9N(YUeZTJee$mnc z<~R06HuznYNl#Cm)<~^r6c3wr?mtN_GTofF(L@V8{H{0yUWn}~j|X14Zr;C0_sw|| zQCuRu>-m4-bvf1WZGCbvXy-x zhu_}n=^r}iY2Atbd8}bO0Xi*EU*%Cwo|F#@#`>8$gIxv8$`FAOJ!Px2a-(JxMF(WA zhmEg^zj^Ev5CcUvgO{U7r+n(&&Zyhc`tr^6fXOu`l{of(ue4F8&hAG@yP2^z)@l*L2KJ{*k1rL(P3uSjosyYn)Fe1La;vUcqTr8P=*EjCFoRXX_N70UuqndpRDe za6ztht_g78hwOE3xADuMZe4-`JU6bXE-8ToEo+a$(L2|fF~pMW8N4le&6rR(XuhlR zuRt*k6KUA|=gUSq(#6SIMrD%s!jVwxVec#-aCkE0Fi?8j6&nQ_l3u-_(_4gqC>lGE z867CPYQ`|~X|&_1dlw{sWvpTb=q8JSBKS;trGWpyzdMSydjYbm&ZBiV60jSEeEt>! z3SmNtFUiJ;+6F zzKqDK_=zXELYTK2%RR_gMIqNE#l9k*PNd${tjm`xkiPLA=uc+jEDZ9Pnh7QCq3lF* zpz2+VAfnnN*$Fv_5MH@Gt^DV`jN&b_lTi5RZ}p+!^|(EF!D7zP<=pwvrH;CYU)!+T zE>C3Po$D_vI}QY0=J)pU`ubv({HSj(ori(q{d$jWgaI8={DA<|^OPe%7l_6wn(Iwd zeHV(l26>*^eMgN`aRXYH*m6Zh@tZbhhZ~f&7+~m58@r`UpgLD~${X^+{VR*uq;LxS zvA0v@qc1xa6_M(E!rI(Dyb1U)22cHwx>aMcADu|roMXInqT%Iu=iVT1Q{_lgt`I`_ z`D|asB(e4)kb~SCRYp-_s?H1GPP3+u3)m%qA6M6hSTTg;PsYn6Mh zrEYr{@@%tv`zwXE?JT99X)8@gva*Qe{TRrQ{<`!J(tl|5^cizLr57*)-jQRRl^sCi z%uCt@Va@-D+z3@bazzfE4t9a=lLjS_WS5#_nPwDC{x&g&23eBbW0t(Oaew)S z--WxKR{AoSYFyIF%NnY2;&ZJiA~Jp5Kzw;~Yufg0`)u;MuCVdT zYyHo{QF!FpppZoVg`ciJ0Y#@ASOHfv7ju)_K@t4)MkEeYS|Zn(x&8Et~*lp?IZRvMmPzlz5b(3Pp7QBoYSIrYL`uJzM~zX9RUZ6 zN9$7Pr`J)SzT1JBGvbc0`tmn82Ad;gfx#|7v62*6gyA&>JL$%{v9msr*m@h@9=(W3~_3|(233RIDbq3+yjjBPxoqs4|rJxg9 z_ziIXt#B5W&JT=1F%8PN2m^6&{SOxiK=B=OPH5;5qP~{QO0f+PjNPSG%<`Erd-=+y zHIPpwF!%D7nN2wvcEAiD8@s+Rq!fr1S0KTxeD~va$ai7tbYbBs+=TFSuD$-s03dXW7afWe9#8tevR}46Ag_@U3 zxLY#0v&2 z(pCl!blW`d%B7e6qkkl`h`cs%(GTBJif+iOiz9PtduI7oxOo7RTwfzh2>LuH^vA{t zOJf4|@#hV@zTO}r!qb>Au+evL51Vd{VM5xEDPpC+K0)`D#M&gg!Zd{soA7a)cJB=< z(L;scG2@e6ztyd2^WS9h4`@wz3PxJ#Lml0!degfiMt_iJ<%AjV^2KHdIEP{3>B`Qq zqbroyyYp{Sf6kAIZ2H@61cB<>=R#Ml@+eUP0B;%Mod?NZBGDiQp;~W~N#=UtGcCNz z_hi8uY$dds4n=)+0Y+MPf`#NX*g-XCvBO|(*&Z>rPl&L<(R_7xpPV~eV_kOD8(($> zgST*$4SBb5d&oP<`__w(`|sn4qr8(Zn@_#~07?Jy&98neL#$1h{H^kJyyUjGTQs2C!UzD1`Q2J z7E=i<2URlQX}hHqBZ+vu^GL2*)`VW619q&^_%in%1mHuNZyv&bT^#=PFh^Q@S4VEQ zd?pe!lOOBg_N!RHUl$x3scB4!u&T0M(-s{L*a5bHzrp*PN67y@uTi(%Hhtj?dg|Rg z00-3>qoZ8gc8+S5PjV+f{0$pGkW1js&-gQ3A`7Png4-s;B-(X5q&U(Us&-_D#ZQn> zy{RDmxkVw$%orW?!ItsrO3B5hUgv+r0AfO+4Xinvw+p`ndx0^5FqRCH89JlD^M~(n z&589%O@0&1KQ08DKZi{N4_bR;ALdcnLJWiG5&bJz#p*c94r|&NY7xHM!Fk(W7k4s3 zEvvp7+%O0eNMe4ZlbHNGXraT!;}QIWaG`f@K4i4mQC9SQA^V|_yKs1Hz#9o_jsY=Q zF;uB_0De|~_&%!?0MpO>NyCqEbxxO-q3AszAsDw`%}mZ5&Efz=`V4=t*X>qd^wyJ-7=z zyOV9!!Xi{csYP>?pOfbn7JLh7^$8gsUD9jy(fmigNoEfRI-4!r z9M=^GdMYGI4I_b;c94wm8|{7+L`8N@>#+l!XE&*&6G5ICsJ(>|D1Z`*1Fc8l^SY+3 z#yO)9bC{2@FYP3@nh}i0mdXwm>DU_*8!ge3S|Prs>dgx6%g4RNM1Sse;aM5%cyZu$ zy&vSvYN#!;YWFN2dr-GstmD@f%m-C1$q_Z5M&qisr1J+rW?n+yQfePQe9)2q$ftu& zR3WUgYy`e6y}kBplOu^#fknFTh&#q@tEM~|3XEok*o1h*V3DkVH}A5sCzgOq!=?MA zj1$8J)kWtHc`ENogCA?AGjkD3ztz@V?}+-notb5fPD+T`ByWgBP!W|ml%}EWRTx)g zf1~;=msvFYMDsYp_ncH>CPb_Qx8A*$_T&W?MOCDy@dj3l!kiQ zN-My!)YJ#bJEej)v2yL7mKNA3mGsa`iw)?jE)r=KY(GDz-80V$dpY&eRhXefpgsm| zb2=0C%P{3!@eKSPjdMXahf&eleavy;ka|gS@ zeLX`RysEB}Ou!kU=7!zt*+J#D&2G>pK7X~Li?RYrQjLEC#vi?xDl-r5yP1B^R#Anl zRZ}y3Q1=S{l3p!9gdqe^WCcM}jxzu{PQd z?mIJ6`A0D;8x=ePF-U(KK&>Mrfd>xJ;&yY%NW+YEI!$Za0prlFG9 z&j09PQ|Dzrx6$F<{&J54?9?@FGgcv(lp$I;H&(L{8GJ@`)?%2lw)EpHENk0OEK=GL zSf<(qsh6(lzvjYcf~7)_?<$lg`ea^GA5dqtrlgK_>LIWxJ{>h>%>Bq2yMc|nH8qn& zJ7+pkFc5U*73hX+Qv*mOQ4V1OD#D`I4VNXEy5@P7(4dLiHn5=^i_rT@IHk&^k?8h6 zw5lBCD>W1m4=NXY|R!g=uX(KC&b#153T;^^j)4IvT42 ze^Vxw5XFt_=<7UUHSlTMfc~Y#u{DqIk-pS*akq4tm-(nzz|m;-qPGq+B2TUMIH2{n zb=;+Jg8|C@)hVJqPGkKq?$hhfnR2Rfjx_f!*Ou4TWwmbsOGL=)mX{v-zfsIU3JWlF zlS4`oCjR|3KB8rV5pkVwWZF>Mi%F(=`1D9f7SiO_+$Dnw!S1k<^hkjcMboqywb_)l zUS<{la)Q}~0qvIUfWWHrL4n&~2khUykQ?!RoO|dL5N}ficnT zTj~9_;o97BVMzxAku+%w?a~uxL3$anNB$o6!INqWrA+R0yG)Igo$H{MXBkGGa%um8 zE5F$Gou`S_Sd>y7NRw)4#?X_CB#&p3FzHZ+DnUJ**wGPLVB?pZUdWH0P-3qR!fO%R z9jnZ|>}x4g^;>dYq5En0S_k@_$_-1;dGnX;dGqrM?{%^4>nA_1c9KBgjSH4=>Az7X z(^Xnx4c+Go)3`K@JfHG={Z7BBM}DLos9ut!rei3hlsMU%H(UZDdd{1RgkH_2%)%ZG z;&fMuEGOp;J()JJc~O)P8?b7#7E{&ej9GLjmQo2l?+FiJP|o!hI9JFILTNTJhZLr+FhBaAak;K z*k`LIC5<8Vi67{~(uOB2`AB3@R89wP*sf2={>Tk(SuYu7<~r~OH<_Nz&~~2mbd;Lo znGY{oHFs&Zn<}f5-H6gb)^40zl@-OBn^$``ry=%|L`5#{11*Av^USak2*|&heAZhJ zZmD<&ty3LMs^%&F%Z$dbl!@oq$QJ0cLq!bek?DhWlzXLf%2P+$K1JjDW=Lg=(=2!VI$ z#Ns|CDw}aimIfC}yuC1X9dpml+7U=QCedpnOq4a2wxCiw=e8(`0(Pb{yS;84fu5v@ z;Y*8#)W$#jL}eFdd5pPMRajReaoaUg3j&kUt8m_wcoO`Ov+;J&_1I1>!buwX-4&Lu z5o0n3?I$|X=82xMC_}${D4JuIWm}p^NWit1CAGp)OKd!XomYYuZz7L!B754G|8{z>lzxu@YSvfbB zGk^ZGv&VE=?GXd@8eDfI;gHBJkYj0n*x?9xcL{?Emyw)r;qto-d@CD-DFQAAU==N> z&Jpn}^E7OpCf;1x%u#Wxn8?=tYTo2A$l#Ta-8Sg{X&XFQJ!pmy<1j2{iR<_VQntt7 zTMLys5TBphf1zCV!Lrf=hQfu-Xngx>kwfZ+P(vP9dV?jQ8*ny>pq>ZxgEfFf%M``` zTVmx4!GIOhy392M&Cg{ZF0sjklNli*A_?9V;FNu7t0@Va#XlW`Fb@MB1%R2IhTuyf zGKH-4UROwte}pQT%eY|$;E#l?)yt{!5L1-$D_~`(VRui{Tn2 zHs`mG-&p!P6wX}DqOAKtqF|XMQ8XMt+Gk(0P$Afgot!j&ccG8Ntxu%3VD^{63mbVdNO9h zLeA0=VCt3nCq+kJ95{f--q1_Ht8=x;PioU@S&pIoSGaE*xi(mj&v4Gw24Rw%DaVK$ z|Cs=yM;8(QolkMNtE3BxP|@Xd$7NQE4!DwQ^W!b@Jhd9_^&G$>0CwIZV$49;GhOcO zV^$&`g?Q)wNku&L&;^ho0rWH^sR2Y%a5w zDe>EY$n~8&0LwRWX$K&F0Hf(BSJ?!~SHPPZKL3l={5#sZy#;i-wWmMDjJDFM?B&UG zX>|4L%a!6;%<_|X{hTCLl1X#awyV5)trE~k|3+K>H)+Jm&wEALD@;hIA;5>H+XKI}L*Mf04s1%W<* z_Sj^SUNv2t%68C}7P!3&t?(UJyUu8wIj<+X!c zm82=_g=#wX(8cVA0l`71QJkN<^KAM(8TAUk@A`Uzu&f+X>A{`BD<^-i9Ai)BXxqo zHSwbUF@)emQ^(5oOO*fOMmuRlGpAZ*r!Lc1Hw}2z^w!b3YZLkTaBeU+7Q1-s$s_ak zg7M){b)L9YiWGWL-Ql~;^*Q}mAR~A}&cOV zuC--!8j)vNRfQ_u-1^*&nk1Z(7vr#rrlX*H5uAsGL&_MHu~3y##|`+|xz5c&nG|Lh zYtYa`aPM{kwOP)}Z!#Bg8{S?@62FhIKVJ#7lf=S!)j{)?yhy)_^oHEo>M-u$Z+AsI zi$jsqI*`DME512@Z`okixdxh&+*d08Lrg`DHIKyF{9-oi_KX<;^+c2AQ%hA3M?&?{ z4blUfK65RbP3ohl(nj@H=jr&f2J$)PR0;E?v5{KirJm9y=E$1IG4L21$9*v90nF>fX1GZma$AP`fYm?T@dCB(hrp#1x?@ALOnz$>H`tPB?lnGMplwf?&u@^FDrR zHm3&f%jM61oWZ%VUe5g-tQA%Qi#YBsD061FgX>Bg4X)1fSVFf5>PEe=bC4YXkq=AP z1YYdwQl5yPj%)gLI2!{JC6z72|KQ3z<$sd%>D7H+G_$nk40RE^hXv-1RTuF)niu;f z>?aQVu0J_YRf*j0hBaM#k^Iw!EVho(cZJ6t?TbfbPrN&Fl$3tgUgA~%Mx=Y3woea< zl@X2fTcR8Z zM;%?zyRMY%EXh0&;GHYoH`o6W$c4o*0+M@0bH(xjr)#4_wl2Y^I^&5RoQJ)wZU^<$#FNqk zc;T&6%urLRBdfJC6n$;FYDA!&erhDk81V;_<%pttcS1J&cuSo;OYreT!wa@asB^WN z9FvY)Cl3YnX%@~%?bU)M4Bz-gUoVnSDkNDBd@ofMTXH}=6bnI7UC4z^-ddtr7ySuELtjry2u{KoadO5j9v$`aOswZT~KbN+yY7x^#YuXOy zdKR4orB_27CC(vq`qk*$j#A@$wwP%u!ccm80*A{3{OYA_{3(+Wj3`M(sUwLGPkc*Ll{yhENYb0U+J_k1BD1(?(&L|Z)kvG@-z z-G{b;JX2E$Gyi~dY)zDN@J(H|LYq+rnR}?zvhc?bwZ=d6%nC7tX}LwYS#)5|77 zbMzG6h1<`6cIl_t=Mc0q@{Sc>=1pm{;KBNrp`yBHHJ6mT32K z%&cGQjf;tM*Pjcg`eFBS##DrRBitXUhjnQsrm$$N$Ypdna8>N@yGFxFr*<)A+U!?( z_}I0u6P4to_P{%UK4d7PDt_9=8xtE}#IIwE%KV<4p`n+$!$tX=Utfl3+ke!BW>*dz zJNj2KBL_*92OVNq#7dtf5|^6a4mk#h()E9AA|QP6h{`{b^9|o!3v4T(N3dG1>;ROe z#3PFR>Bh~=Ew<^q^A2S^$BZ1zQGT@rx`RO*-Fn?72%5*MZl8VEs6c5hjn;0sIU%1{ zK#zPU?t!&6wR9i%v1MczXiEHg630qEy<^~InysA!u^o%IeC~dqm^WEC<_8wi-ujSU zo3*wIFms6~?4>SNoG29|k05wfnF0UE6GphUcZ3)@Z>t7zH6KS}(ZbE{dYh($f z(Klzuuoshr>+YDsGEf8S=$>-&rtnr%Ll@t5S!fj(WO4+{z0B`U=ds5=!V+dp+mQ!X z&Sqr)dW!WV*P)W^pC_6OuboD2<<-se_*z}3ARgwpT$iSVc20AlN8lo7vfkxVfqG-} zW6IU@R=FOR5toSR1ej~^f$~&S&h$Oa&`b(QgFe#5YvV<+OVWzDYb9@+<%2>2d)Nn- zhLz*)E@1K;(M!}|x!DHV{QN7*R!<*}oMoSwuw82Cr&wb-bNkZk*yZkfoP5ntRsRq1 z(jCt$rRdlxy8Y+1i$1BU1fIa?yA_A;mWLV2VR7fY3mg0sb0- zzXyaZorT9r%W6~bB)EYx{^x{mim`92-jz>C{Wn?gx~$nL^M?6gh+itriGIMl9srr+i^5F@bX<*VywLsq<)E$4KW! zAcbCmQURbT7V`ZDSa2q0n(Jd3sY=fI+(yri1Y2B`<;k=Ws;)f4d>q06R^oGe6%?<_ zLS?nj3TGia{R;B^+2Z;N8~r;xV8gBivMG zfGZJ9=sp8|;Q#EQivTI{pNQ#!@igF^VOh5`B6lAaehID*5M}&@>^335!uxL=NQ8pP z;O9l~fB$%Zh@(gt`~TG>{znRD8QXns{R=N~_KJ1~ynMEu*dGT+FI^S_ z8{wapZyO{3ET&lhZ@BHq2em936l9Q24bA3e(%=__YHCp79B~F>jm5uTsQ-{k{8yd( z@?B7}LOmcXHJ6`ud9efZ)=_+RQCBH$6zD=Mnz4!Nr9 zKevw#f!;kR^)E?&&652)4F9*Id|(^|*`YA~>?M$%u)E52FsbVl-v7nY%*fMseDym1 zyX?Vj?86hzitJ%=C>5+#GEZ}9KEE_qEs&8;a*|+J2snvf2%Y{_%w5gt`DVfIk8@** zm+TsrfZib(_mZhCuR9C$`0C<(u9n&ALyp1>fOai1Hx=oW{9iykE7`VvzMf_zkDd-q z>3s!c5VJ#jMkTXA#{6yJ+O7(SsfjrwG^fD~)fp!3hk(T`(K$JfRxPc3-yiBmUb4$D*R|1F6e%>&4x^mbktORVN z{&yu^*GL0ds|`R@u^tvzXTPTH8#+0X-9reuVtX;~B1Q|Ag|5<870N3@2tWw&WowHc z2xM*XEPoKkb=*<^w;Ry5{uc0P&1l-gr|OjJBI;BtSbqc|x2Zcm3tsG9EdF?NV4*hK zBSgv}dPtt}JAGub-KaiOryS-zPifUD8XnP5i4I+|iOBX#A~@2lbp_p6^Q%<{@lD=> zUPwsjRuh(wohv4DwSU#yk3m1WrQiFkdhipf9&!kgOj4sQ$6fcy=w=hoqhlyGN)KdV zGf7^Uv>Cl3{>_AD`YqdDpz8g$^9Qte{cY$ddoiZ0vyI{95zG$`^>Yt&NTtjb$phYn zzD{Vo09+6>_yd@L0-x=|SRMUA^mYeg=9=TeTT=hw-D@*CYBb;0?Ovj*Vv9SP^o>Y6 z&ZgwdQEssZue#48d-v?up1iX zaK9&lQ(eSSHb{d%0_ukI=+NU%j{B)V-SC4X$-H-v*0eiMj;9lNUMHp0qrt*5N*V&Q;$`=2iNhnRX=E4aIjq7f1OR)8@~KdzW+ zm1V%2w#DIAF}ZTO6U3pK(<}|vjSoUOM^8uZEd3W<7D{flDO8FPyzMb>CWK73BtX0e zr+!Z-yzG{7<&%~sODe=~b5rt~ejUtKpr563sKMBBnGbx*yQ{UsPkP%f=Tvv;OE+3J zxAu_&L6Zvkd_-yeha$q&c|!Cnhgw4AQtR2u@OJSGHkq6XfY{YXGfAaA9evLZIN{`2 zu&Q97@|~%_1j}OA{=}Ys_LRELD)<1jffWdRkd9H65>juTH}}ZMOXu`61tYLoAAu{j zZ1!E~2z_nh68%cZpm3^E@%gKYNYKl9ZbUoK@r2?(cYm#YGjKe5%9ZDHKRI>jcil1 zi#g!cj?HU|#FH(3&MUHA!k)TO7qnl)6-mSWs~Opcx<(ffq&EpDySQXnTC2_$TVpbQ zf}I*Z4eDZTXkFY|Qt=i@s_z_zd(i?r^X9o=O`n;~fbx)^)1GAw!^~=VzjUi~kV9Jf zvC%9)nBZ2GLfSzJmO4jCTStn`#va!OL+ikiT3LK6{m&@&{6jmUB#P!?Z~Yp1tRz2h zfS7EhoE9k5%L}!+)L{ZpI5lt?sdDp4C76^h>4hHg{RiVZ)|8v!sZzd(QU~ z5*%Z_?8Z;gU7Pm=H5DRL^uyHSiZ^QVx7u+Fhl}S5tesRZ_Zca}QAw#@F3P%^%~sIi zWNrZ|{;`ZNKVHw#2a#!g9f^~t-QeD7O7QR4D{qLgUj2n4cB>Lq+x=~c_&1Sed*lgu zoj%a>-(Qwc=!3DrQm;hD7qMg|9s!PkIvdxa@z9&13HV!}WPuTsbitB5GxE}}Z!)G8 z86$^h$QtXA*bjfGXuwGG<8+RkQtX)yu&394ymKI>@pv~W(6?PZ0>A-Cd~t81Y0{U& zq=G!rRd@5hk}5zAb@p|N1m@~8&r!AvkCbp~|p@wM}|ig=e8>h(L|XBJ~%H}>exPu3tz z{C;igFf3D1Bn8%C5V+J-1JnI+q{;kD?LIijX;s}18Ye4S;XgYLkn;@U@21H!En$8= z@7}Gp=~HY+KM=rDvq``Tus1I4v?4yOMLdIR7$IfGSJLzQQ9@^roFUztzmXPM4iBEu z9{}WxBQ$Z3ZML>)(Z|;>t)Wl(BvURm4lf}HkfrbHr7!@XCnFi!Js~}@a&kNNMPW7S zLr&~6!@a8`j}jSbS;dNs@hz0FCb};O7uJ2lS@^UEMX$(1das{(X$XjE%tZlj1P#}A z4cLG8z~ZMI!LBfZS^6?-S~AV00F1zpZzlq0Iu~viux>o&SA$|?PF_;*Xrv?c%h#F z@@k+}X6+t6)iLh-c1&^)kWW)#ptsCXbB zy3it6BKdeCPS`?37Tei*w9>iwBQWyB5Avbio2bWxBk8W+S?z(&F7(`pUMOTi%Dvx` zxBs|Af5hY&Q5Uo>VR6HArWW?JS3Zq&;(h1TD4tNi`B|5MIJcrp*m{C@r7iN0fuPnp z=+qX8WNfPFOL(W0gcF<8s1=cAnpfGAwmYt4iZVYpER0FDwKQVwUJA(Y=3hKIg)DZF z3U5S_QAZE#o<)aXZwd;k8O$<~h~vsfI#@@i((72eck2cgR?|XtCMcMux+mT`8I!Qw z%FcS7WVf-8sl?kdSAu4C94S=sTbUiWN33n2Dsc3NTe0%LHGnjWz5KH^*`CVxXSGb$ zfi}vw5(RZNMGLE3oyC~`oX3{=t4uvJbwtz16arM)y3+d>HyFBDEeio*?hU#BN(>*O zfJ$+#YAC}^gy-?*b|`1GXdg@;((sm%REfqT1|u1VY>fKykz`V=av+SJkjYlNerF`B zHgsqH7CHTe@KtW@lz3@kAxH&Wm^{Y9=LOa(Y3wvDrQFFTJf8p9xXL@I;W6{KM(V-> z)>n7K>9+sE8)SQ~4_&-t-*;j#VhngV(sSr_o=5vS{zIW4DtcQ@F>M=k4NmTKV8X(+ zy}yYbI;|F0*YrPT6v8@LM>q6sioAXAk4}kB9APXpV55jDn8cy;FFa(GJ!V(W zFmCvjT0+dKDKC@FBKP)!Jd3|evc5Q(F+YR6VIOTXhDzTNYo%$u2nyin5qd!GRVXjg z@N3L|*qeYx@Lm&n3z)%WR^o>E=Rt{w9GAu4Lchm+%l)&A0&-v@zTibz2y4EjM(e4uqreY62b@K~Bn;k+9vGoa|M99O%3u%G|C_(oXM0RH7`^}7LX1i%Cgun|B`ISao8_;f_+D+|i5|GOiJkTTI1 z8Wxy!n9O*}+l9-Azcx60xtxDFKpw|UjnrM{7S1Z1!hV{+s#-R?+$Fvxy}|si1$aeu z|1I+ThG{9EYWPUBkx>!PUG{%Sf{CEO5wp@B0a?GGJpz#2^;0{veIH91c|MEn1YQgg z7c1Dc+_Jek|J;~d0`Oh~!=PB3vRNurv<;^><1A!0?vXQMl#mD0dwNgXD51U1Z~nH` z{O^>7>}5q2*&{ZWN&rN}gSWz0&?;hwkkr$W;uTF%}^sLurH6) zXvTUr$YB*#qCIpO$ZIc2_8oEzSNC%4QLtbQ3f9?M~0V|X2|M;4!ED0wAEZb;w377&LIQ#sjh7=HIv8AI4Ds)jYBz_}5ou;Q3~ zj?{EwNFtSOW4#z&41q4JY6Eica0yer&l*1{+_tR)^KhRR9-!0=q3BR88g z=WjGomQss@PmV{o*FZxmbf#M@kaYE)`61lpt@rBu9-ZBYWDUMdpJNf3b;lP@(YkIM zrBZ zYxG??xye=BpU~XoW?HssvglKBdmxZWR*xfT+C-vk071NGqzvh|?sPdg_%&^}l-A$9 z^{1y$6$ful1_agfiYXvE%U7xBG#Zo5Y82C-Dkkz{GLh#m%}NW}92uGf4?7k}S6DtZ zDsk|6V^!R;XUFxVl6u&*U%X^jt45qu#8`C;IP2xVJ+h}CJS^0BJ#hXKg0g)7f$&?q z10qZDP(ktR@qXbiJCSh>M1gUBR%HcLp-PbS3c6HYp9q9Z zW|6Fd>hobwy+Od3x45P9`Yfcw!vYc>4~@4OBVpE2NV*xr1Bt_PM2J^V$dcNtgmyyu zGk7pRpvsF(Y67F+(%+2_PYfnfJ6Y5dxJ-0+$qr7Z=GlYt-HYpf8{GiaKf?R{4{!dK zD8%}~U0N~8W9EtrgCi_U)X}mM4I487x-btP>g~4^!J&bSHd@t!<(uhHDHAPxdNxMC z$3d)W!GU@zNyU;{0iGgoX}=`3I4WS%v^JTjO4|pG$etE3&_-T^Ij=l7+E+bxxa3#S zygM(l#6FXW*&CE%!X7-dbLlxyoC+T~f9$fX8nAp;RV_|AfUnz$= zeX7dr%m8*F>L+QNd~H?Rkg9`89p>jNomt1;ov&8)p}u+0)8|kQ$Ai;Xwl9WTT+82E zAfq0VC-jBN7)s3o3$NDKPxL9ipnnT6>PB9?8CFsXhKc192Q<2}21rK_#&w`?tDOFd z;p$dSmlbWJc&8IglkY<8HOvaV`Dzal-|q*bg~>UDp(pcNJaxYp|yBSA%^ag%)x6LaV-T$WK_4F~i2r)0j4pYw^HxHd=pa*zlKJ z4lJ3CD(bDd4NL|v7USeFW=T?seFTUE39X+S-H)T3!%AL`j~$IpNL6UN zrv?3$XMRVe^WCXeW~-TIUo6+e}#$LHD7b=h2IjYWdFGNK(GUyaSpv#!^ z>zBiG%ql#k2TZQZLp@txc~I?Q(<%>Jeci>U-G@g9vGL29N!OJ|1}mPdF@^?NqUYTN z-spiR5L)*7W44N^g`R~yb~kE~*X$IL^)fZXKz|^7a0bRc%4Hhpa6S}IfMTO7V8jdA zi&R+pG{^{f_Nlg9HTK}krr=|#;m5pcN zx!p~0(dLAj=9)-0O47|XLesB^T2j$f_IfmFv^?!~*VWFl0*7^*YglASv?N#}S9#nL zTG>|`F^R^|VN)R+1}*3P#H~%AZyfzltPf_GuK^kVd?UAtw6UGu$w2DUXGo85^BPSB z*uEU_!S`r!@_rf5XI?YEVZh>MF1bnmT>L6A8-o@N-I%~fmKjXSb( zIQ2T-iw##9Ymz!&klECaC)Z_BL@B7FMS;#`({bK9$Lc#yTF@?}w-Y^Vqzh*Q0Ov~C z6_-zN1PbiWVN_s=OVRpl*STHj(@}Rxuk0pc=$V0y?|SLb)u&1k!#h?nyFg?|Z3W2X zjO{p?h%wrKw?j}icZ*$iS^T0h8)PTkxS@F=Vrg`#4A&VauXeHVCA|lX-Xo)K#_cs_ zAyT~NGxO_rBEzP46K>r9NLr`PJQ7EO?w)3YClq8#lCN%!EZeAiG>WV1QZJ_;*1Wve zRp5c2(Ru4_w$>l<7KJY9s80>*W8(CE^pbyk~U?BzmdqZQAFsCfM^>54U zkOhZ0ad=l8xjBM$?*s3od~6c^6)1HT+1C5mg!b>V+$@1l5)4xKNbkci0W%hG& z1uM*$xXvK9R$%qO?L}dF{$%n=H2f}hEw7Tq>K|5Pr ziLOfR1Y!@RBcs-3hCRK2Pwj|-MT`#)vx1UrQzzEr`MlvAMu!=6V<#__OIjD8c_uvd z%81W-RRl752E8x*#pRqEmF-NzxLbzjSS3pzADf=$?~yytC%DY_zTox2*OjV0WS^)r z`^mYqGLkK}Y49-P>Z`ct^%xE!!#Ze7Xn`7-OCEVV4|=#)3t;WNHGx!LN$|YV%$#UH zk`MM$&VaU9m6=Cb?=()DuxSvTlAjFxJNUu4xl6iV&Ig#V(Ut~FgFMU}D+H@rV32qeLRTPzT6A{u zVUqZr!ETkvRcZ1cGG!kBPBMko@>-7cdMB5f?Q?1dQ=(5QA6XJxD5Bu%{~&g)hbAR^ z-Q2`1>OG-+zOZtVwF~4$7rpu@^W!(i5=+~AKLA?DO7D!?2Z(KX=n=sGCAD(n(zQMp z4QXK|b+I9HH8|YZ3`H^qasAr z9cW446EP<8_SK)#nw2872oYFfuM3XttU;bf;f*u#z{pcexwU}CQ^##?yOY3 zYRv{1sjYTA1KFCATa3MLt4UF(fWY41xlnQsbnQT_v%E#1!uM6HN5-%G6c}*q^5@4v z>LiGqJHCQ*>oc17*tDUWf}Chujw<|YvS$5y z`Ofv9F`v`o-2GOXA7b%9A#!Ho{q6WnTHxXc^V@6$)Ij$a-eLI|*@8%Oy>lQNp;1Eh z0&a1daDkFGhah@%-naG!!y=0JgD4{ZLbr*GQF%zd|CIm+n~Q*e4kH4hZ{t}$TYcNc zK3+^=tYxNp#>Jge?H;J?8R}1tCt@kzEvDwNS7%nm6Xl5$1-&}>K{KT8fr(okC>pop z5i*CXNktYrq=S3AQ`4TY9hq$3F=-!P$}R!_KT-PCPaG_5Y>fA(#N9%=J&)DJYmplH zY~8%lXR)(DI8fQdqL|U>e)oN5(R$W=7dwh!rzUYZjinGt&aB$fR62->X1FFFW|xX-mVVl9oQ5Z|ugPm!-(5Mfj{b^McU9{ePzm2> zB?6@tp4b=hd{Y@9l2Nsg$(4xjTc~GQB|K0odcSiBw zxi?{0%tckbD}Af^G>|;{Vwdu_$)k0KZol6p^bdl2UMSR-|a)TL!T!>=uY$0#d?zsBmD+ z6Y2;p4$0>Tx%oXSn@KB-&uOw0Mpw3Tll?DKrP_s-A?|H0_*r?Ks}D7i7Ss1^%zF>x zkBCIOVXx&1^Foo=fX&ASIN-o<2^IQe)@8YkMTX7|25mK?Fr$r*m8eY6LYT=!X7N;| zqaX&!`f2M=6hDzeKZG~;&2{*b&hC?EaAOqOTpkD&mDN|DU76>BG?BL_hW_Z8`xww~ zPk5J|OLZ7~oyaV{)^GM%aVi6{1cM;}Lx@n{2bqKdACc-A#YUm-QWCUOuD{9_(Pr&X ze!I_!Z_Vo&IIoy0&^umIpCI%N-mQ8xTUc7xJTb< z`qZv+Tu<}W*aQ0a@46_4D7>|DI+RcGc>B}Vr&{47Pot|C8qH=mm;0@W2E6_H#a*3p zyhcmh1=XI{IWY*`oL;>J<)8LORM?Y~GRz-2fl9IK>IqcZ0Q3k6SKr?kcPlh+%+%u? zplmqB9!spj?U3v!i|Ts}Ww zpt}P?wC#%C)24Ry-8eeq+Vt{0>cH@u2zo*HE1e~m`j5jI4sB$IuxsbX_pE7K-1-^E z-Gq~$+vG}%wp%LL=eC=_j4Y<#2Tk=ysNw1KjZads4|gxc{~**L5zq658l=dQzWHSh zQm>cOJvM_!p-zp)G?NpQX0qSCGt>q1rMak(t||KAPQj9Cr)p$-m*vF)G0`qLzZA%I z@8D`-%#VDJ#rIxH1c=MFowl~bR&~)W9oNk{5@SNAYbSDyf8%UDyF)(9Q z>Z)f_&v1=TMWD}8hr_YbMsp-~MBZ@nnVA#_X7d|t(h5~C9F8yZxYr&`dCs+53Iwza zn;?j`={eAF+5XHmvVF%%M^->mb8|BI1heR1SxHZfH9p#-skFY#iiG6233@`cc{7AA z9n>s+HW$jMu*?zslH)|aV;Z~QfB0yStb?GE{ zbtQG6NM5EaZCg_Iu$p(5RH}A zqo>E%@}ax(RJM%DEjvVkH#BAyQ`v`X1)Bj}k==8Y8-sn?y!UB-ALcn`;mNQk(p(=y z)STFh*}`WgCWoRNj&Zg6=w6a}jJZSY6A$3pF_l@nBmx$-Vektx>53YnZE21`58{VHUEEu@6jM`5#0c&o2a0N4-u6E5O2R`Ied-k^wB8)1Xf=4jMal|Wv^Jn`FBYUn zyn6l8Ih_2|Qqc8e(u6iv^ql?w=$geF=s1$HFA_cek4`iUE{Mt~ZazLxauB4;9N8+Dgudfd zJHADCq3dc)xj=8Z(U}epMQ=56%kK+$Zb3Vd;hW;Wg&X26cgGO%)Bb{vK5b-5_9q%d zdJ{#vL-`c_R1A^x9m)a>*7iG7bOzPCwEkt)x-Pk-bX;gWRn9quVumrn*=A>ZfDtGL zWghp7zndKl7V$-7c;_`Aw`PTT!Jbsg9Of(OE(8~2B+BqKB1j01H4N{xYKO0(2{h|U z9DB2?1G}f;!otQ?Bf?h_MnALPD#}%(55*JDD!bQSc5f)SJTIEuF{z%Ou9Z64B~y z4gBDjo3t6xU8Hij-oeRYlc8IBIEX>*9Apxp+3n<3{eyCa75ihqR|HD~<|1q6nOPDT z*$C=^G{9%c8rtYP$*@jdDyeKmyXfZ5@$z!KHM0w~zS48O=5nHZu6F79Wg)25Vk#MFD)rVCaV!YTUuGB5&lCl3*FR(O&bRN>aF5usqr%+LG37)&DIKnehmKS{x>5s5rIVEw}MF$g*p!?L6i2uO~X`|_>`vx3lK=)TiI2q_`P4bf0 zQn4k)?g0-ZIz`2ns?(H!X9nJ~C7pEX8?tyNN3o0rH_Z`n(+tGPCq*vyc7WWLnD1Mr z*6GEE6@-Te8|Sb5*!ULip>yu_1VO~rfNhsA7sFz>s) z%@5!kAjR2i5*^a{X(IGv=f2-M1hQ`trRn!H!bx2l+)A+{qc_e$;e6TxD z^C^3~t~wvfhqPlE*}NY2Ec)u_8^! zt`rpk=_oUR(o__rgg7D~HBtf+NP^=iIx4{iN{dR98YuxO3D^ioiPQiggdUQRL(pXe2}(OC#gRv{ZV70a#@GrPnGk(w9!X{I zy&L6(&81qY?-DAdwu(Bh`1nM`lk}CO5Pq-NBTgk)=OH&H#OG(q;epp(gnOk{9xcH< zmnXVW5USoymc_Oz`pf!$ANlTbiv{hC^Fku&my1#BzmD00`tHfyAvAR@Bd>%q?HfJP zh4a{;r~Y}C@(}6jm7V45qA!YR^Vl8p-ZhPH{UH9(y@z(@c7$<)q29JIw8Ck?G3MERmNJk4g`VKA^WI z11TOWfUo_kuB^X>&m8-#fw)mzx~E*ra)7YE%nsPrE_}MOkx=zY%h1|7;=N#M^HQt^ zus@9lfUfcjt%!qOb{?}si?@C_;yZTm&a&BG)Y}2){1f!i%(9LxA7_ZtPnJuI9-v2o zjOAT^3`jm*1RUX2aimK(KJm6_zO%Hvw>+D2XUorNm8Gu~S`+Uvg1dd-nm)Vap8>6t zs|hOm<@F}yuUD`CM@~)H&9J=Y7fjONS>$#y6Fsrajlg0@W#Kq((+(gOY-w}yVlD|e`gC&vG4pDrs|u*$liWih=aT$Yki*(vcL_w>@Hs$c z`ETn#ERlTw)W3lW8b?k68Cy#QIlct(d`owcgBP#Kp*(-%!Emp=yL-SZ9;i-gi`9R# z!|bn%17#rO2}()_;|65^N}E{fFwlj-uemB;F7qcpLXO>(yZ-;47We*(&L-!N0vt`n zj~}-D9rpVz+e#j@BsX3DJwxqV*bcy~fj`j%Xv>6!UcW5tVyhq9Vz0_S02)Am-JWdi zFT?V5=IzK|i8n8Gs7qn2~pP4yoESz93`oE+50)zjgY%Hx1x6Nb2Sz=|wr*FiO zir+2+#>#d`u_LSN9)(+w88`Vs&M^UAElfZU>R!e;fS%R)ehuv((7ykgnT3?;zsn<4xbSp| zbp>E)j+o2o$eaJns|=8fJm?gVt1AEVcOw(PBT)oBlGjZ6uhUckl=BUZyp(eG_jt^| ziS@U9Gk~QdubBdXY_pX$j889BYQYj@*1%d5Un?(G`zPi_ZcPGN$-Y={Qo5^<6XXId z=?L03^*G}j(qsofFX)fsZwd2&%+=x6IFhs)7TZkMN#{K=C4`w`?%BL{aDijm=06#` zavs(4hS{>gT`e|FnI@J@yUPpBOC*$<&#KO9unTvE;H5D26`l5h53D?(KlkniLTl5o znme3WE%M4EHZ9NU!?wnMILNiSI)ui^iQr#&*8s;D`0DUe&x#h0Z7=U}=Zu#Bu=1^( zFf!EQXx2kqaQf(P=7{7f)kA7&OV$`&i=-hqJ+E+&bkVYi15`rV0o2EgF6b%eJ6CRM z+i4Bth%bLq6{GZ)hg_7;hNZj#U~$>nkfctSd)Sj>$ro~bwM8i20Olpm()p`0x%wWXwU`l#NbCoh_}pbKJ9^RD{rrU>Xri|O_8@F=j1pIF^!BRd4Z1vk z?Y5RDiHa)zWmJtY_0Ur1y0p>v7VGD;{;WpNo7i$ti_N>prp<&!{^@OtfW-jswrA_d zcU9yhO{8e2!t6|OL=R9}ColuZ_$e0qvBiI8a>xbl;D$qPGe>kPA%+4+7$#t^3lOEU z_uVb`>o{tF9wV3dV~g$Xw&P_JEFc;28USvqfZMPfXBwJ=vM9`r8nlB#sF_DC?I_%* z?3H2c!yjow%(=-6GI33AOzfdy{;|l1(`$p%bBX9h#0Fg=~0z8Wau%)Qp|G1>9 zfLeC8oPCiIwdlDa*=@e2GU;fUgKr*fHWaSi0)A9JFA8(FYrQ1;8JHd5;BAub6e*+9 zd8*9F`9|*ya%}oYQmjyH4>PzMnH7M;o$1Ph5O3bs1iDi4WU0(%nk61kR9;nhh`O=%`N~v4` zn46VebUm8n)Q`X(-c=Vl2PKynh_8rQvKOx8@m|OR9arOi)gnLbW<%1&!t#i&HCFh4xhwq44XtkG;iX&3cwhIz zep^V!#F6ZP^h&klocALp+N+;l>X#Sl1rWZY-3tI?v&qM~7vOv4n{qXEh@or-+dwmHVa%)1{g0gv1W!w(I@k&u;@Xy-sZkrcH+dn-4 zq#UXQAXbB`%4rAE0G%AjYx_mD%tv$X`b{8H;;3|*)XR}KfBKy}Fp(D1+`7zm$Eaf& zhw`?2a$bfMy$J3HU@D1Y0pbPpYJjWa=#dP0wlcXV8_=v7>9?=V$rE|s20Ej2o?8NL z_6?_qzEi9Jwxq#O~f=~{G8BeM?@)lyc(z#=pMdu(`hm(m!XO;NayQ6bA zNW01gk1s-l98K3%Dg$F*EP7Ht73E2y-p~5b z--ihAU8~3gdEg5WpQrPPnw=I+q0DpULEk)P0D3m&TyQimzuk_-a|CrGA)okk_57>9g|aw)TMj!PF_9c_C4oa6(CX>ig%Pb=Ai# z2waWs0J`jL$bo(xEkz^z%tGmlXwkeycOt3FnI*Dek{vzuprBeGg;&2#9@VM5ZG9Oq zLc|KJZ&)n0!!H8~cI{az0EeZgw0JcRzNY1?3+txO&X3uDvZcNS!7E(oKToW0WndOR zc!+&}G$lXH)$IIuVw$M&?y{Wp>@{EC0Ww%k@Yjz^dZx2uq60-AItkLCq>7vq#IiMbs?e=oM zU2h+WAS}lLs`#}sAn}%DI8?su68}Z-DUCp^Sfokn9nvHYoB%M3Za7>Bs~X760zFfk z*!S_Aw$JII7lXSh)mF9G%#oQFb;rwBt>>+sv5s|k;Br^3#QLdy>+waSM!In@P~GQR z?f2Vyemf8GKUPi^K)oHW0r=~OeRq!DtlTZ$f5I(n>Q1ogKqFMpvhE%4C3~%5 z<;Hpo#9mF@TH|FGOp5X%F1;xQ(#J>N1#4*%qcVRBlJ&W7W2c^6=J^ps5PsjC`!W98zd~1DIH%6tyafFTUfyi6wwxkuv+KHON~?4f>`NO; z4~}WAu=u`9E%*4ZmfY#KgF*EAvWsJ;M0(al>mpseQ(+x$h!lyA0|UjJ~{&fWUrngsADKi>r2$_bsCe^*hzI%DM} zSb#0*Qbh^ik?=Q?%C~j*YuL@7&`Y`d`aj%BG zIY$=%H3REkYM}iat@9hj@_)dd{&&n656a-y=^IL%*e}_y|L^1zeVc#S=^gLnd{baJ zx0{|?Dnz;@Jb#;R{|&k5KgXhcn|c3TStYr2{lhl$cX0!9zS2}OR96C-E*7q7fUzl; z+aUT^YZ54acIxW0vELF^z7&Bur(qWU0;v9(ph-AwE)rE0zS~T5`O2&50@TZR z(D0$9GC_Y&u={EjGg~IN|9k^K|EY+A;sG1Lf8mg}zJ3cp6u&@7L7<tEM z0JU8T{~24edb1tY;w1QU{09DП0DOXDnC8SR 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 literal 0 HcmV?d00001 From e0217921faa029277feb5c31e862ace4ec4b8008 Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Mon, 2 Nov 2026 17:43:08 -0800 Subject: [PATCH 5/7] Apply every key in a coalesced input chunk Ink delivers ordinary keys that arrive together (a held key while a frame renders, buffered input) as one string, so the viewer dropped them. Apply each key in order, rebuilding the tree per key so a filter change followed by navigation in the same chunk stays consistent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/src/App.tsx | 34 ++++++++++++++++++--------- tools/agent-monitor/test/App.test.tsx | 8 +++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/tools/agent-monitor/src/App.tsx b/tools/agent-monitor/src/App.tsx index b568a626..469941ac 100644 --- a/tools/agent-monitor/src/App.tsx +++ b/tools/agent-monitor/src/App.tsx @@ -1,5 +1,6 @@ import {useEffect, useMemo, useRef, useState} from 'react'; import {Box, Text, useApp, useInput, useWindowSize} from 'ink'; +import type {Key} from 'ink'; import {drawChrome, drawInspector, frameLayout, promptRef, resultRef} from './chrome.js'; import type {DetailTexts, Frame, ViewState} from './chrome.js'; import {errorMessage, JsonlReader} from './events.js'; @@ -81,21 +82,20 @@ export function App({model, loadContent, source = 'following', diagnostic, mode // A stopped source records nothing new, so time stays at the last observed event instead of following the wall clock. const now = source === 'stopped' ? sessionSeconds(model, model.lastEventAt) : nowSeconds(model, clock()); - useInput((input, key) => { - const order = tree.order; - const index = order.findIndex(n => n.id === state.selectedId); + /** Applies one key binding; returns false for an unbound key. */ + const applyKey = (input: string, key: Key): boolean => { + const current = buildTree(model, state.filter); + const order = current.order; + const selected = current.byId.get(state.selectedId) ?? current.root; + const index = order.indexOf(selected); const select = (target: number): void => { state.selectedId = order[clamp(target, 0, order.length - 1)]!.id; state.detailScroll = 0; }; - const siblings = node.parent?.children ?? [node]; - const sibling = siblings.indexOf(node); + const siblings = selected.parent?.children ?? [selected]; + const sibling = siblings.indexOf(selected); const pageStep = Math.max(1, frameLayout(cols, rows).box.h - 6); const panStep = Math.max(1, (state.span || now) / 4); - if (input === 'q' || key.escape) { - exit(); - return; - } if (key.tab) state.view = state.view === 'graph' ? 'timeline' : 'graph'; else if (input === 'j' || key.downArrow) select(index + 1); else if (input === 'k' || key.upArrow) select(index - 1); @@ -115,8 +115,20 @@ export function App({model, loadContent, source = 'following', diagnostic, mode else if (state.view === 'timeline' && (key.rightArrow || input === 'l')) state.pan -= panStep; else if (state.view === 'graph' && (key.leftArrow || input === 'h') && sibling > 0) state.selectedId = siblings[sibling - 1]!.id; else if (state.view === 'graph' && (key.rightArrow || input === 'l') && sibling < siblings.length - 1) state.selectedId = siblings[sibling + 1]!.id; - else return; - rerender(); + else return false; + return true; + }; + useInput((input, key) => { + // Ink delivers keys that arrive together (a held key during a slow render, buffered input) as one string. + let handled = false; + for (const char of input.length > 1 ? [...input] : [input]) { + if (char === 'q' || key.escape) { + exit(); + return; + } + handled = applyKey(char, key) || handled; + } + if (handled) rerender(); }); if (cols < MIN_COLS || rows < MIN_ROWS) { diff --git a/tools/agent-monitor/test/App.test.tsx b/tools/agent-monitor/test/App.test.tsx index c329d6a9..c32399ba 100644 --- a/tools/agent-monitor/test/App.test.tsx +++ b/tools/agent-monitor/test/App.test.tsx @@ -158,6 +158,14 @@ test('at 80 columns the header keeps its chips and t shows tool calls in the sin await frame(view, /Tools · 1/); }); +test('keys that arrive in one input chunk are each applied', async t => { + const view = mount(modelOf()); + t.after(() => view.cleanup()); + await frame(view, /Main session/); + view.stdin.write('jj'); + await frame(view, /Child result/); +}); + test('a slow text load for an earlier selection does not replace the current one', async t => { let release!: () => void; const gate = new Promise(resolve => { release = resolve; }); From f1b635ca06868c205e6a1ab2b28b5535fd74077b Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Tue, 3 Nov 2026 09:56:15 -0800 Subject: [PATCH 6/7] Address automated review feedback - Clean terminal control sequences in every drawn, measured or fitted string, and in the error printed after the alternate screen is restored. - Report task-prompt read failures, wrap read errors to the message column, show current warnings ahead of an informational reset notice, and only reuse loaded text within the same source generation. - Close the open main turn on session.shutdown; mark an execution superseded by an idle follow-up as unknown; show the main session as waiting whenever it has an outstanding blocking read; attach waits recorded before their target was known to its first execution. - Let timeline ticks grow in whole days for long windows. - Copy the unconsumed reader tail instead of retaining the whole buffer, and resolve first turns once per tree build (2,000-turn chain: 219 ms to 5 ms per build). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/README.md | 2 + tools/agent-monitor/src/App.tsx | 17 +++++---- tools/agent-monitor/src/chrome.ts | 8 ++-- tools/agent-monitor/src/cli.tsx | 4 +- tools/agent-monitor/src/events.ts | 3 +- tools/agent-monitor/src/model.ts | 19 +++++++-- tools/agent-monitor/src/screen.ts | 20 ++++++---- tools/agent-monitor/src/timeline.ts | 8 ++-- tools/agent-monitor/src/tree.ts | 37 ++++++++++++------ tools/agent-monitor/test/App.test.tsx | 53 ++++++++++++++++++++++++-- tools/agent-monitor/test/model.test.ts | 28 +++++++++++++- tools/agent-monitor/test/tree.test.ts | 6 +++ 12 files changed, 162 insertions(+), 43 deletions(-) diff --git a/tools/agent-monitor/README.md b/tools/agent-monitor/README.md index 47f4aba5..8919ee5b 100644 --- a/tools/agent-monitor/README.md +++ b/tools/agent-monitor/README.md @@ -81,6 +81,8 @@ final answer, not progress commentary, but lifecycle completion was not recorded. Lifecycle events name an agent's original launch call; they settle the agent's open execution and never overwrite an earlier recorded outcome. Follow-up executions on a reused agent remain separate from its earlier work. +If a follow-up arrives while the earlier execution has no recorded outcome, the +earlier one becomes `unknown` instead of staying `running`. The finished filter includes replied executions without claiming the agent was terminated. Running/unknown states are **last observed**, not a process-health check; silence or end-of-file does not prove that an agent is dead or finished. diff --git a/tools/agent-monitor/src/App.tsx b/tools/agent-monitor/src/App.tsx index 469941ac..9efad107 100644 --- a/tools/agent-monitor/src/App.tsx +++ b/tools/agent-monitor/src/App.tsx @@ -47,21 +47,23 @@ export function App({model, loadContent, source = 'following', diagnostic, mode const result = resultRef(model, node); const prompt = promptRef(model, node); const refKey = (ref: ContentRef | undefined): string => ref ? `${ref.eventId}:${ref.field}:${ref.location?.generation ?? ''}` : ''; - const textKey = `${node.id}|${refKey(result)}|${refKey(prompt)}`; - const [loaded, setLoaded] = useState<{key: string; nodeId: string; texts: DetailTexts}>({key: '', nodeId: '', texts: {pending: false}}); + // Loaded text is only reused for the same execution of the same source: a replaced log reuses execution IDs. + const nodeKey = `${model.epoch}|${node.id}`; + const textKey = `${nodeKey}|${refKey(result)}|${refKey(prompt)}`; + const [loaded, setLoaded] = useState<{nodeKey: string; texts: DetailTexts}>({nodeKey: '', texts: {pending: false}}); useEffect(() => { let cancelled = false; void Promise.allSettled([result ? loadContent(result) : undefined, prompt ? loadContent(prompt) : undefined]).then(([r, p]) => { if (cancelled) return; setLoaded({ - key: textKey, - nodeId: node.id, + nodeKey, texts: { pending: false, result: r.status === 'fulfilled' ? r.value : undefined, error: r.status === 'rejected' ? errorMessage(r.reason) : undefined, prompt: p.status === 'fulfilled' ? p.value : undefined, + promptError: p.status === 'rejected' ? errorMessage(p.reason) : undefined, }, }); }); @@ -135,11 +137,12 @@ export function App({model, loadContent, source = 'following', diagnostic, mode return {`Terminal too small for the agent monitor (need ${MIN_COLS}x${MIN_ROWS}, have ${cols}x${rows}). q: quit`}; } // A running agent's latest message changes often; keep showing its previous text until the new one is read. - const texts: DetailTexts = loaded.key === textKey || loaded.nodeId === node.id ? loaded.texts : {pending: Boolean(result || prompt)}; - const warnings = [...model.warnings].join('; '); + const texts: DetailTexts = loaded.nodeKey === nodeKey ? loaded.texts : {pending: Boolean(result || prompt)}; + const warnings = [...model.warnings].join('; ') || undefined; const frame: Frame = { model, state, tree, now, spin, cols, rows, source, texts, - diagnostic: diagnostic ?? (warnings || undefined), + // A stopped source's error comes first; current warnings outrank an informational notice such as a reset. + diagnostic: source === 'stopped' ? diagnostic ?? warnings : warnings ?? diagnostic, }; const layout = frameLayout(cols, rows); const screen = new Screen(cols, rows); diff --git a/tools/agent-monitor/src/chrome.ts b/tools/agent-monitor/src/chrome.ts index 9ec7e7e1..dd536130 100644 --- a/tools/agent-monitor/src/chrome.ts +++ b/tools/agent-monitor/src/chrome.ts @@ -28,6 +28,7 @@ export interface ViewState { export interface DetailTexts { prompt?: string; + promptError?: string; result?: string; pending: boolean; error?: string; @@ -225,8 +226,9 @@ export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): vo scr.rput(x + leftW - 1, top, VISUALS[visual].label, {fg: VISUALS[visual].tone}); scr.put(x + 2, top, fit(title, leftW - 4 - width(VISUALS[visual].label)), {fg: 'fg', bold: true}); scr.put(x, top + 1, fit(metaLine(frame, node), leftW), MUTED); - const prompt = frame.texts.prompt ?? (node.run ? node.run.task : undefined) ?? 'Not recorded'; - scr.seqFit(x, top + 2, [['Task ', {fg: 'fg', bold: true}], [prompt.replace(/\s+/g, ' '), MUTED]], leftW); + const {promptError} = frame.texts; + const prompt = promptError ? `Cannot read recorded prompt: ${promptError}` : frame.texts.prompt ?? (node.run ? node.run.task : undefined) ?? 'Not recorded'; + scr.seqFit(x, top + 2, [['Task ', {fg: 'fg', bold: true}], [prompt.replace(/\s+/g, ' '), promptError ? {fg: 'danger'} : MUTED]], leftW); const bodyTop = top + 4; const bodyRows = Math.max(0, rows - 4); if (!split && frame.state.showTools) { @@ -237,7 +239,7 @@ export function drawInspector(scr: Screen, frame: Frame, box: Layout['box']): vo const [heading, note, headingStyle, empty] = resultHeading(frame, node); let body: string[]; if (frame.texts.pending) body = ['Loading recorded text…']; - else if (frame.texts.error) body = [`Cannot read recorded text: ${frame.texts.error}`]; + else if (frame.texts.error) body = wrap(`Cannot read recorded text: ${frame.texts.error}`, leftW); else body = frame.texts.result ? wrapCached(frame.texts.result, leftW) : [empty]; const maxScroll = Math.max(0, body.length - bodyRows); frame.state.detailScroll = Math.min(frame.state.detailScroll, maxScroll); diff --git a/tools/agent-monitor/src/cli.tsx b/tools/agent-monitor/src/cli.tsx index 5df45ccc..05c2f4d1 100644 --- a/tools/agent-monitor/src/cli.tsx +++ b/tools/agent-monitor/src/cli.tsx @@ -4,6 +4,7 @@ import {parseArgs} from 'node:util'; import {render} from 'ink'; import {LiveApp} from './App.js'; import {errorMessage} from './events.js'; +import {clean} from './screen.js'; import {COLOR_MODES, isColorMode} from './theme.js'; async function main(): Promise { @@ -48,6 +49,7 @@ async function main(): Promise { } void main().catch(error => { - console.error(`agent-monitor: ${errorMessage(error)}`); + // Source errors can quote log content; strip control sequences before they reach the restored terminal. + console.error(`agent-monitor: ${clean(errorMessage(error))}`); process.exitCode = 1; }); diff --git a/tools/agent-monitor/src/events.ts b/tools/agent-monitor/src/events.ts index 818919c7..bb01fc5f 100644 --- a/tools/agent-monitor/src/events.ts +++ b/tools/agent-monitor/src/events.ts @@ -146,7 +146,8 @@ export class JsonlReader { } cursor = end + 1; } - this.pending = buffer.subarray(cursor); + // Copy the unconsumed tail: a subarray would keep a large, already-parsed record's buffer alive. + this.pending = cursor === 0 ? buffer : Buffer.from(buffer.subarray(cursor)); this.pendingOffset += cursor; this.offset += chunk.length; } diff --git a/tools/agent-monitor/src/model.ts b/tools/agent-monitor/src/model.ts index ebc35ced..23e24bc5 100644 --- a/tools/agent-monitor/src/model.ts +++ b/tools/agent-monitor/src/model.ts @@ -86,6 +86,8 @@ export class AgentModel { readonly mainTurns: MainTurn[] = []; readonly mainTools: ToolActivity[] = []; mainToolCount = 0; + /** Counts `reset()` calls, so views can tell a rebuilt source from the one they last displayed. */ + epoch = 0; version?: string; sessionId?: string; sessionModel?: string; @@ -101,6 +103,7 @@ export class AgentModel { private readonly repliedThisTurn = new Set(); reset(): void { + this.epoch++; this.runs.clear(); this.warnings.clear(); this.waits.clear(); @@ -138,9 +141,13 @@ export class AgentModel { this.sessionStart = e.timestamp; if (this.version !== '1.0.104') this.warnings.add(`Untested CLI event format: ${this.version ?? 'unknown'}`); break; - case 'session.shutdown': + case 'session.shutdown': { this.sessionEnded = e.timestamp; + // Work after a later resume starts a new span instead of painting the offline gap as activity. + const last = this.mainTurns.at(-1); + if (last && !last.end) last.end = e.timestamp; break; + } case 'session.resume': this.sessionEnded = undefined; break; @@ -165,6 +172,8 @@ export class AgentModel { if (e.agentId) { const current = this.latest.get(e.agentId); if (!current || isFinished(current) || (current.hasActivity && d.delivery === 'idle')) { + // An idle delivery means the previous execution's turn is over; its outcome was not recorded. + if (current && !isFinished(current)) current.status = 'unknown'; const run = this.create(e, `message:${e.id}`, e.agentId); run.name = current?.name ?? run.name; run.agentType = current?.agentType; @@ -256,9 +265,9 @@ export class AgentModel { mainStatus(): 'running' | 'waiting' | 'idle' | 'ended' { if (this.sessionEnded) return 'ended'; + if ([...this.waits.values()].some(edge => edge.fromRunId === null)) return 'waiting'; const last = this.mainTurns.at(-1); - if (!last || last.end) return 'idle'; - return [...this.waits.values()].some(edge => edge.fromRunId === null) ? 'waiting' : 'running'; + return !last || last.end ? 'idle' : 'running'; } private create(e: SessionEvent, id: string, agentId: string): AgentRun { @@ -267,6 +276,10 @@ export class AgentModel { lastActivity: e.timestamp, tools: [], hasActivity: false, }; this.runs.set(id, run); + // Blocking reads recorded before any execution of this agent was known waited on its first execution. + if (!this.latest.has(agentId)) { + for (const edge of this.waitLog.values()) if (edge.toAgentId === agentId && !edge.toRunId) edge.toRunId = id; + } this.latest.set(agentId, run); return run; } diff --git a/tools/agent-monitor/src/screen.ts b/tools/agent-monitor/src/screen.ts index f660c82d..a8dd1a9a 100644 --- a/tools/agent-monitor/src/screen.ts +++ b/tools/agent-monitor/src/screen.ts @@ -28,25 +28,29 @@ interface Cell { } const graphemes = new Intl.Segmenter(undefined, {granularity: 'grapheme'}); +const UNSAFE = /[\u0000-\u001f\u007f-\u009f]/; const CONTROL = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g; const JUNCTIONS: Record = { '1100': '│', '0011': '─', '0101': '┌', '0110': '┐', '1001': '└', '1010': '┘', '1101': '├', '1110': '┤', '0111': '┬', '1011': '┴', '1111': '┼', '0100': '╷', '1000': '╵', '0001': '╶', '0010': '╴', }; -export const width = (text: string): number => stringWidth(text); - /** Removes terminal control sequences so recorded text cannot move the cursor or restyle the screen. */ export function clean(text: string): string { + if (!UNSAFE.test(text)) return text; return stripVTControlCharacters(text).replace(/\t/g, ' ').replace(CONTROL, ''); } +/** Display width of text as it will be drawn, i.e. after `clean`. */ +export const width = (text: string): number => stringWidth(clean(text)); + export function fit(text: string, max: number): string { if (max <= 0) return ''; - if (stringWidth(text) <= max) return text; + const safe = clean(text); + if (stringWidth(safe) <= max) return safe; let out = ''; let used = 0; - for (const {segment} of graphemes.segment(text)) { + for (const {segment} of graphemes.segment(safe)) { const w = stringWidth(segment); if (used + w > max - 1) break; out += segment; @@ -100,7 +104,7 @@ export function junction(up: boolean, down: boolean, left: boolean, right: boole return JUNCTIONS[`${+up}${+down}${+left}${+right}`] ?? ' '; } -/** Fixed-size grid of styled terminal cells; every write is clipped to the grid. */ +/** Fixed-size grid of styled terminal cells; every write is cleaned of control sequences and clipped to the grid. */ export class Screen { private readonly cells: Cell[][]; @@ -114,7 +118,7 @@ export class Screen { put(x: number, y: number, text: string, style: Style = {}): number { const row = this.cells[y]; - for (const {segment} of graphemes.segment(text)) { + for (const {segment} of graphemes.segment(clean(text))) { const w = stringWidth(segment); if (w === 0) continue; if (row && x >= 0 && x < this.cols) { @@ -133,7 +137,7 @@ export class Screen { } rput(right: number, y: number, text: string, style: Style = {}): number { - return this.put(right - stringWidth(text) + 1, y, text, style); + return this.put(right - width(text) + 1, y, text, style); } seq(x: number, y: number, parts: readonly Part[]): number { @@ -147,7 +151,7 @@ export class Screen { for (const [text, style] of parts) { const room = max - used; if (room <= 0) break; - const shown = stringWidth(text) > room ? fit(text, room) : text; + const shown = fit(text, room); this.put(x + used, y, shown, {...style, ...extra}); used += stringWidth(shown); } diff --git a/tools/agent-monitor/src/timeline.ts b/tools/agent-monitor/src/timeline.ts index 408432f1..53710319 100644 --- a/tools/agent-monitor/src/timeline.ts +++ b/tools/agent-monitor/src/timeline.ts @@ -7,7 +7,8 @@ import type {Node, Visual} from './tree.js'; import type {Tone} from './theme.js'; const MUTED: Style = {fg: 'muted'}; -const STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400]; +const DAY = 86400; +const STEPS = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400, 21600, 43200, DAY]; const BAR: Record = { running: 'accent', waiting: 'accent', done: 'success', failed: 'danger', cancelled: 'muted', replied: 'done', idle: 'muted', ended: 'muted', unknown: 'muted', @@ -15,8 +16,9 @@ const BAR: Record = { const END_MARKS: Visual[] = ['failed', 'cancelled', 'replied']; const clamp = (value: number, lo: number, hi: number): number => Math.max(lo, Math.min(hi, value)); +/** Tick spacing of at least `raw` seconds; beyond a day it grows in whole days so long windows keep few ticks. */ export function niceStep(raw: number): number { - return STEPS.find(step => step >= raw) ?? STEPS.at(-1)!; + return STEPS.find(step => step >= raw) ?? Math.ceil(raw / DAY) * DAY; } const pad2 = (n: number): string => String(n).padStart(2, '0'); @@ -31,7 +33,7 @@ export function tickLabel(seconds: number, step = 60): string { return h ? `${h}:${pad2(m)}:${pad2(s)}` : `${m}:${pad2(s)}`; } if (step < 3600) return h ? `${h}h${pad2(m)}` : `${m}m`; - return `${h}h`; + return step < DAY ? `${h}h` : `${Math.floor(seconds / DAY)}d`; } const TYPE_SHORT: Record = {'general-purpose': 'general'}; diff --git a/tools/agent-monitor/src/tree.ts b/tools/agent-monitor/src/tree.ts index 47fb18f6..225e3e5c 100644 --- a/tools/agent-monitor/src/tree.ts +++ b/tools/agent-monitor/src/tree.ts @@ -59,15 +59,29 @@ function matches(run: AgentRun, filter: Filter): boolean { const startOf = (run: AgentRun): number => Date.parse(run.startedAt ?? run.lastActivity); -/** The agent's first execution; later turns hang off it as siblings so a long conversation does not nest deeper. */ -function firstTurn(model: AgentModel, run: AgentRun): AgentRun { - const seen = new Set(); - let first = run; - while (first.previousRunId && !seen.has(first.id)) { - seen.add(first.id); - const previous = model.runs.get(first.previousRunId); - if (!previous) break; - first = previous; +/** + * Each execution's first turn: later turns hang off it as siblings so a long conversation does not nest deeper. + * Resolved once per build with shared results, because reused agents can have long predecessor chains. + */ +function firstTurns(model: AgentModel): Map { + const first = new Map(); + for (const run of model.runs.values()) { + const chain: AgentRun[] = []; + const onChain = new Set(); + let cursor = run; + let head = first.get(cursor.id); + while (!head) { + chain.push(cursor); + onChain.add(cursor.id); + const previous = cursor.previousRunId ? model.runs.get(cursor.previousRunId) : undefined; + // A missing predecessor or a cycle ends the chain at the last execution reached. + if (!previous || onChain.has(previous.id)) head = cursor; + else { + cursor = previous; + head = first.get(cursor.id); + } + } + for (const member of chain) first.set(member.id, head); } return first; } @@ -76,9 +90,10 @@ function firstTurn(model: AgentModel, run: AgentRun): AgentRun { export function buildTree(model: AgentModel, filter: Filter): Tree { const root: Node = {id: MAIN, children: [], edge: 'root', depth: 0, ctx: filter !== 'all'}; const parentOf = new Map(); + const first = firstTurns(model); for (const run of model.runs.values()) { - const first = firstTurn(model, run); - if (first !== run) parentOf.set(run.id, {id: first.id, edge: 'turn'}); + const head = first.get(run.id)!; + if (head !== run) parentOf.set(run.id, {id: head.id, edge: 'turn'}); else if (run.parentRunId === null) parentOf.set(run.id, {id: MAIN, edge: 'spawn'}); else if (run.parentRunId && model.runs.has(run.parentRunId)) parentOf.set(run.id, {id: run.parentRunId, edge: 'spawn'}); else parentOf.set(run.id, {id: MAIN, edge: 'orphan'}); diff --git a/tools/agent-monitor/test/App.test.tsx b/tools/agent-monitor/test/App.test.tsx index c32399ba..07c47776 100644 --- a/tools/agent-monitor/test/App.test.tsx +++ b/tools/agent-monitor/test/App.test.tsx @@ -4,7 +4,7 @@ import {stripVTControlCharacters} from 'node:util'; import {test} from 'node:test'; import {render} from 'ink-testing-library'; import {App} from '../src/App.js'; -import type {ContentRef} from '../src/events.js'; +import type {ContentRef, EventRecord} from '../src/events.js'; import {AgentModel} from '../src/model.js'; import {fixture, record, start} from './fixture.js'; @@ -93,10 +93,13 @@ test('filtering keeps ancestors as context and narrow terminals show a resize me await frame(small, /Terminal too small/); }); -test('recorded control sequences are inert and long results scroll inside the inspector', async t => { +test('recorded control sequences are inert in every field and long results scroll inside the inspector', async t => { const model = modelOf([ record('session.start', {copilotVersion: '1.0.104', sessionId: 'scroll'}), - start('agent', 'agent'), + record('subagent.started', { + toolCallId: 'agent', agentName: 'general-purpose', agentDisplayName: '\u001b[31mred-agent\u001b[0m', + agentDescription: 'Task \u001b[1mbold\u001b[0m', executionMode: 'background', + }, 'agent'), record('assistant.message', {content: '\u001b[2J' + Array.from({length: 40}, (_, i) => `RESULT-LINE-${i}`).join('\n')}, 'agent'), record('subagent.completed', {toolCallId: 'agent'}, 'agent'), ]); @@ -105,7 +108,9 @@ test('recorded control sequences are inert and long results scroll inside the in await frame(view, /Main session/); view.stdin.write('j'); let output = await frame(view, /RESULT-LINE-0/); - assert.doesNotMatch(output, /\[2J/); + assert.match(output, /red-agent/); + assert.match(output, /Task bold/); + assert.doesNotMatch(output, /\[(2J|31m|1m|0m)/); assert.match(output, /lines 1-/); view.stdin.write('\u001b[6~'); output = await frame(view, /RESULT-LINE-6\b/); @@ -166,6 +171,46 @@ test('keys that arrive in one input chunk are each applied', async t => { await frame(view, /Child result/); }); +test('read failures for the prompt and the result are reported inside the message column', async t => { + const failing = async (): Promise => { throw new Error(`disk read failed ${'x'.repeat(120)}`); }; + const model = modelOf(); + const clock = (): number => Date.parse(model.lastEventAt!) + 5000; + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /Main session/); + view.stdin.write('j'); + const output = await frame(view, /Task Cannot read recorded prompt: disk read failed/); + const error = output.split('\n').find(line => line.includes('Cannot read recorded text')); + assert.match(error ?? '', /Cannot read recorded text: disk read failed\s+│.*│/); +}); + +test('current warnings outrank an informational source notice', async t => { + const model = modelOf([record('session.start', {copilotVersion: '9.9.9', sessionId: 'notice'})]); + const clock = (): number => Date.parse(model.lastEventAt!) + 5000; + const notice = 'Session log was replaced/truncated; view rebuilt from the new source.'; + const view = render(); + t.after(() => view.cleanup()); + await frame(view, /Untested CLI event format: 9\.9\.9/); +}); + +test('text read from a replaced source is not shown for the rebuilt view', async t => { + const rebuilt = (): EventRecord[] => fixture().map(item => item.event.data.content === 'Parent result' + ? {event: {...item.event, data: {...item.event.data, content: 'Rebuilt result'}}} : item); + const loadContent = (ref: ContentRef): Promise => ref.inline === 'Rebuilt result' ? new Promise(() => {}) : load(ref); + const model = modelOf(); + const clock = (): number => Date.parse(model.lastEventAt!) + 5000; + const app = (revision: number) => ; + const view = render(app(0)); + t.after(() => view.cleanup()); + await frame(view, /Main session/); + view.stdin.write('j'); + await frame(view, /Parent result/); + model.reset(); + for (const event of rebuilt()) model.apply(event); + view.rerender(app(1)); + assert.doesNotMatch(await frame(view, /Loading recorded text/), /Parent result/); +}); + test('a slow text load for an earlier selection does not replace the current one', async t => { let release!: () => void; const gate = new Promise(resolve => { release = resolve; }); diff --git a/tools/agent-monitor/test/model.test.ts b/tools/agent-monitor/test/model.test.ts index 756f25c0..0407ab63 100644 --- a/tools/agent-monitor/test/model.test.ts +++ b/tools/agent-monitor/test/model.test.ts @@ -139,14 +139,38 @@ test('tool arguments of unexpected types are ignored instead of stopping the vie assert.equal(model.waits.size, 0); }); -test('a resumed session is live again after a shutdown', () => { +test('a resumed session is live again after a shutdown, and the offline gap is not main-session activity', () => { const model = new AgentModel(); model.apply(record('session.start', {copilotVersion: '1.0.104', sessionId: 'resume'})); - model.apply(record('session.shutdown', {})); + model.apply(record('assistant.turn_start', {turnId: '0'})); + const shutdown = record('session.shutdown', {}); + model.apply(shutdown); assert.equal(model.mainStatus(), 'ended'); model.apply(record('session.resume', {})); model.apply(record('assistant.turn_start', {turnId: '0'})); assert.equal(model.mainStatus(), 'running'); + assert.deepEqual(model.mainTurns.map(turn => turn.end), [shutdown.event.timestamp, undefined]); +}); + +test('an execution left open by an idle follow-up becomes unknown instead of running forever', () => { + const model = new AgentModel(); + model.apply(start('call', 'a')); + model.apply(record('assistant.turn_start', {turnId: '0'}, 'a')); + model.apply(record('assistant.message', {content: 'Still checking', phase: 'commentary'}, 'a')); + model.apply(record('assistant.turn_end', {turnId: '0'}, 'a')); + model.apply(record('user.message', {content: 'Next step', delivery: 'idle'}, 'a')); + model.apply(record('subagent.completed', {toolCallId: 'call'}, 'a')); + const [first, followup] = [...model.runs.values()]; + assert.equal(first!.status, 'unknown'); + assert.equal(followup!.status, 'completed'); +}); + +test('a blocking read recorded before its target is known marks main waiting and attaches to that first execution', () => { + const model = new AgentModel(); + model.apply(record('tool.execution_start', {toolCallId: 'wait', toolName: 'read_agent', arguments: {agent_id: 'b', wait: true}})); + assert.equal(model.mainStatus(), 'waiting'); + model.apply(start('b-call', 'b')); + assert.equal(model.waitLog.get('wait')?.toRunId, 'launch:b-call'); }); test('reset clears session state so a rewritten log replays from scratch', () => { diff --git a/tools/agent-monitor/test/tree.test.ts b/tools/agent-monitor/test/tree.test.ts index fc3e61e4..9fe55464 100644 --- a/tools/agent-monitor/test/tree.test.ts +++ b/tools/agent-monitor/test/tree.test.ts @@ -4,6 +4,7 @@ import {CARD_H, layoutGraph, levelY} from '../src/graph.js'; import type {Placed} from '../src/graph.js'; import {AgentModel} from '../src/model.js'; import {Screen, width} from '../src/screen.js'; +import {niceStep, tickLabel} from '../src/timeline.js'; import {buildTree, MAIN, mainSpans, nowSeconds} from '../src/tree.js'; import {fixture, record, start} from './fixture.js'; @@ -49,6 +50,11 @@ test('a wide character cut off by the right edge never widens a screen row', () assert.equal(scr.segments()[0]!.reduce((sum, segment) => sum + width(segment.text), 0), 10); }); +test('timeline ticks stay few for windows spanning several days', () => { + for (const days of [3, 30]) assert.ok(days * 86400 / niceStep(days * 86400 / 6) <= 6); + assert.equal(tickLabel(2 * 86400, 86400), '2d'); +}); + test('graph layout centres parents over their children without overlapping cards', () => { const model = new AgentModel(); for (const event of fixture()) model.apply(event); From 987994a30fe54ae47237671cbb1519ffd65375e6 Mon Sep 17 00:00:00 2001 From: Yucheng Hu Date: Tue, 3 Nov 2026 10:24:05 -0800 Subject: [PATCH 7/7] Add an /agent-monitor Copilot CLI extension and native-tab mockups - extension/extension.mjs: experimental Copilot CLI extension adding /agent-monitor [graph|timeline|text]. It prints the session's agent tree in the conversation and opens the interactive view beside the CLI in a Windows Terminal or tmux split pane (otherwise it prints the command). - src/summary.ts: the plain-text agent tree, built from the same model. - --view graph|timeline, and v as a second key for switching views. - Treat 1.0.105 session logs as verified (local 1.0.105 sessions replay without errors). - docs/: mockups of the two views as an Agents tab in Copilot CLI's own tab bar, for discussing a built-in version. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tools/agent-monitor/README.md | 37 ++++++++++-- .../docs/mockup-agents-tab-graph.png | Bin 0 -> 112407 bytes .../docs/mockup-agents-tab-timeline.png | Bin 0 -> 102308 bytes tools/agent-monitor/extension/extension.mjs | 56 ++++++++++++++++++ tools/agent-monitor/src/App.tsx | 13 ++-- tools/agent-monitor/src/cli.tsx | 10 +++- tools/agent-monitor/src/model.ts | 4 +- tools/agent-monitor/src/summary.ts | 49 +++++++++++++++ tools/agent-monitor/test/summary.test.ts | 25 ++++++++ 9 files changed, 179 insertions(+), 15 deletions(-) create mode 100644 tools/agent-monitor/docs/mockup-agents-tab-graph.png create mode 100644 tools/agent-monitor/docs/mockup-agents-tab-timeline.png create mode 100644 tools/agent-monitor/extension/extension.mjs create mode 100644 tools/agent-monitor/src/summary.ts create mode 100644 tools/agent-monitor/test/summary.test.ts diff --git a/tools/agent-monitor/README.md b/tools/agent-monitor/README.md index 8919ee5b..1b9ce3a5 100644 --- a/tools/agent-monitor/README.md +++ b/tools/agent-monitor/README.md @@ -32,11 +32,36 @@ records. The directory can also belong to a historical session. There is no automatic scan of other sessions. For development, use `npm start -- --session ""`. -Use `node .\dist\cli.js --help` for command-line help. Add `--theme ` to +Use `node .\dist\cli.js --help` for command-line help. Add `--view timeline` to +open on the Timeline. Add `--theme ` to match the Copilot CLI `/theme` color mode: `default`, `light`, `dim`, `high-contrast`, or `colorblind` (Primer semantic colors; needs a truecolor terminal such as Windows Terminal). Minimum size: 80×24. +## Open it from Copilot CLI (experimental) + +[`extension/`](extension/extension.mjs) is a +[Copilot CLI extension](https://docs.github.com/en/copilot/tutorials/create-an-extension) +that adds an `/agent-monitor` command to the session you are working in: + +- `/agent-monitor` prints the session's agent tree in the conversation and opens + the Graph view beside the CLI. +- `/agent-monitor timeline` does the same with the Timeline view. +- `/agent-monitor text` only prints the tree. + +The interactive view opens as a Windows Terminal split pane (or a tmux split); +in other terminals the command prints the line to run in another tab. +Extensions are an experimental Copilot CLI feature. After `npm run build`, link +this folder into your Copilot home once: + +```powershell +New-Item -ItemType Junction -Path "$HOME\.copilot\extensions\agent-monitor" -Target "$PWD\extension" +``` + +Then start Copilot CLI with `--experimental` (or run `/experimental on`) and use +`/agent-monitor`. Remove the junction to uninstall. Copilot CLI has no extension +API for adding tabs to its own tab bar, so the views open beside it. + ## Views and keys The layout follows Copilot CLI's own interface: a tab bar, blue query chips, a @@ -67,7 +92,7 @@ is loaded from the selected log on demand rather than copied to a new database. | `g` / `G` | Select the first/last execution | | Left/Right or `h`/`l` | Graph: previous/next sibling. Timeline: pan earlier/later | | `+` / `-` | Timeline: zoom in/out (beyond the agent window up to the whole session) | -| Tab | Switch between Graph and Timeline | +| Tab or `v` | Switch between Graph and Timeline | | `f` | Cycle all, active, finished; ancestors remain as dimmed context | | `t` | Show tool calls in the detail dialog instead of relationships (below 100 columns: instead of the message) | | Page Up / Page Down | Scroll the selected result | @@ -89,8 +114,9 @@ check; silence or end-of-file does not prove that an agent is dead or finished. ## Relationships and compatibility -Initial compatibility was verified against Copilot CLI **1.0.104**, using a local -parallel/nested-agent trace and a same-agent follow-up. The on-disk event format +Compatibility was verified against Copilot CLI **1.0.104** and **1.0.105**, using +a local parallel/nested-agent trace, a same-agent follow-up, and a replay of local +session logs. The on-disk event format is version-sensitive, not a supported stable public attachment API. Other CLI versions display a compatibility warning; malformed or unsupported relevant records produce explicit diagnostics rather than fabricated results. @@ -134,4 +160,5 @@ Tests use synthetic records and temporary files, covering replay/tailing, UTF-8 boundaries, source reset, agent/result attribution, lifecycle routing and cancellation, session resume, recorded wait history, reused agents and later turns, graph layout and scrolling, the Graph/Timeline views, filtering, -hidden-by-default tool calls, narrow terminals, and result scrolling. +hidden-by-default tool calls, narrow terminals, result scrolling, and the +text summary printed by the extension. diff --git a/tools/agent-monitor/docs/mockup-agents-tab-graph.png b/tools/agent-monitor/docs/mockup-agents-tab-graph.png new file mode 100644 index 0000000000000000000000000000000000000000..b4c31b58dd5e52ca01db5c80cbb5eb3c485ef4be GIT binary patch literal 112407 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

)Slu&5f~kIRW;QA>Agl-|T!5qY~K59M6^ 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

iyKa{;^78c@|JnN({^+@1|1UsJ BqHh2I literal 0 HcmV?d00001 diff --git a/tools/agent-monitor/docs/mockup-agents-tab-timeline.png b/tools/agent-monitor/docs/mockup-agents-tab-timeline.png new file mode 100644 index 0000000000000000000000000000000000000000..3fc1cb609937e4f9ff80989100510060ee08a77c GIT binary patch literal 102308 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 A-)i#T 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 { + const choice = ctx.args.trim().toLowerCase() || 'graph'; + if (!CHOICES.includes(choice)) { + await session.log('Usage: /agent-monitor [graph|timeline|text]', {level: 'warning', ephemeral: true}); + return; + } + const sessionDir = session.workspacePath ?? join(process.env.COPILOT_HOME ?? join(homedir(), '.copilot'), 'session-state', ctx.sessionId); + try { + await session.log(await summarizeSession(sessionDir), {ephemeral: true}); + } catch (error) { + await session.log(`agent-monitor: ${error instanceof Error ? error.message : String(error)}`, {level: 'error', ephemeral: true}); + return; + } + if (choice !== 'text') openBeside(sessionDir, choice); + }, + }], +}); + +/** Opens the viewer in a split pane where the terminal supports one; otherwise prints the command to run. */ +function openBeside(sessionDir, view) { + const args = [cli, '--session', sessionDir, '--view', view]; + const manual = () => session.log( + `Run this in another terminal tab for the interactive view:\n${[node, ...args].map(arg => JSON.stringify(arg)).join(' ')}`, + {ephemeral: true}, + ); + const pane = process.env.WT_SESSION + ? ['wt.exe', ['-w', '0', 'split-pane', '--vertical', '--title', 'Agent monitor', '--', node, ...args]] + : process.env.TMUX ? ['tmux', ['split-window', '-h', node, ...args]] : undefined; + if (!pane) { + void manual(); + return; + } + const child = spawn(pane[0], pane[1], {detached: true, stdio: 'ignore', windowsHide: true}); + child.on('error', () => void manual()); + child.unref(); +} diff --git a/tools/agent-monitor/src/App.tsx b/tools/agent-monitor/src/App.tsx index 9efad107..e416f973 100644 --- a/tools/agent-monitor/src/App.tsx +++ b/tools/agent-monitor/src/App.tsx @@ -2,7 +2,7 @@ import {useEffect, useMemo, useRef, useState} from 'react'; import {Box, Text, useApp, useInput, useWindowSize} from 'ink'; import type {Key} from 'ink'; import {drawChrome, drawInspector, frameLayout, promptRef, resultRef} from './chrome.js'; -import type {DetailTexts, Frame, ViewState} from './chrome.js'; +import type {DetailTexts, Frame, ViewName, ViewState} from './chrome.js'; import {errorMessage, JsonlReader} from './events.js'; import type {ContentRef} from './events.js'; import {drawGraph} from './graph.js'; @@ -25,18 +25,19 @@ interface AppProps { source?: Frame['source']; diagnostic?: string; mode?: ColorMode; + view?: ViewName; width?: number; height?: number; clock?: () => number; } -export function App({model, loadContent, source = 'following', diagnostic, mode = 'default', width, height, clock = Date.now}: AppProps) { +export function App({model, loadContent, source = 'following', diagnostic, mode = 'default', view = 'graph', width, height, clock = Date.now}: AppProps) { const size = useWindowSize(); const {exit} = useApp(); const cols = width ?? size.columns; const rows = height ?? size.rows; const state = useRef({ - view: 'graph', filter: 'all', selectedId: MAIN, showTools: false, zoom: 1, pan: 0, detailScroll: 0, graphX: 0, graphY: 0, laneTop: 0, span: 0, + view, filter: 'all', selectedId: MAIN, showTools: false, zoom: 1, pan: 0, detailScroll: 0, graphX: 0, graphY: 0, laneTop: 0, span: 0, }).current; const [, setTick] = useState(0); const [spin, setSpin] = useState(0); @@ -98,7 +99,7 @@ export function App({model, loadContent, source = 'following', diagnostic, mode const sibling = siblings.indexOf(selected); const pageStep = Math.max(1, frameLayout(cols, rows).box.h - 6); const panStep = Math.max(1, (state.span || now) / 4); - if (key.tab) state.view = state.view === 'graph' ? 'timeline' : 'graph'; + if (key.tab || input === 'v') state.view = state.view === 'graph' ? 'timeline' : 'graph'; else if (input === 'j' || key.downArrow) select(index + 1); else if (input === 'k' || key.upArrow) select(index - 1); else if (input === 'g' || key.home) select(0); @@ -170,7 +171,7 @@ function clamp(value: number, lo: number, hi: number): number { return Math.max(lo, Math.min(hi, value)); } -export function LiveApp({file, mode, onError}: {file: string; mode: ColorMode; onError: (message: string) => void}) { +export function LiveApp({file, mode, view, onError}: {file: string; mode: ColorMode; view?: ViewName; onError: (message: string) => void}) { const reader = useMemo(() => new JsonlReader(file), [file]); const model = useMemo(() => new AgentModel(), [file]); const [revision, setRevision] = useState(0); @@ -204,5 +205,5 @@ export function LiveApp({file, mode, onError}: {file: string; mode: ColorMode; o }; }, [reader, model, onError]); const source = stopped ? 'stopped' : model.sessionEnded ? 'ended' : 'following'; - return ; + return ; } diff --git a/tools/agent-monitor/src/cli.tsx b/tools/agent-monitor/src/cli.tsx index 05c2f4d1..687aad71 100644 --- a/tools/agent-monitor/src/cli.tsx +++ b/tools/agent-monitor/src/cli.tsx @@ -3,6 +3,7 @@ import {join, resolve} from 'node:path'; import {parseArgs} from 'node:util'; import {render} from 'ink'; import {LiveApp} from './App.js'; +import {VIEWS} from './chrome.js'; import {errorMessage} from './events.js'; import {clean} from './screen.js'; import {COLOR_MODES, isColorMode} from './theme.js'; @@ -12,29 +13,32 @@ async function main(): Promise { options: { session: {type: 'string'}, theme: {type: 'string', default: 'default'}, + view: {type: 'string', default: 'graph'}, help: {type: 'boolean', short: 'h'}, }, strict: true, allowPositionals: false, }); if (values.help) { - console.log('Usage: agent-monitor --session [--theme ]\n' + console.log('Usage: agent-monitor --session [--theme ] [--view graph|timeline]\n' + 'Read-only terminal viewer for that directory\'s events.jsonl.\n' + `Themes: ${COLOR_MODES.join(', ')} (default: default).\n` - + 'Views: Graph (default) and Timeline; Tab switches.\n' + + 'Views: Graph (default) and Timeline; Tab or v switches.\n' + 'Keys: j/k select, f filter, t tools, PgUp/PgDn detail, +/- zoom and ←/→ pan (Timeline), q quit.\n' + 'No CLI configuration changes, network calls, or session writes.'); return; } if (!values.session) throw new Error('--session is required (see --help)'); if (!isColorMode(values.theme)) throw new Error(`--theme must be one of: ${COLOR_MODES.join(', ')}`); + const view = VIEWS.find(name => name === values.view); + if (!view) throw new Error(`--view must be one of: ${VIEWS.join(', ')}`); if (!process.stdin.isTTY || !process.stdout.isTTY) { throw new Error('An interactive terminal is required. Run this command in a separate terminal tab.'); } let sourceError: string | undefined; const onError = (message: string): void => { sourceError = message; }; const instance = render( - , + , {alternateScreen: true, patchConsole: false}, ); const terminate = (): void => { instance.unmount(); }; diff --git a/tools/agent-monitor/src/model.ts b/tools/agent-monitor/src/model.ts index 23e24bc5..d3801cad 100644 --- a/tools/agent-monitor/src/model.ts +++ b/tools/agent-monitor/src/model.ts @@ -67,6 +67,8 @@ export interface MainTurn { } const MAIN_TOOL_LIMIT = 200; +/** CLI versions whose session logs this model was checked against. */ +const TESTED_VERSIONS = ['1.0.104', '1.0.105']; const DETAIL_KEYS = ['path', 'pattern', 'command', 'query', 'url', 'name', 'agent_id', 'description']; export const isFinished = (run: AgentRun): boolean => @@ -139,7 +141,7 @@ export class AgentModel { this.sessionId = optionalString(d, 'sessionId'); this.sessionModel = optionalString(d, 'selectedModel'); this.sessionStart = e.timestamp; - if (this.version !== '1.0.104') this.warnings.add(`Untested CLI event format: ${this.version ?? 'unknown'}`); + if (!TESTED_VERSIONS.includes(this.version ?? '')) this.warnings.add(`Untested CLI event format: ${this.version ?? 'unknown'}`); break; case 'session.shutdown': { this.sessionEnded = e.timestamp; diff --git a/tools/agent-monitor/src/summary.ts b/tools/agent-monitor/src/summary.ts new file mode 100644 index 00000000..1f049add --- /dev/null +++ b/tools/agent-monitor/src/summary.ts @@ -0,0 +1,49 @@ +import {join} from 'node:path'; +import {JsonlReader} from './events.js'; +import {AgentModel} from './model.js'; +import {clean} from './screen.js'; +import {buildTree, elapsed, formatDuration, nodeType, nowSeconds, VISUALS, visualOf, waitLabel, waitSpan} from './tree.js'; +import type {Node, Visual} from './tree.js'; + +const COUNT_ORDER: Visual[] = ['running', 'waiting', 'done', 'replied', 'failed', 'cancelled', 'unknown']; + +/** Plain-text snapshot of the agent tree, for one-off output such as the Copilot CLI extension's `/agents` command. */ +export function summarize(model: AgentModel, nowMs: number): string { + const tree = buildTree(model, 'all'); + const now = nowSeconds(model, nowMs); + const runs = tree.order.slice(1); + const counts = COUNT_ORDER + .map(visual => [visual, runs.filter(node => visualOf(model, node) === visual).length] as const) + .filter(([, count]) => count > 0) + .map(([visual, count]) => `${count} ${VISUALS[visual].label}`); + const lines = [runs.length + ? `Agents · ${runs.length} execution${runs.length === 1 ? '' : 's'}: ${counts.join(', ')}` + : 'Agents · no agent executions recorded yet']; + const describe = (node: Node): string => { + const visual = visualOf(model, node); + const {glyph, label} = VISUALS[visual]; + if (!node.run) return `${glyph} main session · ${label} · ${formatDuration(now)}`; + const parts = [`${glyph} ${clean(node.run.name)}`, nodeType(node)]; + const wait = visual === 'waiting' ? [...model.waits.values()].find(edge => edge.fromRunId === node.run!.id) : undefined; + if (wait) { + const [start, end] = waitSpan(model, wait, now); + parts.push(`waiting on ${clean(waitLabel(model, wait))} ${formatDuration(end - start)}`); + } else parts.push(formatDuration(elapsed(model, node, now)), label); + return parts.join(' · '); + }; + const visit = (node: Node, prefix: string, last: boolean): void => { + lines.push(node.run ? `${prefix}${last ? '└─ ' : '├─ '}${describe(node)}` : describe(node)); + const inner = node.run ? prefix + (last ? ' ' : '│ ') : ''; + node.children.forEach((child, i) => visit(child, inner, i === node.children.length - 1)); + }; + visit(tree.root, '', true); + for (const warning of model.warnings) lines.push(`! ${clean(warning)}`); + return lines.join('\n'); +} + +/** Reads a session directory's events.jsonl once and summarizes it. */ +export async function summarizeSession(directory: string, nowMs = Date.now()): Promise { + const model = new AgentModel(); + await new JsonlReader(join(directory, 'events.jsonl')).poll(record => model.apply(record), () => model.reset()); + return summarize(model, nowMs); +} diff --git a/tools/agent-monitor/test/summary.test.ts b/tools/agent-monitor/test/summary.test.ts new file mode 100644 index 00000000..a0406eba --- /dev/null +++ b/tools/agent-monitor/test/summary.test.ts @@ -0,0 +1,25 @@ +import assert from 'node:assert/strict'; +import {test} from 'node:test'; +import {AgentModel} from '../src/model.js'; +import {summarize} from '../src/summary.js'; +import {fixture} from './fixture.js'; + +test('the text summary nests executions under their launcher with status, duration and the wait in progress', () => { + const model = new AgentModel(); + const events = fixture(); + for (const event of events.slice(0, 11)) model.apply(event); + const now = Date.parse(model.lastEventAt!); + let lines = summarize(model, now).split('\n'); + assert.equal(lines[0], 'Agents · 3 executions: 1 running, 1 waiting, 1 done'); + assert.match(lines[1]!, /^◐ main session · running · /); + assert.match(lines[2]!, /^├─ ◌ parent · general-purpose · waiting on child \d/); + assert.match(lines[3]!, /^│ └─ ◐ child · general-purpose · [\d.]+s · running$/); + assert.match(lines[4]!, /^└─ ✓ control · general-purpose · [\d.]+s · done$/); + for (const event of events.slice(11)) model.apply(event); + lines = summarize(model, Date.parse(model.lastEventAt!)).split('\n'); + assert.match(lines.at(-1)!, /^ └─ ↩ control · general-purpose · turn · [\d.]+s · replied$/); +}); + +test('the text summary says when no agent has run yet', () => { + assert.equal(summarize(new AgentModel(), Date.now()).split('\n')[0], 'Agents · no agent executions recorded yet'); +});