diff --git a/apps/proof-example/README.md b/apps/proof-example/README.md index f9ce3c1..0e18bd1 100644 --- a/apps/proof-example/README.md +++ b/apps/proof-example/README.md @@ -1,10 +1,6 @@ # Proof Example -This workspace is the extraction target for the public `Proof SDK` demo app. - -The current private repo still runs the hosted product, but shared editor, server, and bridge code now lives behind the workspace packages in [packages/doc-core](/Users/danshipper/CascadeProjects/every-proof/.worktrees/proof-sdk-split/packages/doc-core), [packages/doc-editor](/Users/danshipper/CascadeProjects/every-proof/.worktrees/proof-sdk-split/packages/doc-editor), [packages/doc-server](/Users/danshipper/CascadeProjects/every-proof/.worktrees/proof-sdk-split/packages/doc-server), [packages/doc-store-sqlite](/Users/danshipper/CascadeProjects/every-proof/.worktrees/proof-sdk-split/packages/doc-store-sqlite), and [packages/agent-bridge](/Users/danshipper/CascadeProjects/every-proof/.worktrees/proof-sdk-split/packages/agent-bridge). - -When the public repo is extracted, this app should become the neutral self-host example for: +This is the self-host example app for the Proof SDK, demonstrating: - creating a document - loading a shared document @@ -12,12 +8,26 @@ When the public repo is extracted, this app should become the neutral self-host - agent bridge reads and writes - anonymous or token-based access +Shared editor, server, and bridge code lives in the workspace packages: +[packages/doc-core](../../packages/doc-core), +[packages/doc-editor](../../packages/doc-editor), +[packages/doc-server](../../packages/doc-server), +[packages/doc-store-sqlite](../../packages/doc-store-sqlite), and +[packages/agent-bridge](../../packages/agent-bridge). + ## Agent Bridge Demo -Run the reference external-agent flow: +First build the editor and start the server from the repo root: + +```bash +npm run build +npm run serve +``` + +Then run the reference external-agent flow: ```bash -npm run proof-sdk:demo:agent +npm --workspace proof-example run demo:agent ``` Environment variables: @@ -26,4 +36,6 @@ Environment variables: - `PROOF_DEMO_TITLE`: optional document title override - `PROOF_DEMO_MARKDOWN`: optional initial markdown override -The demo creates a document through `POST /documents`, then uses `@proof/agent-bridge` to publish presence, read state, and add a comment through the neutral `/documents/:slug/bridge/*` API. +The demo creates a document through `POST /documents`, then uses `@proof/agent-bridge` to publish +presence, read state, and add a comment through the neutral `/documents/:slug/*` API. It prints a +`viewUrl` you can open in a browser to see the result. diff --git a/apps/proof-example/examples/agent-http-bridge.ts b/apps/proof-example/examples/agent-http-bridge.ts index b884c91..359649d 100644 --- a/apps/proof-example/examples/agent-http-bridge.ts +++ b/apps/proof-example/examples/agent-http-bridge.ts @@ -7,8 +7,10 @@ import { interface CreateDocumentResponse { slug: string; + ownerSecret: string; accessToken: string; shareUrl?: string; + tokenUrl?: string; agent?: { stateApi?: string; }; @@ -56,14 +58,14 @@ async function run(): Promise { const provider = new DemoAgentProvider(); const created = await createDocument(baseUrl, title, markdown); - if (!created.slug || !created.accessToken) { - throw new Error('Create response did not include slug/accessToken'); + if (!created.slug || !created.ownerSecret) { + throw new Error('Create response did not include slug/ownerSecret'); } const bridge = createAgentBridgeClient({ baseUrl, auth: { - shareToken: created.accessToken, + bridgeToken: created.ownerSecret, }, }); @@ -98,10 +100,15 @@ async function run(): Promise { text: review.text || review.message.content, }); + const viewUrl = created.tokenUrl + ?? (created.shareUrl && created.accessToken + ? `${created.shareUrl}?token=${encodeURIComponent(created.accessToken)}` + : `${baseUrl}/d/${created.slug}`); + console.log(JSON.stringify({ success: true, slug: created.slug, - shareUrl: created.shareUrl ?? `${baseUrl}/d/${created.slug}`, + viewUrl, stateApi: created.agent?.stateApi ?? `${baseUrl}/documents/${created.slug}/state`, commentPosted: true, }, null, 2)); diff --git a/packages/agent-bridge/src/index.ts b/packages/agent-bridge/src/index.ts index 6c3ea31..a6d6989 100644 --- a/packages/agent-bridge/src/index.ts +++ b/packages/agent-bridge/src/index.ts @@ -195,7 +195,7 @@ export function createAgentBridgeClient(config: AgentBridgeClientConfig) { }); }, setPresence(slug: string, input: AgentBridgePresenceInput, options: AgentBridgeRequestOptions = {}): Promise { - return requestJson(config, buildBridgePath(slug, '/presence'), { + return requestJson(config, `${documentBasePath(slug)}/presence`, { method: 'POST', body: JSON.stringify(input), ...options, diff --git a/server/index.ts b/server/index.ts index 268cc05..83c1a8e 100644 --- a/server/index.ts +++ b/server/index.ts @@ -13,13 +13,18 @@ import { shareWebRoutes } from './share-web-routes.js'; import { capabilitiesPayload, enforceApiClientCompatibility, - enforceBridgeClientCompatibility, } from './client-capabilities.js'; import { getBuildInfo } from './build-info.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PORT = Number.parseInt(process.env.PORT || '4000', 10); + +// WebSocket is multiplexed on the main HTTP port via setupWebSocket — tell the +// collab session builder to keep the same port rather than offsetting to PORT+1. +if (!process.env.COLLAB_EMBEDDED_WS) { + process.env.COLLAB_EMBEDDED_WS = '1'; +} const DEFAULT_ALLOWED_CORS_ORIGINS = [ 'http://localhost:3000', 'http://127.0.0.1:3000', @@ -43,6 +48,7 @@ async function main(): Promise { const allowedCorsOrigins = parseAllowedCorsOrigins(); app.use(express.json({ limit: '10mb' })); + app.use(express.static(path.join(__dirname, '..', 'dist'))); app.use(express.static(path.join(__dirname, '..', 'public'))); app.use((req, res, next) => { @@ -120,8 +126,8 @@ async function main(): Promise { app.use('/api', enforceApiClientCompatibility, apiRoutes); app.use('/api/agent', agentRoutes); app.use(apiRoutes); - app.use('/d', createBridgeMountRouter(enforceBridgeClientCompatibility)); - app.use('/documents', createBridgeMountRouter(enforceBridgeClientCompatibility)); + app.use('/d', createBridgeMountRouter()); + app.use('/documents', createBridgeMountRouter()); app.use('/documents', agentRoutes); app.use(shareWebRoutes); diff --git a/src/bridge/collab-client.ts b/src/bridge/collab-client.ts index 3ef2ba5..2c74fb6 100644 --- a/src/bridge/collab-client.ts +++ b/src/bridge/collab-client.ts @@ -610,6 +610,7 @@ export class CollabClient { provider.on('authenticationFailed', (event: { reason?: string }) => { const reason = typeof event?.reason === 'string' ? event.reason : 'permission-denied'; this.lastAuthenticationFailureReason = reason; + this.terminalCloseReason = 'permission-denied'; this.connectionStatus = 'disconnected'; this.hasSynced = false; this.lastDisconnectAt = Date.now();