Repository navigation
[ADR-0057] Console AI chat unification — implementation work order (P1–P4, agent-executable) #2412
Description
Single, self-contained work order for the ADR-0057 implementation — written for an agent with no prior conversation context. Rolls up to epic #2409.
Read first: docs/adr/0057-console-ai-chat-one-conversation-docked.md (merged in #2411), plus upstream invariants cloud ADR-0063 (two agents, surface-bound) and cloud ADR-0025 (OSS = MCP only).
Objective
Collapse the console's fragmented AI-chat surfaces into one chat system, on the principle:
surface = view · conversation = model · product (
ask/build) = binding axis.
This is NOT a merge of ask and build — cloud ADR-0063 already fixed the agent model (two products, bound by surface, no roster, no per-turn classifier; it reversed ADR-0040's unified classifier — do not reintroduce it). This is objectui console-layer consolidation only. No cloud backend change except the optional deps below.
⛔ Repo workflow (mandatory)
- Worktree-first. This repo is edited by multiple agents and a PreToolUse hook blocks edits outside a dedicated worktree. Create one:
git worktree add ../objectui-<task> -b <branch> main && cd ../objectui-<task> && pnpm install(see AGENTS.md / CLAUDE.md). - One PR per phase, reversible, each with an ADR-0054 browser proof. No big-bang: P3 touches the most-used AI entry (the FAB) and the Studio layout.
Current state (starting points — line numbers drift, grep the symbols)
Three shells over one backend (POST /api/v1/ai/agents/:name/chat), forking the conversation:
packages/app-shell/src/layout/ConsoleFloatingChatbot.tsx— bottom-right FAB (ask); agent pick ~L915; wired toassistant/assistantBus.ts+useReconcileOnError.packages/app-shell/src/console/ai/AiChatPage.tsx— full-page/ai/build+/ai/ask+ Live Canvas (ADR-0037); resolve ~L610; exportsChatPane.packages/app-shell/src/views/studio-design/StudioAiCopilot.tsx— left-docked build copilot (ADR-0080aiSlot); forks the thread via scopestudio:${packageId}:${agent}(~L56); reusesChatPane.packages/plugin-chatbot/src/renderer.tsx— SDUItype:'chatbot'embedded in runtime end-user apps.
Two state layers: app-shell (useChatConversation, useConversationList, useAiSurface, conversationLanguage, reconcileTurn, assistantBus) vs plugin-chatbot (useObjectChat, useAgents, usePendingActions, useHitlInChat, agentAliases).
~5 divergent surface→agent picks (each re-implements the ADR-0063 chain): StudioAiCopilot.tsx:44, ConsoleFloatingChatbot.tsx:915, ConsoleLayout.tsx:84 (unique !aiStudioEnabled && isBuildAgent → downgrade special case), AiChatPage.tsx:610, layout/agentPicker.ts:46.
Two gates: hooks/useAiSurface.ts (correct — access-filtered agent catalog, ADR-0068) vs discovery.services.ai / isAiEnabled (per-user 403 gap; useAiSurface's docstring explicitly warns against it).
Already shared (don't rebuild): ChatbotEnhanced render body, the /api/v1/ai/agents/:name/chat transport, and the assistantBus context bridge (editor context / open / review / canvas-invalidation signals).
Phases
P1 — one conversation context + one gate (no visible change) — LAND FIRST
- Introduce
ChatSessionProvider(or consolidate into@object-ui/plugin-chatbot) owning agent resolution, conversation binding, list, streaming, pending actions, reconcile. All three shells become thin views over it. - Key conversations on
(user, app, product)— changeStudioAiCopilot'sstudio:${packageId}:${agent}→app:${packageId}:${product}so the full-page focus view and the docked copilot share ONE thread. - Delete
isAiEnabled/discovery.services.ai; useuseAiSurfaceeverywhere. - Acceptance: same thread visible in full-page
/ai/build↔ Studio copilot for one app; seat-less user sees no AI surface; no other visible change.
P2 — declarative surface→agent resolver
const SURFACE_DEFAULT = { 'studio-build': 'build', 'default': 'ask' } as const;
function resolveSurfaceAgent(surface, { catalog, appDefaultAgent, aiStudioEnabled }) {
const want = appDefaultAgent ?? SURFACE_DEFAULT[surface] ?? 'ask';
const eff = !aiStudioEnabled && isBuildAgent(want) ? 'ask' : want; // ConsoleLayout special case, folded in ONCE
return resolveAgentParam(eff, catalog) ?? resolveDefaultAgentName(catalog);
}- Migrate every call site (StudioAiCopilot, ConsoleFloatingChatbot, ConsoleLayout, AiChatPage, agentPicker) to it; delete the scattered picks + the ConsoleLayout downgrade.
- Acceptance: unit table for ADR-0063 — Studio→build, console/app→ask, aiStudio-off downgrade,
app.defaultAgentbounded to ask/build (reject anything else). No roster/classifier representable.
P3 — docked rail + Studio right reflow; retire the FAB — ⚠ needs founder sign-off (dock side)
ChatDock: render the shared panel as a right, collapsible, resizable rail, default collapsed (keep the FAB's zero-idle-cost). FAB → launcher / ⌘I toggle./ai= the same panel maximized (build + Live Canvas), same thread; deep links open the dock maximized.- Move the Studio copilot into the shared right dock → Studio reflows to
[left: nav/tree] [center: canvas+properties] [right: chat]. - Runtime end-user apps (SDUI
chatbot) keep the bubble — same state code, different chrome. - Optional dep objectstack-ai/cloud#816 (render debug/canvas/resume by declared
capabilities, notisBuildAgentchecks). Non-blocking. - Acceptance: chat location identical across console pages; expand/collapse preserves the thread; Studio 3-pane reflow; runtime bubble unchanged.
P4 — ask→build explicit handoff — ⚠ needs founder sign-off (explicit vs seamless); dep cloud#817
- In
ask, a build-shaped request surfaces an explicit "Open in Builder →" carrying the conversation context via the generalizedassistantBus(ADR-0063 decline-and-redirect — never a silent re-route into authoring). - Dep objectstack-ai/cloud#817 upgrades a context-less handoff to context-carrying.
- Acceptance: ask thread opens the Builder with context; no authoring ever happens on the ask surface.
Invariants (do not violate)
- ADR-0063: exactly two agents (
ask/build), bound by surface, no roster, no per-turn classifier; a build-shaped request onaskis declined+redirected, never silently rerouted. - ADR-0025 / OSS: empty agent catalog → the WHOLE surface (rail, launcher, focus route, bubble) is inert; AI is MCP-only. Keep the "inert without backend" open-core property.
- Do not modify the cloud backend; consume 视图配置面板存在无效项目,需排查并修复 #816/fix: comprehensive view config panel cleanup — remove unused fields, correct view-type visibility for all controls #817 only when available.
Verification (end-to-end)
- Boot the cloud local stack (serves
/api/v1/ai/*):cd <cloud> && OBJECTUI_ROOT=<objectui> OS_MULTI_ORG_ENABLED=true CLOUD_PORT=4000 OBJECTOS_PORT=5050 bash scripts/dev-local/run-stack.sh --with-ui --seed --keep --workdir /tmp/os-stack. Needscloud/scripts/dev-local/.env.localwithAI_GATEWAY_API_KEYor the agent catalog is empty. - Backend/API checks work headless with the seeded session:
curl GET /api/v1/ai/agents→build,ask. - UI checks: the
better-auth.session_tokencookie is httpOnly — can't be JS-injected, and the automation browser tab is a separate Chrome profile from the user's, so UI verification needs a login into the controlled tab (or a maintainer). Backend verification needs no browser. - Each phase ships an ADR-0054 UI-testability proof.
Open founder decisions (block P3/P4 only)
- Dock side + Studio reflow (chat vs properties on the right).
- Handoff explicit "Open in Builder" vs a more seamless in-place switch.
- P1/P2 do not depend on these — start there.
Related
Epic #2409 · ADR docs/adr/0057-console-ai-chat-one-conversation-docked.md (#2411) · Cloud deps objectstack-ai/cloud#816 (P3, optional), objectstack-ai/cloud#817 (P4).
Activity
xuyushun441-sys commentedon Aug 3, 2026
维护者裁决(2026-08-03)
排期:v17 后;开工前先核对 P1/P2 实际完成状态(本裁决不假设其已落地——按各阶段验收标准逐条验证后再派 P3)。摘
needs-user-decision。Generated by Claude Code
Generated by Claude Code
claude commentedon Aug 6, 2026
分诊:过时前提检查判定「已全部交付」⇒ 关闭(completed),附否决窗口。
维护者 2026-08-03 的裁决明确写了一句前置条件:「开工前先核对 P1/P2 实际完成状态(本裁决不假设其已落地——按各阶段验收标准逐条验证后再派 P3)」。本轮执行了这次核对,结论是四个阶段连同收尾清理全部已落地,本工单已无可派发内容。
逐阶段核验(全部读
origin/main,不读共享工作树)isAiEnabled/discovery.services.ai;Studio 不再 fork 线程StudioAiCopilot.tsx:64注释写死「nostudio:fork」,scope 已是(user, package, build);app-shell 内isAiEnabled仅存于useAiSurface.ts的反面警告文档与packages/react的通用 discovery hookresolveSurfaceAgentpackages/app-shell/src/hooks/surfaceAgent.ts存在,含配套__tests__/surfaceAgent.test.ts;模块头写明ConsoleLayout的降级特例已 folded in ONCE,与工单 P2 代码草图逐条对应layout/ChatDock.tsx+layout/ConsoleChatbotFab.tsx均在;layout/ConsoleFloatingChatbot.tsx已从 main 删除(工单「Current state」把它列为起点文件之一 —— 起点已不复存在)最硬的一条读数:
docs/adr/0057-console-ai-chat-one-conversation-docked.md的 Status 行自陈 ——即 epic #2409 收尾评论里列的三项遗留(① 翻开 flag 放量、② 清理 PR 删 flag 与死浮层路径、③ ADR Status 翻牌)也已全部完成 —— #2409 本身已于 2026-07-14 关为 completed。
为什么关闭而不是留
pm:on-hold等 v17:维护者裁决排期到 v17 后,针对的是尚未开工的 P3。既然 P3 已由 #2464/#2465/#2467/#2469/#2470 交付并完成 go-live 与清理,继续挂着这份工单只会让下一次 sweep 把已交付的工作重新派发一次 —— 工单正文的「Current state」还指向一个已被删除的文件,任何 dev 拿到它都会从一个不存在的起点开工。这正是过时前提检查要拦的形状。查重与关联:epic #2409(closed/completed)、#2443(P4 follow-ups,同批分诊,见该单结论)、ADR 单 #2411。⛔ 未发现其它仍在流通的 ADR-0057 派发入口。
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code