diff --git a/README.md b/README.md index ca238010a1c..8f4cfdccb81 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,9 @@ Agent orchestration has subtleties in how you coordinate who has work checked ou **Do agents run continuously?** By default, agents run on scheduled heartbeats and event-based triggers (task assignment, @-mentions). You can also hook in continuous agents like OpenClaw. You bring your agent and Paperclip coordinates. +**Can I chat with my CEO?** +Yes, when Conference Room Chat is enabled. It is still issue-backed Paperclip work: the room resolves a `board_chat` issue, posts your message as an issue comment, and wakes a real selected agent by `targetAgentId` with the CEO as the default target. +
## Development @@ -389,7 +392,7 @@ See [doc/DEVELOPING.md](doc/DEVELOPING.md) for the full development guide. - ⚪ Work Queues - ⚪ Self-Organization - ⚪ Automatic Organizational Learning -- ⚪ CEO Chat +- ✅ Issue-backed CEO / selected-agent chat - ⚪ Cloud deployments - ⚪ Desktop App diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index d914d700a67..262bc9e26c6 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -124,6 +124,32 @@ These browser suites are intended for targeted local verification and CI, not th For normal issue work, start with the smallest targeted check that proves the change. Reserve repo-wide typecheck/build/test runs for PR-ready handoff or changes broad enough that narrow checks do not cover the risk. +## Selected-Agent Conference Room Verification + +The Conference Room is an experimental UI surface, but its transport is ordinary issue work. It is enabled through `Instance Settings > Experimental > Conference Room Chat`, or by patching the experimental instance setting: + +```sh +curl -X PATCH http://localhost:3100/api/instance/settings/experimental \ + -H 'Content-Type: application/json' \ + -d '{"enableConferenceRoomChat":true}' +``` + +Maintainers should verify the flow as issue-backed selected-agent chat: + +1. Open `/board-chat` after enabling the setting. +2. Confirm the page resolves or creates a `board_chat` issue through `POST /api/board/chat/conversations`. +3. Send a message. The UI posts to `POST /api/issues/:issueId/selected-agent-chat/comments`. +4. Confirm the request targets the selected real agent by `targetAgentId`; if omitted, the server defaults to the company CEO. +5. Confirm history is the issue's comments, live output comes from `/api/issues/:issueId/active-run?targetAgentId=...` and `/api/issues/:issueId/live-runs?targetAgentId=...`, and next-step cards are normal issue-thread interactions. + +Targeted regression commands: + +```sh +pnpm vitest run server/src/__tests__/issue-selected-agent-chat-routes.test.ts +pnpm vitest run server/src/__tests__/board-chat-conversations-routes.test.ts server/src/__tests__/board-chat-route-authz.test.ts +pnpm vitest run ui/src/components/SelectedAgentChat.test.tsx ui/src/pages/BoardChat.test.tsx +``` + ## One-Command Local Run For a first-time local install, you can bootstrap and run in one command: diff --git a/doc/PRODUCT.md b/doc/PRODUCT.md index 7a3d1f7b401..3494b23aa75 100644 --- a/doc/PRODUCT.md +++ b/doc/PRODUCT.md @@ -101,6 +101,17 @@ See [SPEC.md](./SPEC.md) for the full technical specification and [TASKS.md](./T Paperclip’s core identity is a **control plane for autonomous AI companies**, centered on **companies, org charts, goals, issues/comments, heartbeats, budgets, approvals, and board governance**. The public docs are also explicit about the current boundaries: **tasks/comments are the built-in communication model**, Paperclip is **not a chatbot**, and it is **not a code review tool**. The roadmap already points toward **easier onboarding, cloud agents, easier agent configuration, plugins, better docs, and ClipMart/ClipHub-style reusable companies/templates**. +## Conversational Work Surfaces + +Some Paperclip surfaces feel conversational, but they still use the same issue/comment control-plane model: + +- The issue detail chat tab is the issue's comment thread with run, interaction, and work-product context rendered inline. +- The Conference Room is a board-facing entry point backed by ordinary company-scoped `board_chat` issues. History is issue history, not a separate conversation store. +- Sending a Conference Room message posts a comment to the backing issue and wakes a real selected agent through `targetAgentId`. If no target is supplied, Paperclip selects the company's CEO. +- Agent replies are real agent-authored issue comments and their live work is shown from the target agent's active run for that issue. + +This is why "Chat with CEO" still resolves to strategy threads, decisions, tasks, approvals, and work products. It is a fast path into the work graph, not a board-concierge subsystem or a general-purpose chatbot. + ## What Paperclip should do vs. not do **Do** @@ -131,7 +142,7 @@ Paperclip’s core identity is a **control plane for autonomous AI companies**, The default UI should answer: what is the company doing, who is doing it, why does it matter, what did it cost, and what needs my approval. 3. **Conversation stays attached to work objects** - “Chat with CEO” should still resolve to strategy threads, decisions, tasks, or approvals. + “Chat with CEO” should still resolve to issue-backed strategy threads, decisions, tasks, approvals, and work products. 4. **Progressive disclosure** Top layer: human-readable summary. Middle layer: checklist/steps/artifacts. Bottom layer: raw logs/tool calls/transcript. diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md index 2c43e572add..6056446b88e 100644 --- a/doc/SPEC-implementation.md +++ b/doc/SPEC-implementation.md @@ -35,7 +35,7 @@ These decisions close open questions from `SPEC.md` for V1. | Board | Single human board operator per deployment | | Org graph | Strict tree (`reports_to` nullable root); no multi-manager reporting | | Visibility | Company-scoped visibility: board + all in-company agents can see all work objects by default; public/private deployment flags affect external exposure only and do **not** imply project/issue privacy | -| Communication | Tasks + comments only (no separate chat system) | +| Communication | Tasks + comments only (no separate chat system). Conversational surfaces such as the Conference Room are issue-backed and wake real selected agents by `targetAgentId` (CEO by default). | | Task ownership | Single assignee; atomic checkout required for `in_progress` transition | | Recovery | Liveness/watchdog recovery preserves explicit ownership: retry lost execution continuity where safe, otherwise open visible source-scoped recovery actions by default, use issue-backed recovery only for independent repair work, or require human escalation (see `doc/execution-semantics.md`) | | Agent adapters | Built-in `process`, `http`, local CLI/session adapters, and OpenClaw gateway support; external adapters can also be loaded through the adapter plugin flow | @@ -632,12 +632,26 @@ All endpoints are under `/api` and return JSON. - `POST /issues/:issueId/admin/force-release` (board-only lock recovery) - `POST /issues/:issueId/comments` - `GET /issues/:issueId/comments` +- `POST /issues/:issueId/selected-agent-chat/comments` (board-only; add a comment and wake a real selected agent, defaulting to the company CEO when `targetAgentId` is omitted) +- `GET /issues/:issueId/live-runs?targetAgentId=:agentId` +- `GET /issues/:issueId/active-run?targetAgentId=:agentId` - `POST /companies/:companyId/issues/:issueId/attachments` (multipart upload) - `GET /issues/:issueId/attachments` - `GET /attachments/:attachmentId/content` - `DELETE /attachments/:attachmentId` -### 10.4.1 Atomic Checkout Contract +### 10.4.1 Selected-Agent Issue Chat Contract + +Selected-agent chat is a thin board-user convenience over normal issue work: + +1. The board posts `body` to `POST /api/issues/:issueId/selected-agent-chat/comments`. +2. `targetAgentId` is optional. When omitted, the server selects the most recently created non-terminated CEO agent in the issue company. +3. The target agent must belong to the issue company and must not be paused, terminated, or otherwise unavailable. +4. The server persists the board message as a normal issue comment, writes activity, and wakes only the selected target agent with `selectedAgentChat: true` and a stable `taskKey` of `selected-agent-chat:{issueId}:{targetAgentId}`. +5. The UI reads durable history from `GET /api/issues/:issueId/comments`, reads issue-thread interactions from the normal interactions endpoints, and scopes live-run polling with `targetAgentId`. +6. If another selected-agent chat target is already running for the issue, retargeting returns `409 selected_agent_chat_target_active` unless the caller explicitly requests interruption. + +### 10.4.2 Atomic Checkout Contract `POST /issues/:issueId/checkout` request: @@ -721,6 +735,7 @@ The current app also exposes V1-supporting surfaces for: - issue thread interactions (`suggest_tasks`, `ask_user_questions`, `request_confirmation`) - issue approvals, issue references/search, labels, read state, inbox/archive state, and work products +- Conference Room conversation issue resolution through `POST /api/board/chat/conversations` (experimental `enableConferenceRoomChat`; resolves or creates a `board_chat` issue for the selected-agent chat surface) - execution workspaces, project workspaces, workspace runtime services, and workspace operations - routines and scheduled/API/webhook triggers - plugin installation, configuration, state, jobs, logs, webhooks, and plugin database namespace migration diff --git a/ui/src/components/SelectedAgentChat.test.tsx b/ui/src/components/SelectedAgentChat.test.tsx index 3f7c3a748c9..177b1f219a5 100644 --- a/ui/src/components/SelectedAgentChat.test.tsx +++ b/ui/src/components/SelectedAgentChat.test.tsx @@ -1,6 +1,7 @@ // @vitest-environment jsdom -import { act } from "react"; +import type { ReactNode } from "react"; +import { flushSync } from "react-dom"; import { createRoot, type Root } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { Agent } from "@paperclipai/shared"; @@ -81,13 +82,13 @@ describe("SelectedAgentChatView", () => { }); afterEach(() => { - act(() => root.unmount()); + flushSync(() => root.unmount()); container.remove(); vi.clearAllMocks(); }); - function render(node: React.ReactNode) { - act(() => root.render(node)); + function render(node: ReactNode) { + flushSync(() => root.render(node)); } it("renders the real selected-agent identity in the header", () => { @@ -131,7 +132,7 @@ describe("SelectedAgentChatView", () => { (b) => b.textContent === "Try again", ); expect(retry).toBeTruthy(); - act(() => retry!.dispatchEvent(new MouseEvent("click", { bubbles: true }))); + flushSync(() => retry!.dispatchEvent(new MouseEvent("click", { bubbles: true }))); expect(onRetry).toHaveBeenCalledOnce(); }); @@ -144,9 +145,8 @@ describe("SelectedAgentChatView", () => { "Sarah", ); const send = container.querySelector('[data-testid="send"]') as HTMLButtonElement; - await act(async () => { - send.dispatchEvent(new MouseEvent("click", { bubbles: true })); - }); + flushSync(() => send.dispatchEvent(new MouseEvent("click", { bubbles: true }))); + await Promise.resolve(); expect(onSend).toHaveBeenCalledWith("hello"); });