// Row 129: the single seam between Console chat operations and the bus transport it is given. // The signed-in listener passes the human transport (cli.mjs, `chat = createChat({ call })`); the row 176 // tester listener passes the tester transport (row 177, `createChat({ call })` over TESTER_CLI). // No browser-selected bus verb, tenant, owner, capability or message class crosses this seam. import { createChannelCaller } from '../../plugins/src/channel.mjs'; import { PluginError } from '../../plugins/src/kinds.mjs'; import { BusReadError } from './bus.mjs'; // FED R6 (#1800, sage-3153): `owner`, when given, is the owner's transport, and it carries only // the channel settings read, `plugins` with {}. A granted user's chat needs it since `plugins` is // not a user verb. The result gates the webui channel server-side and is never sent to the user. // Every chat verb goes over the user's own `call`. export function createChat({ call, owner = null }) { if (typeof call !== 'function') throw new TypeError('chat needs a bus transport function'); if (owner !== null && typeof owner !== 'function') throw new TypeError('chat owner must be a bus transport function'); const dispatch = createChannelCaller({ name: 'webui', call: owner === null ? call : (verb, args) => verb === 'plugins' ? owner('plugins', {}) : call(verb, args) }); const channel = async (verb, args) => { try { return await dispatch(verb, args); } catch (err) { if (err instanceof PluginError) throw new BusReadError(err.code, err.message); throw err; } }; return Object.freeze({ roomOpen: ({ project }) => channel('room.open', { project }), roomRead: ({ project, after, limit }) => channel('room.read', { project, ...(after ? { after } : {}), ...(limit === undefined ? {} : { limit }) }), roomSend: ({ project, body }) => channel('room.send', { project, body }), // #797 MVP3: includeArchived asks project.list for the hidden projects too, each flagged. projects: ({ includeArchived } = {}) => channel('project.list', includeArchived === true ? { includeArchived: true } : {}), createProject: ({ name, repo }) => channel('project.create', { name, ...(repo === undefined ? {} : { repo }) }), archiveProject: ({ project }) => channel('project.archive', { project }), unarchiveProject: ({ project }) => channel('project.unarchive', { project }), threads: ({ project, shared } = {}) => channel('thread.list', { ...(project === undefined ? {} : { project }), ...(shared === true ? { shared: true } : {}) }), shares: ({ thread }) => channel('thread.shares', { thread }), share: ({ thread, to }) => channel('thread.share', { thread, to }), unshare: ({ thread, to }) => channel('thread.unshare', { thread, to }), createThread: ({ project, name, instance }) => channel('thread.create', { project, name, ...(instance === undefined ? {} : { instance }) }), sessions: ({ thread }) => channel('session.list', { thread }), createSession: ({ thread, name, op }) => channel('session.create', { thread, name, op }), clearSession: ({ thread, session, revision, confirm, op }) => channel('session.clear', { thread, session, revision, confirm, op }), cancelSession: ({ thread, op, confirm }) => channel('session.cancel', { thread, op, confirm }), sessionOperation: ({ thread, op }) => channel('session.operation', { thread, op }), messages: ({ thread, session, after, limit }) => channel('thread.messages', { thread, ...(session ? { session } : {}), ...(after ? { after } : {}), limit }), send: ({ thread, session, body }) => channel('thread.send', { thread, ...(session ? { session } : {}), body, class: 'REQUEST' }), open: ({ thread, session }) => channel('thread.open', { thread, ...(session ? { session } : {}) }), }); }