feat: connect chat and runs, cancellable chat messages, unified frontend runs module

This commit is contained in:
Shivering Ocelot committed 2026-05-31 12:48:06 -05:00
1 parent d7e92f38f3
commit bc71fa4d59
7 files changed
+244 -119

No files matched your search

+32 -22
View File
@@ -29,8 +29,9 @@ let credentialsDeferred = createDeferred();
// Handlers
const pendingActions = {}; // Maps nonce to { resolve, reject }
const chatListeners = {}; // Maps chatId to an array of callback functions
const actionListeners = {}; // Listeners for other arbitrary actions
const orphanHandlers = []; // Queue of callbacks for orphan server messages
const runListeners = []; // Queue of callbacks for run events
const connectListeners = []; // Listeners for horde connection
function createDeferred() {
let resolve, reject;
@@ -164,8 +165,6 @@ function handleIncomingMessage(rawData) {
return;
}
console.log("Incoming Packet:", packet);
if (packet.nonce != null) {
const nonce = packet.nonce;
const actionPromise = pendingActions[nonce];
@@ -179,6 +178,7 @@ function handleIncomingMessage(rawData) {
console.warn(`Orphan response for action nonce ${nonce}. No pending action found.`);
}
} else if (packet.action === 'chat-message') {
// This can be in a 'chat-message' listener in the future
const chatId = packet.chatId;
const listeners = chatListeners[chatId];
if (listeners) {
@@ -192,22 +192,20 @@ function handleIncomingMessage(rawData) {
} else {
console.log(`No active listeners for chatId ${chatId}`);
}
} else if (packet.action == 'run-event') {
// Broadcast run events to all listeners
runListeners.forEach(callback => {
try {
callback(packet);
} catch (e) {
console.error(`Error in run event listener:`, e);
} else if (packet.action) {
let listeners = actionListeners[packet.action];
if (listeners && listeners.length) {
for (let f of listeners) {
f(packet);
}
});
} else {
if (orphanHandlers.length) {
const f = orphanHandlers.shift();
f(packet);
} else if (packet.error) {
console.error(`Backend Error: ${packet.error}`);
}
} else if (orphanHandlers.length) {
const f = orphanHandlers.shift();
f(packet);
} else if (packet.error) {
console.error(`Backend Error: ${packet.error}`);
} else {
console.warn('ignored server message', packet);
}
}
@@ -257,18 +255,23 @@ export function onChatMessages(chatId, callback) {
};
}
export function onRunEvents(callback) {
export function onAction(action, callback) {
if (typeof callback !== 'function') {
throw new TypeError("onRunEvents: callback must be a function.");
throw new TypeError("onAction: callback must be a function.");
}
runListeners.push(callback);
let listeners = actionListeners[action];
if (!listeners) {
listeners = [];
actionListeners[action] = listeners;
}
listeners.push(callback);
// Return an unsubscribe function
return () => {
const index = runListeners.indexOf(callback);
const index = listeners.indexOf(callback);
if (index > -1) {
runListeners.splice(index, 1);
listeners.splice(index, 1);
}
};
}
@@ -326,6 +329,11 @@ export function invalidateCache({ chats=false, agents=false } = {}) {
// --- Connection ---
export function onConnect (callback) {
// TODO: return cancel function
connectListeners.push(callback);
}
function reconnect() {
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
@@ -361,6 +369,8 @@ function connect() {
// Successful connection, store token long term
authDeferred.resolve(true);
localStorage.setItem('loginToken', loginToken);
for (let cb of connectListeners) cb();
} else {
authDeferred.resolve(false);
console.error("Socket authentication failed:", response ? response.error : "Unknown error");