@framers/agentos 0.1.26 → 0.1.28

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.
Files changed (79) hide show
  1. package/dist/api/AgentOSOrchestrator.js +1 -1
  2. package/dist/api/AgentOSOrchestrator.js.map +1 -1
  3. package/dist/channels/ChannelRouter.d.ts.map +1 -1
  4. package/dist/channels/ChannelRouter.js +1 -1
  5. package/dist/channels/ChannelRouter.js.map +1 -1
  6. package/dist/channels/IChannelAdapter.d.ts.map +1 -1
  7. package/dist/channels/adapters/BaseChannelAdapter.d.ts +130 -0
  8. package/dist/channels/adapters/BaseChannelAdapter.d.ts.map +1 -0
  9. package/dist/channels/adapters/BaseChannelAdapter.js +257 -0
  10. package/dist/channels/adapters/BaseChannelAdapter.js.map +1 -0
  11. package/dist/channels/adapters/DiscordChannelAdapter.d.ts +86 -0
  12. package/dist/channels/adapters/DiscordChannelAdapter.d.ts.map +1 -0
  13. package/dist/channels/adapters/DiscordChannelAdapter.js +550 -0
  14. package/dist/channels/adapters/DiscordChannelAdapter.js.map +1 -0
  15. package/dist/channels/adapters/GoogleChatChannelAdapter.d.ts +117 -0
  16. package/dist/channels/adapters/GoogleChatChannelAdapter.d.ts.map +1 -0
  17. package/dist/channels/adapters/GoogleChatChannelAdapter.js +632 -0
  18. package/dist/channels/adapters/GoogleChatChannelAdapter.js.map +1 -0
  19. package/dist/channels/adapters/IRCChannelAdapter.d.ts +86 -0
  20. package/dist/channels/adapters/IRCChannelAdapter.d.ts.map +1 -0
  21. package/dist/channels/adapters/IRCChannelAdapter.js +253 -0
  22. package/dist/channels/adapters/IRCChannelAdapter.js.map +1 -0
  23. package/dist/channels/adapters/RedditChannelAdapter.d.ts +123 -0
  24. package/dist/channels/adapters/RedditChannelAdapter.d.ts.map +1 -0
  25. package/dist/channels/adapters/RedditChannelAdapter.js +516 -0
  26. package/dist/channels/adapters/RedditChannelAdapter.js.map +1 -0
  27. package/dist/channels/adapters/SignalChannelAdapter.d.ts +120 -0
  28. package/dist/channels/adapters/SignalChannelAdapter.d.ts.map +1 -0
  29. package/dist/channels/adapters/SignalChannelAdapter.js +521 -0
  30. package/dist/channels/adapters/SignalChannelAdapter.js.map +1 -0
  31. package/dist/channels/adapters/SlackChannelAdapter.d.ts +96 -0
  32. package/dist/channels/adapters/SlackChannelAdapter.d.ts.map +1 -0
  33. package/dist/channels/adapters/SlackChannelAdapter.js +535 -0
  34. package/dist/channels/adapters/SlackChannelAdapter.js.map +1 -0
  35. package/dist/channels/adapters/TeamsChannelAdapter.d.ts +138 -0
  36. package/dist/channels/adapters/TeamsChannelAdapter.d.ts.map +1 -0
  37. package/dist/channels/adapters/TeamsChannelAdapter.js +639 -0
  38. package/dist/channels/adapters/TeamsChannelAdapter.js.map +1 -0
  39. package/dist/channels/adapters/TelegramChannelAdapter.d.ts +83 -0
  40. package/dist/channels/adapters/TelegramChannelAdapter.d.ts.map +1 -0
  41. package/dist/channels/adapters/TelegramChannelAdapter.js +463 -0
  42. package/dist/channels/adapters/TelegramChannelAdapter.js.map +1 -0
  43. package/dist/channels/adapters/TwitterChannelAdapter.d.ts +117 -0
  44. package/dist/channels/adapters/TwitterChannelAdapter.d.ts.map +1 -0
  45. package/dist/channels/adapters/TwitterChannelAdapter.js +489 -0
  46. package/dist/channels/adapters/TwitterChannelAdapter.js.map +1 -0
  47. package/dist/channels/adapters/WebChatChannelAdapter.d.ts +141 -0
  48. package/dist/channels/adapters/WebChatChannelAdapter.d.ts.map +1 -0
  49. package/dist/channels/adapters/WebChatChannelAdapter.js +539 -0
  50. package/dist/channels/adapters/WebChatChannelAdapter.js.map +1 -0
  51. package/dist/channels/adapters/WhatsAppChannelAdapter.d.ts +122 -0
  52. package/dist/channels/adapters/WhatsAppChannelAdapter.d.ts.map +1 -0
  53. package/dist/channels/adapters/WhatsAppChannelAdapter.js +643 -0
  54. package/dist/channels/adapters/WhatsAppChannelAdapter.js.map +1 -0
  55. package/dist/channels/adapters/index.d.ts +34 -0
  56. package/dist/channels/adapters/index.d.ts.map +1 -0
  57. package/dist/channels/adapters/index.js +25 -0
  58. package/dist/channels/adapters/index.js.map +1 -0
  59. package/dist/channels/index.d.ts +24 -0
  60. package/dist/channels/index.d.ts.map +1 -1
  61. package/dist/channels/index.js +16 -0
  62. package/dist/channels/index.js.map +1 -1
  63. package/dist/cognitive_substrate/personas/metaprompt_presets.d.ts.map +1 -1
  64. package/dist/cognitive_substrate/personas/metaprompt_presets.js +0 -2
  65. package/dist/cognitive_substrate/personas/metaprompt_presets.js.map +1 -1
  66. package/dist/core/provenance/crypto/HashChain.d.ts.map +1 -1
  67. package/dist/core/provenance/enforcement/AutonomyGuard.d.ts.map +1 -1
  68. package/dist/core/provenance/enforcement/ProvenanceStorageHooks.d.ts.map +1 -1
  69. package/dist/core/provenance/enforcement/ProvenanceStorageHooks.js +2 -1
  70. package/dist/core/provenance/enforcement/ProvenanceStorageHooks.js.map +1 -1
  71. package/dist/core/provenance/ledger/EventTypes.d.ts.map +1 -1
  72. package/dist/core/safety/ToolExecutionGuard.d.ts.map +1 -1
  73. package/dist/core/safety/ToolExecutionGuard.js +0 -1
  74. package/dist/core/safety/ToolExecutionGuard.js.map +1 -1
  75. package/dist/extensions/ExtensionManager.d.ts.map +1 -1
  76. package/dist/extensions/ExtensionManager.js +3 -9
  77. package/dist/extensions/ExtensionManager.js.map +1 -1
  78. package/dist/rag/RetrievalAugmentor.js.map +1 -1
  79. package/package.json +1 -1
