From e4df87628b18fc2b9ea27b105aa44e4de25d5e1b Mon Sep 17 00:00:00 2001 From: 0XSIKIPON Date: Thu, 17 Sep 2026 23:35:47 +0100 Subject: [PATCH 1/7] feat: add chat comments functionality with UI and backend integration under root repo (restruct repo) --- .../chat_comments/__init__.py => __init__.py | 0 {usr/plugins/chat_comments/api => api}/comments.py | 0 .../default_config.yaml => default_config.yaml | 0 .../webui/chat-top-end/chat-comments-mount.html | 0 .../chat_comments/helpers => helpers}/__init__.py | 0 .../chat_comments/helpers => helpers}/constants.py | 0 usr/plugins/chat_comments/meta.yaml => meta.yaml | 0 .../chat_comments/plugin.yaml => plugin.yaml | 0 .../chat_comments/webui => webui}/anchoring.js | 0 .../webui => webui}/chat-comments-store.js | 0 .../webui => webui}/comments-modal.html | 0 .../chat_comments/webui => webui}/thumbnail.png | Bin 12 files changed, 0 insertions(+), 0 deletions(-) rename usr/plugins/chat_comments/__init__.py => __init__.py (100%) rename {usr/plugins/chat_comments/api => api}/comments.py (100%) rename usr/plugins/chat_comments/default_config.yaml => default_config.yaml (100%) rename {usr/plugins/chat_comments/extensions => extensions}/webui/chat-top-end/chat-comments-mount.html (100%) rename {usr/plugins/chat_comments/helpers => helpers}/__init__.py (100%) rename {usr/plugins/chat_comments/helpers => helpers}/constants.py (100%) rename usr/plugins/chat_comments/meta.yaml => meta.yaml (100%) rename usr/plugins/chat_comments/plugin.yaml => plugin.yaml (100%) rename {usr/plugins/chat_comments/webui => webui}/anchoring.js (100%) rename {usr/plugins/chat_comments/webui => webui}/chat-comments-store.js (100%) rename {usr/plugins/chat_comments/webui => webui}/comments-modal.html (100%) rename {usr/plugins/chat_comments/webui => webui}/thumbnail.png (100%) diff --git a/usr/plugins/chat_comments/__init__.py b/__init__.py similarity index 100% rename from usr/plugins/chat_comments/__init__.py rename to __init__.py diff --git a/usr/plugins/chat_comments/api/comments.py b/api/comments.py similarity index 100% rename from usr/plugins/chat_comments/api/comments.py rename to api/comments.py diff --git a/usr/plugins/chat_comments/default_config.yaml b/default_config.yaml similarity index 100% rename from usr/plugins/chat_comments/default_config.yaml rename to default_config.yaml diff --git a/usr/plugins/chat_comments/extensions/webui/chat-top-end/chat-comments-mount.html b/extensions/webui/chat-top-end/chat-comments-mount.html similarity index 100% rename from usr/plugins/chat_comments/extensions/webui/chat-top-end/chat-comments-mount.html rename to extensions/webui/chat-top-end/chat-comments-mount.html diff --git a/usr/plugins/chat_comments/helpers/__init__.py b/helpers/__init__.py similarity index 100% rename from usr/plugins/chat_comments/helpers/__init__.py rename to helpers/__init__.py diff --git a/usr/plugins/chat_comments/helpers/constants.py b/helpers/constants.py similarity index 100% rename from usr/plugins/chat_comments/helpers/constants.py rename to helpers/constants.py diff --git a/usr/plugins/chat_comments/meta.yaml b/meta.yaml similarity index 100% rename from usr/plugins/chat_comments/meta.yaml rename to meta.yaml diff --git a/usr/plugins/chat_comments/plugin.yaml b/plugin.yaml similarity index 100% rename from usr/plugins/chat_comments/plugin.yaml rename to plugin.yaml diff --git a/usr/plugins/chat_comments/webui/anchoring.js b/webui/anchoring.js similarity index 100% rename from usr/plugins/chat_comments/webui/anchoring.js rename to webui/anchoring.js diff --git a/usr/plugins/chat_comments/webui/chat-comments-store.js b/webui/chat-comments-store.js similarity index 100% rename from usr/plugins/chat_comments/webui/chat-comments-store.js rename to webui/chat-comments-store.js diff --git a/usr/plugins/chat_comments/webui/comments-modal.html b/webui/comments-modal.html similarity index 100% rename from usr/plugins/chat_comments/webui/comments-modal.html rename to webui/comments-modal.html diff --git a/usr/plugins/chat_comments/webui/thumbnail.png b/webui/thumbnail.png similarity index 100% rename from usr/plugins/chat_comments/webui/thumbnail.png rename to webui/thumbnail.png From 991993f904bf8a294eefd7e7fc9838af0ec95978 Mon Sep 17 00:00:00 2001 From: 0XSIKIPON Date: Thu, 17 Sep 2026 23:36:44 +0100 Subject: [PATCH 2/7] refactor: update plugin directory structure to root for consistency across configurations --- .devkit.yml | 2 +- CONTRIBUTING.md | 2 +- Makefile | 2 +- docs/marketplace-draft.md | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.devkit.yml b/.devkit.yml index 341b04a..4f1ccec 100644 --- a/.devkit.yml +++ b/.devkit.yml @@ -1,4 +1,4 @@ -plugin_dir: usr/plugins/chat_comments +plugin_dir: . display_name: Chat Comments devkit_major: 2 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6b64e3..2809b62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Thanks for helping improve the Chat Comments plugin for Agent Zero. ## Repo layout -- `usr/plugins/chat_comments/` — the plugin itself (API handler, webui store/modal, extension mount). +- Repo root — the plugin itself (root layout, `.devkit.yml` `plugin_dir: .`): API handler, webui store/modal, extension mount. - `docs/spec/` — the four living BDD docs (`behaviour-spec.md`, `implementation-plan.md`, `e2e.feature.md`, `e2e-steps-spec.md`). Behaviour changes MUST update these. - `tests/` — L1 testkit shape suite (`pytest`) + L3 Playwright BDD (`tests/e2e/`). - `tests/_testkit/` — the plugin devkit (git submodule). Never edit here; contribute upstream. diff --git a/Makefile b/Makefile index 666be43..32cb7cf 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ -PLUGIN_DIR := usr/plugins/chat_comments +PLUGIN_DIR := . PLUGIN_DISPLAY_NAME := Chat Comments -include tests/_testkit/e2e/Makefile.devkit diff --git a/docs/marketplace-draft.md b/docs/marketplace-draft.md index 6b61f72..aecd0ce 100644 --- a/docs/marketplace-draft.md +++ b/docs/marketplace-draft.md @@ -33,7 +33,7 @@ screenshots: ## Thumbnail -`usr/plugins/chat_comments/webui/thumbnail.png` — square 512×512, 2,751 bytes (< 20 KB). Ships inside +`webui/thumbnail.png` — square 512×512, 2,751 bytes (< 20 KB). Ships inside the plugin; copy to `plugins/chat_comments/thumbnail.png` in the index submission if a card image is wanted there too. @@ -46,7 +46,7 @@ plugin installed, because honest marketplace screenshots must be real UI, not mo 1. `docs/screenshot-comment.png` — a chat message with a highlighted phrase and the view popover (note + Edit/Delete) open, plus the toolbar badge showing a count. 2. `docs/screenshot-modal.png` — the comments manager modal listing one anchored comment (with its - quoted text) and one **General** comment, with the *Send to prompt* footer. + quoted text) and one **General** comment, with the _Send to prompt_ footer. Capture recipe (once a live A0 is reachable with credentials): log in → new chat → send a message → select text → **Comment** → screenshot; open the comments button → screenshot the modal. The e2e BDD From 1ecb50d2dad4165b9e1240653104165ef0caefd4 Mon Sep 17 00:00:00 2001 From: 0XSIKIPON Date: Thu, 17 Sep 2026 23:37:07 +0100 Subject: [PATCH 3/7] feat: add unit testing workflow for Python contract tests --- .github/workflows/plugin-e2e.yml | 8 ++++-- .github/workflows/unit.yml | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/unit.yml diff --git a/.github/workflows/plugin-e2e.yml b/.github/workflows/plugin-e2e.yml index 587c7c9..27851ab 100644 --- a/.github/workflows/plugin-e2e.yml +++ b/.github/workflows/plugin-e2e.yml @@ -26,6 +26,10 @@ name: plugin-e2e on: pull_request: branches: ["main", "master"] + # push on main: proves the shipped code, and gives the README badge a status + # (PR-only runs leave branch=main with zero runs → "no status"). + push: + branches: ["main", "master"] workflow_dispatch: inputs: capture_all_traces: @@ -35,8 +39,8 @@ on: permissions: contents: read - pull-requests: write # merge-guard drafts a PR whose plugin-e2e is red - actions: write # DEC-074 count-based artifact retention: delete superseded e2e artifacts + pull-requests: write # merge-guard drafts a PR whose plugin-e2e is red + actions: write # DEC-074 count-based artifact retention: delete superseded e2e artifacts jobs: e2e: diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml new file mode 100644 index 0000000..5a67e1d --- /dev/null +++ b/.github/workflows/unit.yml @@ -0,0 +1,43 @@ +name: unit +# Python unit suite (shape/skill contract tests). +# +# The testkit submodule is PRIVATE — the default GITHUB_TOKEN cannot clone it. +# Mirror the reusable plugin-e2e workflow: checkout without submodules, mint a +# GitHub App token (same sync-App secrets), init the submodule with it. +on: + pull_request: + push: + branches: [main, master] + workflow_dispatch: + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: false + - name: Mint App token (private submodule clone) + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.SKILLS_SYNC_APP_CLIENT_ID }} + private-key: ${{ secrets.SKILLS_SYNC_APP_PRIVATE_KEY }} + owner: agent-zero-plugins + - name: Init testkit submodule (App token) + env: + APP_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + AUTH=$(printf 'x-access-token:%s' "$APP_TOKEN" | base64 -w0) + # --recursive: the L1 shape tests audit the plugin against REAL A0 + # source, which the testkit resolves from its nested (public) + # .agent-zero submodule. + git -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $AUTH" \ + submodule update --init --recursive tests/_testkit + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install test deps + run: python -m pip install --upgrade pip pytest pytest-asyncio pyyaml + - name: Run unit suite + run: python -m pytest tests -q From bf99e7ca903f901a6be49e8db0cf408a5056f45e Mon Sep 17 00:00:00 2001 From: 0XSIKIPON Date: Thu, 17 Sep 2026 23:37:21 +0100 Subject: [PATCH 4/7] docs: update README with chat comments features and usage; add demo images --- README.md | 214 ++++++++++-------------------------------- docs/cc.gif | Bin 0 -> 1603427 bytes docs/how-it-works.png | Bin 0 -> 251060 bytes 3 files changed, 47 insertions(+), 167 deletions(-) create mode 100644 docs/cc.gif create mode 100644 docs/how-it-works.png diff --git a/README.md b/README.md index 3f70907..10daaaa 100644 --- a/README.md +++ b/README.md @@ -6,204 +6,84 @@ -> Select text in any chat message and attach a **Google-Docs-style comment** to it. -> Comments are highlighted inline, listed in a manager modal, **persisted per chat**, -> and can be sent to the prompt box as a single review request for the agent to address. - -`chat_comments` turns an Agent Zero conversation into something you can *mark up*. Reviewing a long -agent answer? Highlight the sentence that's wrong, comment on it, and — when you're done — send every -comment to the prompt in one go so the agent addresses them together. - ---- - -## Why you'd want it - -| Without | With Chat Comments | -|---|---| -| Scroll back, retype "the third paragraph is wrong because…" | Select the text → **Comment** → it's anchored right there | -| Lose your review notes when you switch chats | Comments **persist on the chat**, survive reloads and chat switches | -| Feed corrections one message at a time | **Send to prompt** bundles every comment into one numbered request | -| No record of what you flagged | A comments modal lists every note, anchored or general | - ---- - -## Features - -- **Anchor a comment to selected text** — right-click a selection inside a message → *Comment*; the - phrase is wrapped in a highlight (``) that survives re-renders via occurrence-based re-anchoring. -- **General (untethered) comments** — add a chat-level note from the modal that isn't tied to any span. -- **View / edit / delete** — click a highlight for a popover with the note plus Edit and Delete. -- **Persistence** — comments are stored on the chat itself and reload with it; switch away and back and - they're still there. -- **Send to prompt** — turns all comments into one numbered instruction (anchored items quote their - referenced text) and drops it into the composer. -- **Copy / send a selection** — the selection menu also offers *Copy text* and *Send to prompt* (append). -- **Badge** — the toolbar button shows a live count. - ---- - -## Architecture - -```mermaid -flowchart TD - subgraph Browser["Browser (Alpine store: chatComments)"] - BTN["chat-top-end button
(cc-toolbar-btn + badge)"] - SEL["contextmenu over a selection
inside #message-<id>"] - MENU["cc-menu:
Comment · Copy · Send to prompt"] - EDIT["cc-editor / cc-popover
create · edit · delete"] - ANCH["anchoring.js
occurrence-based <mark> re-anchor"] - MODAL["comments-modal.html
list · General tag · Send to prompt"] - OBS["MutationObserver on #chat-history
→ re-anchor / reload on chat switch"] - end - - subgraph Backend["Agent Zero backend"] - API["api/comments.py
ApiHandler /plugins/chat_comments/comments"] - CTX["AgentContext.data['chat_comments']"] - DISK[("usr/chats/<ctxid>/chat.json
via save_tmp_chat")] - end - - SEL --> MENU --> EDIT --> ANCH - BTN --> MODAL - EDIT -->|"POST action:save"| API - MODAL -->|"POST action:save"| API - OBS -->|"POST action:load"| API - API --> CTX --> DISK - DISK -->|"action:load on bootstrap / switch"| API --> ANCH -``` - -**Persistence flow:** every create/edit/delete calls `persist()` → `POST {action:"save"}` → -`api/comments.py` sanitises the list and writes `AgentContext.data["chat_comments"]`, then -`save_tmp_chat` flushes it to `usr/chats//chat.json`. On bootstrap and on chat switch the store -`POST {action:"load"}`s that same key back and re-anchors the highlights. **No data ever leaves the -machine running Agent Zero.** - ---- - -## Usage - -Every behaviour below is covered by a BDD scenario in [`tests/e2e/features/`](tests/e2e/features/), -and [`docs/BEHAVIOUR.md`](docs/BEHAVIOUR.md) shows each one as a screenshot captured from a passing -run — so what you read here is what CI proves on every push. +> Select text in any chat message and attach a **Google-Docs-style comment** to it. Comments are highlighted inline, persisted per chat, and sendable to the prompt as one numbered review request. -### Comment on a phrase inside a message +![Chat Comments in action](docs/cc.gif) -Select any text in a message (yours or the agent's) and choose **Comment** from the selection menu. -The phrase is wrapped in a highlight and the comment records the text it refers to, so the note still -makes sense when you come back to it later. Click a highlight to reopen the note, with **Edit** and -**Delete**. +Chat_comments turns an Agent Zero conversation into something you can mark up. Reviewing a long agent answer? Highlight the sentence that's wrong, comment on it, and, when you're done, send every comment to the prompt in one go so the agent addresses them together. -One message can carry several comments, and re-anchoring is occurrence-based — the highlight survives -the re-renders A0 does as a chat grows. +## ✨ Features -### Comment on the chat as a whole +- **Anchored comments** : select a phrase in any message (yours or the agent's) → right-click → **Comment**. The phrase gets an inline highlight that survives re-renders via occurrence-based re-anchoring. +- **General comments** : chat-level notes that aren't tied to any span, for "this whole exchange went wrong" moments. +- **Manage from one place** : click a highlight for its popover (**Edit** / **Delete**), or open the toolbar modal to see every note; the toolbar button carries a live count badge. +- **Send to prompt** : collapses all comments into a single numbered instruction — anchored items quote their text — and drops it into the composer. Review the transcript, then hand the whole critique back in one turn. +- **Persistence** : comments are stored on the chat itself (`usr/chats//chat.json`), so they survive reloads and chat switches. +- **Honest refusals** : empty comments are rejected, and the backing service refuses malformed requests — both are asserted BDD scenarios. -Open the toolbar control and add a note without selecting anything first. Useful for "this whole -exchange went wrong" observations that do not belong to one phrase. +## ⚙️ How it works -### Turn your comments into a prompt +![How it works](docs/how-it-works.png) -**Send to prompt** collapses every comment on the chat into a single numbered instruction and drops it -into the composer. Anchored comments quote the text they refer to, so the agent sees both your note -and what it was about. This is the point of the plugin: review the transcript, mark it up, then hand -the whole critique back in one turn instead of retyping it. -### What persists +A `chat-top-end` webui extension plus one API handler. Every create/edit/delete saves through the plugin's own endpoint; bootstrap and chat switches load the same key back and re-anchor. No external network calls, no telemetry. -Comments are stored on the chat, not in the browser. Switch to another chat and back, or reload, and -they are still there — the `A comment added to a chat is remembered` scenario asserts exactly that, -including across a reload. +## 💬 Usage -The toolbar badge shows a live count so you can see at a glance whether a chat has been annotated. +Nothing to configure — the comment button appears in the chat top bar. Every behaviour below has an asserted [BDD scenario](tests/e2e/features/), and [`docs/BEHAVIOUR.md`](docs/BEHAVIOUR.md) shows each as a screenshot from a passing run. -### What it refuses +**Anchored comments** — select text, right-click, **Comment**. One message can carry several; the comment records which text it refers to, so the note still makes sense later. Click a highlight to reopen, edit, or delete it. -An empty comment is rejected rather than silently stored, and the backing service rejects malformed -requests instead of accepting partial data. Both are asserted scenarios, not just intentions. +**General comments & send-to-prompt** — open the toolbar modal to add a chat-level note, or hit **Send to prompt** to bundle every comment into one numbered request for the agent. The selection menu also offers _Copy text_ and a direct _Send to prompt_. ---- +## 📦 Installation -## Install +**Plugin Hub** — once listed in the [Plugin Index](https://github.com/agent0ai/a0-plugins): **Settings → Plugins → Chat Comments → Install**. -### Plugin Hub (recommended) - -Open **Settings → Plugins**, find **Chat Comments**, and install. Enable it; the comment button appears -in the chat top bar. - -### Manual - -Copy the plugin into your Agent Zero instance: +**Manual** (Zip) — build it yourself, or grab the zip from the [latest release](https://github.com/agent-zero-plugins/agent-zero-plugin-chat-comments/releases/latest): ```bash -cp -r usr/plugins/chat_comments /path/to/agent-zero/usr/plugins/chat_comments +make package # → dist/chat_comments.zip ``` -Restart / reload plugins. No configuration required. - ---- - -## Configuration +Then **Settings → Plugins → Install from file** → pick the zip. Or copy the plugin into your instance (`usr/plugins/chat_comments/`) and reload plugins. -The plugin is zero-config. For reference: +## 🔧 Configuration -| Manifest field | Value | Meaning | -|---|---|---| -| `name` | `chat_comments` | plugin id (matches the Plugin Index folder) | -| `settings_sections` | `[]` | no settings screen — nothing to configure | -| `per_project_config` | `false` | comments are per-chat, not per-project | -| `per_agent_config` | `false` | not agent-scoped | -| `license` | `Apache-2.0` | see [LICENSE](LICENSE) | +None — zero-config, no settings screen. One internal constant: `MAX_COMMENT_LENGTH = 2000` (both client and server clip to it). -Internal constant: `MAX_COMMENT_LENGTH = 2000` (both client and server clip to this). - ---- - -## Comment record schema - -Each comment persisted on a chat: - -| Field | Type | Notes | -|---|---|---| -| `id` | str | client-generated uuid | -| `message_id` | str | id of the message the highlight anchors to (`""` for General) | -| `quoted_text` | str | the selected text (`""` for General) | -| `occurrence` | int | which match of `quoted_text` in the message (0-based) | -| `comment` | str | the note (≤2000 chars) | -| `created_at` | int | unix seconds | - ---- - -## Development & testing +## 🛠️ Development ```bash -git clone --recursive https://github.com/agent-zero-plugins/agent-zero-plugin-chat-comments +git clone --recurse-submodules https://github.com/agent-zero-plugins/agent-zero-plugin-chat-comments cd agent-zero-plugin-chat-comments -# L1 shape suite (fast, testkit assertions) -pip install pytest pyyaml -pytest # 11 tests: extension surface, hooks, thumbnail, deps, A0-API, auth posture - -# Tier-1 BDD static gates (feature-purity, honesty, traceability) -make verify - -# Full L3 behaviour BDD against a disposable A0 (needs podman/docker) -make e2e +make verify # BDD static gates +python -m pytest tests -q # unit + L1 shape suite (needs tests/_testkit submodule) +make e2e # full behaviour BDD run on a nested disposable A0 (podman) ``` -The behaviour contract lives in [`docs/spec/`](docs/spec/) (`behaviour-spec.md` BEH-1..14) and the -executable BDD in [`tests/e2e/features/10-comments.feature`](tests/e2e/features/10-comments.feature). -CI (`plugin-e2e`) runs the full suite on every PR, including a **seam-off red-proof** (the suite must -fail with the plugin uninstalled) — so green means the behaviour is genuinely exercised live. +Each comment persisted on a chat: ---- +| Field | Type | Notes | +| ------------- | ---- | ----------------------------------------------------- | +| `id` | str | client-generated uuid | +| `message_id` | str | message the highlight anchors to (`""` for General) | +| `quoted_text` | str | the selected text (`""` for General) | +| `occurrence` | int | which match of `quoted_text` in the message (0-based) | +| `comment` | str | the note (≤2000 chars) | +| `created_at` | int | unix seconds | -## Security +## 🧪 Test layout -Comments live only in your chat data (`AgentContext.data["chat_comments"]` → `usr/chats//chat.json`) -on the machine running Agent Zero. The plugin makes **no external network calls** and adds **no** -telemetry. See [SECURITY.md](SECURITY.md). +| Layer | Where | What | +| ------------- | -------------------------------- | ----------------------------------------------------------------------- | +| L1 shape | `tests/test_plugin_shape.py` | static validator, deps/A0-API audits, dead hooks | +| Static gates | `make verify` | feature-purity, honesty, traceability | +| Behaviour BDD | `tests/e2e/features/` + `steps/` | anchored/general comments, edit/delete, persistence, refusals, e2e APIs | ---- +Specs live in [`docs/spec/`](docs/spec/) (BEH-1..14). CI runs the whole pyramid on every PR, including a seam-off red-proof — a scenario that passes without the plugin fails the build. -## License +## ⚖️ License -[Apache-2.0](LICENSE). \ No newline at end of file +Apache-2.0 — see [LICENSE](LICENSE). Comments never leave the machine running Agent Zero; see [SECURITY.md](SECURITY.md). diff --git a/docs/cc.gif b/docs/cc.gif new file mode 100644 index 0000000000000000000000000000000000000000..8d4b00a853376c54a1951d58240f3b795a2060f8 GIT binary patch literal 1603427 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