feat: support per-bot workspace switching

This commit is contained in:
xmanrui 2026-08-17 23:56:27 +08:00
parent adcace3512
commit 44dd50216a
64 changed files with 3878 additions and 712 deletions

View file

@ -8,6 +8,12 @@ import { WhatsappStateStore } from '../../../../src/channels/whatsapp/state-stor
import { WhatsappController } from '../../../../src/channels/whatsapp/whatsapp-controller.mjs';
import { WhatsappRuntime } from '../../../../src/channels/whatsapp/whatsapp-runtime.mjs';
import { createWhatsappWebSession } from '../../../../src/channels/whatsapp/whatsapp-web-session.mjs';
import {
BotWorkspaceStore,
createBotWorkspaceScope,
createWorkspaceAwareController,
observeBotWorkspaceRemovals,
} from '../../../../src/channels/shared/bot-workspace-store.mjs';
import { createTokenConnectionSupervisor } from '../shared/connection-supervisor.mjs';
const AUTH_DIRECTORY_PATTERN = /^[a-f0-9-]{36}$/;
@ -32,6 +38,7 @@ function pluginPaths(config) {
return {
config: resolve(config.configPath ?? join(root, 'config.json')),
bots: resolve(config.botsDir ?? join(root, 'bots')),
workspaces: resolve(config.workspacesPath ?? join(root, 'workspaces.json')),
authPath,
};
}
@ -49,6 +56,16 @@ export async function createProductionController(ctx, config = {}, internals = {
const createSupervisor = internals.createConnectionSupervisor ?? createTokenConnectionSupervisor;
const paths = pluginPaths(config);
const configStore = await new ConfigStore(paths.config).load();
const defaultWorkspace = resolve(config.workspace ?? process.cwd());
const WorkspaceStore = internals.WorkspaceStore ?? BotWorkspaceStore;
const workspaces = internals.workspaces
?? await new WorkspaceStore(paths.workspaces, { defaultWorkspace }).load();
const configuredBots = configStore.list();
await workspaces.reconcile(configuredBots.map((bot) => bot.botId));
await Promise.all(configuredBots.map((bot) => workspaces.ensure(bot.botId)));
const observedConfigStore = typeof configStore.remove === 'function'
? observeBotWorkspaceRemovals(configStore, { workspaces })
: configStore;
const stateStores = new Map();
const statePath = (botId) => resolve(paths.bots, botId, 'state.json');
const stateFor = async (botId) => {
@ -61,31 +78,36 @@ export async function createProductionController(ctx, config = {}, internals = {
};
const harness = new Harness({
baseUrl: harnessOrigin(ctx.webServer, config.harnessBaseUrl),
workspace: resolve(config.workspace ?? process.cwd()),
workspace: defaultWorkspace,
agentPreset: config.agentPreset ?? 'standard',
autostart: false,
dshBin: config.dshBin ?? 'dsh',
});
const controller = new Controller({
configStore,
const coreController = new Controller({
configStore: observedConfigStore,
authPath: paths.authPath,
createSession,
logger,
createRuntime: async ({ botId, config: botConfig, authDir }) => new Runtime({
config: botConfig,
authDir,
harness,
state: await stateFor(botId),
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
connectTimeoutMs: config.connectTimeoutMs ?? 30_000,
createSession,
logger: {
error: (...args) => logger.error?.(`[${botId}]`, ...args),
warn: (...args) => logger.warn?.(`[${botId}]`, ...args),
info: (...args) => logger.info?.(`[${botId}]`, ...args),
debug: (...args) => logger.debug?.(`[${botId}]`, ...args),
},
}),
createRuntime: async ({ botId, config: botConfig, authDir }) => {
const state = await stateFor(botId);
await workspaces.ensure(botId);
const workspaceScope = createBotWorkspaceScope(harness, { botId, workspaces, state });
return new Runtime({
config: botConfig,
authDir,
harness: workspaceScope.harness,
state: workspaceScope.state,
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
connectTimeoutMs: config.connectTimeoutMs ?? 30_000,
createSession,
logger: {
error: (...args) => logger.error?.(`[${botId}]`, ...args),
warn: (...args) => logger.warn?.(`[${botId}]`, ...args),
info: (...args) => logger.info?.(`[${botId}]`, ...args),
debug: (...args) => logger.debug?.(`[${botId}]`, ...args),
},
});
},
deleteAuth: (authDirectory) => rm(paths.authPath(authDirectory), {
recursive: true,
force: true,
@ -93,14 +115,18 @@ export async function createProductionController(ctx, config = {}, internals = {
deleteState: async ({ botId }) => {
const state = stateStores.get(botId);
stateStores.delete(botId);
if (state && typeof state.remove === 'function') return state.remove();
try {
await unlink(statePath(botId));
} catch (error) {
if (error?.code !== 'ENOENT') throw error;
if (state && typeof state.remove === 'function') {
await state.remove();
} else {
try {
await unlink(statePath(botId));
} catch (error) {
if (error?.code !== 'ENOENT') throw error;
}
}
},
});
const controller = createWorkspaceAwareController(coreController, { workspaces, stateFor });
const supervisor = createSupervisor({
channel: 'whatsapp',
controller,