Files
openclaw/src/gateway/server-node-session-runtime.ts
Peter Steinberger ec8f6e5e03 feat(browser): add secure per-tab copilot panel (#109817)
* feat(browser): add copilot security contracts

* fix(gateway): expose verified client identity to handlers

* feat(browser): add secure per-tab copilot panel

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* refactor(browser): separate copilot gateway hint custody

* fix(browser): preserve legacy pairing parse shape

* fix(browser): harden copilot lifecycle custody

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): enforce copilot lifecycle boundaries

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* style(browser): format copilot sources

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): preserve copilot consent revocation

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* refactor(browser): split copilot custody owners

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(browser): normalize websocket array buffers

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* chore(protocol): regenerate Swift gateway models

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* refactor(browser): model copilot runtime entrypoints

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): honor extension build boundaries

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(gateway): assert targeted chat delivery

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(gateway): cover targeted delivery calls

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): declare copilot build dependencies

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(ci): clear browser copilot gate failures

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(ci): cover copilot lint exclusion

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* fix(browser): gate copilot on relay custody

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

* test(browser): bound copilot relay frames

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>

---------

Co-authored-by: Cameron Beeley <cameron.beeley@gmail.com>
2026-07-18 01:00:23 +01:00

67 lines
2.9 KiB
TypeScript

// Gateway node session runtime factory.
// Creates node registry, subscription, and voice-wake fanout state.
import {
NodeRegistry,
type NodeRegistryOptions,
type SerializedEventPayload,
} from "./node-registry.js";
import type {
SessionEventSubscriberRegistry,
SessionMessageSubscriberRegistry,
} from "./server-chat-state.js";
import { createNodeSubscriptionManager } from "./server-node-subscriptions.js";
import { hasConnectedTalkNode } from "./server-talk-nodes.js";
// Node session runtime owns connected node registry state, session event
// subscriptions, and voice-wake fanout helpers for the gateway process.
/** Creates node registry/subscription runtime state for a gateway server. */
export function createGatewayNodeSessionRuntime(params: {
broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void;
listRegisteredNodePluginToolCommands?: NodeRegistryOptions["listRegisteredNodePluginToolCommands"];
nodePluginToolsEnabled?: boolean;
nodeSkillsEnabled?: boolean;
sessionEventSubscribers: SessionEventSubscriberRegistry;
sessionMessageSubscribers: SessionMessageSubscriberRegistry;
}) {
const nodeRegistry = new NodeRegistry({
listRegisteredNodePluginToolCommands: params.listRegisteredNodePluginToolCommands,
nodePluginToolsEnabled: params.nodePluginToolsEnabled,
nodeSkillsEnabled: params.nodeSkillsEnabled,
});
const nodePresenceTimers = new Map<string, ReturnType<typeof setInterval>>();
const nodeSubscriptions = createNodeSubscriptionManager();
const sessionEventSubscribers = params.sessionEventSubscribers;
const sessionMessageSubscribers = params.sessionMessageSubscribers;
const nodeSendEvent = (opts: {
nodeId: string;
event: string;
payloadJSON?: SerializedEventPayload | null;
}) => {
nodeRegistry.sendEventRaw(opts.nodeId, opts.event, opts.payloadJSON ?? null);
};
// Session fanout goes through the subscription manager so node reconnects and
// explicit unsubscribes keep both node->session indexes in sync.
const nodeSendToSession = (sessionKey: string, event: string, payload: unknown) =>
nodeSubscriptions.sendToSession(sessionKey, event, payload, nodeSendEvent);
const nodeSendToAllSubscribed = (event: string, payload: unknown) =>
nodeSubscriptions.sendToAllSubscribed(event, payload, nodeSendEvent);
const broadcastVoiceWakeChanged = (triggers: string[]) => {
params.broadcast("voicewake.changed", { triggers }, { dropIfSlow: true });
};
const hasTalkNodeConnected = () => hasConnectedTalkNode(nodeRegistry);
return {
nodeRegistry,
nodePresenceTimers,
sessionEventSubscribers,
sessionMessageSubscribers,
nodeSendToSession,
nodeSendToAllSubscribed,
nodeSubscribe: nodeSubscriptions.subscribe,
nodeUnsubscribe: nodeSubscriptions.unsubscribe,
nodeUnsubscribeAll: nodeSubscriptions.unsubscribeAll,
broadcastVoiceWakeChanged,
hasTalkNodeConnected,
};
}