@tritard/waterbrother 0.16.125 → 0.16.127
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/channels.js +3 -2
- package/src/cli.js +136 -35
- package/src/discord.js +2 -2
package/package.json
CHANGED
package/src/channels.js
CHANGED
|
@@ -229,7 +229,7 @@ export function buildChannelOnboardingPayload(serviceId) {
|
|
|
229
229
|
"Enable Message Content Intent before testing DMs or mentions"
|
|
230
230
|
],
|
|
231
231
|
commands: [
|
|
232
|
-
"waterbrother config set-json channels '{\"discord\":{\"enabled\":true,\"botToken\":\"
|
|
232
|
+
"waterbrother config set-json channels '{\"discord\":{\"enabled\":true,\"botToken\":\"YOUR_BOT_TOKEN\",\"applicationId\":\"YOUR_APP_ID\",\"pairingMode\":\"manual\",\"allowedUserIds\":[]}}'",
|
|
233
233
|
"waterbrother channels status",
|
|
234
234
|
"waterbrother discord status",
|
|
235
235
|
"waterbrother discord run"
|
|
@@ -237,7 +237,8 @@ export function buildChannelOnboardingPayload(serviceId) {
|
|
|
237
237
|
notes: [
|
|
238
238
|
"Start with direct messages before any guild or channel workflow.",
|
|
239
239
|
"This first runtime slice handles DMs and mentions, not full Roundtable rooms yet.",
|
|
240
|
-
"Prefer explicit user allowlists."
|
|
240
|
+
"Prefer explicit user allowlists.",
|
|
241
|
+
"The interactive TUI onboarding wizard can store the Discord token and application id directly, just like Telegram."
|
|
241
242
|
]
|
|
242
243
|
};
|
|
243
244
|
}
|
package/src/cli.js
CHANGED
|
@@ -2925,7 +2925,7 @@ function isProcessAlive(pid) {
|
|
|
2925
2925
|
|
|
2926
2926
|
async function maybeAutostartGateway(runtime, { cwd, io = console } = {}) {
|
|
2927
2927
|
if (process.env.WATERBROTHER_GATEWAY_CHILD === "1") {
|
|
2928
|
-
return { attempted: false, started: false, reason: "gateway-child" };
|
|
2928
|
+
return { attempted: false, started: false, reason: "gateway-child", handles: [] };
|
|
2929
2929
|
}
|
|
2930
2930
|
|
|
2931
2931
|
const gateway = runtime?.gateway || {};
|
|
@@ -2933,38 +2933,54 @@ async function maybeAutostartGateway(runtime, { cwd, io = console } = {}) {
|
|
|
2933
2933
|
? gateway.startupChannels.map((value) => String(value || "").trim().toLowerCase()).filter(Boolean)
|
|
2934
2934
|
: [];
|
|
2935
2935
|
|
|
2936
|
-
if (!gateway.enabled ||
|
|
2937
|
-
return { attempted: false, started: false, reason: "not-configured",
|
|
2936
|
+
if (!gateway.enabled || startupChannels.length === 0) {
|
|
2937
|
+
return { attempted: false, started: false, reason: "not-configured", handles: [] };
|
|
2938
2938
|
}
|
|
2939
2939
|
|
|
2940
|
-
const
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
}
|
|
2940
|
+
const handles = [];
|
|
2941
|
+
let attempted = false;
|
|
2942
|
+
let started = false;
|
|
2944
2943
|
|
|
2945
|
-
const
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2944
|
+
for (const serviceId of startupChannels) {
|
|
2945
|
+
if (serviceId === "telegram") {
|
|
2946
|
+
const telegram = runtime?.channels?.telegram || {};
|
|
2947
|
+
if (!telegram.enabled || !String(telegram.botToken || "").trim()) continue;
|
|
2948
|
+
} else if (serviceId === "discord") {
|
|
2949
|
+
const discord = runtime?.channels?.discord || {};
|
|
2950
|
+
if (!discord.enabled || !String(discord.botToken || "").trim() || !String(discord.applicationId || "").trim()) continue;
|
|
2951
|
+
} else {
|
|
2952
|
+
continue;
|
|
2953
|
+
}
|
|
2949
2954
|
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
WATERBROTHER_GATEWAY_CHILD: "1"
|
|
2955
|
+
attempted = true;
|
|
2956
|
+
const processInfo = await loadGatewayProcessInfo(serviceId);
|
|
2957
|
+
if (isProcessAlive(processInfo.pid)) {
|
|
2958
|
+
handles.push({ service: serviceId, attempted: true, started: false, reason: "already-running", pid: processInfo.pid, child: null });
|
|
2959
|
+
continue;
|
|
2956
2960
|
}
|
|
2957
|
-
});
|
|
2958
|
-
child.unref();
|
|
2959
2961
|
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2962
|
+
const child = spawn(process.execPath, [BIN_PATH, serviceId === "telegram" ? "gateway" : "discord", "run", ...(serviceId === "telegram" ? [serviceId] : []), "--skip-onboarding"].filter(Boolean), {
|
|
2963
|
+
cwd,
|
|
2964
|
+
stdio: "ignore",
|
|
2965
|
+
env: {
|
|
2966
|
+
...process.env,
|
|
2967
|
+
WATERBROTHER_GATEWAY_CHILD: "1"
|
|
2968
|
+
}
|
|
2969
|
+
});
|
|
2970
|
+
child.unref();
|
|
2971
|
+
|
|
2972
|
+
await saveGatewayProcessInfo(serviceId, {
|
|
2973
|
+
pid: child.pid,
|
|
2974
|
+
startedAt: new Date().toISOString(),
|
|
2975
|
+
command: serviceId === "telegram" ? "gateway run telegram --skip-onboarding" : "discord run --skip-onboarding"
|
|
2976
|
+
});
|
|
2977
|
+
|
|
2978
|
+
io.log?.(dim(`${serviceId} gateway autostarted in background (pid ${child.pid})`));
|
|
2979
|
+
handles.push({ service: serviceId, attempted: true, started: true, pid: child.pid, child });
|
|
2980
|
+
started = true;
|
|
2981
|
+
}
|
|
2965
2982
|
|
|
2966
|
-
|
|
2967
|
-
return { attempted: true, started: true, pid: child.pid, child };
|
|
2983
|
+
return { attempted, started, reason: handles.length ? "" : "not-ready", handles };
|
|
2968
2984
|
}
|
|
2969
2985
|
|
|
2970
2986
|
async function stopManagedGateway(service, gatewayHandle, { io = console } = {}) {
|
|
@@ -2997,8 +3013,8 @@ async function stopManagedGateway(service, gatewayHandle, { io = console } = {})
|
|
|
2997
3013
|
|
|
2998
3014
|
async function stopTrackedGateway(service = "telegram", { io = console } = {}) {
|
|
2999
3015
|
const normalized = String(service || "telegram").trim().toLowerCase();
|
|
3000
|
-
if (
|
|
3001
|
-
throw new Error("Usage: waterbrother gateway stop [telegram]");
|
|
3016
|
+
if (!["telegram", "discord"].includes(normalized)) {
|
|
3017
|
+
throw new Error("Usage: waterbrother gateway stop [telegram|discord]");
|
|
3002
3018
|
}
|
|
3003
3019
|
const processInfo = await loadGatewayProcessInfo(normalized);
|
|
3004
3020
|
const pid = Number(processInfo?.pid || 0);
|
|
@@ -3694,6 +3710,87 @@ async function runOnboardingWizard(config, { cwd }) {
|
|
|
3694
3710
|
console.log(dim("Skipping Telegram setup for now. You can always run `waterbrother onboarding telegram` later."));
|
|
3695
3711
|
}
|
|
3696
3712
|
}
|
|
3713
|
+
|
|
3714
|
+
const configureDiscord = await promptYesNo("Enable Discord DM control now?", {
|
|
3715
|
+
input: process.stdin,
|
|
3716
|
+
output: process.stdout
|
|
3717
|
+
});
|
|
3718
|
+
if (configureDiscord) {
|
|
3719
|
+
const alreadyHaveDiscordSetup = await promptYesNo("Do you already have a Discord bot token and application id?", {
|
|
3720
|
+
input: process.stdin,
|
|
3721
|
+
output: process.stdout
|
|
3722
|
+
});
|
|
3723
|
+
if (!alreadyHaveDiscordSetup) {
|
|
3724
|
+
console.log("Create a Discord application and bot, enable Message Content Intent, then come back here.");
|
|
3725
|
+
console.log(dim(`Guide: ${getDocsUrl("/onboarding/discord")}`));
|
|
3726
|
+
const openGuide = await promptYesNo("Open the Discord setup guide in your browser?", {
|
|
3727
|
+
input: process.stdin,
|
|
3728
|
+
output: process.stdout
|
|
3729
|
+
});
|
|
3730
|
+
if (openGuide) {
|
|
3731
|
+
const opened = await maybeOpenUrl(getDocsUrl("/onboarding/discord"));
|
|
3732
|
+
if (!opened) {
|
|
3733
|
+
console.log(dim(`Could not open browser automatically. Visit ${getDocsUrl("/onboarding/discord")}`));
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
const existingDiscord = next.channels?.discord && typeof next.channels.discord === "object" ? next.channels.discord : {};
|
|
3739
|
+
let discordToken = String(existingDiscord.botToken || existingDiscord.token || "").trim();
|
|
3740
|
+
let discordApplicationId = String(existingDiscord.applicationId || "").trim();
|
|
3741
|
+
|
|
3742
|
+
if (discordToken) {
|
|
3743
|
+
const keepExistingDiscordToken = await promptYesNo(`Use saved Discord bot token (${formatApiKeyHint(discordToken)})?`, {
|
|
3744
|
+
input: process.stdin,
|
|
3745
|
+
output: process.stdout
|
|
3746
|
+
});
|
|
3747
|
+
if (!keepExistingDiscordToken) {
|
|
3748
|
+
discordToken = "";
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
while (!discordToken) {
|
|
3753
|
+
const enteredDiscordToken = (await promptLine("Paste Discord bot token (Enter to skip for now): ")).trim();
|
|
3754
|
+
if (!enteredDiscordToken) break;
|
|
3755
|
+
discordToken = enteredDiscordToken;
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
if (discordApplicationId) {
|
|
3759
|
+
const keepExistingDiscordAppId = await promptYesNo(`Use saved Discord application id (${discordApplicationId})?`, {
|
|
3760
|
+
input: process.stdin,
|
|
3761
|
+
output: process.stdout
|
|
3762
|
+
});
|
|
3763
|
+
if (!keepExistingDiscordAppId) {
|
|
3764
|
+
discordApplicationId = "";
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
while (!discordApplicationId && discordToken) {
|
|
3769
|
+
const enteredDiscordApplicationId = (await promptLine("Paste Discord application id (Enter to skip for now): ")).trim();
|
|
3770
|
+
if (!enteredDiscordApplicationId) break;
|
|
3771
|
+
discordApplicationId = enteredDiscordApplicationId;
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
if (discordToken && discordApplicationId) {
|
|
3775
|
+
const channels = next.channels && typeof next.channels === "object" ? { ...next.channels } : {};
|
|
3776
|
+
const discord = channels.discord && typeof channels.discord === "object" ? { ...channels.discord } : {};
|
|
3777
|
+
channels.discord = {
|
|
3778
|
+
...discord,
|
|
3779
|
+
enabled: true,
|
|
3780
|
+
botToken: discordToken,
|
|
3781
|
+
applicationId: discordApplicationId,
|
|
3782
|
+
pairingMode: discord.pairingMode || "manual",
|
|
3783
|
+
allowedUserIds: Array.isArray(discord.allowedUserIds) ? discord.allowedUserIds : []
|
|
3784
|
+
};
|
|
3785
|
+
next.channels = channels;
|
|
3786
|
+
|
|
3787
|
+
console.log(green("Discord configured for DM-first control."));
|
|
3788
|
+
console.log(dim("Run `waterbrother discord run` in a second terminal when you want the Discord gateway live."));
|
|
3789
|
+
} else {
|
|
3790
|
+
console.log(dim("Skipping Discord setup for now. You can always run `waterbrother onboarding discord` later."));
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3793
|
+
|
|
3697
3794
|
if (!next.approvalMode) next.approvalMode = 'on-request';
|
|
3698
3795
|
if (!next.designModel) next.designModel = getDefaultDesignModelForProvider(next.provider);
|
|
3699
3796
|
if (next.traceMode === undefined) next.traceMode = 'on';
|
|
@@ -7834,18 +7931,22 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
|
|
|
7834
7931
|
if (gatewayCleanupDone) return;
|
|
7835
7932
|
gatewayCleanupDone = true;
|
|
7836
7933
|
await clearTelegramBridgeHost();
|
|
7837
|
-
|
|
7838
|
-
|
|
7934
|
+
for (const handle of Array.isArray(gatewayHandle?.handles) ? gatewayHandle.handles : []) {
|
|
7935
|
+
if (handle?.started && handle?.child) {
|
|
7936
|
+
await stopManagedGateway(handle.service, handle, { io: console });
|
|
7937
|
+
}
|
|
7839
7938
|
}
|
|
7840
7939
|
};
|
|
7841
7940
|
const cleanupManagedGatewaySync = () => {
|
|
7842
7941
|
if (gatewayCleanupDone) return;
|
|
7843
7942
|
gatewayCleanupDone = true;
|
|
7844
|
-
const
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7943
|
+
for (const handle of Array.isArray(gatewayHandle?.handles) ? gatewayHandle.handles : []) {
|
|
7944
|
+
const pid = Number(handle?.pid || 0);
|
|
7945
|
+
if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid)) {
|
|
7946
|
+
try {
|
|
7947
|
+
process.kill(pid, "SIGTERM");
|
|
7948
|
+
} catch {}
|
|
7949
|
+
}
|
|
7849
7950
|
}
|
|
7850
7951
|
};
|
|
7851
7952
|
const handleExitSignal = () => {
|
package/src/discord.js
CHANGED
|
@@ -381,8 +381,8 @@ export async function runDiscordGateway(runtime = {}, { log = console.log, signa
|
|
|
381
381
|
log(`discord: replied in ${msg.channel_id}`);
|
|
382
382
|
return;
|
|
383
383
|
}
|
|
384
|
-
if (
|
|
385
|
-
const bridged = await runPromptViaBridge(runtime, msg, String(msg.content || "").trim());
|
|
384
|
+
if (shouldReplyToMessage(msg, botUser?.id)) {
|
|
385
|
+
const bridged = await runPromptViaBridge(runtime, msg, extractMentionContent(String(msg.content || "").trim(), botUser?.id));
|
|
386
386
|
if (bridged?.content) {
|
|
387
387
|
await sendChannelMessage(discord, msg.channel_id, bridged.content);
|
|
388
388
|
log(`discord: bridged reply in ${msg.channel_id}`);
|