@@ -0,0 +1,141 @@
1
+ /**
2
+ * @fileoverview WebChat Channel Adapter for AgentOS.
3
+ *
4
+ * Provides a built-in HTTP/WebSocket server for embedding a chat widget
5
+ * directly in web applications. No external SDK is required — this adapter
6
+ * uses Node.js built-in `http` and the `ws` package for WebSocket support.
7
+ *
8
+ * **Modes of operation**:
9
+ * - **Standalone**: Creates its own HTTP server on a configurable port.
10
+ * - **Attached**: Attaches to an existing HTTP server (e.g., Express, Fastify).
11
+ *
12
+ * **Dependencies**: Requires the `ws` npm package for WebSocket support.
13
+ * Uses a dynamic import so the package is only loaded at connection time.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Standalone mode
18
+ * const webchat = new WebChatChannelAdapter();
19
+ * await webchat.initialize({
20
+ * platform: 'webchat',
21
+ * credential: 'optional-api-key',
22
+ * params: {
23
+ * port: '8080',
24
+ * corsOrigins: '*',
25
+ * },
26
+ * });
27
+ *
28
+ * // Attached mode (with existing Express server)
29
+ * const webchat = new WebChatChannelAdapter();
30
+ * webchat.attachToServer(existingHttpServer);
31
+ * await webchat.initialize({
32
+ * platform: 'webchat',
33
+ * credential: 'optional-api-key',
34
+ * params: { corsOrigins: 'https://myapp.com' },
35
+ * });
36
+ * ```
37
+ *
38
+ * @module @framers/agentos/channels/adapters/WebChatChannelAdapter
39
+ */
40
+ import type { ChannelAuthConfig, ChannelCapability, ChannelPlatform, ChannelSendResult, MessageContent } from '../types.js';
41
+ import { BaseChannelAdapter } from './BaseChannelAdapter.js';
42
+ /** Platform-specific parameters for WebChat connections. */
43
+ export interface WebChatAuthParams extends Record<string, string | undefined> {
44
+ /** API key for authenticating WebSocket clients. Optional. */
45
+ apiKey?: string;
46
+ /** Comma-separated CORS origins (default: '*'). */
47
+ corsOrigins?: string;
48
+ /** Port for standalone HTTP server (default: '8080'). Ignored in attached mode. */
49
+ port?: string;
50
+ /** Path prefix for the WebSocket endpoint (default: '/ws'). */
51
+ wsPath?: string;
52
+ }
53
+ /**
54
+ * Channel adapter for web-based chat using HTTP/WebSocket.
55
+ *
56
+ * Uses dynamic import for the `ws` package so it is only required
57
+ * at runtime when the adapter is actually initialized.
58
+ *
59
+ * Capabilities: text, rich_text, images, buttons, typing_indicator,
60
+ * read_receipts.
61
+ */
62
+ export declare class WebChatChannelAdapter extends BaseChannelAdapter<WebChatAuthParams> {
63
+ readonly platform: ChannelPlatform;
64
+ readonly displayName = "WebChat";
65
+ readonly capabilities: readonly ChannelCapability[];
66
+ /** Node.js HTTP server (standalone mode). */
67
+ private httpServer;
68
+ /** WebSocket server (ws package). */
69
+ private wss;
70
+ /** Connected clients, keyed by client ID. */
71
+ private clients;
72
+ /** External HTTP server (attached mode). */
73
+ private externalServer;
74
+ /** Whether we created the HTTP server ourselves (vs attached). */
75
+ private ownServer;
76
+ /** API key for client authentication. Empty string means no auth required. */
77
+ private apiKey;
78
+ /** Allowed CORS origins. */
79
+ private corsOrigins;
80
+ /** Counter for generating client IDs. */
81
+ private clientIdCounter;
82
+ /**
83
+ * Attach to an existing HTTP server instead of creating a standalone one.
84
+ * Must be called before {@link initialize}.
85
+ *
86
+ * @param server - Node.js http.Server instance (e.g., from Express).
87
+ */
88
+ attachToServer(server: any): void;
89
+ protected doConnect(auth: ChannelAuthConfig & {
90
+ params?: WebChatAuthParams;
91
+ }): Promise<void>;
92
+ protected doSendMessage(conversationId: string, content: MessageContent): Promise<ChannelSendResult>;
93
+ protected doShutdown(): Promise<void>;
94
+ sendTypingIndicator(conversationId: string, isTyping: boolean): Promise<void>;
95
+ getConversationInfo(conversationId: string): Promise<{
96
+ name?: string;
97
+ memberCount?: number;
98
+ isGroup: boolean;
99
+ metadata?: Record<string, unknown>;
100
+ }>;
101
+ /** Get the number of currently connected clients. */
102
+ getConnectedClientCount(): number;
103
+ /** Get all connected client IDs. */
104
+ getConnectedClientIds(): string[];
105
+ /**
106
+ * Broadcast a message to ALL connected and authenticated clients.
107
+ */
108
+ broadcast(content: MessageContent): Promise<void>;
109
+ /**
110
+ * Handle plain HTTP requests (standalone mode only).
111
+ * Provides a simple health-check endpoint and CORS headers.
112
+ */
113
+ private handleHttpRequest;
114
+ /**
115
+ * Handle a new WebSocket connection.
116
+ */
117
+ private handleNewConnection;
118
+ /**
119
+ * Handle a message from a connected WebSocket client.
120
+ */
121
+ private handleClientMessage;
122
+ /**
123
+ * Handle client authentication.
124
+ */
125
+ private handleAuth;
126
+ /**
127
+ * Handle an inbound chat message from a WebSocket client.
128
+ */
129
+ private handleInboundChatMessage;
130
+ /**
131
+ * Handle a client disconnection.
132
+ */
133
+ private handleClientDisconnect;
134
+ /**
135
+ * Safely send data over a WebSocket, catching errors.
136
+ */
137
+ private safeSend;
138
+ private generateClientId;
139
+ private generateMessageId;
140
+ }
141
+ //# sourceMappingURL=WebChatChannelAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebChatChannelAdapter.d.ts","sourceRoot":"","sources":["../../../src/channels/adapters/WebChatChannelAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAEjB,eAAe,EACf,iBAAiB,EACjB,cAAc,EAGf,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAM7D,4DAA4D;AAC5D,MAAM,WAAW,iBAAkB,SAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3E,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAuED;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,SAAQ,kBAAkB,CAAC,iBAAiB,CAAC;IAC9E,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAa;IAC/C,QAAQ,CAAC,WAAW,aAAa;IACjC,QAAQ,CAAC,YAAY,EAAE,SAAS,iBAAiB,EAAE,CAOxC;IAEX,6CAA6C;IAE7C,OAAO,CAAC,UAAU,CAAkB;IAEpC,qCAAqC;IAErC,OAAO,CAAC,GAAG,CAAkB;IAE7B,6CAA6C;IAC7C,OAAO,CAAC,OAAO,CAAyC;IAExD,4CAA4C;IAE5C,OAAO,CAAC,cAAc,CAAkB;IAExC,kEAAkE;IAClE,OAAO,CAAC,SAAS,CAAS;IAE1B,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAM;IAEpB,4BAA4B;IAC5B,OAAO,CAAC,WAAW,CAAO;IAE1B,yCAAyC;IACzC,OAAO,CAAC,eAAe,CAAK;IAI5B;;;;;OAKG;IAEH,cAAc,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI;cAMjB,SAAS,CACvB,IAAI,EAAE,iBAAiB,GAAG;QAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;KAAE,GACvD,OAAO,CAAC,IAAI,CAAC;cA8EA,aAAa,CAC3B,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,iBAAiB,CAAC;cAsCb,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCrC,mBAAmB,CACvB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,IAAI,CAAC;IAeV,mBAAmB,CACvB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;IAkBzG,qDAAqD;IACrD,uBAAuB,IAAI,MAAM;IAIjC,oCAAoC;IACpC,qBAAqB,IAAI,MAAM,EAAE;IAIjC;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBvD;;;OAGG;IAEH,OAAO,CAAC,iBAAiB;IAiCzB;;OAEG;IAEH,OAAO,CAAC,mBAAmB;IAuD3B;;OAEG;IAEH,OAAO,CAAC,mBAAmB;IAuE3B;;OAEG;IACH,OAAO,CAAC,UAAU;IA6ClB;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA+ChC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAsB9B;;OAEG;IAEH,OAAO,CAAC,QAAQ;IAWhB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,iBAAiB;CAG1B"}
@@ -0,0 +1,539 @@
1
+ /**
2
+ * @fileoverview WebChat Channel Adapter for AgentOS.
3
+ *
4
+ * Provides a built-in HTTP/WebSocket server for embedding a chat widget
5
+ * directly in web applications. No external SDK is required — this adapter
6
+ * uses Node.js built-in `http` and the `ws` package for WebSocket support.
7
+ *
8
+ * **Modes of operation**:
9
+ * - **Standalone**: Creates its own HTTP server on a configurable port.
10
+ * - **Attached**: Attaches to an existing HTTP server (e.g., Express, Fastify).
11
+ *
12
+ * **Dependencies**: Requires the `ws` npm package for WebSocket support.
13
+ * Uses a dynamic import so the package is only loaded at connection time.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // Standalone mode
18
+ * const webchat = new WebChatChannelAdapter();
19
+ * await webchat.initialize({
20
+ * platform: 'webchat',
21
+ * credential: 'optional-api-key',
22
+ * params: {
23
+ * port: '8080',
24
+ * corsOrigins: '*',
25
+ * },
26
+ * });
27
+ *
28
+ * // Attached mode (with existing Express server)
29
+ * const webchat = new WebChatChannelAdapter();
30
+ * webchat.attachToServer(existingHttpServer);
31
+ * await webchat.initialize({
32
+ * platform: 'webchat',
33
+ * credential: 'optional-api-key',
34
+ * params: { corsOrigins: 'https://myapp.com' },
35
+ * });
36
+ * ```
37
+ *
38
+ * @module @framers/agentos/channels/adapters/WebChatChannelAdapter
39
+ */
40
+ import { BaseChannelAdapter } from './BaseChannelAdapter.js';
41
+ // ============================================================================
42
+ // WebChatChannelAdapter
43
+ // ============================================================================
44
+ /**
45
+ * Channel adapter for web-based chat using HTTP/WebSocket.
46
+ *
47
+ * Uses dynamic import for the `ws` package so it is only required
48
+ * at runtime when the adapter is actually initialized.
49
+ *
50
+ * Capabilities: text, rich_text, images, buttons, typing_indicator,
51
+ * read_receipts.
52
+ */
53
+ export class WebChatChannelAdapter extends BaseChannelAdapter {
54
+ constructor() {
55
+ super(...arguments);
56
+ this.platform = 'webchat';
57
+ this.displayName = 'WebChat';
58
+ this.capabilities = [
59
+ 'text',
60
+ 'rich_text',
61
+ 'images',
62
+ 'buttons',
63
+ 'typing_indicator',
64
+ 'read_receipts',
65
+ ];
66
+ /** Connected clients, keyed by client ID. */
67
+ this.clients = new Map();
68
+ /** Whether we created the HTTP server ourselves (vs attached). */
69
+ this.ownServer = false;
70
+ /** API key for client authentication. Empty string means no auth required. */
71
+ this.apiKey = '';
72
+ /** Allowed CORS origins. */
73
+ this.corsOrigins = '*';
74
+ /** Counter for generating client IDs. */
75
+ this.clientIdCounter = 0;
76
+ }
77
+ // ── Public: Server attachment ──
78
+ /**
79
+ * Attach to an existing HTTP server instead of creating a standalone one.
80
+ * Must be called before {@link initialize}.
81
+ *
82
+ * @param server - Node.js http.Server instance (e.g., from Express).
83
+ */
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ attachToServer(server) {
86
+ this.externalServer = server;
87
+ }
88
+ // ── Abstract hook implementations ──
89
+ async doConnect(auth) {
90
+ const params = auth.params ?? {};
91
+ this.apiKey = params.apiKey ?? auth.credential ?? '';
92
+ this.corsOrigins = params.corsOrigins ?? '*';
93
+ const port = parseInt(params.port ?? '8080', 10);
94
+ const wsPath = params.wsPath ?? '/ws';
95
+ // Dynamic import for ws package
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ let WsModule;
98
+ try {
99
+ WsModule = await import('ws');
100
+ }
101
+ catch {
102
+ throw new Error('The "ws" package is required for the WebChat adapter. ' +
103
+ 'Install it with: npm install ws');
104
+ }
105
+ const WebSocketServer = WsModule.WebSocketServer ?? WsModule.Server ?? WsModule.default?.WebSocketServer ?? WsModule.default?.Server;
106
+ // Create or reuse HTTP server
107
+ if (this.externalServer) {
108
+ // Attached mode — use provided server
109
+ this.httpServer = this.externalServer;
110
+ this.ownServer = false;
111
+ }
112
+ else {
113
+ // Standalone mode — create our own HTTP server
114
+ const http = await import('http');
115
+ this.httpServer = http.createServer((req, res) => {
116
+ this.handleHttpRequest(req, res);
117
+ });
118
+ this.ownServer = true;
119
+ }
120
+ // Create WebSocket server attached to HTTP server
121
+ this.wss = new WebSocketServer({
122
+ server: this.httpServer,
123
+ path: wsPath,
124
+ });
125
+ // Wire up WebSocket events
126
+ this.wss.on('connection', (ws, req) => {
127
+ this.handleNewConnection(ws, req);
128
+ });
129
+ // Start HTTP server if we own it
130
+ if (this.ownServer) {
131
+ await new Promise((resolve, reject) => {
132
+ const listenTimeout = setTimeout(() => {
133
+ reject(new Error(`WebChat HTTP server failed to start on port ${port} within 10s`));
134
+ }, 10000);
135
+ this.httpServer.listen(port, () => {
136
+ clearTimeout(listenTimeout);
137
+ resolve();
138
+ });
139
+ this.httpServer.on('error', (err) => {
140
+ clearTimeout(listenTimeout);
141
+ reject(err);
142
+ });
143
+ });
144
+ }
145
+ this.platformInfo = {
146
+ port: this.ownServer ? port : 'attached',
147
+ wsPath,
148
+ corsOrigins: this.corsOrigins,
149
+ requiresAuth: !!this.apiKey,
150
+ };
151
+ console.log(`[WebChat] ${this.ownServer ? `Server started on port ${port}` : 'Attached to existing server'} (ws: ${wsPath}, auth: ${this.apiKey ? 'required' : 'none'})`);
152
+ }
153
+ async doSendMessage(conversationId, content) {
154
+ // Find all clients in this conversation
155
+ const targets = [];
156
+ for (const client of this.clients.values()) {
157
+ if (client.conversationId === conversationId && client.authenticated) {
158
+ targets.push(client);
159
+ }
160
+ }
161
+ if (targets.length === 0) {
162
+ throw new Error(`[WebChat] No connected clients for conversation ${conversationId}.`);
163
+ }
164
+ const messageId = this.generateMessageId();
165
+ const timestamp = new Date().toISOString();
166
+ const outbound = {
167
+ type: 'message',
168
+ messageId,
169
+ blocks: content.blocks,
170
+ text: content.blocks
171
+ .filter((b) => b.type === 'text')
172
+ .map((b) => b.text)
173
+ .join('\n'),
174
+ timestamp,
175
+ replyToMessageId: content.replyToMessageId,
176
+ };
177
+ const payload = JSON.stringify(outbound);
178
+ for (const client of targets) {
179
+ this.safeSend(client.ws, payload);
180
+ }
181
+ return { messageId, timestamp };
182
+ }
183
+ async doShutdown() {
184
+ // Close all client connections
185
+ for (const client of this.clients.values()) {
186
+ try {
187
+ client.ws.close(1001, 'Server shutting down');
188
+ }
189
+ catch {
190
+ // Best effort
191
+ }
192
+ }
193
+ this.clients.clear();
194
+ // Close WebSocket server
195
+ if (this.wss) {
196
+ try {
197
+ this.wss.close();
198
+ }
199
+ catch {
200
+ // Best effort
201
+ }
202
+ this.wss = undefined;
203
+ }
204
+ // Close HTTP server only if we own it
205
+ if (this.httpServer && this.ownServer) {
206
+ await new Promise((resolve) => {
207
+ this.httpServer.close(() => resolve());
208
+ // Force resolve after 5s if server doesn't close cleanly
209
+ setTimeout(resolve, 5000);
210
+ });
211
+ }
212
+ this.httpServer = undefined;
213
+ this.externalServer = undefined;
214
+ this.ownServer = false;
215
+ console.log('[WebChat] Adapter shut down.');
216
+ }
217
+ // ── IChannelAdapter optional methods ──
218
+ async sendTypingIndicator(conversationId, isTyping) {
219
+ const outbound = {
220
+ type: 'typing',
221
+ timestamp: new Date().toISOString(),
222
+ };
223
+ // Add isTyping to the payload (not in the strict type but useful)
224
+ const payload = JSON.stringify({ ...outbound, isTyping });
225
+ for (const client of this.clients.values()) {
226
+ if (client.conversationId === conversationId && client.authenticated) {
227
+ this.safeSend(client.ws, payload);
228
+ }
229
+ }
230
+ }
231
+ async getConversationInfo(conversationId) {
232
+ let memberCount = 0;
233
+ for (const client of this.clients.values()) {
234
+ if (client.conversationId === conversationId) {
235
+ memberCount++;
236
+ }
237
+ }
238
+ return {
239
+ name: `WebChat ${conversationId}`,
240
+ memberCount,
241
+ isGroup: memberCount > 1,
242
+ metadata: { platform: 'webchat' },
243
+ };
244
+ }
245
+ // ── Public: Accessors ──
246
+ /** Get the number of currently connected clients. */
247
+ getConnectedClientCount() {
248
+ return this.clients.size;
249
+ }
250
+ /** Get all connected client IDs. */
251
+ getConnectedClientIds() {
252
+ return [...this.clients.keys()];
253
+ }
254
+ /**
255
+ * Broadcast a message to ALL connected and authenticated clients.
256
+ */
257
+ async broadcast(content) {
258
+ const messageId = this.generateMessageId();
259
+ const timestamp = new Date().toISOString();
260
+ const outbound = {
261
+ type: 'message',
262
+ messageId,
263
+ blocks: content.blocks,
264
+ text: content.blocks
265
+ .filter((b) => b.type === 'text')
266
+ .map((b) => b.text)
267
+ .join('\n'),
268
+ timestamp,
269
+ };
270
+ const payload = JSON.stringify(outbound);
271
+ for (const client of this.clients.values()) {
272
+ if (client.authenticated) {
273
+ this.safeSend(client.ws, payload);
274
+ }
275
+ }
276
+ }
277
+ // ── Private: HTTP ──
278
+ /**
279
+ * Handle plain HTTP requests (standalone mode only).
280
+ * Provides a simple health-check endpoint and CORS headers.
281
+ */
282
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
283
+ handleHttpRequest(req, res) {
284
+ // CORS headers
285
+ res.setHeader('Access-Control-Allow-Origin', this.corsOrigins);
286
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
287
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
288
+ if (req.method === 'OPTIONS') {
289
+ res.writeHead(204);
290
+ res.end();
291
+ return;
292
+ }
293
+ if (req.url === '/health' || req.url === '/') {
294
+ res.writeHead(200, { 'Content-Type': 'application/json' });
295
+ res.end(JSON.stringify({
296
+ status: 'ok',
297
+ platform: 'webchat',
298
+ clients: this.clients.size,
299
+ uptime: this.connectedSince
300
+ ? Date.now() - new Date(this.connectedSince).getTime()
301
+ : 0,
302
+ }));
303
+ return;
304
+ }
305
+ res.writeHead(404, { 'Content-Type': 'application/json' });
306
+ res.end(JSON.stringify({ error: 'Not found' }));
307
+ }
308
+ // ── Private: WebSocket ──
309
+ /**
310
+ * Handle a new WebSocket connection.
311
+ */
312
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
313
+ handleNewConnection(ws, req) {
314
+ const clientId = this.generateClientId();
315
+ const client = {
316
+ id: clientId,
317
+ ws,
318
+ conversationId: clientId, // Default conversation is 1:1 with this client
319
+ connectedAt: new Date().toISOString(),
320
+ authenticated: !this.apiKey, // Auto-authenticated if no API key required
321
+ };
322
+ this.clients.set(clientId, client);
323
+ console.log(`[WebChat] Client ${clientId} connected (${this.clients.size} total, auth: ${client.authenticated ? 'yes' : 'pending'})`);
324
+ // If no auth required, emit connection event immediately
325
+ if (client.authenticated) {
326
+ this.emit({
327
+ type: 'member_joined',
328
+ platform: 'webchat',
329
+ conversationId: client.conversationId,
330
+ timestamp: client.connectedAt,
331
+ data: { clientId, user: client.user },
332
+ });
333
+ }
334
+ // Wire up message handler
335
+ ws.on('message', (data) => {
336
+ this.handleClientMessage(client, data);
337
+ });
338
+ ws.on('close', () => {
339
+ this.handleClientDisconnect(client);
340
+ });
341
+ ws.on('error', (err) => {
342
+ console.error(`[WebChat] Client ${clientId} error:`, err.message);
343
+ this.handleClientDisconnect(client);
344
+ });
345
+ // Send welcome/auth-required message
346
+ if (!client.authenticated) {
347
+ this.safeSend(ws, JSON.stringify({
348
+ type: 'auth_result',
349
+ authenticated: false,
350
+ error: 'Authentication required. Send { type: "auth", apiKey: "..." }.',
351
+ timestamp: new Date().toISOString(),
352
+ }));
353
+ }
354
+ }
355
+ /**
356
+ * Handle a message from a connected WebSocket client.
357
+ */
358
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
359
+ handleClientMessage(client, rawData) {
360
+ let msg;
361
+ try {
362
+ const text = typeof rawData === 'string' ? rawData : rawData.toString();
363
+ msg = JSON.parse(text);
364
+ }
365
+ catch {
366
+ this.safeSend(client.ws, JSON.stringify({
367
+ type: 'error',
368
+ error: 'Invalid JSON',
369
+ timestamp: new Date().toISOString(),
370
+ }));
371
+ return;
372
+ }
373
+ switch (msg.type) {
374
+ case 'auth': {
375
+ this.handleAuth(client, msg);
376
+ break;
377
+ }
378
+ case 'message': {
379
+ if (!client.authenticated) {
380
+ this.safeSend(client.ws, JSON.stringify({
381
+ type: 'error',
382
+ error: 'Not authenticated',
383
+ timestamp: new Date().toISOString(),
384
+ }));
385
+ return;
386
+ }
387
+ this.handleInboundChatMessage(client, msg);
388
+ break;
389
+ }
390
+ case 'typing': {
391
+ if (!client.authenticated)
392
+ return;
393
+ this.emit({
394
+ type: 'typing',
395
+ platform: 'webchat',
396
+ conversationId: msg.conversationId ?? client.conversationId,
397
+ timestamp: new Date().toISOString(),
398
+ data: {
399
+ userId: client.user?.id ?? client.id,
400
+ isTyping: msg.isTyping ?? true,
401
+ },
402
+ });
403
+ break;
404
+ }
405
+ case 'ping': {
406
+ this.safeSend(client.ws, JSON.stringify({
407
+ type: 'pong',
408
+ timestamp: new Date().toISOString(),
409
+ }));
410
+ break;
411
+ }
412
+ default:
413
+ break;
414
+ }
415
+ }
416
+ /**
417
+ * Handle client authentication.
418
+ */
419
+ handleAuth(client, msg) {
420
+ if (!this.apiKey || msg.apiKey === this.apiKey) {
421
+ client.authenticated = true;
422
+ // Update user info if provided
423
+ if (msg.user) {
424
+ client.user = msg.user;
425
+ }
426
+ // Update conversation ID if provided
427
+ if (msg.conversationId) {
428
+ client.conversationId = msg.conversationId;
429
+ }
430
+ this.safeSend(client.ws, JSON.stringify({
431
+ type: 'auth_result',
432
+ authenticated: true,
433
+ timestamp: new Date().toISOString(),
434
+ }));
435
+ this.emit({
436
+ type: 'member_joined',
437
+ platform: 'webchat',
438
+ conversationId: client.conversationId,
439
+ timestamp: new Date().toISOString(),
440
+ data: { clientId: client.id, user: client.user },
441
+ });
442
+ console.log(`[WebChat] Client ${client.id} authenticated`);
443
+ }
444
+ else {
445
+ this.safeSend(client.ws, JSON.stringify({
446
+ type: 'auth_result',
447
+ authenticated: false,
448
+ error: 'Invalid API key',
449
+ timestamp: new Date().toISOString(),
450
+ }));
451
+ }
452
+ }
453
+ /**
454
+ * Handle an inbound chat message from a WebSocket client.
455
+ */
456
+ handleInboundChatMessage(client, msg) {
457
+ const conversationId = msg.conversationId ?? client.conversationId;
458
+ const timestamp = new Date().toISOString();
459
+ // Build content blocks
460
+ let contentBlocks;
461
+ if (msg.blocks && msg.blocks.length > 0) {
462
+ contentBlocks = msg.blocks;
463
+ }
464
+ else if (msg.text) {
465
+ contentBlocks = [{ type: 'text', text: msg.text }];
466
+ }
467
+ else {
468
+ contentBlocks = [{ type: 'text', text: '' }];
469
+ }
470
+ const messageId = this.generateMessageId();
471
+ const channelMessage = {
472
+ messageId,
473
+ platform: 'webchat',
474
+ conversationId,
475
+ conversationType: 'direct',
476
+ sender: client.user ?? {
477
+ id: client.id,
478
+ displayName: `User ${client.id}`,
479
+ },
480
+ content: contentBlocks,
481
+ text: msg.text ??
482
+ contentBlocks
483
+ .filter((b) => b.type === 'text')
484
+ .map((b) => b.text)
485
+ .join('\n'),
486
+ timestamp,
487
+ replyToMessageId: msg.replyToMessageId,
488
+ };
489
+ this.emit({
490
+ type: 'message',
491
+ platform: 'webchat',
492
+ conversationId,
493
+ timestamp,
494
+ data: channelMessage,
495
+ });
496
+ }
497
+ /**
498
+ * Handle a client disconnection.
499
+ */
500
+ handleClientDisconnect(client) {
501
+ if (!this.clients.has(client.id))
502
+ return;
503
+ this.clients.delete(client.id);
504
+ if (client.authenticated) {
505
+ this.emit({
506
+ type: 'member_left',
507
+ platform: 'webchat',
508
+ conversationId: client.conversationId,
509
+ timestamp: new Date().toISOString(),
510
+ data: { clientId: client.id, user: client.user },
511
+ });
512
+ }
513
+ console.log(`[WebChat] Client ${client.id} disconnected (${this.clients.size} remaining)`);
514
+ }
515
+ // ── Private: Utilities ──
516
+ /**
517
+ * Safely send data over a WebSocket, catching errors.
518
+ */
519
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
520
+ safeSend(ws, data) {
521
+ try {
522
+ if (ws.readyState === 1) {
523
+ // OPEN
524
+ ws.send(data);
525
+ }
526
+ }
527
+ catch (err) {
528
+ console.error('[WebChat] Failed to send message:', err);
529
+ }
530
+ }
531
+ generateClientId() {
532
+ this.clientIdCounter++;
533
+ return `wc-${Date.now()}-${this.clientIdCounter}`;
534
+ }
535
+ generateMessageId() {
536
+ return `wcm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
537
+ }
538
+ }
539
+ //# sourceMappingURL=WebChatChannelAdapter.js.map