diff --git a/catalog.json b/catalog.json index 79dfc14..0889eaa 100644 --- a/catalog.json +++ b/catalog.json @@ -2,7 +2,7 @@ "schemaVersion": 1, "providerId": "official", "name": "PI-Desktop Official Plugins", - "updatedAt": "2026-09-20T17:49:14Z", + "updatedAt": "2026-10-05T16:34:09Z", "homepage": "https://github.com/vastsa/pi-desktop-plugins", "plugins": [ { @@ -150,6 +150,54 @@ } ] }, + { + "id": "io.github.kennymcsimpson.governed-roundtable", + "name": "Governed Roundtable", + "description": "A governed room (room-dev) inside PI-Desktop: PI conversations, external coding agents and text-only hosted seats take turns under a program-moderated protocol with source banners, artifact freeze and staleness, one-way review and user-approved disclosure.", + "i18n": { + "en": { + "name": "Governed Roundtable", + "description": "Run a governed room (room-dev) inside PI-Desktop. A PI conversation can create a room and take a seat through the Room tool; Codex, Claude Code and other agents join through the seat kit (JOIN.md) exactly as with room-dev; text-only hosted seats speak through PI-Desktop's own model completion. Task, start, disclosure approval, stale acceptance and closing stay with you in the panel.", + "safetyNotes": "High risk. The background service runs the room-dev engine inside this plugin's own process with ordinary Node.js access (PI-Desktop does not sandbox raw Node calls in plugins). It writes rooms under %USERPROFILE%\\room-dev\\rooms (never AppData), each seat's outbox under \\.room-outbox\\, artifact snapshot copies inside the room, and this plugin's own data folder (seat bindings); it appends one line per authorized reviewer verdict (room id, reviewer agent kind, preset, verdict, artifact hash) to %USERPROFILE%\\room-dev\\adjudications.jsonl. It starts child processes only for room-dev's own purposes: a disclosure command (for example git diff; only the room's allowlist or the task's acceptance commands, no shell) after you approve it in the panel or room UI, git init inside artifact snapshot copies, and the Codex queue wake push for rooms that registered one (the Room tool never registers wakes). For seats registered outside this plugin with a native-log audit or a Codex wake thread, it reads that agent's own session logs read-only (Codex under %USERPROFILE%\\.codex, Claude Code under %USERPROFILE%\\.claude\\projects); seats created through the Room tool register neither. It serves the room UI on 127.0.0.1 only. The Room tool binds a seat to the calling conversation by the host-supplied session id, never by tool input, and it cannot set the task, start a round, approve a disclosure, accept a stale verdict or close a room. That rule covers the Room tool only: each room's admin token is a file (%USERPROFILE%\\room-dev\\rooms\\\\admin.token), so a PI conversation whose own file or shell tools can read that path can issue admin commands through the room's admin-queue or the loopback /api/admin. Deny those tools the rooms folder to keep administration with you; seat tokens in the same folder can likewise be read by any local process. Hosted seats call your configured model through PI-Desktop (agent.complete) once per turn after you start a round, which uses your model quota. Text from other seats reaches a PI conversation as tool output inside room-dev's labelled authority=none blocks; it is not user instruction. No network permission, no desktop control, no credentials read." + }, + "zh-CN": { + "name": "\u5706\u684c\uff08\u53d7\u6cbb\u7406\u623f\u95f4\uff09", + "description": "\u5728 PI-Desktop \u91cc\u8fd0\u884c\u53d7\u6cbb\u7406\u7684\u623f\u95f4\uff08room-dev\uff09\u3002PI \u5bf9\u8bdd\u53ef\u4ee5\u7ecf Room \u5de5\u5177\u5efa\u623f\u5e76\u5165\u5e2d\uff1bCodex\u3001Claude Code \u7b49\u5916\u90e8 agent \u7167 room-dev \u539f\u6837\u7ecf\u5e2d\u4f4d\u5305\uff08JOIN.md\uff09\u5165\u5e2d\uff1b\u53ea\u8ba8\u8bba\u6863\u7684\u6258\u7ba1\u5e2d\u4f4d\u7ecf PI-Desktop \u81ea\u5df1\u7684\u6a21\u578b\u8865\u5168\u53d1\u8a00\u3002\u4efb\u52a1\u3001\u5f00\u59cb\u3001\u62ab\u9732\u6279\u51c6\u3001\u63a5\u53d7\u8fc7\u671f\u4e0e\u6563\u4f1a\u90fd\u53ea\u5728\u9762\u677f\u91cc\u7531\u4f60\u6765\u505a\u3002", + "safetyNotes": "\u9ad8\u98ce\u9669\u3002\u540e\u53f0\u670d\u52a1\u5728\u672c\u63d2\u4ef6\u81ea\u5df1\u7684\u8fdb\u7a0b\u91cc\u8fd0\u884c room-dev \u5f15\u64ce\uff0c\u62e5\u6709\u666e\u901a\u7684 Node.js \u80fd\u529b\uff08PI-Desktop \u4e0d\u5bf9\u63d2\u4ef6\u7684\u539f\u751f Node \u8c03\u7528\u505a\u6c99\u7bb1\uff09\u3002\u5b83\u5199\u5165\uff1a%USERPROFILE%\\room-dev\\rooms \u4e0b\u7684\u623f\u95f4\uff08\u4ece\u4e0d\u5199 AppData\uff09\u3001\u5404\u5e2d\u4f4d\u5de5\u4f5c\u6587\u4ef6\u5939\u4e0b\u7684\u53d1\u4ef6\u7bb1 .room-outbox\\<\u5e2d\u4f4d>\u3001\u623f\u95f4\u5185\u7684\u4ea7\u7269\u5feb\u7167\u526f\u672c\uff0c\u4ee5\u53ca\u672c\u63d2\u4ef6\u81ea\u5df1\u7684\u6570\u636e\u76ee\u5f55\uff08\u5e2d\u4f4d\u7ed1\u5b9a\uff09\uff1b\u6bcf\u6761\u88ab\u8ba4\u53ef\u7684\u5ba1\u65b9\u7ed3\u8bba\u8fd8\u4f1a\u5728 %USERPROFILE%\\room-dev\\adjudications.jsonl \u8ffd\u52a0\u4e00\u884c\uff08\u623f\u95f4 id\u3001\u5ba1\u65b9 agent \u7c7b\u578b\u3001\u9884\u8bbe\u3001\u7ed3\u8bba\u3001\u4ea7\u7269\u54c8\u5e0c\uff09\u3002\u5b83\u53ea\u4e3a room-dev \u81ea\u8eab\u7528\u9014\u8d77\u5b50\u8fdb\u7a0b\uff1a\u4f60\u5728\u9762\u677f\u6216\u623f\u95f4\u754c\u9762\u6279\u51c6\u540e\u7684\u62ab\u9732\u547d\u4ee4\uff08\u4f8b\u5982 git diff\uff1b\u53ea\u9650\u623f\u95f4\u767d\u540d\u5355\u6216\u4efb\u52a1\u5355\u7684\u9a8c\u6536\u547d\u4ee4\uff0c\u4e0d\u7ecf shell\uff09\u3001\u5728\u4ea7\u7269\u5feb\u7167\u526f\u672c\u91cc\u8fd0\u884c\u7684 git init\u3001\u4ee5\u53ca\u767b\u8bb0\u4e86\u5524\u9192\u7684\u623f\u95f4\u7684 Codex queue \u63a8\u9001\uff08Room \u5de5\u5177\u4ece\u4e0d\u767b\u8bb0\u5524\u9192\uff09\u3002\u5bf9\u4e8e\u5728\u672c\u63d2\u4ef6\u4e4b\u5916\u767b\u8bb0\u4e86\u539f\u751f\u65e5\u5fd7\u5ba1\u8ba1\u6216 Codex \u5524\u9192\u7ebf\u7a0b\u7684\u5e2d\u4f4d\uff0c\u5b83\u4f1a\u53ea\u8bfb\u8be5 agent \u81ea\u5df1\u7684\u4f1a\u8bdd\u65e5\u5fd7\uff08Codex \u5728 %USERPROFILE%\\.codex\uff0cClaude Code \u5728 %USERPROFILE%\\.claude\\projects\uff09\uff1b\u7ecf Room \u5de5\u5177\u521b\u5efa\u7684\u5e2d\u4f4d\u4e24\u8005\u90fd\u4e0d\u767b\u8bb0\u3002\u623f\u95f4\u754c\u9762\u53ea\u5728 127.0.0.1 \u4e0a\u63d0\u4f9b\u3002Room \u5de5\u5177\u6309\u5bbf\u4e3b\u63d0\u4f9b\u7684\u4f1a\u8bdd id \u628a\u5e2d\u4f4d\u7ed1\u5b9a\u5230\u8c03\u7528\u5b83\u7684\u5bf9\u8bdd\uff0c\u4ece\u4e0d\u91c7\u4fe1\u5de5\u5177\u53c2\u6570\u91cc\u7684\u8eab\u4efd\uff1b\u5b83\u4e0d\u80fd\u8bbe\u4efb\u52a1\u3001\u5f00\u59cb\u4e00\u8f6e\u3001\u6279\u51c6\u62ab\u9732\u3001\u63a5\u53d7\u8fc7\u671f\u6216\u6563\u4f1a\u3002\u8fd9\u6761\u89c4\u5219\u53ea\u7ba1 Room \u5de5\u5177\uff1a\u6bcf\u4e2a\u623f\u95f4\u7684 admin \u4ee4\u724c\u662f\u4e00\u4e2a\u6587\u4ef6\uff08%USERPROFILE%\\room-dev\\rooms\\<\u623f\u95f4>\\admin.token\uff09\uff0cPI \u5bf9\u8bdd\u81ea\u5df1\u7684\u6587\u4ef6\u6216 shell \u5de5\u5177\u82e5\u80fd\u8bfb\u5230\u8fd9\u4e2a\u8def\u5f84\uff0c\u5c31\u80fd\u7ecf\u623f\u95f4\u7684 admin-queue \u6216\u56de\u73af\u5730\u5740\u4e0a\u7684 /api/admin \u53d1\u7ba1\u7406\u547d\u4ee4\u3002\u8981\u8ba9\u7ba1\u7406\u6743\u7559\u5728\u4f60\u624b\u91cc\uff0c\u5c31\u4e0d\u8ba9\u8fd9\u4e9b\u5de5\u5177\u78b0\u623f\u95f4\u76ee\u5f55\uff1b\u540c\u4e00\u76ee\u5f55\u91cc\u7684\u5e2d\u4f4d\u4ee4\u724c\u540c\u6837\u80fd\u88ab\u672c\u673a\u4efb\u4f55\u8fdb\u7a0b\u8bfb\u5230\u3002\u6258\u7ba1\u5e2d\u4f4d\u5728\u4f60\u5f00\u59cb\u4e00\u8f6e\u4e4b\u540e\uff0c\u6bcf\u56de\u5408\u7ecf PI-Desktop\uff08agent.complete\uff09\u8c03\u7528\u4e00\u6b21\u4f60\u914d\u7f6e\u7684\u6a21\u578b\uff0c\u4f1a\u6d88\u8017\u6a21\u578b\u989d\u5ea6\u3002\u5176\u4ed6\u5e2d\u4f4d\u7684\u53d1\u8a00\u4ee5 room-dev \u5e26\u6807\u7b7e\u7684 authority=none \u5757\u51fa\u73b0\u5728 PI \u5bf9\u8bdd\u7684\u5de5\u5177\u8f93\u51fa\u91cc\uff0c\u4e0d\u662f\u7528\u6237\u6307\u4ee4\u3002\u4e0d\u7533\u8bf7\u7f51\u7edc\u6743\u9650\u3001\u4e0d\u7533\u8bf7\u684c\u9762\u63a7\u5236\u3001\u4e0d\u8bfb\u53d6\u51ed\u636e\u3002" + } + }, + "author": "KennyMcSimpson", + "categories": [ + "developer-tools", + "productivity", + "community" + ], + "verified": true, + "downloads": 0, + "homepage": "https://github.com/vastsa/pi-desktop-plugins/tree/main/plugins/io.github.kennymcsimpson.governed-roundtable", + "repository": "https://github.com/vastsa/pi-desktop-plugins", + "readmeMarkdown": "# Governed Roundtable for PI-Desktop\n\nPlugin id `io.github.kennymcsimpson.governed-roundtable` \u00b7 version 0.1.0 \u00b7 [\u4e2d\u6587](README.zh-CN.md)\n\n> **Status: tested against a fake host that mirrors the upstream plugin SDK, and in the official\n> PI-Desktop 0.16.1 portable build** with a stub model: Load dev plugin, the permission review, the\n> command palette, the panel, the `Room` tool in a conversation, a full round with a hosted seat, and\n> Install plugin package (see [What is verified](#what-is-verified-and-what-is-not)). Not tested\n> yet: a real model, and external agents joining a room served in the real app. Two limits are by\n> design, not open bugs:\n> - **PI seats are not woken automatically.** No plugin API can wake a PI conversation from a\n> background service, so you tell the conversation to continue.\n> - **Hosted seats are discussion-only.** They get one text-only `agent.complete` call per turn,\n> with no tools.\n\nThis plugin brings room-dev's governed room into upstream\n[PI-Desktop](https://github.com/vastsa/PI-Desktop) using only the public plugin APIs. It needs no\nchange to the host or its core. The room engine is room-dev itself, vendored unchanged into the\nplugin folder (`room-dev/`, with the commit recorded in `room-dev/VENDORED.json`). It runs inside the\nplugin's own process.\n\n## Why\n\nroom-dev is a local, Windows-first room in which coding agents you already run (Codex, Claude Code,\nor any agent that can run one local command) take turns in one conversation; the room never\nlaunches, configures or attaches to them. Because it owns none of the agents, its governance detects\nand records instead of enforcing: relayed text carries source labels, a reviewer's verdict is checked\nagainst hashes of the frozen artifacts and marked stale if they changed, and the user approves every\ndisclosure command. The only things the room enforces are turn order and attempt validity.\n\nThis plugin lets PI conversations sit in such a room next to external agents, and lets the user run\nthe room from a PI-Desktop panel. It is a plugin rather than a core change because upstream keeps\ncross-session coordination in plugins (ADR 0165 withdrew the core agent-to-agent stack). What is\nverified so far is listed below; in short: room-dev end to end with scripted seats and one real\nseat kind (a Codex thread started with `codex exec`), and this plugin against a fake host only.\nWhether the governance layer catches failures that manual forwarding misses has not been evaluated.\n\n## What you get\n\n| Part | Upstream API | What it does |\n|---|---|---|\n| **Room host** (resident service `room-host`) | `contributes.services`, `pi.services.register`, permission `background.service` | Runs room-dev's room service (`lib/api.mjs` `openRoom`) for each room you serve. It also serves the loopback room UI on `127.0.0.1` and keeps `service.lock` fresh. When the service stops, every room it served is closed and `service.lock` is removed. Rooms you were serving are served again when the service next starts. |\n| **Room tool** (Agent tool `Room`, risk high) | `pi.agent.registerTool`, `agent.tool.register` | Lets a PI conversation `list` rooms, `create` a room, `join` a PI seat, and use **its own seat**: `wait`, `submit`, `pass`, `point`, `quote`, `misquoted`, `mark`, `verdict`, `disclose`, `assign`, `artifacts`, `status`, `leave`. |\n| **Panel** and command `Governed Roundtable: Open panel` | `ui.panel`, `pi.commands.register`, `onPanelInvoke` | Your side of the room. Lists rooms with their seats and bindings. Serves or stops a room. Sets the task and starts a round. Sends a user message, and handles retry and skip. Approves or denies disclosures, accepts stale verdicts, closes the room, and releases a seat binding. A closed room is read-only there: no serve, stop or room-UI button, and its Release asks for a second click, because the conversation reads the farewell through its binding. |\n| **Room UI in the browser** | `pi.shell.openExternal`, permission `shell.openExternal` | Opens room-dev's full room UI (`http://127.0.0.1:/#`) in your system browser. |\n| **Hosted seats** (lane `pi-complete`) | `pi.agent.complete`, permission `agent.complete` | A text-only seat that speaks through a model you have configured in PI-Desktop: one completion per turn, with no tools. It plugs into room-dev as a `hostedProviders.lane` factory. |\n| **Turn toast** | `pi.ui.showToast` (needs no permission) | When a seat that waits manually gets its turn, you get one toast per attempt, in the host's language (Chinese for any `zh` locale, else English): the plugin reads `pi.app.getLocale` again for each toast and follows `appearance:changed` on `pi.events`. |\n\nA PI seat's packets describe only Room tool calls (the model sees `plugin_io_github_kennymcsimpson_governed_roundtable_Room`), never room-dev command lines; a hosted seat's packets describe only the ASCII markers it writes into its reply.\n\nExternal agents (Codex, Claude Code, OpenCode, Gemini CLI, \u2026) join exactly as they do with\nroom-dev: the room writes a `JOIN.md` for each seat, and the agent runs the vendored\n`room-dev/room.mjs` (`wait`, `submit`, \u2026) against the room folder. The plugin does not touch them.\n\n## Permissions\n\nRisk tier: **high** (background service, file writes, child processes, an agent tool).\n\n| Permission | Why it is needed | Without it |\n|---|---|---|\n| `ui.panel` | The panel is where the user administers rooms (task, start, disclosure approval, stale acceptance, close, serve / stop, release a binding). | No panel: the command's `openPanel` fails with `PERMISSION_DENIED`. |\n| `agent.tool.register` | The `Room` tool (risk high): a PI conversation lists, creates and joins rooms and acts for its own seat. | `registerTool` fails with `PERMISSION_DENIED`, so `onLoad` fails and the plugin does not load. |\n| `background.service` | The room host must outlive single tool calls: it ticks the room, serves the loopback room UI and keeps `service.lock` fresh. | The service does not start; `create` and seat actions answer `SERVICE_NOT_RUNNING` and write nothing. |\n| `agent.complete` | Text-only hosted seats speak through the model you configured in PI-Desktop; the host resolves credentials. | A hosted seat's turn fails as `seat_failed {class: denied}`; the round goes on. |\n| `shell.openExternal` | \"Open room UI in browser\" opens `http://127.0.0.1:/#`. | That button answers `PERMISSION_DENIED`; the panel still works. |\n\nNot requested: `desktop.control`, `net.fetch`, `fs.*`, `clipboard.*`, `agent.prompt.inject`,\n`session.read`. The plugin never calls `pi.desktop.*` or `pi.net.*`.\n\n## Capability and data-flow matrix\n\n| Entry | Reads | Writes / sends to | Permission | User action | Bounds | Cleanup |\n|---|---|---|---|---|---|---|\n| Room tool `create` | tool input; `room.json` of existing rooms | a new room folder under `%USERPROFILE%\\room-dev\\rooms\\` (room, seat files, tokens, `JOIN.md`, `admin.token`, events) | `agent.tool.register` + running service | the Agent session's permission policy for a `risk: high` tool | room id `[A-Za-z0-9_-]{1,64}`, 1\u20138 seats, existing absolute seat folders, no `session*/token*/admin*` fields | a room whose seat creation fails is removed; existing rooms are refused (`ROOM_EXISTS`) |\n| Room tool seat actions (`submit`, `pass`, `point`, \u2026) | the calling conversation's binding (`ctx.sessionId`) | the seat's outbox `\\.room-outbox\\` and the room's event log | `agent.tool.register` | tool call | only the seat bound to this session; text \u2264 100,000 chars; current attempt only | \u2014 |\n| Room tool `wait` | the packet file in the room | packet text to the calling conversation (tool output) | `agent.tool.register` | tool call | every block carries the packet nonce; other seats' words are `authority=none`, forged `[ROOM:` markers are escaped | \u2014 |\n| Room tool `artifacts` | files the seat names inside its own working folder | hashes and snapshot copies inside the room folder; `git init -q` inside the snapshot copy | `agent.tool.register` | tool call | paths must resolve inside the seat folder (room-dev's guard); `.git` entries and symlinks are not collected; size cap | snapshot copies stay with the room |\n| Room tool `disclose` | \u2014 | a request only | `agent.tool.register` | **the user approves or denies it** in the panel or room UI | argv \u2264 32 entries; the room denies any argv outside its disclosure allowlist or the task's exact acceptance commands; runs without a shell in the snapshot copy, with a timeout and a 1 MiB output cap | child process tree killed on timeout |\n| Resident service `room-host` | rooms it serves | loopback HTTP server on `127.0.0.1` (random port), `service.lock` heartbeat, events | `background.service` | serve / stop in the panel | rooms the user stopped are never re-served by the tool | stop / unload closes every room, removes `service.lock` |\n| Hosted seat (lane `pi-complete`) | its packet (task, other seats' words) | one `pi.agent.complete` call per turn to **your configured model** (uses your model quota) | `agent.complete` | the user starts the round | \u2264 200,000 chars per packet, system prompt \u2264 32 KiB, host rate limit 8 / 60 s, `tools: []`, no session context | \u2014 |\n| Panel `roundtable/*` channels | room state | admin commands to the served room (allowlist `task, start, say, close, skip, retry, approve-disclose, deny-disclose, accept-stale`) | `ui.panel` | a click in the panel | unknown commands refused (`UNKNOWN_CMD`) | close stops serving the room |\n| Panel \"Open room UI\" | the room's admin token | the system browser via `pi.shell.openExternal` | `shell.openExternal` | a click | loopback URL only; the token is in the URL fragment (local browser history may keep it) | \u2014 |\n| Turn toast | seat and room id; the host language (`pi.app.getLocale`, `appearance:changed`) | `pi.ui.showToast` | none | \u2014 | one toast per attempt of a manually waiting seat | the `appearance:changed` listener is removed on unload |\n| Plugin state | \u2014 | `roundtable.json` in `pi.plugin.getDataPath()` (seat bindings with session ids, rooms to serve again) | none | \u2014 | plugin-private | release a binding in the panel (on a closed room, with a second click) |\n| Codex wake push (room-dev) | a room's registered wake | room-dev's `codex queue` child process | \u2014 | only for rooms whose seats registered a wake **outside** this plugin | the Room tool never registers a wake | \u2014 |\n| Native-log audit and wake idle check (room-dev) | that agent's own session logs, read-only: Codex under `%USERPROFILE%\\.codex`, Claude Code under `%USERPROFILE%\\.claude\\projects` | audit results into the room's event log | \u2014 | only for seats registered **outside** this plugin with an audit source or a Codex wake thread | read-only; seats created through the Room tool register neither | \u2014 |\n| Reviewer verdict record (room-dev) | an authorized reviewer verdict | one appended line in `%USERPROFILE%\\room-dev\\adjudications.jsonl` (room id, reviewer agent kind, preset, verdict, artifact hash, annotation) | \u2014 | a reviewer seat's verdict | append-only, through room-dev's single-file guard | \u2014 |\n\nCredentials: none read. The plugin passes `savedPiKey: false` to room-dev, so it never decrypts\nroom-dev's DPAPI-saved Pi key; the host gives the plugin process a minimal environment, so\n`ROOM_PI_API_KEY` does not reach it either. A room made elsewhere with room-dev's built-in Pi seat\ntherefore gets no key here (that seat fails with `PI_NO_KEY`). No outbound network: the only socket\nis the loopback room UI server.\n\n## Authority: who may do what\n\n- **Seat identity comes from the host, never from tool input.** PI-Desktop passes the calling\n conversation's `ctx.sessionId` to the tool. A `join` binds one free PI seat to that session id,\n and every seat command acts only on that seat. If the tool input has a field named like\n `session\u2026`, `token\u2026` or `admin\u2026`, the call is refused with `IDENTITY_FROM_CONTEXT_ONLY`. Other\n refusals:\n - `NOT_YOUR_SEAT`: the call names someone else's seat.\n - `SEAT_TAKEN`: another conversation already holds that seat.\n - `NOT_A_PI_SEAT`: the seat is for an external agent.\n - `NO_SESSION`: the host gave no session id.\n- **Administration is the user's.** The tool has no action to set the task, start a round, approve\n or deny a disclosure, accept a stale verdict, skip, retry, close a room or release a binding. If\n it is asked to, it answers `ADMIN_ONLY_IN_PANEL`. Those actions exist only as panel channels\n (`roundtable/admin` with an allowlist of `task, start, say, close, skip, retry, approve-disclose,\n deny-disclose, accept-stale`) and in the room UI.\n- **Serving is the user's too.** Serve and stop live in the panel. A Room tool call serves a room\n only if the user has not stopped it: after **Stop serving**, every seat action that would need the\n room served answers `NOT_SERVED` (ask the user to serve it from the panel), and `join` binds the\n seat but reports `served: 'stopped'`. **Serve here** clears that.\n- **The binding is a plugin rule, not a file-system boundary.** As in room-dev, any local process\n that can read a seat folder can run that seat's commands with its token. The binding decides which\n PI conversation the Room tool acts for. It does not sandbox other local software.\n- **\"Administration is the user's\" holds for the Room tool only.** The room's admin token is a file,\n `%USERPROFILE%\\room-dev\\rooms\\\\admin.token`. A PI conversation whose own file or shell\n tools can read that path can issue admin commands without the panel, through the room's\n `admin-queue` or the loopback `/api/admin`. To keep administration with you, deny those tools\n the rooms folder (or at least `admin.token`). Leaving `roomDir` and `roomsRoot` out of the tool's\n output would only hide the path; it would not protect the token.\n\n## Decisions taken from the upstream source\n\nThey were read from the upstream clones at PI-Desktop `10e824ee12f8` and pi-desktop-plugins\n`c35605a5ed31`. Paths are relative to those repositories.\n\n1. **Waking a PI conversation: not implemented. PI seats use `waitMode manual`.**\n `session/collaboration/send` and `spawn` (through `pi.desktop.invoke`) refuse unless an Agent\n tool invocation is active (`apps/desktop/electron/main/services/session-collaboration.ts`\n 107-114). A resident service is never inside one. A timer created during a tool call does\n inherit that call's invocation context (an `AsyncLocalStorage` store), but the invocation is\n dead once the call returns: every `pi.*` call made from it afterwards is rejected with\n `PLUGIN_TOOL_ABORTED` (`plugin-host-process.mjs` 54-69, 531-557), so send and spawn still\n refuse. Room-dev's waker contract (pushed by the service when a turn is issued) therefore cannot\n be met. The alternative, `desktop.control`\n `agent/prompt`, has no such check. But it would deliver the wake as an ordinary prompt with\n user authority, which contradicts room-dev's labelling, so it is not used. The plugin requests\n no `desktop.control`. What you get instead: a toast tells you whose turn it is. You tell that\n conversation to continue, and it calls `wait`, which never blocks.\n2. **Hosted seats through `pi.agent.complete`: implemented for the discussion tier only.**\n `agent.complete` works only from a context that is not a finished tool invocation, and only\n when no session context is requested (`plugin-runtime.ts` 4089-4174, `plugin-host-process.mjs`\n 54-69). A room served from inside a tool call (`create`, or a seat action that serves the room)\n would otherwise run its timers in that call's dead context, and every completion and turn toast\n would fail. So the plugin opens rooms, runs their timers and makes every `agent.complete` and\n `showToast` call in a context captured at `onLoad` and `service.start` (`AsyncResource.bind`),\n never inside a tool invocation. It is one-shot and text-only, with `tools: []` on the host\n side. That fits room-dev's discussion tier and nothing else, so `create` refuses a `complete`\n seat as executor. Limits from the host: 8 completions per plugin per rolling 60 s\n (`RATE_LIMITED`, recorded by the room as `seat_failed {class: rate_limited}`), 90 s timeout,\n system prompt \u2264 32 KiB, messages \u2264 200,000 characters. The plugin holds no API key, because the\n host resolves credentials.\n3. **Panel: a minimal panel, with the room UI opened in the system browser.** The host loads\n panels as sandboxed `file://` pages and adds no CSP (`plugin-panel-host.ts` 500-560). But\n room-dev's own server forbids framing (`frame-ancestors 'none'`, `X-Frame-Options: DENY`) and\n accepts only same-origin API calls, and the marketplace audit blocks any\n `