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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions