refactor!: rebrand bterminal to agor (agents-orchestrator)

Rename Cargo crates (bterminal-core→agor-core, bterminal-relay→agor-relay),
env vars (BTERMINAL_*→AGOR_*), config paths (~/.config/agor), CSS custom
properties, plugin API object, package names, and all documentation.

BREAKING CHANGE: config/data paths changed from bterminal to agor.
This commit is contained in:
Hibryda 2026-03-17 01:12:25 +01:00
parent ef3548a569
commit a63e6711ac
52 changed files with 3889 additions and 169 deletions

View file

@ -11,7 +11,7 @@ export interface PluginMeta {
permissions: string[];
}
/** Discover all plugins in ~/.config/bterminal/plugins/ */
/** Discover all plugins in ~/.config/agor/plugins/ */
export async function discoverPlugins(): Promise<PluginMeta[]> {
return invoke<PluginMeta[]>('plugins_discover');
}

View file

@ -748,7 +748,7 @@
flex: 1;
overflow-y: auto;
container-type: inline-size;
padding: 0.5rem var(--bterminal-pane-padding-inline, 0.75rem);
padding: 0.5rem var(--agor-pane-padding-inline, 0.75rem);
display: flex;
flex-direction: column;
gap: 0.125rem;
@ -1270,7 +1270,7 @@
/* === Status strip === */
.status-strip {
padding: 0.25rem var(--bterminal-pane-padding-inline, 0.75rem);
padding: 0.25rem var(--agor-pane-padding-inline, 0.75rem);
border-top: 1px solid var(--ctp-surface1);
flex-shrink: 0;
font-size: 0.8125rem;
@ -1408,7 +1408,7 @@
.session-controls {
display: flex;
gap: 0.5rem;
padding: 0.375rem var(--bterminal-pane-padding-inline, 0.75rem);
padding: 0.375rem var(--agor-pane-padding-inline, 0.75rem);
justify-content: center;
flex-shrink: 0;
}
@ -1451,7 +1451,7 @@
/* === Prompt container === */
.prompt-container {
padding: 0.5rem var(--bterminal-pane-padding-inline, 0.75rem);
padding: 0.5rem var(--agor-pane-padding-inline, 0.75rem);
flex-shrink: 0;
border-top: 1px solid var(--ctp-surface0);
}

View file

@ -150,7 +150,7 @@
}
.markdown-body {
padding: 1.5rem var(--bterminal-pane-padding-inline, 2rem);
padding: 1.5rem var(--agor-pane-padding-inline, 2rem);
font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
font-size: 0.9rem;
line-height: 1.7;

View file

@ -885,7 +885,7 @@
<section class="settings-section">
<h2>Plugins</h2>
{#if pluginEntries.length === 0}
<p class="empty-notice">No plugins found in ~/.config/bterminal/plugins/</p>
<p class="empty-notice">No plugins found in ~/.config/agor/plugins/</p>
{:else}
<div class="plugin-list">
{#each pluginEntries as entry (entry.meta.id)}

View file

@ -73,15 +73,15 @@ class MockWorker {
const permissions = (data.permissions as string[]) || [];
const meta = data.meta as Record<string, unknown>;
// Build a mock bterminal API that mimics worker-side behavior
// Build a mock agor API that mimics worker-side behavior
// by sending messages back to the main thread (this.sendToMain)
const bterminal: Record<string, unknown> = {
const agor: Record<string, unknown> = {
meta: Object.freeze({ ...meta }),
};
if (permissions.includes('palette')) {
let cbId = 0;
bterminal.palette = {
agor.palette = {
registerCommand: (label: string, callback: () => void) => {
if (typeof label !== 'string' || !label.trim()) {
throw new Error('Command label must be a non-empty string');
@ -96,14 +96,14 @@ class MockWorker {
}
if (permissions.includes('bttask:read')) {
bterminal.tasks = {
agor.tasks = {
list: () => this.rpc('tasks.list', {}),
comments: (taskId: string) => this.rpc('tasks.comments', { taskId }),
};
}
if (permissions.includes('btmsg:read')) {
bterminal.messages = {
agor.messages = {
inbox: () => this.rpc('messages.inbox', {}),
channels: () => this.rpc('messages.channels', {}),
};
@ -111,7 +111,7 @@ class MockWorker {
if (permissions.includes('events')) {
let cbId = 0;
bterminal.events = {
agor.events = {
on: (event: string, callback: (data: unknown) => void) => {
if (typeof event !== 'string' || typeof callback !== 'function') {
throw new Error('event.on requires (string, function)');
@ -125,12 +125,12 @@ class MockWorker {
};
}
Object.freeze(bterminal);
Object.freeze(agor);
// Execute the plugin code
try {
const fn = new Function('bterminal', `"use strict"; ${code}`);
fn(bterminal);
const fn = new Function('agor', `"use strict"; ${code}`);
fn(agor);
this.sendToMain({ type: 'loaded' });
} catch (err) {
this.sendToMain({ type: 'error', message: String(err) });
@ -242,7 +242,7 @@ describe('plugin-host Worker isolation', () => {
const meta = makeMeta({ id: 'freeze-test', permissions: [] });
mockPluginCode(`
try {
bterminal.hacked = true;
agor.hacked = true;
throw new Error('FREEZE FAILED: could add property');
} catch (e) {
if (e.message === 'FREEZE FAILED: could add property') throw e;
@ -255,7 +255,7 @@ describe('plugin-host Worker isolation', () => {
const meta = makeMeta({ id: 'freeze-delete-test', permissions: [] });
mockPluginCode(`
try {
delete bterminal.meta;
delete agor.meta;
throw new Error('FREEZE FAILED: could delete property');
} catch (e) {
if (e.message === 'FREEZE FAILED: could delete property') throw e;
@ -267,14 +267,14 @@ describe('plugin-host Worker isolation', () => {
it('meta is accessible and frozen', async () => {
const meta = makeMeta({ id: 'meta-access', permissions: [] });
mockPluginCode(`
if (bterminal.meta.id !== 'meta-access') {
if (agor.meta.id !== 'meta-access') {
throw new Error('meta.id mismatch');
}
if (bterminal.meta.name !== 'Test Plugin') {
if (agor.meta.name !== 'Test Plugin') {
throw new Error('meta.name mismatch');
}
try {
bterminal.meta.id = 'hacked';
agor.meta.id = 'hacked';
throw new Error('META FREEZE FAILED');
} catch (e) {
if (e.message === 'META FREEZE FAILED') throw e;
@ -291,7 +291,7 @@ describe('plugin-host permissions', () => {
it('plugin with palette permission can register commands', async () => {
const meta = makeMeta({ id: 'palette-plugin', permissions: ['palette'] });
mockPluginCode(`
bterminal.palette.registerCommand('Test Command', function() {});
agor.palette.registerCommand('Test Command', function() {});
`);
await loadPlugin(meta, GROUP_ID, AGENT_ID);
@ -306,7 +306,7 @@ describe('plugin-host permissions', () => {
it('plugin without palette permission has no palette API', async () => {
const meta = makeMeta({ id: 'no-palette-plugin', permissions: [] });
mockPluginCode(`
if (bterminal.palette !== undefined) {
if (agor.palette !== undefined) {
throw new Error('palette API should not be available');
}
`);
@ -316,7 +316,7 @@ describe('plugin-host permissions', () => {
it('palette.registerCommand rejects non-string label', async () => {
const meta = makeMeta({ id: 'bad-label-plugin', permissions: ['palette'] });
mockPluginCode(`
bterminal.palette.registerCommand(123, function() {});
agor.palette.registerCommand(123, function() {});
`);
await expect(loadPlugin(meta, GROUP_ID, AGENT_ID)).rejects.toThrow(
'execution failed',
@ -326,7 +326,7 @@ describe('plugin-host permissions', () => {
it('palette.registerCommand rejects non-function callback', async () => {
const meta = makeMeta({ id: 'bad-cb-plugin', permissions: ['palette'] });
mockPluginCode(`
bterminal.palette.registerCommand('Test', 'not-a-function');
agor.palette.registerCommand('Test', 'not-a-function');
`);
await expect(loadPlugin(meta, GROUP_ID, AGENT_ID)).rejects.toThrow(
'execution failed',
@ -336,7 +336,7 @@ describe('plugin-host permissions', () => {
it('palette.registerCommand rejects empty label', async () => {
const meta = makeMeta({ id: 'empty-label-plugin', permissions: ['palette'] });
mockPluginCode(`
bterminal.palette.registerCommand(' ', function() {});
agor.palette.registerCommand(' ', function() {});
`);
await expect(loadPlugin(meta, GROUP_ID, AGENT_ID)).rejects.toThrow(
'execution failed',
@ -348,7 +348,7 @@ describe('plugin-host permissions', () => {
it('plugin with bttask:read can call tasks.list', async () => {
const meta = makeMeta({ id: 'task-plugin', permissions: ['bttask:read'] });
mockPluginCode(`
bterminal.tasks.list();
agor.tasks.list();
`);
await expect(loadPlugin(meta, GROUP_ID, AGENT_ID)).resolves.toBeUndefined();
});
@ -356,7 +356,7 @@ describe('plugin-host permissions', () => {
it('plugin without bttask:read has no tasks API', async () => {
const meta = makeMeta({ id: 'no-task-plugin', permissions: [] });
mockPluginCode(`
if (bterminal.tasks !== undefined) {
if (agor.tasks !== undefined) {
throw new Error('tasks API should not be available');
}
`);
@ -368,7 +368,7 @@ describe('plugin-host permissions', () => {
it('plugin with btmsg:read can call messages.inbox', async () => {
const meta = makeMeta({ id: 'msg-plugin', permissions: ['btmsg:read'] });
mockPluginCode(`
bterminal.messages.inbox();
agor.messages.inbox();
`);
await expect(loadPlugin(meta, GROUP_ID, AGENT_ID)).resolves.toBeUndefined();
});
@ -376,7 +376,7 @@ describe('plugin-host permissions', () => {
it('plugin without btmsg:read has no messages API', async () => {
const meta = makeMeta({ id: 'no-msg-plugin', permissions: [] });
mockPluginCode(`
if (bterminal.messages !== undefined) {
if (agor.messages !== undefined) {
throw new Error('messages API should not be available');
}
`);
@ -388,7 +388,7 @@ describe('plugin-host permissions', () => {
it('plugin with events permission can subscribe', async () => {
const meta = makeMeta({ id: 'events-plugin', permissions: ['events'] });
mockPluginCode(`
bterminal.events.on('test-event', function(data) {});
agor.events.on('test-event', function(data) {});
`);
await loadPlugin(meta, GROUP_ID, AGENT_ID);
expect(pluginEventBus.on).toHaveBeenCalledWith('test-event', expect.any(Function));
@ -397,7 +397,7 @@ describe('plugin-host permissions', () => {
it('plugin without events permission has no events API', async () => {
const meta = makeMeta({ id: 'no-events-plugin', permissions: [] });
mockPluginCode(`
if (bterminal.events !== undefined) {
if (agor.events !== undefined) {
throw new Error('events API should not be available');
}
`);
@ -437,7 +437,7 @@ describe('plugin-host lifecycle', () => {
it('unloadPlugin removes the plugin and cleans up commands', async () => {
const meta = makeMeta({ id: 'lifecycle-unload', permissions: ['palette'] });
mockPluginCode(`
bterminal.palette.registerCommand('Cmd1', function() {});
agor.palette.registerCommand('Cmd1', function() {});
`);
await loadPlugin(meta, GROUP_ID, AGENT_ID);
@ -491,7 +491,7 @@ describe('plugin-host lifecycle', () => {
it('unloadPlugin cleans up event subscriptions', async () => {
const meta = makeMeta({ id: 'events-cleanup', permissions: ['events'] });
mockPluginCode(`
bterminal.events.on('my-event', function() {});
agor.events.on('my-event', function() {});
`);
await loadPlugin(meta, GROUP_ID, AGENT_ID);
@ -507,11 +507,11 @@ describe('plugin-host lifecycle', () => {
describe('plugin-host RPC routing', () => {
it('tasks.list RPC is routed to main thread', async () => {
const meta = makeMeta({ id: 'rpc-tasks', permissions: ['bttask:read'] });
mockPluginCode(`bterminal.tasks.list();`);
mockPluginCode(`agor.tasks.list();`);
// Mock the bttask bridge
mockInvoke.mockImplementation((cmd: string) => {
if (cmd === 'plugin_read_file') return Promise.resolve('bterminal.tasks.list();');
if (cmd === 'plugin_read_file') return Promise.resolve('agor.tasks.list();');
if (cmd === 'bttask_list') return Promise.resolve([]);
return Promise.reject(new Error(`Unexpected: ${cmd}`));
});
@ -521,10 +521,10 @@ describe('plugin-host RPC routing', () => {
it('messages.inbox RPC is routed to main thread', async () => {
const meta = makeMeta({ id: 'rpc-messages', permissions: ['btmsg:read'] });
mockPluginCode(`bterminal.messages.inbox();`);
mockPluginCode(`agor.messages.inbox();`);
mockInvoke.mockImplementation((cmd: string) => {
if (cmd === 'plugin_read_file') return Promise.resolve('bterminal.messages.inbox();');
if (cmd === 'plugin_read_file') return Promise.resolve('agor.messages.inbox();');
if (cmd === 'btmsg_get_unread') return Promise.resolve([]);
return Promise.reject(new Error(`Unexpected: ${cmd}`));
});

View file

@ -1,5 +1,5 @@
/**
* Plugin Host Web Worker sandbox for BTerminal plugins.
* Plugin Host Web Worker sandbox for Agents Orchestrator plugins.
*
* Each plugin runs in a dedicated Web Worker, providing true process-level
* isolation from the main thread. The Worker has no access to the DOM,
@ -38,7 +38,7 @@ const loadedPlugins = new Map<string, LoadedPlugin>();
/**
* Build the Worker script as an inline blob.
* The Worker receives plugin code + permissions and builds a sandboxed bterminal API
* The Worker receives plugin code + permissions and builds a sandboxed agor API
* that proxies all calls to the main thread via postMessage.
*/
function buildWorkerScript(): string {
@ -73,7 +73,7 @@ self.onmessage = function(e) {
const permissions = msg.permissions || [];
const meta = msg.meta;
// Build the bterminal API based on permissions
// Build the agor API based on permissions
const api = { meta: Object.freeze(meta) };
if (permissions.includes('palette')) {
@ -128,7 +128,7 @@ self.onmessage = function(e) {
// Execute the plugin code
try {
const fn = (0, eval)(
'(function(bterminal) { "use strict"; ' + msg.code + '\\n})'
'(function(agor) { "use strict"; ' + msg.code + '\\n})'
);
fn(api);
self.postMessage({ type: 'loaded' });

View file

@ -57,5 +57,5 @@
--border-radius: 4px;
/* Pane content padding — shared between AgentPane and MarkdownPane */
--bterminal-pane-padding-inline: clamp(0.75rem, 3.5cqi, 2rem);
--agor-pane-padding-inline: clamp(0.75rem, 3.5cqi, 2rem);
}

View file

@ -257,7 +257,7 @@ function buildEnvironmentSection(group: GroupConfig): string {
return `## Environment
**Platform:** BTerminal Mission Control multi-agent orchestration system
**Platform:** Agents Orchestrator Mission Control multi-agent orchestration system
**Group:** ${group.name}
**Your working directory:** Same as the monorepo root (shared across Tier 1 agents)

View file

@ -23,7 +23,7 @@ export async function detachPane(pane: Pane): Promise<void> {
const webview = new WebviewWindow(label, {
url: `index.html?${params.toString()}`,
title: `BTerminal${pane.title}`,
title: `Agents Orchestrator${pane.title}`,
width: 800,
height: 600,
decorations: true,