Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions .commonplace-canonical
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand All @@ -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": [
Expand All @@ -129,15 +130,16 @@
"Tests cover session open and one turn",
"Happy path has no openworklabs or opencode product dependency"
],
"proof_command": "pnpm --filter <chat-register-package> test"
"proof_command": "pnpm --filter <chat-register-package> 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": [
Expand All @@ -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": [
Expand All @@ -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": [
Expand All @@ -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."
}
]
}
1 change: 1 addition & 0 deletions apps/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
20 changes: 17 additions & 3 deletions apps/console/src/app/chat/[threadId]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<div data-register-impl="theorem.chat" data-theorem-chat-register>
{reason}
</div>
);
},
}));

import { resolveHarnessPrincipal } from '@/lib/server/harness-principal';
import ChatThreadPage from './page';

Expand All @@ -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: {
Expand All @@ -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');
});
});
13 changes: 7 additions & 6 deletions apps/console/src/app/chat/[threadId]/page.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -23,8 +23,9 @@ export default async function ChatThreadPage({
return <ChatUnavailable settingsHref={null} />;
}
return (
<OpenworkChatRegister
reason={`Thread ${threadId} would open on the openwork door once CONSOLE_WORKSPACE_URL is set and the console proxies /chat.`}
<TheoremChatRegisterView
reason={`Thread ${threadId} on the Theorem chat register (theorem.chat).`}
endpoint="/api/chat/stream"
/>
);
}
18 changes: 11 additions & 7 deletions apps/console/src/app/chat/page.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -20,16 +18,22 @@ 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(() => {
mocks.resolveHarnessPrincipal.mockReset();
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: {
Expand All @@ -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 () => {
Expand Down
12 changes: 6 additions & 6 deletions apps/console/src/app/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -16,8 +15,9 @@ export default async function ChatIndexPage() {
return <ChatUnavailable settingsHref={null} />;
}
return (
<OpenworkChatRegister
reason="CONSOLE_WORKSPACE_URL is unset on this deploy, so the openwork chat door was not proxied. Set the workspace URL and redeploy the console."
<TheoremChatRegisterView
reason="Theorem ACP stream via /api/chat/stream. OpenWork is no longer the product /chat host."
endpoint="/api/chat/stream"
/>
);
}
2 changes: 1 addition & 1 deletion apps/console/src/lib/register-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
13 changes: 7 additions & 6 deletions apps/console/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
35 changes: 9 additions & 26 deletions apps/console/src/views/OpenworkChatRegister.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className="flex h-full min-h-0 flex-col items-center justify-center gap-3 bg-ij-editor p-6 text-ij-ink"
data-register-impl="openwork.chat"
data-openwork-chat-register
>
<p style={{ fontWeight: 'var(--rec-weight-cap)' }}>Openwork chat register</p>
<p className="max-w-md text-center text-sm text-ij-ink-info">{reason}</p>
{/* 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 */}
<a
className="h-ij-control rounded-ij-arc border border-ij-control-border px-3 leading-ij-control text-ij-link"
href="/chat"
>
Open /chat
</a>
</div>
);
return <TheoremChatRegisterView reason={reason} />;
}
30 changes: 30 additions & 0 deletions apps/console/src/views/TheoremChatRegister.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<TheoremChatRegister
transport={transport}
reason={reason}
className="flex h-full min-h-0 flex-col bg-ij-editor text-ij-ink"
autoOpen
/>
);
}
Loading
Loading