Compare commits
12 Commits
be30b4b3f4
...
zhi-2026-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb53af642d | ||
|
|
a6f63e15f4 | ||
|
|
a7d541dcc0 | ||
|
|
1881ce6168 | ||
|
|
ce61834738 | ||
|
|
2d3d24b0ac | ||
|
|
86c9d43c97 | ||
|
|
4a66a7cc90 | ||
|
|
9af8204376 | ||
|
|
248adfaafd | ||
|
|
e4ac7b7af3 | ||
|
|
2088cd12b4 |
@@ -1,172 +0,0 @@
|
|||||||
/**
|
|
||||||
* Discord-based agent wakeup: create a private channel and send a wakeup message.
|
|
||||||
*
|
|
||||||
* If Dirigent is detected (via globalThis.__dirigent), creates a work-type channel.
|
|
||||||
* Otherwise, creates a plain private Discord channel.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DISCORD_API = 'https://discord.com/api/v10';
|
|
||||||
|
|
||||||
interface WakeupConfig {
|
|
||||||
botToken: string;
|
|
||||||
guildId: string;
|
|
||||||
agentDiscordId?: string;
|
|
||||||
agentId: string;
|
|
||||||
message: string;
|
|
||||||
logger: {
|
|
||||||
info: (...args: any[]) => void;
|
|
||||||
warn: (...args: any[]) => void;
|
|
||||||
error: (...args: any[]) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DirigentApi {
|
|
||||||
createWorkChannel?: (params: {
|
|
||||||
guildId: string;
|
|
||||||
name: string;
|
|
||||||
agentDiscordId: string;
|
|
||||||
}) => Promise<string>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get bot user ID from token (decode JWT-like Discord token).
|
|
||||||
*/
|
|
||||||
function getBotUserIdFromToken(token: string): string | null {
|
|
||||||
try {
|
|
||||||
const base64 = token.split('.')[0];
|
|
||||||
const decoded = Buffer.from(base64, 'base64').toString('utf8');
|
|
||||||
return decoded || null;
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a private Discord channel visible only to the target agent and bot.
|
|
||||||
*/
|
|
||||||
async function createPrivateChannel(
|
|
||||||
token: string,
|
|
||||||
guildId: string,
|
|
||||||
name: string,
|
|
||||||
memberIds: string[],
|
|
||||||
logger: WakeupConfig['logger']
|
|
||||||
): Promise<string | null> {
|
|
||||||
const botId = getBotUserIdFromToken(token);
|
|
||||||
|
|
||||||
// Permission overwrites: deny @everyone, allow specific members
|
|
||||||
const permissionOverwrites = [
|
|
||||||
{ id: guildId, type: 0, deny: '1024' }, // deny @everyone view
|
|
||||||
...memberIds.map(id => ({ id, type: 1, allow: '1024' })), // allow members view
|
|
||||||
...(botId ? [{ id: botId, type: 1, allow: '1024' }] : []),
|
|
||||||
];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${DISCORD_API}/guilds/${guildId}/channels`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bot ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
name,
|
|
||||||
type: 0, // text channel
|
|
||||||
permission_overwrites: permissionOverwrites,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
logger.warn(`Discord channel creation failed: ${res.status} ${await res.text()}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json() as { id: string };
|
|
||||||
return data.id;
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Discord channel creation error: ${String(err)}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a message to a Discord channel.
|
|
||||||
*/
|
|
||||||
async function sendMessage(
|
|
||||||
token: string,
|
|
||||||
channelId: string,
|
|
||||||
content: string,
|
|
||||||
logger: WakeupConfig['logger']
|
|
||||||
): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${DISCORD_API}/channels/${channelId}/messages`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bot ${token}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ content }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
logger.warn(`Discord message send failed: ${res.status}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Discord message send error: ${String(err)}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wake an agent via Discord: create a private channel and send the wakeup message.
|
|
||||||
*/
|
|
||||||
export async function wakeAgentViaDiscord(config: WakeupConfig): Promise<boolean> {
|
|
||||||
const { botToken, guildId, agentDiscordId, agentId, message, logger } = config;
|
|
||||||
|
|
||||||
if (!botToken || !guildId) {
|
|
||||||
logger.warn('Discord wakeup: botToken or guildId not configured');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Dirigent is available for work channel creation
|
|
||||||
const dirigent = (globalThis as Record<string, unknown>)['__dirigent'] as DirigentApi | undefined;
|
|
||||||
|
|
||||||
let channelId: string | null = null;
|
|
||||||
const channelName = `hf-wakeup-${agentId}-${Date.now()}`;
|
|
||||||
|
|
||||||
if (dirigent?.createWorkChannel && agentDiscordId) {
|
|
||||||
// Use Dirigent to create a work-type channel (with turn management)
|
|
||||||
try {
|
|
||||||
channelId = await dirigent.createWorkChannel({
|
|
||||||
guildId,
|
|
||||||
name: channelName,
|
|
||||||
agentDiscordId,
|
|
||||||
});
|
|
||||||
logger.info(`Wakeup channel created via Dirigent: ${channelId}`);
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`Dirigent work channel creation failed, falling back to plain channel: ${String(err)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!channelId) {
|
|
||||||
// Fallback: create a plain private Discord channel
|
|
||||||
const memberIds = agentDiscordId ? [agentDiscordId] : [];
|
|
||||||
channelId = await createPrivateChannel(botToken, guildId, channelName, memberIds, logger);
|
|
||||||
if (channelId) {
|
|
||||||
logger.info(`Wakeup channel created (plain): ${channelId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!channelId) {
|
|
||||||
logger.error('Failed to create wakeup channel');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the wakeup message
|
|
||||||
const sent = await sendMessage(botToken, channelId, message, logger);
|
|
||||||
if (sent) {
|
|
||||||
logger.info(`Wakeup message sent to ${channelId} for agent ${agentId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return sent;
|
|
||||||
}
|
|
||||||
@@ -32,4 +32,3 @@ export * from './types';
|
|||||||
export * from './calendar-bridge';
|
export * from './calendar-bridge';
|
||||||
export * from './scheduler';
|
export * from './scheduler';
|
||||||
export * from './schedule-cache';
|
export * from './schedule-cache';
|
||||||
export * from './discord-wakeup';
|
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
import { execFile } from 'child_process';
|
|
||||||
import { promisify } from 'util';
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
|
||||||
|
|
||||||
export interface OpenClawAgentInfo {
|
export interface OpenClawAgentInfo {
|
||||||
name: string;
|
name: string;
|
||||||
isDefault?: boolean;
|
isDefault?: boolean;
|
||||||
@@ -14,70 +9,38 @@ export interface OpenClawAgentInfo {
|
|||||||
routing?: string;
|
routing?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listOpenClawAgents(logger?: { debug?: (...args: any[]) => void; warn?: (...args: any[]) => void }): Promise<OpenClawAgentInfo[]> {
|
export async function listOpenClawAgents(_logger?: { debug?: (...args: any[]) => void; warn?: (...args: any[]) => void }): Promise<OpenClawAgentInfo[]> {
|
||||||
try {
|
|
||||||
const { stdout } = await execFileAsync('openclaw', ['agents', 'list'], {
|
|
||||||
timeout: 15000,
|
|
||||||
maxBuffer: 1024 * 1024,
|
|
||||||
});
|
|
||||||
return parseOpenClawAgents(stdout);
|
|
||||||
} catch (err) {
|
|
||||||
logger?.warn?.('Failed to run `openclaw agents list`', err);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseOpenClawAgents(text: string): OpenClawAgentInfo[] {
|
export function parseOpenClawAgents(text: string): OpenClawAgentInfo[] {
|
||||||
const lines = text.split(/\r?\n/);
|
const lines = text.split(/\r?\n/);
|
||||||
const out: OpenClawAgentInfo[] = [];
|
const out: OpenClawAgentInfo[] = [];
|
||||||
let current: OpenClawAgentInfo | null = null;
|
let current: OpenClawAgentInfo | null = null;
|
||||||
|
const push = () => { if (current) out.push(current); current = null; };
|
||||||
const push = () => {
|
|
||||||
if (current) out.push(current);
|
|
||||||
current = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const raw of lines) {
|
for (const raw of lines) {
|
||||||
const line = raw.trimEnd();
|
const line = raw.trimEnd();
|
||||||
if (!line.trim() || line.startsWith('Agents:') || line.startsWith('Routing rules map') || line.startsWith('Channel status reflects')) continue;
|
if (!line.trim() || line.startsWith("Agents:") || line.startsWith("Routing rules map") || line.startsWith("Channel status reflects")) continue;
|
||||||
if (line.startsWith('- ')) {
|
if (line.startsWith("- ")) {
|
||||||
push();
|
push();
|
||||||
const m = line.match(/^-\s+(.+?)(?:\s+\((default)\))?$/);
|
const m = line.match(/^-\s+(.+?)(?:\s+\((default)\))?$/);
|
||||||
current = {
|
current = { name: m?.[1] || line.slice(2).trim(), isDefault: m?.[2] === "default" };
|
||||||
name: m?.[1] || line.slice(2).trim(),
|
|
||||||
isDefault: m?.[2] === 'default',
|
|
||||||
};
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!current) continue;
|
if (!current) continue;
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
const idx = trimmed.indexOf(':');
|
const idx = trimmed.indexOf(":");
|
||||||
if (idx === -1) continue;
|
if (idx === -1) continue;
|
||||||
const key = trimmed.slice(0, idx).trim();
|
const key = trimmed.slice(0, idx).trim();
|
||||||
const value = trimmed.slice(idx + 1).trim();
|
const value = trimmed.slice(idx + 1).trim();
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'Identity':
|
case "Identity": current.identity = value; break;
|
||||||
current.identity = value;
|
case "Workspace": current.workspace = value; break;
|
||||||
break;
|
case "Agent dir": current.agentDir = value; break;
|
||||||
case 'Workspace':
|
case "Model": current.model = value; break;
|
||||||
current.workspace = value;
|
case "Routing rules": { const n = Number(value); current.routingRules = Number.isFinite(n) ? n : undefined; break; }
|
||||||
break;
|
case "Routing": current.routing = value; break;
|
||||||
case 'Agent dir':
|
default: break;
|
||||||
current.agentDir = value;
|
|
||||||
break;
|
|
||||||
case 'Model':
|
|
||||||
current.model = value;
|
|
||||||
break;
|
|
||||||
case 'Routing rules': {
|
|
||||||
const n = Number(value);
|
|
||||||
current.routingRules = Number.isFinite(n) ? n : undefined;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Routing':
|
|
||||||
current.routing = value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
push();
|
push();
|
||||||
|
|||||||
137
plugin/index.ts
137
plugin/index.ts
@@ -14,7 +14,7 @@
|
|||||||
import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os';
|
import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os';
|
||||||
import { getPluginConfig } from './core/config';
|
import { getPluginConfig } from './core/config';
|
||||||
import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge';
|
import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge';
|
||||||
import { listOpenClawAgents } from './core/openclaw-agents';
|
import type { OpenClawAgentInfo } from './core/openclaw-agents';
|
||||||
import { registerGatewayStartHook } from './hooks/gateway-start';
|
import { registerGatewayStartHook } from './hooks/gateway-start';
|
||||||
import { registerGatewayStopHook } from './hooks/gateway-stop';
|
import { registerGatewayStopHook } from './hooks/gateway-stop';
|
||||||
import {
|
import {
|
||||||
@@ -32,6 +32,12 @@ interface PluginAPI {
|
|||||||
warn: (...args: any[]) => void;
|
warn: (...args: any[]) => void;
|
||||||
};
|
};
|
||||||
version?: string;
|
version?: string;
|
||||||
|
runtime?: {
|
||||||
|
version?: string;
|
||||||
|
config?: {
|
||||||
|
loadConfig?: () => any;
|
||||||
|
};
|
||||||
|
};
|
||||||
config?: Record<string, unknown>;
|
config?: Record<string, unknown>;
|
||||||
pluginConfig?: Record<string, unknown>;
|
pluginConfig?: Record<string, unknown>;
|
||||||
on: (event: string, handler: () => void) => void;
|
on: (event: string, handler: () => void) => void;
|
||||||
@@ -96,7 +102,7 @@ export default {
|
|||||||
avg15: load[2],
|
avg15: load[2],
|
||||||
},
|
},
|
||||||
openclaw: {
|
openclaw: {
|
||||||
version: api.version || 'unknown',
|
version: api.runtime?.version || api.version || 'unknown',
|
||||||
pluginVersion: '0.3.1', // Bumped for PLG-CAL-004
|
pluginVersion: '0.3.1', // Bumped for PLG-CAL-004
|
||||||
},
|
},
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -118,10 +124,21 @@ export default {
|
|||||||
const bridgeClient = getBridgeClient();
|
const bridgeClient = getBridgeClient();
|
||||||
if (!bridgeClient) return;
|
if (!bridgeClient) return;
|
||||||
|
|
||||||
|
let agentNames: string[] = [];
|
||||||
|
try {
|
||||||
|
const cfg = api.runtime?.config?.loadConfig?.();
|
||||||
|
const agentsList = cfg?.agents?.list;
|
||||||
|
if (Array.isArray(agentsList)) {
|
||||||
|
agentNames = agentsList
|
||||||
|
.map((a: any) => typeof a === 'string' ? a : a?.name)
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
} catch { /* non-fatal */ }
|
||||||
|
|
||||||
const meta: OpenClawMeta = {
|
const meta: OpenClawMeta = {
|
||||||
version: api.version || 'unknown',
|
version: api.runtime?.version || api.version || 'unknown',
|
||||||
plugin_version: '0.3.1',
|
plugin_version: '0.3.1',
|
||||||
agents: await listOpenClawAgents(logger),
|
agents: agentNames.map(name => ({ name })),
|
||||||
};
|
};
|
||||||
|
|
||||||
const ok = await bridgeClient.pushOpenClawMeta(meta);
|
const ok = await bridgeClient.pushOpenClawMeta(meta);
|
||||||
@@ -171,55 +188,99 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wake agent via Discord channel creation + message.
|
* Wake agent via gateway WebSocket API.
|
||||||
* This is the callback invoked by CalendarScheduler when a slot is ready.
|
* Uses callGateway("agent") to trigger an agent turn — the same mechanism
|
||||||
*
|
* used by sessions_spawn and cron internally.
|
||||||
* Priority:
|
|
||||||
* 1. Discord wakeup (create private channel + send message)
|
|
||||||
* 2. OpenClaw spawn API (fallback if Discord not configured)
|
|
||||||
*/
|
*/
|
||||||
async function wakeAgent(context: AgentWakeContext): Promise<boolean> {
|
async function wakeAgent(context: AgentWakeContext): Promise<boolean> {
|
||||||
logger.info(`Waking agent for slot: ${context.taskDescription}`);
|
logger.info(`Waking agent for slot: ${context.taskDescription}`);
|
||||||
const live = resolveConfig();
|
|
||||||
const agentId = process.env.AGENT_ID || 'unknown';
|
const agentId = process.env.AGENT_ID || 'unknown';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Method 1: Discord wakeup (preferred)
|
// Connect to gateway via WebSocket and trigger an agent turn.
|
||||||
const discordBotToken = (live as any).discordBotToken as string | undefined;
|
// Uses the same gateway RPC that sessions_spawn and cron use internally.
|
||||||
const discordGuildId = (live as any).discordGuildId as string | undefined;
|
const cfg = api.runtime?.config?.loadConfig?.() ?? {};
|
||||||
|
const gwCfg = cfg.gateway ?? {};
|
||||||
|
const gwPort = gwCfg.port ?? 18789;
|
||||||
|
const gwToken = gwCfg.auth?.token ?? '';
|
||||||
|
const gatewayUrl = `ws://127.0.0.1:${gwPort}`;
|
||||||
|
|
||||||
if (discordBotToken && discordGuildId) {
|
const result = await new Promise<{ sessionId?: string; error?: string }>((resolve, reject) => {
|
||||||
const { wakeAgentViaDiscord } = await import('./calendar/discord-wakeup.js');
|
const timeout = setTimeout(() => {
|
||||||
const success = await wakeAgentViaDiscord({
|
try { client.close(); } catch {}
|
||||||
botToken: discordBotToken,
|
reject(new Error('Gateway connection timeout'));
|
||||||
guildId: discordGuildId,
|
}, 15000);
|
||||||
agentId,
|
|
||||||
|
// Pass token via Authorization header in the upgrade request
|
||||||
|
const wsUrl = gwToken ? `${gatewayUrl}?token=${encodeURIComponent(gwToken)}` : gatewayUrl;
|
||||||
|
const client = new (globalThis as any).WebSocket(wsUrl);
|
||||||
|
|
||||||
|
client.onerror = (err: any) => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
reject(err?.error || err);
|
||||||
|
};
|
||||||
|
|
||||||
|
client.onopen = () => {
|
||||||
|
// Gateway uses "connect" method with auth in params
|
||||||
|
client.send(JSON.stringify({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'connect',
|
||||||
|
params: {
|
||||||
|
clientName: 'harbor-forge-calendar',
|
||||||
|
mode: 'backend',
|
||||||
|
...(gwToken ? { auth: { token: gwToken } } : {}),
|
||||||
|
},
|
||||||
|
id: 1,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let helloAcked = false;
|
||||||
|
|
||||||
|
client.onmessage = (ev: any) => {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(typeof ev.data === 'string' ? ev.data : ev.data.toString());
|
||||||
|
|
||||||
|
if (!helloAcked && msg.id === 1) {
|
||||||
|
helloAcked = true;
|
||||||
|
client.send(JSON.stringify({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'agent',
|
||||||
|
params: {
|
||||||
message: context.prompt,
|
message: context.prompt,
|
||||||
logger,
|
agentId,
|
||||||
});
|
|
||||||
if (success) return true;
|
|
||||||
logger.warn('Discord wakeup failed, trying spawn fallback');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method 2: OpenClaw spawn API (fallback)
|
|
||||||
if (api.spawn) {
|
|
||||||
const result = await api.spawn({
|
|
||||||
task: context.prompt,
|
|
||||||
timeoutSeconds: context.slot.estimated_duration * 60,
|
timeoutSeconds: context.slot.estimated_duration * 60,
|
||||||
|
},
|
||||||
|
id: 2,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.id === 2) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
try { client.close(); } catch {}
|
||||||
|
if (msg.error) {
|
||||||
|
resolve({ error: msg.error.message || JSON.stringify(msg.error) });
|
||||||
|
} else {
|
||||||
|
resolve({ sessionId: msg.result?.sessionId || 'ok' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore parse errors
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result?.sessionId) {
|
if (result.error) {
|
||||||
logger.info(`Agent spawned for calendar slot: session=${result.sessionId}`);
|
logger.error(`Gateway agent call failed: ${result.error}`);
|
||||||
trackSessionCompletion(result.sessionId, context);
|
return false;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('No wakeup method available (configure discordBotToken + discordGuildId)');
|
logger.info(`Agent woken via gateway for slot: session=${result.sessionId}`);
|
||||||
return false;
|
return true;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Failed to wake agent:', err);
|
logger.error('Failed to wake agent via gateway:', err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,14 +63,6 @@
|
|||||||
"managedMonitor": {
|
"managedMonitor": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Absolute path to an installed HarborForge.Monitor binary managed by this plugin installer. If set, gateway_start/gateway_stop hooks will start/stop the monitor process automatically."
|
"description": "Absolute path to an installed HarborForge.Monitor binary managed by this plugin installer. If set, gateway_start/gateway_stop hooks will start/stop the monitor process automatically."
|
||||||
},
|
|
||||||
"discordBotToken": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Discord bot token for agent wakeup. Used to create private channels and send wakeup messages. Set to the same value as Dirigent moderator bot token."
|
|
||||||
},
|
|
||||||
"discordGuildId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Discord guild ID where wakeup channels are created."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user