Skip to content

[ADR-0057] Console AI chat unification — implementation work order (P1–P4, agent-executable) #2412

Description

@os-zhuang

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 to assistant/assistantBus.ts + useReconcileOnError.
  • packages/app-shell/src/console/ai/AiChatPage.tsx — full-page /ai/build + /ai/ask + Live Canvas (ADR-0037); resolve ~L610; exports ChatPane.
  • packages/app-shell/src/views/studio-design/StudioAiCopilot.tsx — left-docked build copilot (ADR-0080 aiSlot); forks the thread via scope studio:${packageId}:${agent} (~L56); reuses ChatPane.
  • packages/plugin-chatbot/src/renderer.tsx — SDUI type:'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) — change StudioAiCopilot's studio:${packageId}:${agent}app:${packageId}:${product} so the full-page focus view and the docked copilot share ONE thread.
  • Delete isAiEnabled / discovery.services.ai; use useAiSurface everywhere.
  • 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.defaultAgent bounded 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, not isBuildAgent checks). 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 generalized assistantBus (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)

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. Needs cloud/scripts/dev-local/.env.local with AI_GATEWAY_API_KEY or the agent catalog is empty.
  • Backend/API checks work headless with the seeded session: curl GET /api/v1/ai/agentsbuild,ask.
  • UI checks: the better-auth.session_token cookie 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions