Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
797cc145c3 | ||
|
|
6d9cdf9128 | ||
|
|
a7534dc223 | ||
|
|
109ac1c549 | ||
|
|
01e0d3a320 |
No files matched your search
+1
-1
@@ -72,12 +72,12 @@ Status: beta.
|
|||||||
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- CLI: initialize plugins before registering the pairing CLI to fix `pairing approve/list` commands. (#3272) Thanks @araa47.
|
|
||||||
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
||||||
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
||||||
- Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma.
|
- Agents: prevent retries on oversized image errors and surface size limits. (#2871) Thanks @Suksham-sharma.
|
||||||
- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94.
|
- Agents: inherit provider baseUrl/api for inline models. (#2740) Thanks @lploc94.
|
||||||
- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.
|
- Memory Search: keep auto provider model defaults and only include remote when configured. (#2576) Thanks @papago2355.
|
||||||
|
- TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys.
|
||||||
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
||||||
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
|
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
|
||||||
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
|
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ const LOBSTER_ASCII = [
|
|||||||
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
||||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
||||||
" 🦞 FRESH DAILY 🦞 ",
|
" 🦞 FRESH DAILY 🦞 ",
|
||||||
|
" ",
|
||||||
];
|
];
|
||||||
|
|
||||||
export function formatCliBannerArt(options: BannerOptions = {}): string {
|
export function formatCliBannerArt(options: BannerOptions = {}): string {
|
||||||
|
|||||||
@@ -69,7 +69,8 @@ export function printWizardHeader(runtime: RuntimeEnv) {
|
|||||||
"██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████",
|
"██░█░█░██░███░██░██████░████░▄▄▀██░███░███░████",
|
||||||
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
"██░███░██░▀▀▀░██░▀▀░███░████░▀▀░██░▀▀▀░███░████",
|
||||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
||||||
" 🦞 FRESH DAILY 🦞 ",
|
" 🦞 FRESH DAILY 🦞 ",
|
||||||
|
" ",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
runtime.log(header);
|
runtime.log(header);
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-7
@@ -757,11 +757,19 @@ export const OPENAI_TTS_MODELS = ["gpt-4o-mini-tts", "tts-1", "tts-1-hd"] as con
|
|||||||
* Custom OpenAI-compatible TTS endpoint.
|
* Custom OpenAI-compatible TTS endpoint.
|
||||||
* When set, model/voice validation is relaxed to allow non-OpenAI models.
|
* When set, model/voice validation is relaxed to allow non-OpenAI models.
|
||||||
* Example: OPENAI_TTS_BASE_URL=http://localhost:8880/v1
|
* Example: OPENAI_TTS_BASE_URL=http://localhost:8880/v1
|
||||||
|
*
|
||||||
|
* Note: Read at runtime (not module load) to support config.env loading.
|
||||||
*/
|
*/
|
||||||
const OPENAI_TTS_BASE_URL = (
|
function getOpenAITtsBaseUrl(): string {
|
||||||
process.env.OPENAI_TTS_BASE_URL?.trim() || "https://api.openai.com/v1"
|
return (process.env.OPENAI_TTS_BASE_URL?.trim() || "https://api.openai.com/v1").replace(
|
||||||
).replace(/\/+$/, "");
|
/\/+$/,
|
||||||
const isCustomOpenAIEndpoint = OPENAI_TTS_BASE_URL !== "https://api.openai.com/v1";
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCustomOpenAIEndpoint(): boolean {
|
||||||
|
return getOpenAITtsBaseUrl() !== "https://api.openai.com/v1";
|
||||||
|
}
|
||||||
export const OPENAI_TTS_VOICES = [
|
export const OPENAI_TTS_VOICES = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"ash",
|
"ash",
|
||||||
@@ -778,13 +786,13 @@ type OpenAiTtsVoice = (typeof OPENAI_TTS_VOICES)[number];
|
|||||||
|
|
||||||
function isValidOpenAIModel(model: string): boolean {
|
function isValidOpenAIModel(model: string): boolean {
|
||||||
// Allow any model when using custom endpoint (e.g., Kokoro, LocalAI)
|
// Allow any model when using custom endpoint (e.g., Kokoro, LocalAI)
|
||||||
if (isCustomOpenAIEndpoint) return true;
|
if (isCustomOpenAIEndpoint()) return true;
|
||||||
return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]);
|
return OPENAI_TTS_MODELS.includes(model as (typeof OPENAI_TTS_MODELS)[number]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidOpenAIVoice(voice: string): voice is OpenAiTtsVoice {
|
function isValidOpenAIVoice(voice: string): voice is OpenAiTtsVoice {
|
||||||
// Allow any voice when using custom endpoint (e.g., Kokoro Chinese voices)
|
// Allow any voice when using custom endpoint (e.g., Kokoro Chinese voices)
|
||||||
if (isCustomOpenAIEndpoint) return true;
|
if (isCustomOpenAIEndpoint()) return true;
|
||||||
return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice);
|
return OPENAI_TTS_VOICES.includes(voice as OpenAiTtsVoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1011,7 +1019,7 @@ async function openaiTTS(params: {
|
|||||||
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${OPENAI_TTS_BASE_URL}/audio/speech`, {
|
const response = await fetch(`${getOpenAITtsBaseUrl()}/audio/speech`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { renderNodes } from "./views/nodes";
|
|||||||
import { renderOverview } from "./views/overview";
|
import { renderOverview } from "./views/overview";
|
||||||
import { renderSessions } from "./views/sessions";
|
import { renderSessions } from "./views/sessions";
|
||||||
import { renderExecApprovalPrompt } from "./views/exec-approval";
|
import { renderExecApprovalPrompt } from "./views/exec-approval";
|
||||||
|
import { renderGatewayUrlConfirmation } from "./views/gateway-url-confirmation";
|
||||||
import {
|
import {
|
||||||
approveDevicePairing,
|
approveDevicePairing,
|
||||||
loadDevices,
|
loadDevices,
|
||||||
@@ -578,6 +579,7 @@ export function renderApp(state: AppViewState) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</main>
|
</main>
|
||||||
${renderExecApprovalPrompt(state)}
|
${renderExecApprovalPrompt(state)}
|
||||||
|
${renderGatewayUrlConfirmation(state)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,7 @@ type SettingsHost = {
|
|||||||
basePath: string;
|
basePath: string;
|
||||||
themeMedia: MediaQueryList | null;
|
themeMedia: MediaQueryList | null;
|
||||||
themeMediaHandler: ((event: MediaQueryListEvent) => void) | null;
|
themeMediaHandler: ((event: MediaQueryListEvent) => void) | null;
|
||||||
|
pendingGatewayUrl?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function applySettings(host: SettingsHost, next: UiSettings) {
|
export function applySettings(host: SettingsHost, next: UiSettings) {
|
||||||
@@ -98,7 +99,7 @@ export function applySettingsFromUrl(host: SettingsHost) {
|
|||||||
if (gatewayUrlRaw != null) {
|
if (gatewayUrlRaw != null) {
|
||||||
const gatewayUrl = gatewayUrlRaw.trim();
|
const gatewayUrl = gatewayUrlRaw.trim();
|
||||||
if (gatewayUrl && gatewayUrl !== host.settings.gatewayUrl) {
|
if (gatewayUrl && gatewayUrl !== host.settings.gatewayUrl) {
|
||||||
applySettings(host, { ...host.settings, gatewayUrl });
|
host.pendingGatewayUrl = gatewayUrl;
|
||||||
}
|
}
|
||||||
params.delete("gatewayUrl");
|
params.delete("gatewayUrl");
|
||||||
shouldCleanUrl = true;
|
shouldCleanUrl = true;
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export type AppViewState = {
|
|||||||
execApprovalQueue: ExecApprovalRequest[];
|
execApprovalQueue: ExecApprovalRequest[];
|
||||||
execApprovalBusy: boolean;
|
execApprovalBusy: boolean;
|
||||||
execApprovalError: string | null;
|
execApprovalError: string | null;
|
||||||
|
pendingGatewayUrl: string | null;
|
||||||
configLoading: boolean;
|
configLoading: boolean;
|
||||||
configRaw: string;
|
configRaw: string;
|
||||||
configRawOriginal: string;
|
configRawOriginal: string;
|
||||||
@@ -165,6 +166,8 @@ export type AppViewState = {
|
|||||||
handleNostrProfileImport: () => Promise<void>;
|
handleNostrProfileImport: () => Promise<void>;
|
||||||
handleNostrProfileToggleAdvanced: () => void;
|
handleNostrProfileToggleAdvanced: () => void;
|
||||||
handleExecApprovalDecision: (decision: "allow-once" | "allow-always" | "deny") => Promise<void>;
|
handleExecApprovalDecision: (decision: "allow-once" | "allow-always" | "deny") => Promise<void>;
|
||||||
|
handleGatewayUrlConfirm: () => void;
|
||||||
|
handleGatewayUrlCancel: () => void;
|
||||||
handleConfigLoad: () => Promise<void>;
|
handleConfigLoad: () => Promise<void>;
|
||||||
handleConfigSave: () => Promise<void>;
|
handleConfigSave: () => Promise<void>;
|
||||||
handleConfigApply: () => Promise<void>;
|
handleConfigApply: () => Promise<void>;
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ export class MoltbotApp extends LitElement {
|
|||||||
@state() execApprovalQueue: ExecApprovalRequest[] = [];
|
@state() execApprovalQueue: ExecApprovalRequest[] = [];
|
||||||
@state() execApprovalBusy = false;
|
@state() execApprovalBusy = false;
|
||||||
@state() execApprovalError: string | null = null;
|
@state() execApprovalError: string | null = null;
|
||||||
|
@state() pendingGatewayUrl: string | null = null;
|
||||||
|
|
||||||
@state() configLoading = false;
|
@state() configLoading = false;
|
||||||
@state() configRaw = "{\n}\n";
|
@state() configRaw = "{\n}\n";
|
||||||
@@ -448,6 +449,21 @@ export class MoltbotApp extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleGatewayUrlConfirm() {
|
||||||
|
const nextGatewayUrl = this.pendingGatewayUrl;
|
||||||
|
if (!nextGatewayUrl) return;
|
||||||
|
this.pendingGatewayUrl = null;
|
||||||
|
applySettingsInternal(
|
||||||
|
this as unknown as Parameters<typeof applySettingsInternal>[0],
|
||||||
|
{ ...this.settings, gatewayUrl: nextGatewayUrl },
|
||||||
|
);
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
handleGatewayUrlCancel() {
|
||||||
|
this.pendingGatewayUrl = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Sidebar handlers for tool output viewing
|
// Sidebar handlers for tool output viewing
|
||||||
handleOpenSidebar(content: string) {
|
handleOpenSidebar(content: string) {
|
||||||
if (this.sidebarCloseTimer != null) {
|
if (this.sidebarCloseTimer != null) {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { html, nothing } from "lit";
|
||||||
|
|
||||||
|
import type { AppViewState } from "../app-view-state";
|
||||||
|
|
||||||
|
export function renderGatewayUrlConfirmation(state: AppViewState) {
|
||||||
|
const { pendingGatewayUrl } = state;
|
||||||
|
if (!pendingGatewayUrl) return nothing;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div class="exec-approval-overlay" role="dialog" aria-modal="true" aria-live="polite">
|
||||||
|
<div class="exec-approval-card">
|
||||||
|
<div class="exec-approval-header">
|
||||||
|
<div>
|
||||||
|
<div class="exec-approval-title">Change Gateway URL</div>
|
||||||
|
<div class="exec-approval-sub">This will reconnect to a different gateway server</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="exec-approval-command mono">${pendingGatewayUrl}</div>
|
||||||
|
<div class="callout danger" style="margin-top: 12px;">
|
||||||
|
Only confirm if you trust this URL. Malicious URLs can compromise your system.
|
||||||
|
</div>
|
||||||
|
<div class="exec-approval-actions">
|
||||||
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
@click=${() => state.handleGatewayUrlConfirm()}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
@click=${() => state.handleGatewayUrlCancel()}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user