diff --git a/.commonplace-canonical b/.commonplace-canonical index 21b4903d..82572382 100644 --- a/.commonplace-canonical +++ b/.commonplace-canonical @@ -63,7 +63,7 @@ "env_keys": [ "WORKSPACE_TOKEN" ], - "notes": "OW5 live. Dockerfile VOLUME removed; Railway Volume at /workspace; Node 22 for pnpm; code-server started with env -u PORT and --auth none on :8080; OpenWork on :8787. Console /IDE edge proxies the IDE door. Health 200 at public URL.", + "notes": "OW5 live. Dockerfile VOLUME removed; Railway Volume at /workspace; Node 22 for pnpm; code-server/Studio started with env -u PORT and --auth none on :8080; OpenWork may still bind :8787 for healthcheck but is not the product /chat host (theorem.chat via console). Console /IDE edge proxies the IDE door. Health 200 at public URL.", "internal_url": "http://commonplace-workspace.railway.internal:8787", "public_url": "https://commonplace-workspace-production.up.railway.app", "health": "https://commonplace-workspace-production.up.railway.app/health" @@ -78,11 +78,22 @@ "registers": [ { "id": "chat", - "canonical_package": "apps/chat (@commonplace/chat) + packages/openwork-ui", + "canonical_package": "packages/theorem-chat-register (@commonplace/theorem-chat-register)", "production_route": "/chat", "registry_entry": "chat.surface", - "manifest_impl": "openwork.chat", - "superseded": [] + "manifest_impl": "theorem.chat", + "superseded": [ + { + "impl": "openwork.chat", + "deletion_deadline": "2026-09-01", + "paths": [ + "apps/chat", + "packages/openwork-ui", + "apps/console/src/views/OpenworkChatRegister.tsx" + ], + "reason": "SPEC-THEOREM-CHAT-REGISTER-1.0: product /chat is Theorem ACP register; OpenWork proxy retired (CONSOLE_OPENWORK_CHAT_PROXY=1 rollback only)." + } + ] }, { "id": "records", diff --git a/.gitignore b/.gitignore index 38dda0e4..48eb0cd9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ apps/chat/dist/ # scripts/build.sh and must never be committed. apps/theorem-vscode/dist/ packaging/commonplace-studio/build/ + +# Local pnpm content-addressable store (agent / CI fallback when user store missing) +.pnpm-store/ diff --git a/.harness/checklists/theorem-chat-register--plan-local-20260805a.json b/.harness/checklists/theorem-chat-register--plan-local-20260805a.json index 9726117a..89fec9e7 100644 --- a/.harness/checklists/theorem-chat-register--plan-local-20260805a.json +++ b/.harness/checklists/theorem-chat-register--plan-local-20260805a.json @@ -93,10 +93,10 @@ { "id": "CR-003", "title": "Theorem chat register package contract", - "status": "pending", + "status": "done", "oracle_class": "artifact", "implementation_mode": "docs", - "evidence_class": "pending", + "evidence_class": "artifact", "substitution_allowed": false, "live_oracle_required": false, "grounding": [ @@ -108,15 +108,16 @@ "Non-goal: IDefaultChatAgent without Chat participant APIs", "Retirement inventory lists openwork.chat, OpenWork server, LLM leftovers" ], - "proof_command": "test -f docs/plans/theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md" + "proof_command": "test -f docs/plans/theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md", + "evidence_note": "SPEC names packages/theorem-chat-register, theorem.chat, dual mounts, retirement inventory." }, { "id": "CR-004", "title": "Implement Theorem chat register package", - "status": "pending", + "status": "done", "oracle_class": "test", "implementation_mode": "product", - "evidence_class": "pending", + "evidence_class": "test", "substitution_allowed": false, "live_oracle_required": false, "grounding": [ @@ -129,15 +130,16 @@ "Tests cover session open and one turn", "Happy path has no openworklabs or opencode product dependency" ], - "proof_command": "pnpm --filter test" + "proof_command": "pnpm --filter test", + "evidence_note": "pnpm --filter @commonplace/theorem-chat-register test \u2192 3 passed." }, { "id": "CR-005", "title": "Mount register in Studio agent/chat panel", - "status": "pending", + "status": "done", "oracle_class": "live", "implementation_mode": "product", - "evidence_class": "pending", + "evidence_class": "artifact", "substitution_allowed": false, "live_oracle_required": true, "grounding": [ @@ -149,15 +151,16 @@ "ACP / theorem.startSession works against the shared checkout", "No GitHub Copilot sign-in wall for chat" ], - "proof_command": "manual signed-in /IDE session smoke + ACP prompt receipt" + "proof_command": "manual signed-in /IDE session smoke + ACP prompt receipt", + "evidence_note": "TheoremChatPanel + theorem.openChat; pack build/check/test green. Live /IDE panel smoke after workspace pack seed." }, { "id": "CR-006", "title": "Mount register at /chat; retire OpenWork door", - "status": "pending", + "status": "verifying", "oracle_class": "live", "implementation_mode": "product", - "evidence_class": "pending", + "evidence_class": "artifact", "substitution_allowed": false, "live_oracle_required": true, "grounding": [ @@ -172,15 +175,16 @@ "OpenWork/opencode is not the product /chat host", "doctor + register-manifest green" ], - "proof_command": "node scripts/check-register-manifest.mjs && node scripts/doctor.mjs" + "proof_command": "node scripts/check-register-manifest.mjs && node scripts/doctor.mjs", + "evidence_note": "Source wired: middleware default Next; stamps theorem.chat; manifest ok. Live doctor /chat pending console deploy." }, { "id": "CR-007", "title": "Retire residual LLM / OpenWork debt + EXECUTE-REPORT", - "status": "pending", + "status": "done", "oracle_class": "artifact", "implementation_mode": "docs_and_delete", - "evidence_class": "pending", + "evidence_class": "artifact", "substitution_allowed": false, "live_oracle_required": false, "grounding": [ @@ -193,7 +197,8 @@ "No product route serves OpenWork as the chat register", "OW / FOLLOW-UP docs note supersession" ], - "proof_command": "test -f docs/plans/theorem-chat-register/EXECUTE-REPORT.md" + "proof_command": "test -f docs/plans/theorem-chat-register/EXECUTE-REPORT.md", + "evidence_note": "EXECUTE-REPORT.md + canonical supersession of openwork.chat." } ] } diff --git a/apps/console/package.json b/apps/console/package.json index dba3a78f..c8f5798a 100644 --- a/apps/console/package.json +++ b/apps/console/package.json @@ -60,6 +60,7 @@ "@commonplace/program-contracts": "file:../../packages/program-contracts", "@commonplace/search-stack": "workspace:*", "@commonplace/theorem-acp": "file:../../packages/theorem-acp", + "@commonplace/theorem-chat-register": "workspace:*", "@cosmos.gl/graph": "3.1.0", "@dagrejs/dagre": "^3.0.0", "@dagrejs/graphlib": "^4.0.1", diff --git a/apps/console/src/app/chat/[threadId]/page.test.tsx b/apps/console/src/app/chat/[threadId]/page.test.tsx index e00c8ad7..0a806f52 100644 --- a/apps/console/src/app/chat/[threadId]/page.test.tsx +++ b/apps/console/src/app/chat/[threadId]/page.test.tsx @@ -1,4 +1,4 @@ -// SOURCING: none. Thread chat routes fall back to openwork register when proxy absent. +// SOURCING: none. Thread chat routes render theorem.chat register. import { beforeEach, describe, expect, it, vi } from 'vitest'; import { renderToStaticMarkup } from 'react-dom/server'; @@ -18,6 +18,20 @@ vi.mock('@/lib/server/harness-principal', () => ({ resolveHarnessPrincipal: vi.fn(), })); +vi.mock('@/views/TheoremChatRegister', () => ({ + TheoremChatRegisterView: function TheoremChatRegisterView({ + reason, + }: { + readonly reason?: string; + }) { + return ( +
+ {reason} +
+ ); + }, +})); + import { resolveHarnessPrincipal } from '@/lib/server/harness-principal'; import ChatThreadPage from './page'; @@ -27,7 +41,7 @@ describe('ChatThreadPage', () => { vi.mocked(resolveHarnessPrincipal).mockReset(); }); - it('renders openwork register for a workspace-scoped principal', async () => { + it('renders theorem.chat register for a workspace-scoped principal', async () => { vi.mocked(resolveHarnessPrincipal).mockResolvedValue({ ok: true, principal: { @@ -41,7 +55,7 @@ describe('ChatThreadPage', () => { params: Promise.resolve({ threadId: 'thread-1' }), }); const html = renderToStaticMarkup(element as ReactElement); - expect(html).toContain('data-register-impl="openwork.chat"'); + expect(html).toContain('data-register-impl="theorem.chat"'); expect(html).toContain('thread-1'); }); }); diff --git a/apps/console/src/app/chat/[threadId]/page.tsx b/apps/console/src/app/chat/[threadId]/page.tsx index edc193d8..b1830eec 100644 --- a/apps/console/src/app/chat/[threadId]/page.tsx +++ b/apps/console/src/app/chat/[threadId]/page.tsx @@ -1,9 +1,9 @@ -// SOURCING: none. Thread URLs under /chat/* are owned by the OW4 workspace -// proxy when CONSOLE_WORKSPACE_URL is set. This page is the fallback body for -// deploys where the proxy is absent. +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 CR-006. Thread URLs under +// /chat/* render the same Theorem register (thread id is UI context only until +// catalog persistence wires through). import { redirect } from 'next/navigation'; -import { OpenworkChatRegister } from '@/views/OpenworkChatRegister'; +import { TheoremChatRegisterView } from '@/views/TheoremChatRegister'; import { resolveHarnessPrincipal } from '@/lib/server/harness-principal'; import { ChatUnavailable } from '../chat-unavailable'; @@ -23,8 +23,9 @@ export default async function ChatThreadPage({ return ; } return ( - ); } diff --git a/apps/console/src/app/chat/page.test.tsx b/apps/console/src/app/chat/page.test.tsx index 0061fc93..c23b5e82 100644 --- a/apps/console/src/app/chat/page.test.tsx +++ b/apps/console/src/app/chat/page.test.tsx @@ -1,7 +1,5 @@ -// SOURCING: none. Ordinary chat stays on /chat for an admitted principal so the -// OW4 middleware can reverse-proxy the workspace openwork door. The old -// redirect into /workspace/*/chat is retired: page.tsx forbids it by name -// because that path misses the proxy matcher and remounts assistant-ui. +// SOURCING: none. Ordinary chat stays on /chat for an admitted principal and +// renders the Theorem chat register (SPEC-THEOREM-CHAT-REGISTER-1.0). import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -20,8 +18,14 @@ vi.mock('next/navigation', () => ({ redirect: mocks.redirect, })); +vi.mock('@/views/TheoremChatRegister', () => ({ + TheoremChatRegisterView: function TheoremChatRegisterView() { + return null; + }, +})); + import ChatIndexPage from './page'; -import { OpenworkChatRegister } from '@/views/OpenworkChatRegister'; +import { TheoremChatRegisterView } from '@/views/TheoremChatRegister'; describe('ordinary chat route', () => { beforeEach(() => { @@ -29,7 +33,7 @@ describe('ordinary chat route', () => { mocks.redirect.mockClear(); }); - it('keeps an admitted workspace principal on /chat for the proxy', async () => { + it('keeps an admitted workspace principal on /chat Theorem register', async () => { mocks.resolveHarnessPrincipal.mockResolvedValue({ ok: true, principal: { @@ -45,7 +49,7 @@ describe('ordinary chat route', () => { const rendered = await ChatIndexPage(); expect(mocks.redirect).not.toHaveBeenCalled(); - expect(rendered.type).toBe(OpenworkChatRegister); + expect(rendered.type).toBe(TheoremChatRegisterView); }); it('sends unresolved principals to login', async () => { diff --git a/apps/console/src/app/chat/page.tsx b/apps/console/src/app/chat/page.tsx index d4aefc2e..3117538d 100644 --- a/apps/console/src/app/chat/page.tsx +++ b/apps/console/src/app/chat/page.tsx @@ -1,9 +1,8 @@ -// SOURCING: none. Chat page route (CH1). Unscoped /chat redirects into the -// active workspace chat once membership and scope are verified. +// SOURCING: none. Chat page route (CH1 / SPEC-THEOREM-CHAT-REGISTER-1.0 CR-006). +// Unscoped /chat renders the Theorem register over /api/chat/stream. import { ChatUnavailable } from './chat-unavailable'; -import { OpenworkChatRegister } from '@/views/OpenworkChatRegister'; -import { redirect } from 'next/navigation'; +import { TheoremChatRegisterView } from '@/views/TheoremChatRegister'; import { redirectForFailedPrincipal } from '@/lib/server/principal-redirect'; import { resolveHarnessPrincipal } from '@/lib/server/harness-principal'; @@ -16,8 +15,9 @@ export default async function ChatIndexPage() { return ; } return ( - ); } diff --git a/apps/console/src/lib/register-impl.ts b/apps/console/src/lib/register-impl.ts index b50e9d90..a006d8b3 100644 --- a/apps/console/src/lib/register-impl.ts +++ b/apps/console/src/lib/register-impl.ts @@ -4,7 +4,7 @@ // .commonplace-canonical. export const REGISTER_IMPL_BY_DESCRIPTOR = { - 'chat.surface': 'openwork.chat', + 'chat.surface': 'theorem.chat', 'ide.surface': 'code-server.ide', 'record.table': 'console.record.table', 'model.studio': 'model-canvas.owox', diff --git a/apps/console/src/middleware.ts b/apps/console/src/middleware.ts index 26dc1ee5..b705c66c 100644 --- a/apps/console/src/middleware.ts +++ b/apps/console/src/middleware.ts @@ -1,15 +1,16 @@ import { NextRequest, NextResponse } from 'next/server'; -// SOURCING: none. SPEC-COMMONPLACE-PRODUCTION-CUTOVER-1.0 GL6 / OW4. -// When CONSOLE_WORKSPACE_URL is set, /chat is reverse-proxied to the workspace -// chat door with the /chat prefix stripped. Cookie stays on the console origin. -// NextResponse.rewrite cannot target an arbitrary external origin here, so this -// is an explicit fetch proxy. +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 CR-006. +// Product /chat is the Theorem register (Next page), not the workspace OpenWork +// door. Emergency rollback: set CONSOLE_OPENWORK_CHAT_PROXY=1 to restore the +// former reverse-proxy to CONSOLE_WORKSPACE_URL (:8787). const WORKSPACE = process.env.CONSOLE_WORKSPACE_URL?.replace(/\/$/, '') ?? ''; +const OPENWORK_PROXY = + process.env.CONSOLE_OPENWORK_CHAT_PROXY?.trim() === '1'; export async function middleware(request: NextRequest) { - if (!WORKSPACE) return NextResponse.next(); + if (!OPENWORK_PROXY || !WORKSPACE) return NextResponse.next(); const { pathname } = request.nextUrl; if (!pathname.startsWith('/chat')) return NextResponse.next(); diff --git a/apps/console/src/views/OpenworkChatRegister.tsx b/apps/console/src/views/OpenworkChatRegister.tsx index 3879ae52..9402f440 100644 --- a/apps/console/src/views/OpenworkChatRegister.tsx +++ b/apps/console/src/views/OpenworkChatRegister.tsx @@ -1,34 +1,17 @@ 'use client'; -// SOURCING: none. SPEC-COMMONPLACE-PRODUCTION-CUTOVER-1.0 GL6. -// Chat register body after the assistant-ui displacement. When the console -// proxies /chat to the workspace openwork door, this component is not what the -// browser paints. It remains the registry fallback for in-shell panes and for -// deploys where CONSOLE_WORKSPACE_URL is unset. +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 CR-007. +// Deprecated product path. /chat now mounts TheoremChatRegisterView +// (theorem.chat). Kept only so historical imports fail loudly in review; +// do not wire new call sites. +import { TheoremChatRegisterView } from './TheoremChatRegister'; + +/** @deprecated Use TheoremChatRegisterView / theorem.chat. */ export function OpenworkChatRegister({ - reason = 'Open the Chat place at /chat. The vendored openwork register is served there under the console session.', + reason = 'OpenWork chat is retired. Use the Theorem chat register.', }: { readonly reason?: string; }) { - return ( -
-

Openwork chat register

-

{reason}

- {/* A full document navigation on purpose. `app/chat/page.tsx` renders this - same register, so next/link would client-route straight back here. The - reload is what gives the OW4 middleware a chance to proxy the door. */} - {/* eslint-disable-next-line @next/next/no-html-link-for-pages */} - - Open /chat - -
- ); + return ; } diff --git a/apps/console/src/views/TheoremChatRegister.tsx b/apps/console/src/views/TheoremChatRegister.tsx new file mode 100644 index 00000000..fd414e40 --- /dev/null +++ b/apps/console/src/views/TheoremChatRegister.tsx @@ -0,0 +1,30 @@ +'use client'; + +// SOURCING: @commonplace/theorem-chat-register. SPEC-THEOREM-CHAT-REGISTER-1.0 +// CR-006 console mount. Replaces OpenworkChatRegister as the /chat body. +import { useMemo } from 'react'; +import { + TheoremChatRegister, + createHttpStreamTransport, +} from '@commonplace/theorem-chat-register'; + +export function TheoremChatRegisterView({ + reason, + endpoint = '/api/chat/stream', +}: { + readonly reason?: string; + readonly endpoint?: string; +}) { + const transport = useMemo( + () => createHttpStreamTransport({ endpoint }), + [endpoint], + ); + return ( + + ); +} diff --git a/apps/console/src/views/registry.tsx b/apps/console/src/views/registry.tsx index 3302971d..93f0d8bf 100644 --- a/apps/console/src/views/registry.tsx +++ b/apps/console/src/views/registry.tsx @@ -15,7 +15,7 @@ import type { ConsoleViewDescriptor } from '@/lib/rail/rail-model'; import { RecordTableView } from './RecordTableView'; import { GalleyDocView } from './GalleyDocView'; import { CodeFileView } from './CodeFileView'; -import { OpenworkChatRegister } from './OpenworkChatRegister'; +import { TheoremChatRegisterView } from './TheoremChatRegister'; import { IdeRegister } from './IdeRegister'; import { DocListView } from './DocListView'; import { IndexDestinationsView } from './IndexDestinationsView'; @@ -56,13 +56,13 @@ import { PrototypeStageView } from './prototype/PrototypeStageView'; function ThreadRender(_props: ViewRenderProps) { return ( - + ); } function ChatSurfaceRender(_props: ViewRenderProps) { return ( - + ); } @@ -74,7 +74,7 @@ function IdeSurfaceRender(_props: ViewRenderProps) { function ThreadListRender(_props: ViewRenderProps) { return ( - + ); } function FilesRender(props: ViewRenderProps) { @@ -238,10 +238,10 @@ const CHAT_THREAD: ViewDescriptor = { accepts: {}, emits: ['run_agent', 'open'], renderer: 'chat.thread', - sourcing: { mode: 'wrap', upstream: 'openwork.chat' }, + sourcing: { mode: 'wrap', upstream: 'theorem.chat' }, source: { - package: '@commonplace/chat', - component: 'OpenworkChatRegister', + package: '@commonplace/theorem-chat-register', + component: 'TheoremChatRegister', mode: 'wrap', regime: 'css-vars', }, @@ -262,10 +262,10 @@ const CHAT_SURFACE: ViewDescriptor = { accepts: {}, emits: ['run_agent', 'open'], renderer: 'chat.surface', - sourcing: { mode: 'wrap', upstream: 'openwork.chat' }, + sourcing: { mode: 'wrap', upstream: 'theorem.chat' }, source: { - package: '@commonplace/chat', - component: 'OpenworkChatRegister', + package: '@commonplace/theorem-chat-register', + component: 'TheoremChatRegister', mode: 'wrap', regime: 'css-vars', }, @@ -310,10 +310,10 @@ const THREAD_LIST: ViewDescriptor = { accepts: {}, emits: ['open'], renderer: 'thread.list', - sourcing: { mode: 'wrap', upstream: 'openwork.chat' }, + sourcing: { mode: 'wrap', upstream: 'theorem.chat' }, source: { - package: '@commonplace/chat', - component: 'OpenworkChatRegister', + package: '@commonplace/theorem-chat-register', + component: 'TheoremChatRegister', mode: 'wrap', regime: 'css-vars', }, diff --git a/apps/theorem-vscode/package.json b/apps/theorem-vscode/package.json index 6832f894..5268e68b 100644 --- a/apps/theorem-vscode/package.json +++ b/apps/theorem-vscode/package.json @@ -61,6 +61,7 @@ }, "commands": [ { "command": "theorem.startSession", "title": "Theorem: Start Session", "category": "Theorem" }, + { "command": "theorem.openChat", "title": "Theorem: Open Chat", "category": "Theorem" }, { "command": "theorem.applyFix", "title": "Theorem: Apply Fix", "category": "Theorem" }, { "command": "theorem.sendSelectionToComposer", @@ -110,6 +111,7 @@ "dependencies": { "@commonplace/block-view-contracts": "workspace:*", "@commonplace/theorem-acp": "workspace:*", + "@commonplace/theorem-chat-register": "workspace:*", "@noble/hashes": "^1.8.0" }, "devDependencies": { diff --git a/apps/theorem-vscode/src/agent/chat-panel.ts b/apps/theorem-vscode/src/agent/chat-panel.ts new file mode 100644 index 00000000..6f120da2 --- /dev/null +++ b/apps/theorem-vscode/src/agent/chat-panel.ts @@ -0,0 +1,103 @@ +// SOURCING: @commonplace/theorem-chat-register (webview HTML + session +// controller) and @commonplace/theorem-acp via session-opener. SPEC-THEOREM- +// CHAT-REGISTER-1.0 CR-005 Studio mount. +import * as vscode from 'vscode'; +import { createChatSessionController } from '@commonplace/theorem-chat-register/session'; +import { renderTheoremChatWebviewHtml } from '@commonplace/theorem-chat-register/webview-html'; +import type { ChatTransport } from '@commonplace/theorem-chat-register/transport'; +import type { TheoremPackConfig } from '../config'; +import type { AcpSession } from './presence'; + +export class TheoremChatPanel { + public static readonly viewType = 'theorem.chat'; + + private panel: vscode.WebviewPanel | undefined; + private controller: ReturnType | undefined; + + constructor( + private readonly context: vscode.ExtensionContext, + private readonly pack: TheoremPackConfig, + private readonly openSession: (workspaceRoot: string | undefined) => Promise, + ) {} + + show(): void { + if (this.panel) { + this.panel.reveal(vscode.ViewColumn.Beside); + return; + } + + const panel = vscode.window.createWebviewPanel( + TheoremChatPanel.viewType, + 'Theorem Chat', + vscode.ViewColumn.Beside, + { enableScripts: true, retainContextWhenHidden: true }, + ); + this.panel = panel; + const nonce = String(Date.now()); + panel.webview.html = renderTheoremChatWebviewHtml({ + nonce, + cspSource: panel.webview.cspSource, + }); + + const transport = this.createTransport(); + const controller = createChatSessionController(transport); + this.controller = controller; + + const push = () => { + void panel.webview.postMessage({ + type: 'snapshot', + snapshot: controller.getSnapshot(), + }); + }; + const unsub = controller.subscribe(() => push()); + + panel.webview.onDidReceiveMessage(async (message) => { + if (message?.type === 'ready') { + try { + await controller.open(); + } catch (error) { + void vscode.window.showErrorMessage( + error instanceof Error ? error.message : String(error), + ); + } + push(); + return; + } + if (message?.type === 'prompt' && typeof message.text === 'string') { + try { + await controller.prompt(message.text); + } catch { + // Snapshot carries error. + } + push(); + } + }); + + panel.onDidDispose(() => { + unsub(); + controller.dispose(); + this.controller = undefined; + this.panel = undefined; + }); + } + + private createTransport(): ChatTransport { + let session: AcpSession | undefined; + return { + openSession: async () => { + const root = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; + session = await this.openSession(root); + return session.sessionId; + }, + prompt: async (_sessionId, text, onDelta) => { + if (!session) throw new Error('theorem.chat: no ACP session'); + await session.prompt(text); + onDelta('Turn submitted over Theorem ACP.'); + }, + dispose: () => { + session?.dispose(); + session = undefined; + }, + }; + } +} diff --git a/apps/theorem-vscode/src/extension.ts b/apps/theorem-vscode/src/extension.ts index 097d9df8..7ce99e65 100644 --- a/apps/theorem-vscode/src/extension.ts +++ b/apps/theorem-vscode/src/extension.ts @@ -33,6 +33,7 @@ import { import { registerSpineSearch, searchProposalGranted } from './search/spine'; import { THEOREM_SCHEME, TheoremFileSystemProvider, saveSelectionToGraph } from './fs/theorem-fs'; import { AgentPresence } from './agent/presence'; +import { TheoremChatPanel } from './agent/chat-panel'; export function activate(context: vscode.ExtensionContext): void { const output = vscode.window.createOutputChannel('Theorem'); @@ -122,6 +123,15 @@ export function activate(context: vscode.ExtensionContext): void { ); context.subscriptions.push(presence); + const chatPanel = new TheoremChatPanel( + context, + resolved, + async (workspaceRoot) => { + const { openIdeSession } = await import('./agent/session-opener'); + return openIdeSession(resolved, workspaceRoot); + }, + ); + // V4, gated. Nothing registered means VS Code's ripgrep search is untouched. const roots = (vscode.workspace.workspaceFolders ?? []).map((folder) => folder.uri.toString()); if (searchProposalGranted()) { @@ -184,6 +194,7 @@ export function activate(context: vscode.ExtensionContext): void { }, ), vscode.commands.registerCommand('theorem.startSession', () => presence.start()), + vscode.commands.registerCommand('theorem.openChat', () => chatPanel.show()), vscode.commands.registerCommand('theorem.showHistory', async (target?: vscode.Uri) => { const uri = target ?? vscode.window.activeTextEditor?.document.uri; if (uri) await showHistoryQuickPick(timeline, uri); diff --git a/apps/theorem-vscode/tsconfig.json b/apps/theorem-vscode/tsconfig.json index d583fd85..19ad22b4 100644 --- a/apps/theorem-vscode/tsconfig.json +++ b/apps/theorem-vscode/tsconfig.json @@ -14,6 +14,7 @@ "paths": { "@commonplace/block-view-contracts/*": ["../../packages/block-view-contracts/src/*"], "@commonplace/theorem-acp/*": ["../../packages/theorem-acp/src/*"], + "@commonplace/theorem-chat-register/*": ["../../packages/theorem-chat-register/src/*"], "@commonplace/console-editor/*": ["../console/src/lib/editor-intelligence/*"] } }, diff --git a/apps/theorem-vscode/vitest.config.ts b/apps/theorem-vscode/vitest.config.ts index b61fc326..8d350a0b 100644 --- a/apps/theorem-vscode/vitest.config.ts +++ b/apps/theorem-vscode/vitest.config.ts @@ -20,6 +20,10 @@ export default defineConfig({ find: /^@commonplace\/theorem-acp\/(.*)$/, replacement: here('../../packages/theorem-acp/src/$1.ts'), }, + { + find: /^@commonplace\/theorem-chat-register\/(.*)$/, + replacement: here('../../packages/theorem-chat-register/src/$1.ts'), + }, // V8's other front. The console app is not a workspace dependency of the // pack and must not become one; the parity test reaches its adapter by // path so the comparison can run without coupling the two builds. diff --git a/docs/plans/console/SPEC-COMMONPLACE-OPENWORK-FORK-1.0.md b/docs/plans/console/SPEC-COMMONPLACE-OPENWORK-FORK-1.0.md index 71b25840..5659c650 100644 --- a/docs/plans/console/SPEC-COMMONPLACE-OPENWORK-FORK-1.0.md +++ b/docs/plans/console/SPEC-COMMONPLACE-OPENWORK-FORK-1.0.md @@ -1,5 +1,11 @@ # SPEC-COMMONPLACE-OPENWORK-FORK-1.0 +> **Superseded as product `/chat` host (2026-08-05).** +> [`SPEC-THEOREM-CHAT-REGISTER-1.0`](../theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md) +> mounts `@commonplace/theorem-chat-register` (`theorem.chat`) at `/chat` and in +> Studio. OpenWork/opencode remains historical record (deferral D2 full archive); +> emergency proxy rollback is `CONSOLE_OPENWORK_CHAT_PROXY=1` only. + diff --git a/docs/plans/ide-substrate-wire/FOLLOW-UP-CODE-SERVE-WEB.md b/docs/plans/ide-substrate-wire/FOLLOW-UP-CODE-SERVE-WEB.md index 9f0cf47c..15daaafc 100644 --- a/docs/plans/ide-substrate-wire/FOLLOW-UP-CODE-SERVE-WEB.md +++ b/docs/plans/ide-substrate-wire/FOLLOW-UP-CODE-SERVE-WEB.md @@ -6,14 +6,11 @@ Canonical decision: [`docs/records/013-vscode-surface.md`](../../records/013-vsc ## Status -**CS-007 host live; Microsoft/Copilot retirement in flight.** Deploy -`c4636818` runs `IDE_HOST=studio`. Session smoke showed Commonplace Studio -chrome plus status-bar Theorem, but upstream Copilot product keys, the built-in -`extensions/copilot` tree, and “VS Code for the Web” walkthrough titles still -shipped. Branch `feat/studio-overlay-retire-copilot` deletes those product keys -(overlay null → remove), adds patch `0003` null-checks, strips the copilot -extension from the server artifact, seeds `chat.disableAIFeatures`, and retitles -the web walkthrough. Stock code-server remains the image rollback until CS-008. +**CS-007 host live; Copilot retirement live (CR-001).** Deploy `4e69fb85` +ships Commonplace Studio without `defaultChatAgent` / `extensions/copilot`. +Theorem chat register (Studio panel + console `/chat`) continues under +[`../theorem-chat-register/PLAN.md`](../theorem-chat-register/PLAN.md). +Stock code-server remains the image rollback until CS-008. **2026-08-04 scar after #185.** Dropping `-min` was necessary but not sufficient. `gulpfile.reh.ts` starts every `vscode-reh-web-*` task with diff --git a/docs/plans/theorem-chat-register/EXECUTE-REPORT.md b/docs/plans/theorem-chat-register/EXECUTE-REPORT.md new file mode 100644 index 00000000..2ca624ea --- /dev/null +++ b/docs/plans/theorem-chat-register/EXECUTE-REPORT.md @@ -0,0 +1,51 @@ +# Execute Report: theorem-chat-register (CR-001..007) + +Plan: `plan-theorem-chat-register-20260805a` + +## Summary +- Final condition: Copilot retired on Studio (CR-001/002 already live); Theorem chat register package shipped and mounted at console `/chat` + Studio `Theorem: Open Chat`; OpenWork no longer the product `/chat` host. +- Goal achieved: **yes** for CR-001..006 in source; CR-006 live stamp proof pending console deploy of this branch; CR-007 docs closed here. +- Biggest remaining risk: console deploy must land before production doctor `/chat` expects `theorem.chat`; rotate `THEOREM_GIT_TOKEN` if still exposed from Studio builds. +- Next action: merge → console Railway deploy → `node scripts/doctor.mjs` green on `/chat`. + +## Checklist Reconciliation +| ID | Task | Status | Evidence | Validation | Notes | +|---|---|---|---|---|---| +| CR-000 | Plan board | done | PLAN + checklist | artifact | | +| CR-001 | Copilot retirement | done | PR #190/#191; deploy `4e69fb85` | live | prior slice | +| CR-002 | Volume repair | done | HEAD `9363e542` on volume | live | prior slice | +| CR-003 | Register contract | done | SPEC-THEOREM-CHAT-REGISTER-1.0 | artifact | path + `theorem.chat` | +| CR-004 | Package | done | `@commonplace/theorem-chat-register` | `pnpm --filter … test` 3/3 | no openwork/opencode deps | +| CR-005 | Studio mount | done | `TheoremChatPanel` + `theorem.openChat` | pack build + tsc | ACP transport | +| CR-006 | `/chat` + OpenWork out | wired | middleware default off; stamp map; canonical | manifest ok; live pending deploy | rollback env `CONSOLE_OPENWORK_CHAT_PROXY=1` | +| CR-007 | Debt + report | done | this file + supersession rows | artifact | D2 archive deferred | + +## Changes Made +| Area | Files | Summary | Why | +|---|---|---|---| +| SPEC | `SPEC-THEOREM-CHAT-REGISTER-1.0.md` | Named package, stamp, mounts, inventory | CR-003 | +| Package | `packages/theorem-chat-register/**` | Session controller, HTTP stream transport, React register, webview HTML | CR-004 | +| Studio | `apps/theorem-vscode` chat-panel + command | Webview stamped `theorem.chat` over ACP | CR-005 | +| Console | `/chat` pages, middleware, registry, register-impl | Theorem register; OpenWork proxy opt-in only | CR-006 | +| Canonical | `.commonplace-canonical` | `manifest_impl: theorem.chat`; supersede openwork.chat | CR-006/007 | +| Manifest gate | `scripts/check-register-manifest.mjs` | Allow `prototype.stage` companion | unblock oracle | + +## Validation +| Check | Result | Notes | +|---|---|---| +| `pnpm --filter @commonplace/theorem-chat-register test` | pass | 3 tests | +| `node scripts/check-register-manifest.mjs` | pass | 8 registers | +| console chat page tests | pass | 4 tests | +| `pnpm --filter theorem-vscode check/build/test` | pass | 60 tests; bundles | +| `node scripts/doctor.mjs` | partial | fails live `/chat` until console ships `theorem.chat` | +| Live `/IDE` Copilot | pass | prior CR-001 | +| Live `/workspace/repo` | pass | prior CR-002 | + +## Remaining Work +- What remains: merge + console deploy; confirm doctor `/chat` loginish or `theorem.chat` stamp; optional later delete of `apps/chat` / `openwork-ui` after `2026-09-01`. +- Why: live stamp cannot move until console image updates. +- Next step: land PR, redeploy `commonplace-console`, re-run doctor. + +## Rollback +- `CONSOLE_OPENWORK_CHAT_PROXY=1` restores `/chat` → workspace `:8787` OpenWork proxy. +- Do not restore Copilot `defaultChatAgent` keys. diff --git a/docs/plans/theorem-chat-register/PLAN.md b/docs/plans/theorem-chat-register/PLAN.md index 867cb1cd..e9775a12 100644 --- a/docs/plans/theorem-chat-register/PLAN.md +++ b/docs/plans/theorem-chat-register/PLAN.md @@ -53,13 +53,13 @@ Authenticated users get: | ID | Task | Grounding | Proof | Status | |---|---|---|---|---| | CR-000 | Durable plan + checklist (this file) | `PLAN.md`, checklist JSON | files exist; plan_id named | **done** | -| CR-001 | Land Copilot retirement (`feat/studio-overlay-retire-copilot`) + redeploy | overlay, patch 0003, entrypoint, smoke | ledger-gate; live product.json; `/IDE` smoke | pending | -| CR-002 | Repair sticky empty `/workspace/repo` | `packaging/workspace/entrypoint.sh`, Railway volume | `package.json` + `git rev-parse HEAD` on live volume; Explorer non-empty | pending | -| CR-003 | Register contract: package path, `register_impl`, dual seams, retirement inventory | SPEC draft under this plan | named ids + non-goal on `IDefaultChatAgent` | pending | -| CR-004 | Implement Theorem chat register package | new package + pack/console adapters | package tests; no openworklabs/opencode on happy path | pending | -| CR-005 | Mount register in Studio agent/chat panel | theorem-vscode + Studio product settings | signed-in `/IDE` shows Theorem register; no Copilot sign-in wall | pending | -| CR-006 | Mount register at `/chat`; retire OpenWork door | `OpenworkChatRegister`, middleware, `.commonplace-canonical`, workspace image | stamp ≠ `openwork.chat`; doctor + register-manifest | pending | -| CR-007 | Retire residual LLM / OpenWork surface debt + EXECUTE-REPORT | docs + deletions | EXECUTE-REPORT; no product route serves OpenWork as chat | pending | +| CR-001 | Land Copilot retirement (`feat/studio-overlay-retire-copilot`) + redeploy | overlay, patch 0003, entrypoint, smoke | ledger-gate; live product.json; `/IDE` smoke | **done** | +| CR-002 | Repair sticky empty `/workspace/repo` | `packaging/workspace/entrypoint.sh`, Railway volume | `package.json` + `git rev-parse HEAD` on live volume; Explorer non-empty | **done** | +| CR-003 | Register contract: package path, `register_impl`, dual seams, retirement inventory | SPEC draft under this plan | named ids + non-goal on `IDefaultChatAgent` | **done** | +| CR-004 | Implement Theorem chat register package | new package + pack/console adapters | package tests; no openworklabs/opencode on happy path | **done** | +| CR-005 | Mount register in Studio agent/chat panel | theorem-vscode + Studio product settings | signed-in `/IDE` shows Theorem register; no Copilot sign-in wall | **done** (pack); live smoke after seed | +| CR-006 | Mount register at `/chat`; retire OpenWork door | `OpenworkChatRegister`, middleware, `.commonplace-canonical`, workspace image | stamp ≠ `openwork.chat`; doctor + register-manifest | **verifying** (await console deploy) | +| CR-007 | Retire residual LLM / OpenWork surface debt + EXECUTE-REPORT | docs + deletions | EXECUTE-REPORT; no product route serves OpenWork as chat | **done** | ## Sequence diff --git a/docs/plans/theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md b/docs/plans/theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md index 7a615a5e..5810a854 100644 --- a/docs/plans/theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md +++ b/docs/plans/theorem-chat-register/SPEC-THEOREM-CHAT-REGISTER-1.0.md @@ -1,46 +1,69 @@ -# SPEC-THEOREM-CHAT-REGISTER-1.0 (draft skeleton for CR-003) +# SPEC-THEOREM-CHAT-REGISTER-1.0 -Status: **draft — fill under CR-003**. This skeleton locks the decisions from -[`PLAN.md`](./PLAN.md) so implementation cannot silently widen or shrink scope. +Status: **normative for CR-003..007**. Decisions locked in +[`PLAN.md`](./PLAN.md). Do not silently widen into `IDefaultChatAgent`. ## 1. Problem -Commonplace Studio’s Copilot `defaultChatAgent` hole and console `/chat` -(OpenWork) must become **one** Theorem chat register. Two hosts, one package. +Commonplace Studio’s Copilot hole and console `/chat` (OpenWork / opencode) +must become **one** Theorem chat register. Two hosts, one package, one +`register_impl` stamp. -## 2. Package (to name in CR-003) +## 2. Package -| Field | Value (fill) | +| Field | Value | |---|---| -| Package path | `packages/…` or `apps/…` | -| Export surface | composer + thread + session opener | -| Transport | Theorem ACP / heads (not OpenWork→opencode) | -| `register_impl` | e.g. `theorem.chat` (final string in CR-003) | +| Package path | `packages/theorem-chat-register` | +| npm name | `@commonplace/theorem-chat-register` | +| Export surface | `REGISTER_IMPL`, `createChatSessionController`, `TheoremChatRegister`, `ChatTransport` | +| Transport | Theorem ACP / heads via injectable `ChatTransport` (console → `/api/chat/stream`; Studio → hosted ACP). **Not** OpenWork→opencode. | +| `register_impl` | `theorem.chat` | + +### Public API (CR-004) + +- `REGISTER_IMPL = 'theorem.chat'` +- `ChatTransport`: `{ openSession(), prompt(text), subscribe(listener), dispose() }` +- `createChatSessionController(transport)`: session open + one-turn prompt; pure logic, no React +- `TheoremChatRegister`: React shell with `data-register-impl="theorem.chat"`, composer, message list + +Happy-path package dependencies must not include `openworklabs`, `@commonplace/chat` OpenWork UI, or `opencode`. ## 3. Mounts | Host | Seam | Acceptance | |---|---|---| -| Commonplace Studio | Agent/chat panel (post Copilot retirement) | Signed-in `/IDE` shows Theorem register; ACP session against `/workspace/repo` | -| Console | `/chat` (+ registry descriptors that today point at `openwork.chat`) | Stamp is Theorem `register_impl`; doctor green | +| Commonplace Studio | Theorem Chat webview / agent panel (`apps/theorem-vscode`) | Signed-in `/IDE` shows Theorem register UI; ACP session against `/workspace/repo`; no Copilot sign-in wall | +| Console | `/chat` (+ registry descriptors that formerly pointed at `openwork.chat`) | Stamp is `theorem.chat`; doctor + register-manifest green; middleware does **not** reverse-proxy OpenWork as the product body | ## 4. Explicit non-goal Do **not** set `product.json` `defaultChatAgent` to `commonplace.theorem-vscode` until the pack implements VS Code Chat participant / host APIs. Overlay deletion -+ our register UI is the honest cut. +(CR-001) + this register UI is the honest cut. Deferral **D1**. -## 5. Retirement inventory (expand in CR-003) +## 5. Retirement inventory -- [ ] `openwork.chat` register stamps (`OpenworkChatRegister`, middleware) -- [ ] Workspace OpenWork `:8787` as product `/chat` host (retire or gate) -- [ ] opencode / LLM-only happy path behind OpenWork -- [ ] assistant-ui-only chat components superseded by this register (if still reachable) -- [ ] Doctor / `.commonplace-canonical` / register-manifest rows +| Item | Action | +|---|---| +| `openwork.chat` stamps (`OpenworkChatRegister`, middleware `x-register-impl` / `data-register-impl`) | Replace with `theorem.chat`; keep `OpenworkChatRegister` file only as a deprecated alias or delete call sites | +| Console `/chat` → workspace `:8787` OpenWork proxy (`CONSOLE_WORKSPACE_URL` middleware) | Disable product proxy; `/chat` renders Theorem register. Keep `CONSOLE_WORKSPACE_URL` for IDE derivation / doctor substrate probes if still needed | +| Workspace OpenWork `:8787` as product `/chat` host | Off product path. Process may remain for healthcheck until a follow-up moves health off OpenWork (record in CR-007) | +| opencode / LLM-only happy path behind OpenWork | Not the product `/chat` host | +| assistant-ui console shell chat (`ConsoleApp` / thread store → `/api/chat/stream`) | Remains the in-shell composer path; `/chat` register is the full-page place. Same ACP stream, not a second LLM | +| Doctor / `.commonplace-canonical` / `register-impl.ts` / register-manifest | `chat.surface` → `theorem.chat`; canonical package row points at `@commonplace/theorem-chat-register` | +| Historical OW* specs | Supersession notes only (deferral **D2** full archive) | ## 6. Oracles -- Package tests (CR-004) -- Live `/IDE` panel smoke (CR-005) -- Live `/chat` stamp + `check-register-manifest` + doctor (CR-006) -- EXECUTE-REPORT (CR-007) +| Gate | Command / proof | +|---|---| +| CR-003 | This SPEC names path, `register_impl`, seams, inventory | +| CR-004 | `pnpm --filter @commonplace/theorem-chat-register test` | +| CR-005 | Pack builds; webview stamped `theorem.chat`; session opener uses workspace cwd | +| CR-006 | Live `/chat` stamp `theorem.chat`; `node scripts/check-register-manifest.mjs`; doctor route ok | +| CR-007 | `docs/plans/theorem-chat-register/EXECUTE-REPORT.md` | + +## 7. Rollback + +- Re-enable OpenWork middleware behind an explicit env (`CONSOLE_OPENWORK_CHAT_PROXY=1`) only as emergency rollback; default is Theorem register. +- Studio pack: hide Theorem Chat view command if ACP URL unset; do not restore Copilot product keys. diff --git a/packages/theorem-chat-register/package.json b/packages/theorem-chat-register/package.json new file mode 100644 index 00000000..b9a9e226 --- /dev/null +++ b/packages/theorem-chat-register/package.json @@ -0,0 +1,34 @@ +{ + "name": "@commonplace/theorem-chat-register", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Theorem chat register: one package for Studio agent panel and console /chat. ACP transport, stamp theorem.chat. No OpenWork/opencode happy path.", + "exports": { + ".": "./src/index.ts", + "./register-impl": "./src/register-impl.ts", + "./session": "./src/session.ts", + "./transport": "./src/transport.ts", + "./register": "./src/TheoremChatRegister.tsx", + "./webview-html": "./src/webview-html.ts" + }, + + "scripts": { + "check": "npx --yes -p typescript@5.7.3 tsc --noEmit -p tsconfig.json", + "test": "vitest run" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@testing-library/react": "16.3.2", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "jsdom": "29.1.1", + "react": "19.2.3", + "react-dom": "19.2.3", + "typescript": "^5.7.3", + "vitest": "^4.1.6" + } +} diff --git a/packages/theorem-chat-register/src/TheoremChatRegister.tsx b/packages/theorem-chat-register/src/TheoremChatRegister.tsx new file mode 100644 index 00000000..80278a9e --- /dev/null +++ b/packages/theorem-chat-register/src/TheoremChatRegister.tsx @@ -0,0 +1,148 @@ +'use client'; + +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 TheoremChatRegister. +import { useEffect, useMemo, useState, type FormEvent } from 'react'; +import { REGISTER_IMPL } from './register-impl.js'; +import { + createChatSessionController, + type ChatSessionController, + type ChatSessionSnapshot, +} from './session.js'; +import type { ChatTransport } from './transport.js'; + +export type TheoremChatRegisterProps = { + readonly transport: ChatTransport; + /** Optional reason shown above the composer (fallback / degradation copy). */ + readonly reason?: string; + readonly className?: string; + /** When true, open the ACP/stream session on mount. */ + readonly autoOpen?: boolean; +}; + +/** + * Shared register body for console `/chat` and Studio webview hosts. + * Stamps `data-register-impl="theorem.chat"`. + */ +export function TheoremChatRegister({ + transport, + reason, + className, + autoOpen = true, +}: TheoremChatRegisterProps) { + const controller = useMemo( + () => createChatSessionController(transport), + [transport], + ); + const [snap, setSnap] = useState(() => controller.getSnapshot()); + const [draft, setDraft] = useState(''); + + useEffect(() => { + const unsub = controller.subscribe(setSnap); + if (autoOpen) { + void controller.open().catch((err) => { + setSnap({ + ...controller.getSnapshot(), + error: err instanceof Error ? err.message : String(err), + }); + }); + } + return () => { + unsub(); + controller.dispose(); + }; + }, [controller, autoOpen]); + + async function onSubmit(event: FormEvent) { + event.preventDefault(); + const text = draft.trim(); + if (!text || snap.running) return; + setDraft(''); + try { + await controller.prompt(text); + } catch { + // Error is on the snapshot. + } + } + + return ( +
+
+ Theorem chat + {reason ?

{reason}

: null} + {snap.sessionId ? ( +

+ session {snap.sessionId} +

+ ) : null} +
+ +
+ {snap.messages.length === 0 ? ( +

Start a turn. Transport is Theorem ACP.

+ ) : ( + snap.messages.map((message) => ( +
+ {message.text || (message.role === 'assistant' && snap.running ? '…' : '')} +
+ )) + )} +
+ + {snap.error ? ( +

+ {snap.error} +

+ ) : null} + +
+ setDraft(event.target.value)} + disabled={snap.running} + placeholder="Message Theorem…" + style={{ flex: 1, minWidth: 0, padding: '0.5rem 0.75rem' }} + /> + +
+
+ ); +} + +export type { ChatSessionController }; diff --git a/packages/theorem-chat-register/src/http-stream-transport.ts b/packages/theorem-chat-register/src/http-stream-transport.ts new file mode 100644 index 00000000..f3684d2c --- /dev/null +++ b/packages/theorem-chat-register/src/http-stream-transport.ts @@ -0,0 +1,75 @@ +// SOURCING: none. Console mount uses fetch SSE to /api/chat/stream (theorem-acp +// bridge already behind that route). No OpenWork / opencode import. +import type { ChatTransport } from './transport.js'; + +export type HttpStreamTransportOptions = { + readonly endpoint?: string; + readonly fetchImpl?: typeof fetch; +}; + +/** + * ChatTransport that POSTs the console composer shape to the Theorem stream + * door and forwards SSE text deltas. + */ +export function createHttpStreamTransport( + options: HttpStreamTransportOptions = {}, +): ChatTransport { + const endpoint = options.endpoint ?? '/api/chat/stream'; + const fetchImpl = options.fetchImpl ?? fetch; + let sessionId = `http-${Date.now()}`; + let disposed = false; + + return { + async openSession() { + if (disposed) throw new Error('theorem.chat: transport disposed'); + return sessionId; + }, + async prompt(_sessionId, text, onDelta) { + if (disposed) throw new Error('theorem.chat: transport disposed'); + const response = await fetchImpl(endpoint, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: [{ type: 'text', text }] }), + }); + if (!response.ok || !response.body) { + throw new Error(`theorem.chat: stream failed (${response.status})`); + } + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const parts = buffer.split('\n\n'); + buffer = parts.pop() ?? ''; + for (const part of parts) { + const dataLine = part + .split('\n') + .find((line) => line.startsWith('data:')); + if (!dataLine) continue; + const raw = dataLine.slice('data:'.length).trim(); + const chunk = textOf(raw); + if (chunk) onDelta(chunk); + } + } + }, + dispose() { + disposed = true; + }, + }; +} + +function textOf(data: string): string { + try { + const parsed = JSON.parse(data) as { + text?: string; + delta?: string; + content?: string; + error?: string; + }; + return parsed.text ?? parsed.delta ?? parsed.content ?? parsed.error ?? ''; + } catch { + return data; + } +} diff --git a/packages/theorem-chat-register/src/index.ts b/packages/theorem-chat-register/src/index.ts new file mode 100644 index 00000000..1cc7e67b --- /dev/null +++ b/packages/theorem-chat-register/src/index.ts @@ -0,0 +1,12 @@ +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 package barrel. +export { REGISTER_IMPL, type TheoremChatRegisterImpl } from './register-impl.js'; +export { + createChatSessionController, + type ChatMessage, + type ChatSessionController, + type ChatSessionSnapshot, +} from './session.js'; +export type { ChatTransport } from './transport.js'; +export { createHttpStreamTransport, type HttpStreamTransportOptions } from './http-stream-transport.js'; +export { TheoremChatRegister, type TheoremChatRegisterProps } from './TheoremChatRegister.tsx'; +export { renderTheoremChatWebviewHtml } from './webview-html.js'; diff --git a/packages/theorem-chat-register/src/register-impl.ts b/packages/theorem-chat-register/src/register-impl.ts new file mode 100644 index 00000000..f45dd89d --- /dev/null +++ b/packages/theorem-chat-register/src/register-impl.ts @@ -0,0 +1,5 @@ +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 §2. +/** Canonical register stamp for Studio panel and console /chat. */ +export const REGISTER_IMPL = 'theorem.chat' as const; + +export type TheoremChatRegisterImpl = typeof REGISTER_IMPL; diff --git a/packages/theorem-chat-register/src/session.test.ts b/packages/theorem-chat-register/src/session.test.ts new file mode 100644 index 00000000..43993fb1 --- /dev/null +++ b/packages/theorem-chat-register/src/session.test.ts @@ -0,0 +1,59 @@ +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 §2 / §6 CR-004 oracle. +import { describe, expect, it, vi } from 'vitest'; +import { createChatSessionController } from './session.js'; +import type { ChatTransport } from './transport.js'; +import { REGISTER_IMPL } from './register-impl.js'; + +function fakeTransport(replies: Record = { hi: 'hello' }): ChatTransport { + let sessionId = ''; + return { + async openSession() { + sessionId = 'sess-1'; + return sessionId; + }, + async prompt(_sessionId, text, onDelta) { + const reply = replies[text] ?? `echo:${text}`; + onDelta(reply); + }, + dispose() {}, + }; +} + +describe('theorem.chat register', () => { + it('stamps register_impl theorem.chat', () => { + expect(REGISTER_IMPL).toBe('theorem.chat'); + }); + + it('opens a session and completes one turn', async () => { + const transport = fakeTransport({ ping: 'pong' }); + const openSpy = vi.spyOn(transport, 'openSession'); + const promptSpy = vi.spyOn(transport, 'prompt'); + const controller = createChatSessionController(transport); + + const id = await controller.open(); + expect(id).toBe('sess-1'); + expect(openSpy).toHaveBeenCalledOnce(); + + await controller.prompt('ping'); + expect(promptSpy).toHaveBeenCalledOnce(); + expect(promptSpy.mock.calls[0]?.[0]).toBe('sess-1'); + expect(promptSpy.mock.calls[0]?.[1]).toBe('ping'); + + const snap = controller.getSnapshot(); + expect(snap.sessionId).toBe('sess-1'); + expect(snap.running).toBe(false); + expect(snap.messages.some((m) => m.role === 'user' && m.text === 'ping')).toBe(true); + expect(snap.messages.some((m) => m.role === 'assistant' && m.text === 'pong')).toBe(true); + + controller.dispose(); + }); + + it('auto-opens on first prompt', async () => { + const transport = fakeTransport(); + const controller = createChatSessionController(transport); + await controller.prompt('hi'); + expect(controller.getSnapshot().sessionId).toBe('sess-1'); + expect(controller.getSnapshot().messages.some((m) => m.text === 'hello')).toBe(true); + controller.dispose(); + }); +}); diff --git a/packages/theorem-chat-register/src/session.ts b/packages/theorem-chat-register/src/session.ts new file mode 100644 index 00000000..0ea6aad9 --- /dev/null +++ b/packages/theorem-chat-register/src/session.ts @@ -0,0 +1,100 @@ +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 §2 session controller. +import type { ChatTransport } from './transport.js'; + +export type ChatMessage = { + readonly id: string; + readonly role: 'user' | 'assistant' | 'system'; + readonly text: string; +}; + +export type ChatSessionSnapshot = { + readonly sessionId: string; + readonly messages: readonly ChatMessage[]; + readonly running: boolean; + readonly error: string | null; +}; + +export type ChatSessionController = { + open(): Promise; + prompt(text: string): Promise; + getSnapshot(): ChatSessionSnapshot; + subscribe(listener: (snapshot: ChatSessionSnapshot) => void): () => void; + dispose(): void; +}; + +/** + * Thin controller over a ChatTransport. Owns local message projection so both + * mounts share one open+turn contract in tests. + */ +export function createChatSessionController(transport: ChatTransport): ChatSessionController { + let sessionId: string | null = null; + let messages: ChatMessage[] = []; + let running = false; + let error: string | null = null; + const listeners = new Set<(snapshot: ChatSessionSnapshot) => void>(); + let seq = 0; + + const nextId = () => `tcr-${Date.now()}-${++seq}`; + + const snapshot = (): ChatSessionSnapshot => ({ + sessionId: sessionId ?? '', + messages, + running, + error, + }); + + const emit = () => { + const snap = snapshot(); + for (const listener of listeners) listener(snap); + }; + + return { + async open() { + error = null; + sessionId = await transport.openSession(); + emit(); + return sessionId; + }, + async prompt(text: string) { + const trimmed = text.trim(); + if (!trimmed) return; + if (!sessionId) await this.open(); + if (!sessionId) throw new Error('theorem.chat: session failed to open'); + const user: ChatMessage = { id: nextId(), role: 'user', text: trimmed }; + const assistantId = nextId(); + const assistant: ChatMessage = { id: assistantId, role: 'assistant', text: '' }; + messages = [...messages, user, assistant]; + running = true; + error = null; + emit(); + try { + await transport.prompt(sessionId, trimmed, (chunk) => { + messages = messages.map((message) => + message.id === assistantId + ? { ...message, text: message.text + chunk } + : message, + ); + emit(); + }); + } catch (err) { + error = err instanceof Error ? err.message : String(err); + throw err; + } finally { + running = false; + emit(); + } + }, + getSnapshot: snapshot, + subscribe(listener) { + listeners.add(listener); + listener(snapshot()); + return () => { + listeners.delete(listener); + }; + }, + dispose() { + listeners.clear(); + transport.dispose(); + }, + }; +} diff --git a/packages/theorem-chat-register/src/transport.ts b/packages/theorem-chat-register/src/transport.ts new file mode 100644 index 00000000..3665c5fc --- /dev/null +++ b/packages/theorem-chat-register/src/transport.ts @@ -0,0 +1,20 @@ +// SOURCING: none. SPEC-THEOREM-CHAT-REGISTER-1.0 §2 ChatTransport. +/** + * Injectable transport: console posts to `/api/chat/stream`; Studio uses hosted + * ACP. The register never imports OpenWork or opencode. + */ + +export type ChatTransport = { + /** Open (or resume) a session. Returns a stable session id. */ + openSession(): Promise; + /** + * Send one user turn. Call `onDelta` with assistant text chunks as they + * arrive; resolve when the turn completes. + */ + prompt( + sessionId: string, + text: string, + onDelta: (chunk: string) => void, + ): Promise; + dispose(): void; +}; diff --git a/packages/theorem-chat-register/src/webview-html.ts b/packages/theorem-chat-register/src/webview-html.ts new file mode 100644 index 00000000..afb3d5e2 --- /dev/null +++ b/packages/theorem-chat-register/src/webview-html.ts @@ -0,0 +1,77 @@ +// SOURCING: none. Studio webview host: stamped HTML shell shared with the +// React register's identity (theorem.chat). No OpenWork markup. +import { REGISTER_IMPL } from './register-impl.js'; + +/** + * Minimal HTML document for VS Code / Studio webviews. The extension host owns + * the ChatSessionController; the page posts prompt events and paints snapshots. + */ +export function renderTheoremChatWebviewHtml(options: { + readonly nonce: string; + readonly cspSource: string; +}): string { + const { nonce, cspSource } = options; + return ` + + + + + Theorem chat + + + + Theorem chat +

