Jason's owner report 2026-11-06: no way to archive or delete projects.
Archive is the reversible half; delete stays out of scope because project
and run records are write-once evidence.
- project.archive / project.unarchive: human-only (project.create's gate
minus the tester carve-out; tester, user and agent refused). State is the
latest row of a new append-only project_archives journal (3s->3t, additive;
the rows are the audit trail), so projects itself never changes and nothing
is deleted. Repeats refuse project-already-archived / project-not-archived.
- project.list hides archived projects until {includeArchived:true}, which
flags each with archived:true; a user still lists granted projects only.
- While archived, thread.create, thread.send, room.open and room.send refuse
project-archived; every existing record stays readable and unarchive
restores everything.
- Console: new public/project-archive.js (+css) and project-archive-http.mjs
routes (chatHTTP's write fence); chat.js gains two one-line hooks,
index.html one script and one link tag; styling only through the shell's
.project-archive-control / .project-archived-filter hooks.
- CLI: mosaic project archive|unarchive <name-or-id> and list --archived.
- stack-release COUNTED/LISTED gain project_archives (schema order); schema
step-count pins and migration fixtures updated for 3t.
bus 1601/1601 (Node 24; Node 26 has the pre-existing project-file socket
stall), webui 582/582, plugins 132/132, cli 1191/1194 (two pre-existing
load flakes of the row253 C4 and land_work families; both pass isolated and
the base commit's full suite fails four of the same family).
48 lines
3.7 KiB
JavaScript
48 lines
3.7 KiB
JavaScript
// 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 } : {}) }),
|
|
});
|
|
}
|