Files
stack/packages/cli/tests/channel-cli.test.mjs
jason.woltjeandClaude Opus 5.5 008746b6ed plugins: channel kind for the CLI and Console chat paths (row 164, #1661)
Adds plugin kind `channel` (interfaceVersion 1, dispatch(request, {call}))
with cli and webui modules. Project and thread surfaces and thread
talk/follow in the CLI and the Console chat go through the selected
channel. An empty or absent channel selection is off: the CLI exits 3 and
the Console answers 503, each naming `mosaic plugins` and `mosaic plugins
set` with the business selector. Management plugins and plugins.set stay
on the core transport. The loader gains a synchronous snapshot(kind)
getter; loaded(), modules() and last-good handling are unchanged. No lock
is held across dispatch. serve.mjs, public/ and the bus are untouched.

Owner Rocko. Reviewer Dewey: r1 changes (comment 28980, B1: the recovery
sentence was never asserted), r2 approve (comment 28984) on candidate
manifest ae2fcfda...1fc8, the B1 assert killing mutant D1. Design accepted
by Mos (mos-0768, mos-0773). Fixture rewrite at project.test.mjs:47 acked
by Mos (mos-0789) on Dewey's line-by-line check: lines 48-51 identical,
one added assert. Sage gate on e5b10d275: 25 steps green, 2081 package tests, 156 queue node tests, 422 script selftests, 0 failed.

Live stays as pinned. The re-pin that takes this live puts `mosaic
plugins set` in Jason's window right after activate (mos-0768).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 14:24:42 -05:00

43 lines
2.3 KiB
JavaScript

// Row 164: switching CLI chat off never gates the core management verbs.
import test from "node:test";
import assert from "node:assert/strict";
import { run } from "../src/cli.mjs";
import { io, tmp } from "./helpers.mjs";
function setup(t, selected) {
const calls = [];
const deps = { system: { dataRoot: tmp(t, "channel-cli-") }, transport: () => async (verb, args = {}) => {
calls.push([verb, args]);
if (verb === "plugins") return { business: "demo", revision: "a".repeat(64), plugins: selected === undefined ? {} : { channel: selected } };
if (["project.list", "thread.list"].includes(verb)) return [];
assert.fail("unexpected operation " + verb);
} };
return { calls, async cli(argv) { const x = io(); return { code: await run([...argv, "--business", "demo"], x, deps), out: x.out.text, err: x.err.text }; } };
}
test("CLI off, absent and other-only channel selections refuse every project/thread flow before any effect", async t => {
for (const selected of [[], undefined, ["webui"]]) {
const s = setup(t, selected);
for (const argv of [["project", "list"], ["project", "create", "work"], ["threads"], ["thread", "read", "stack/chat"], ["thread", "open", "stack/chat"], ["thread", "create", "stack", "chat"], ["talk", "--thread", "stack/chat", "hello", "--wait", "0"], ["follow", "--thread", "stack/chat"]]) {
s.calls.length = 0;
const result = await s.cli(argv);
assert.equal(result.code, 3, argv.join(" "));
assert.match(result.err, /mosaic plugins.*mosaic plugins set/s);
assert.match(result.err, /plugins\.channel/);
assert.ok(result.err.includes('Pass --business "demo" to both commands.'));
assert.match(result.err, /cli.*webui/);
assert.equal(result.out, "");
assert.deepEqual(s.calls, [["plugins", {}]]);
}
}
});
test("CLI enabled project/thread lists dispatch through selection without changing output", async t => {
const s = setup(t, ["cli"]);
assert.deepEqual(await s.cli(["project", "list"]), { code: 0, out: "no projects\n", err: "" });
assert.deepEqual(s.calls, [["plugins", {}], ["project.list", {}]]);
s.calls.length = 0;
assert.deepEqual(await s.cli(["threads"]), { code: 0, out: "no threads\n", err: "" });
assert.deepEqual(s.calls, [["plugins", {}], ["thread.list", {}]]);
});