Repository navigation
[v2] chat:接真实接口(会话 CRUD、SSE、快照映射) - #236
Merged
Merged
Conversation
A conversation owns its title, target and executor choice and maps to one or more execution sessions: stopping, idling, deleting or a restart ends the current session and the next prompt starts a new one in the same workspace. Idempotency keys are the turns' attempt IDs, so retries never run a prompt twice, and a refused first prompt leaves no empty conversation.
The chat routes live outside api/openapi.json in chat.openapi.json. Session events stream as Server-Sent Events keyed by session sequence, so a reconnect resumes with Last-Event-ID. Without an execution runtime every chat route answers 503 execution_unavailable.
Generate the chat client from v2-server/chat.openapi.json and rebuild each session's turns from its latest snapshot through the reducer the samples use, so snapshots replace rather than append. An answer that was not read back in full shows as failed, and a marker says where an ended session stops so the next one reads as a fresh Agent.
An idle live session keeps its workspace until End, and stopping only cancels a running turn. POST .../end ends the session so a busy workspace can be handed to another conversation; the next prompt starts a new session.
The history column now lists the backend's projects, worktrees and conversations next to the samples. A conversation is created by its first prompt, followed over its sessions' event streams and rebuilt from each snapshot; a sent prompt shows until the history has it. A busy workspace names the conversation holding it and offers to end its session. The input sends text only and keeps a conversation on its executor, and new projects and worktrees stay hidden until the backend can create them. Samples still replay in the browser.
The session_ended event carries no reason, so a page learns it from the conversation. Chat now writes the reason before asking the port to end the session and withdraws it if ending fails, so the reason is readable as soon as the event is: an idle or released session no longer reads as interrupted.
A conversation nobody is watching and that waits on nothing stops following its sessions, since browsers allow six connections per origin over HTTP/1.1; following again resumes after the last event seen, so nothing is applied twice. A session that ended without this page acting reads its reason from the conversation. Prompts queued while a first prompt is admitted carry over to the conversation.
npm run dev:demo (task v2:demo) serves the chat routes and their event streams from the browser with v2-server's chat rules, made-up projects and a scripted Agent that writes fixed-Node history: busy workspaces, stopping, idle ends, failed answers and preview-to-settled replies can all be shown without an execution runtime. The page says it is a demo.
Each conversation now runs in its own directory, so conversations never
contend for a workspace. Stopping a turn ends the fixed Node session, and
the conversation then stays read-only instead of opening a new session.
- A conversation binds one execution session; the store refuses a second.
- Sending to an ended conversation returns 409 conversation_ended, and an
Append refused after a restart records the session as interrupted.
- A prompt sent while the previous turn runs returns 409 turn_running for
the page to queue.
- Remove idle reclaim, POST .../end, busy occupant hints and workspace
selection; events move to GET .../conversations/{cid}/events.
Follows the backend's one-session-per-conversation rules on the chat page. - A new chat picks only its project: no workspace tab, and the project's "+" starts a chat directly. Conversations list straight under their project with an "own directory" badge. - Once the session ends (stopped, or the server restarted) the thread ends with a marker and the input becomes a "new chat" button; nothing is sent and no new session is started. - A prompt the backend refuses with turn_running goes back to the front of the queue and waits for the running turn's end event. - Drop the busy-workspace notice, release action and worktree loading; follow events per conversation. - The demo backend gives every conversation its own session and seeds a live, a stopped and a restart-ended conversation.
Every conversation gets a new worktree from its project, so the input shows a "Worktree" label, as desktop marks worktree mode, with a tooltip explaining it; the conversation header shows the same label in place of the workspace badge. The worktree sample no longer sits under a branch in the history, since no conversation is grouped by worktree any more.
Every conversation runs in a sandbox of its own, its code cloned from the project's repository, so the chat page no longer knows any workspace kind: sessions and new chats drop the main/isolated/new-worktree workspace, the sidebar lists a project's conversations directly, and the unused worktree picker, naming dialog and branch-name helper go. The Worktree label becomes a 沙盒 label whose tooltip says the code is cloned and never written back.
A project's + or 新建对话 used to do nothing visible when the new chat view already showed that project. Each request for the new chat view now puts the cursor in its input, so the click always answers.
Chat no longer takes its own Options.Chat service. server.New builds the chat service from the store and the injected Options.Execution and Catalog, as every page does, so main.go stays untouched and the real service arrives with #774's assembly. Until then the injected unavailable Stub keeps the history readable and answers executors, create and send with 503 execution_unavailable; tests inject an executiontest.Stub the same way.
The project's + is a menu again, holding 新建对话 next to 运行工作流, and the chat page reads its projects and tenant from the backend, so the workflow-run flow renders with a query client and the chat backend fake.
v2-server does not serve the formal v1 project list; the chat page now
uses the candidate GET /api/v2/tenants/{tid}/projects through its own
generated client and follows nextCursor so no project is left out. The
test and demo backends answer the new route, the test one paged so the
page has to follow the cursor.
The chat page reads projects from v2-server, which serves none until the execution runtime is wired, so the workflow-run e2e gives it one project and executor to hang the run on, and returns to a sample session at the end. The chat e2e picks its notice out of dnd-kit's live region.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
对话页从前端 store 和占位数据改为走 v2-server。v2-server 新增 chat 领域、HTTP 和 SSE 事件流;chat 通过统一注入点
server.Options.Execution/Catalog取执行服务,不改main.go。真实执行服务装配后,页面侧不需要再改代码。v2-server
internal/chat:对话、对话与执行 session 的一一对应、项目顺序。一个对话只对应一个 session:停止或服务重启后对话只读,再发消息返回 409conversation_ended;上一轮未结束返回 409turn_running,由页面排队。Idempotency-Key即该轮的 attempt ID,同 key 重试不会重复执行;结果未知返回 503outcome_unknown;首条消息被拒绝时不留空对话。internal/server/chat.go:会话列表、创建、改名、删除、发消息、停止、执行器清单、项目顺序,以及GET …/chat/conversations/{cid}/events(Server-Sent Events,按Last-Event-ID续传)。契约之外的候选接口定义在v2-server/chat.openapi.json,并登记在v2-server/README.md。internal/store:对话表和项目顺序表。main注入的executiontest.Stub)时,执行器清单、创建和发消息返回 503execution_unavailable,历史仍可读。frontend-v2
orval.chat.config.ts);项目列表读 v2-server 的候选接口GET /api/v2/tenants/{tid}/projects,按nextCursor读完所有页。history_snapshot整份替换后重建轮次(同位置替换、settled 优先),没能完整读取的回答显示为失败。@插件、会话内切换模型、新建项目)留空。npm run dev:demo/task v2:demo:浏览器内的演示后端,按 v2-server 的 chat 规则回放脚本回复,界面右上角标"演示模式"。chat.spec.ts验证无运行时时的提示、示例回放和左栏调宽;workflow-runs.spec.ts因项目改由后端提供,用路由替身给一个项目和执行器。验证
gofmt、go vet ./...,真实 PostgreSQL 上go test -race -count=1 ./...全部通过。api:check:*、tsc -b、oxlint --deny-warnings、vite build通过;单元测试 67 个文件 490 个用例全部通过。playwright test18 个用例中 16 个通过;失败的 chat 与 workflow-runs 两个修正后单独重跑通过。executiontest.Stub验证;真实 Agent 链路的 e2e 和截图等执行服务装配进main后再补。已知限制
main尚未注入项目和执行服务,运行中的页面只有"示例"分组,项目的 + 菜单(新建对话、运行工作流)要等装配后才出现。v2,不涉及 specs。
Refs ora-space/circus#787