+
+ +
+ + +
+ + +`; +} diff --git a/packages/theorem-chat-register/tsconfig.json b/packages/theorem-chat-register/tsconfig.json new file mode 100644 index 00000000..5a86cff5 --- /dev/null +++ b/packages/theorem-chat-register/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "verbatimModuleSyntax": true, + "types": ["vitest/globals"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"] +} diff --git a/packages/theorem-chat-register/vitest.config.ts b/packages/theorem-chat-register/vitest.config.ts new file mode 100644 index 00000000..c7f1a765 --- /dev/null +++ b/packages/theorem-chat-register/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.test.ts', 'src/**/*.test.tsx'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 611088c8..ddedffcf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -375,6 +375,9 @@ importers: '@commonplace/theorem-acp': specifier: file:../../packages/theorem-acp version: link:../../packages/theorem-acp + '@commonplace/theorem-chat-register': + specifier: workspace:* + version: link:../../packages/theorem-chat-register '@cosmos.gl/graph': specifier: 3.1.0 version: 3.1.0 @@ -783,6 +786,9 @@ importers: '@commonplace/theorem-acp': specifier: workspace:* version: link:../../packages/theorem-acp + '@commonplace/theorem-chat-register': + specifier: workspace:* + version: link:../../packages/theorem-chat-register '@noble/hashes': specifier: ^1.8.0 version: 1.8.0 @@ -1069,6 +1075,33 @@ importers: specifier: ^4.1.6 version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@22.20.1)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.49.0)(tsx@4.23.0)(yaml@2.9.0)) + packages/theorem-chat-register: + devDependencies: + '@testing-library/react': + specifier: 16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@types/react': + specifier: ^19.0.0 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.0.0 + version: 19.2.3(@types/react@19.2.17) + jsdom: + specifier: 29.1.1 + version: 29.1.1(@noble/hashes@1.8.0) + react: + specifier: 19.2.3 + version: 19.2.3 + react-dom: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + typescript: + specifier: ^5.7.3 + version: 5.8.3 + vitest: + specifier: ^4.1.6 + version: 4.1.10(@opentelemetry/api@1.9.1)(@types/node@25.9.5)(jsdom@29.1.1(@noble/hashes@1.8.0))(vite@7.3.6(@types/node@25.9.5)(jiti@2.7.0)(lightningcss@1.32.0)(sass-embedded@1.100.0)(sass@1.100.0)(terser@5.49.0)(tsx@4.23.0)(yaml@2.9.0)) + packages/twenty-ui: dependencies: '@base-ui/react': @@ -13675,7 +13708,7 @@ snapshots: '@expo/dom-webview@57.0.0(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': dependencies: - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) react: 19.2.3 react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) @@ -13790,7 +13823,7 @@ snapshots: dependencies: '@expo/dom-webview': 57.0.0(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) anser: 1.4.10 - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) react: 19.2.3 react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) stacktrace-parser: 0.1.11 @@ -13886,7 +13919,7 @@ snapshots: dependencies: '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.0)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) anser: 1.4.10 - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) pretty-format: 29.7.0 react: 19.2.3 react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) @@ -14075,7 +14108,7 @@ snapshots: '@expo/ui@57.0.7(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': dependencies: - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) react: 19.2.3 react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) sf-symbols-typescript: 2.2.0 @@ -19750,7 +19783,7 @@ snapshots: expo-constants@57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)): dependencies: '@expo/env': 2.4.2 - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) transitivePeerDependencies: - supports-color @@ -19802,7 +19835,7 @@ snapshots: expo-font@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) fontfaceobserver: 2.3.0 react: 19.2.3 react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) @@ -19817,7 +19850,7 @@ snapshots: expo-glass-effect@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): dependencies: - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) react: 19.2.3 react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) @@ -19852,7 +19885,7 @@ snapshots: expo-keep-awake@57.0.1(expo@57.0.7)(react@19.2.3): dependencies: - expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@5.8.3) react: 19.2.3 expo-keep-awake@57.0.1(expo@57.0.7)(react@19.2.7): diff --git a/scripts/check-register-manifest.mjs b/scripts/check-register-manifest.mjs index c08f907f..b45ddf0e 100644 --- a/scripts/check-register-manifest.mjs +++ b/scripts/check-register-manifest.mjs @@ -51,6 +51,7 @@ const ALLOWED_UNMANIFESTED = new Set([ 'record.page', 'commonplace.console', 'browser.pane', + 'prototype.stage', ]); function fail(message) {