@phuetz/code-buddy 0.1.12 → 0.1.14

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 (321) hide show
  1. package/README.md +228 -13
  2. package/dist/agent/architect-mode.d.ts +11 -0
  3. package/dist/agent/architect-mode.js +133 -25
  4. package/dist/agent/architect-mode.js.map +1 -1
  5. package/dist/agent/codebuddy-agent.d.ts +24 -0
  6. package/dist/agent/codebuddy-agent.js +118 -16
  7. package/dist/agent/codebuddy-agent.js.map +1 -1
  8. package/dist/agent/execution/agent-executor.d.ts +9 -0
  9. package/dist/agent/execution/agent-executor.js +62 -1
  10. package/dist/agent/execution/agent-executor.js.map +1 -1
  11. package/dist/agent/message-queue.d.ts +77 -0
  12. package/dist/agent/message-queue.js +116 -0
  13. package/dist/agent/message-queue.js.map +1 -0
  14. package/dist/agent/middleware/auto-observation.d.ts +37 -0
  15. package/dist/agent/middleware/auto-observation.js +231 -0
  16. package/dist/agent/middleware/auto-observation.js.map +1 -0
  17. package/dist/agent/middleware/index.d.ts +2 -0
  18. package/dist/agent/middleware/index.js +1 -0
  19. package/dist/agent/middleware/index.js.map +1 -1
  20. package/dist/agent/tool-handler.js +3 -2
  21. package/dist/agent/tool-handler.js.map +1 -1
  22. package/dist/agent/turn-diff-tracker.js +3 -0
  23. package/dist/agent/turn-diff-tracker.js.map +1 -1
  24. package/dist/agent/types.d.ts +7 -2
  25. package/dist/analytics/budget-alerts.d.ts +81 -0
  26. package/dist/analytics/budget-alerts.js +126 -0
  27. package/dist/analytics/budget-alerts.js.map +1 -0
  28. package/dist/analytics/cost-predictor.d.ts +79 -0
  29. package/dist/analytics/cost-predictor.js +150 -0
  30. package/dist/analytics/cost-predictor.js.map +1 -0
  31. package/dist/analytics/index.d.ts +2 -0
  32. package/dist/analytics/index.js +2 -0
  33. package/dist/analytics/index.js.map +1 -1
  34. package/dist/auth/profile-manager.d.ts +205 -0
  35. package/dist/auth/profile-manager.js +484 -0
  36. package/dist/auth/profile-manager.js.map +1 -0
  37. package/dist/browser-automation/browser-manager.d.ts +79 -1
  38. package/dist/browser-automation/browser-manager.js +265 -2
  39. package/dist/browser-automation/browser-manager.js.map +1 -1
  40. package/dist/browser-automation/profile-manager.d.ts +32 -0
  41. package/dist/browser-automation/profile-manager.js +83 -0
  42. package/dist/browser-automation/profile-manager.js.map +1 -0
  43. package/dist/browser-automation/route-interceptor.d.ts +29 -0
  44. package/dist/browser-automation/route-interceptor.js +103 -0
  45. package/dist/browser-automation/route-interceptor.js.map +1 -0
  46. package/dist/browser-automation/screenshot-annotator.d.ts +23 -0
  47. package/dist/browser-automation/screenshot-annotator.js +86 -0
  48. package/dist/browser-automation/screenshot-annotator.js.map +1 -0
  49. package/dist/browser-automation/types.d.ts +47 -0
  50. package/dist/cache/llm-response-cache.js +3 -0
  51. package/dist/cache/llm-response-cache.js.map +1 -1
  52. package/dist/canvas/canvas-server.js +4 -3
  53. package/dist/canvas/canvas-server.js.map +1 -1
  54. package/dist/channels/discord/client.d.ts +2 -1
  55. package/dist/channels/discord/client.js +28 -16
  56. package/dist/channels/discord/client.js.map +1 -1
  57. package/dist/channels/dm-pairing.js +6 -3
  58. package/dist/channels/dm-pairing.js.map +1 -1
  59. package/dist/channels/google-chat/index.d.ts +210 -0
  60. package/dist/channels/google-chat/index.js +505 -0
  61. package/dist/channels/google-chat/index.js.map +1 -0
  62. package/dist/channels/group-security.d.ts +182 -0
  63. package/dist/channels/group-security.js +407 -0
  64. package/dist/channels/group-security.js.map +1 -0
  65. package/dist/channels/index.d.ts +17 -1
  66. package/dist/channels/index.js +16 -0
  67. package/dist/channels/index.js.map +1 -1
  68. package/dist/channels/matrix/index.d.ts +181 -0
  69. package/dist/channels/matrix/index.js +643 -0
  70. package/dist/channels/matrix/index.js.map +1 -0
  71. package/dist/channels/offline-queue.d.ts +92 -0
  72. package/dist/channels/offline-queue.js +112 -0
  73. package/dist/channels/offline-queue.js.map +1 -0
  74. package/dist/channels/reconnection-manager.d.ts +117 -0
  75. package/dist/channels/reconnection-manager.js +171 -0
  76. package/dist/channels/reconnection-manager.js.map +1 -0
  77. package/dist/channels/signal/index.d.ts +184 -0
  78. package/dist/channels/signal/index.js +488 -0
  79. package/dist/channels/signal/index.js.map +1 -0
  80. package/dist/channels/slack/client.d.ts +2 -1
  81. package/dist/channels/slack/client.js +30 -15
  82. package/dist/channels/slack/client.js.map +1 -1
  83. package/dist/channels/teams/index.d.ts +196 -0
  84. package/dist/channels/teams/index.js +477 -0
  85. package/dist/channels/teams/index.js.map +1 -0
  86. package/dist/channels/telegram/client.d.ts +3 -1
  87. package/dist/channels/telegram/client.js +29 -2
  88. package/dist/channels/telegram/client.js.map +1 -1
  89. package/dist/channels/webchat/index.d.ts +103 -0
  90. package/dist/channels/webchat/index.js +697 -0
  91. package/dist/channels/webchat/index.js.map +1 -0
  92. package/dist/channels/whatsapp/index.d.ts +105 -0
  93. package/dist/channels/whatsapp/index.js +533 -0
  94. package/dist/channels/whatsapp/index.js.map +1 -0
  95. package/dist/codebuddy/client.js +11 -5
  96. package/dist/codebuddy/client.js.map +1 -1
  97. package/dist/codebuddy/tool-definitions/advanced-tools.d.ts +1 -0
  98. package/dist/codebuddy/tool-definitions/advanced-tools.js +103 -3
  99. package/dist/codebuddy/tool-definitions/advanced-tools.js.map +1 -1
  100. package/dist/codebuddy/tool-definitions/index.d.ts +1 -1
  101. package/dist/codebuddy/tool-definitions/index.js +1 -1
  102. package/dist/codebuddy/tool-definitions/index.js.map +1 -1
  103. package/dist/codebuddy/tools.js +3 -1
  104. package/dist/codebuddy/tools.js.map +1 -1
  105. package/dist/commands/cli/config-command.d.ts +8 -0
  106. package/dist/commands/cli/config-command.js +90 -0
  107. package/dist/commands/cli/config-command.js.map +1 -0
  108. package/dist/commands/cli/openclaw-commands.d.ts +12 -0
  109. package/dist/commands/cli/openclaw-commands.js +446 -0
  110. package/dist/commands/cli/openclaw-commands.js.map +1 -0
  111. package/dist/commands/cli/utility-commands.js +30 -0
  112. package/dist/commands/cli/utility-commands.js.map +1 -1
  113. package/dist/commands/client-dispatcher.js +22 -2
  114. package/dist/commands/client-dispatcher.js.map +1 -1
  115. package/dist/commands/enhanced-command-handler.js +21 -2
  116. package/dist/commands/enhanced-command-handler.js.map +1 -1
  117. package/dist/commands/handlers/extra-handlers.d.ts +30 -0
  118. package/dist/commands/handlers/extra-handlers.js +547 -0
  119. package/dist/commands/handlers/extra-handlers.js.map +1 -0
  120. package/dist/commands/handlers/index.d.ts +1 -0
  121. package/dist/commands/handlers/index.js +2 -0
  122. package/dist/commands/handlers/index.js.map +1 -1
  123. package/dist/commands/slash/builtin-commands.js +41 -34
  124. package/dist/commands/slash/builtin-commands.js.map +1 -1
  125. package/dist/config/env-schema.d.ts +58 -0
  126. package/dist/config/env-schema.js +789 -0
  127. package/dist/config/env-schema.js.map +1 -0
  128. package/dist/config/feature-flags.js +2 -1
  129. package/dist/config/feature-flags.js.map +1 -1
  130. package/dist/context/bootstrap-loader.d.ts +48 -0
  131. package/dist/context/bootstrap-loader.js +123 -0
  132. package/dist/context/bootstrap-loader.js.map +1 -0
  133. package/dist/context/codebase-rag/chunker.js +2 -2
  134. package/dist/context/codebase-rag/chunker.js.map +1 -1
  135. package/dist/copilot/copilot-proxy.d.ts +15 -1
  136. package/dist/copilot/copilot-proxy.js +92 -23
  137. package/dist/copilot/copilot-proxy.js.map +1 -1
  138. package/dist/daemon/health-monitor.js +11 -7
  139. package/dist/daemon/health-monitor.js.map +1 -1
  140. package/dist/daemon/heartbeat.d.ts +112 -0
  141. package/dist/daemon/heartbeat.js +339 -0
  142. package/dist/daemon/heartbeat.js.map +1 -0
  143. package/dist/desktop-automation/smart-snapshot.d.ts +11 -0
  144. package/dist/desktop-automation/smart-snapshot.js +38 -0
  145. package/dist/desktop-automation/smart-snapshot.js.map +1 -1
  146. package/dist/extensions/extension-loader.js +4 -0
  147. package/dist/extensions/extension-loader.js.map +1 -1
  148. package/dist/identity/identity-manager.d.ts +95 -0
  149. package/dist/identity/identity-manager.js +242 -0
  150. package/dist/identity/identity-manager.js.map +1 -0
  151. package/dist/index.js +147 -17
  152. package/dist/index.js.map +1 -1
  153. package/dist/input/text-to-speech.js +4 -2
  154. package/dist/input/text-to-speech.js.map +1 -1
  155. package/dist/input/voice-control.js +5 -3
  156. package/dist/input/voice-control.js.map +1 -1
  157. package/dist/integrations/github-integration.js +1 -1
  158. package/dist/integrations/github-integration.js.map +1 -1
  159. package/dist/orchestration/orchestrator.js +3 -0
  160. package/dist/orchestration/orchestrator.js.map +1 -1
  161. package/dist/persistence/conversation-branches.js +2 -1
  162. package/dist/persistence/conversation-branches.js.map +1 -1
  163. package/dist/persistence/session-store.d.ts +1 -1
  164. package/dist/persistence/session-store.js +1 -1
  165. package/dist/persistence/session-store.js.map +1 -1
  166. package/dist/plugins/plugin-system.js +5 -2
  167. package/dist/plugins/plugin-system.js.map +1 -1
  168. package/dist/providers/gemini-provider.js +6 -4
  169. package/dist/providers/gemini-provider.js.map +1 -1
  170. package/dist/providers/local-llm-provider.js +8 -0
  171. package/dist/providers/local-llm-provider.js.map +1 -1
  172. package/dist/sandbox/auto-sandbox.d.ts +59 -0
  173. package/dist/sandbox/auto-sandbox.js +145 -0
  174. package/dist/sandbox/auto-sandbox.js.map +1 -0
  175. package/dist/scheduler/cron-scheduler.js +2 -0
  176. package/dist/scheduler/cron-scheduler.js.map +1 -1
  177. package/dist/scheduler/scheduler.js +11 -2
  178. package/dist/scheduler/scheduler.js.map +1 -1
  179. package/dist/security/audit-logger.d.ts +127 -0
  180. package/dist/security/audit-logger.js +194 -0
  181. package/dist/security/audit-logger.js.map +1 -0
  182. package/dist/security/bash-allowlist/allowlist-store.js +3 -2
  183. package/dist/security/bash-allowlist/allowlist-store.js.map +1 -1
  184. package/dist/security/bash-parser.js +0 -2
  185. package/dist/security/bash-parser.js.map +1 -1
  186. package/dist/security/code-validator.d.ts +51 -0
  187. package/dist/security/code-validator.js +185 -0
  188. package/dist/security/code-validator.js.map +1 -0
  189. package/dist/security/dangerous-patterns.d.ts +68 -0
  190. package/dist/security/dangerous-patterns.js +218 -0
  191. package/dist/security/dangerous-patterns.js.map +1 -0
  192. package/dist/security/remote-approval.d.ts +65 -0
  193. package/dist/security/remote-approval.js +138 -0
  194. package/dist/security/remote-approval.js.map +1 -0
  195. package/dist/security/security-audit.d.ts +7 -0
  196. package/dist/security/security-audit.js +23 -0
  197. package/dist/security/security-audit.js.map +1 -1
  198. package/dist/security/syntax-validator.d.ts +17 -0
  199. package/dist/security/syntax-validator.js +292 -0
  200. package/dist/security/syntax-validator.js.map +1 -0
  201. package/dist/server/index.js +277 -2
  202. package/dist/server/index.js.map +1 -1
  203. package/dist/server/middleware/logging.js +9 -1
  204. package/dist/server/middleware/logging.js.map +1 -1
  205. package/dist/server/routes/memory.js +4 -1
  206. package/dist/server/routes/memory.js.map +1 -1
  207. package/dist/server/routes/metrics.js +1 -1
  208. package/dist/server/routes/metrics.js.map +1 -1
  209. package/dist/server/routes/sessions.js +5 -4
  210. package/dist/server/routes/sessions.js.map +1 -1
  211. package/dist/server/websocket/handler.js +8 -2
  212. package/dist/server/websocket/handler.js.map +1 -1
  213. package/dist/services/prompt-builder.js +16 -0
  214. package/dist/services/prompt-builder.js.map +1 -1
  215. package/dist/skills/hub.d.ts +231 -0
  216. package/dist/skills/hub.js +694 -0
  217. package/dist/skills/hub.js.map +1 -0
  218. package/dist/skills/skill-loader.js +1 -1
  219. package/dist/skills/skill-loader.js.map +1 -1
  220. package/dist/skills/skill-manager.js +2 -1
  221. package/dist/skills/skill-manager.js.map +1 -1
  222. package/dist/skills/skill-registry.js +4 -0
  223. package/dist/skills/skill-registry.js.map +1 -1
  224. package/dist/talk-mode/providers/audioreader-tts.js +1 -0
  225. package/dist/talk-mode/providers/audioreader-tts.js.map +1 -1
  226. package/dist/tools/apply-patch.d.ts +1 -0
  227. package/dist/tools/apply-patch.js +66 -12
  228. package/dist/tools/apply-patch.js.map +1 -1
  229. package/dist/tools/bash/bash-tool.d.ts +123 -0
  230. package/dist/tools/bash/bash-tool.js +549 -0
  231. package/dist/tools/bash/bash-tool.js.map +1 -0
  232. package/dist/tools/bash/command-validator.d.ts +49 -0
  233. package/dist/tools/bash/command-validator.js +223 -0
  234. package/dist/tools/bash/command-validator.js.map +1 -0
  235. package/dist/tools/bash/index.d.ts +7 -0
  236. package/dist/tools/bash/index.js +8 -0
  237. package/dist/tools/bash/index.js.map +1 -0
  238. package/dist/tools/bash/security-patterns.d.ts +44 -0
  239. package/dist/tools/bash/security-patterns.js +234 -0
  240. package/dist/tools/bash/security-patterns.js.map +1 -0
  241. package/dist/tools/bash/streaming-executor.d.ts +23 -0
  242. package/dist/tools/bash/streaming-executor.js +134 -0
  243. package/dist/tools/bash/streaming-executor.js.map +1 -0
  244. package/dist/tools/bash.js +5 -3
  245. package/dist/tools/bash.js.map +1 -1
  246. package/dist/tools/code-formatter.js +41 -27
  247. package/dist/tools/code-formatter.js.map +1 -1
  248. package/dist/tools/code-review.js +1 -1
  249. package/dist/tools/code-review.js.map +1 -1
  250. package/dist/tools/computer-control-tool.js +21 -0
  251. package/dist/tools/computer-control-tool.js.map +1 -1
  252. package/dist/tools/document-tool.js +3 -2
  253. package/dist/tools/document-tool.js.map +1 -1
  254. package/dist/tools/git-tool.d.ts +45 -0
  255. package/dist/tools/git-tool.js +224 -2
  256. package/dist/tools/git-tool.js.map +1 -1
  257. package/dist/tools/index.d.ts +1 -1
  258. package/dist/tools/index.js +1 -1
  259. package/dist/tools/index.js.map +1 -1
  260. package/dist/tools/morph-editor.js +1 -0
  261. package/dist/tools/morph-editor.js.map +1 -1
  262. package/dist/tools/multi-edit.js +31 -3
  263. package/dist/tools/multi-edit.js.map +1 -1
  264. package/dist/tools/notebook-tool.js +8 -2
  265. package/dist/tools/notebook-tool.js.map +1 -1
  266. package/dist/tools/process-tool.d.ts +69 -0
  267. package/dist/tools/process-tool.js +222 -0
  268. package/dist/tools/process-tool.js.map +1 -0
  269. package/dist/tools/registry/git-tools.d.ts +32 -0
  270. package/dist/tools/registry/git-tools.js +211 -0
  271. package/dist/tools/registry/git-tools.js.map +1 -0
  272. package/dist/tools/registry/index.d.ts +2 -0
  273. package/dist/tools/registry/index.js +8 -0
  274. package/dist/tools/registry/index.js.map +1 -1
  275. package/dist/tools/registry/misc-tools.d.ts +32 -4
  276. package/dist/tools/registry/misc-tools.js +230 -90
  277. package/dist/tools/registry/misc-tools.js.map +1 -1
  278. package/dist/tools/registry/process-tools.d.ts +20 -0
  279. package/dist/tools/registry/process-tools.js +141 -0
  280. package/dist/tools/registry/process-tools.js.map +1 -0
  281. package/dist/tools/registry/types.d.ts +2 -0
  282. package/dist/tools/search.js +4 -2
  283. package/dist/tools/search.js.map +1 -1
  284. package/dist/tools/video-tool.js +30 -14
  285. package/dist/tools/video-tool.js.map +1 -1
  286. package/dist/tools/web-search.js +4 -1
  287. package/dist/tools/web-search.js.map +1 -1
  288. package/dist/ui/components/ChatInterface.js +9 -0
  289. package/dist/ui/components/ChatInterface.js.map +1 -1
  290. package/dist/utils/autonomy-manager.js +3 -2
  291. package/dist/utils/autonomy-manager.js.map +1 -1
  292. package/dist/utils/config-validation/schema.d.ts +15 -15
  293. package/dist/utils/confirmation-service.d.ts +16 -0
  294. package/dist/utils/confirmation-service.js +37 -3
  295. package/dist/utils/confirmation-service.js.map +1 -1
  296. package/dist/utils/custom-instructions.js +2 -1
  297. package/dist/utils/custom-instructions.js.map +1 -1
  298. package/dist/utils/diff-generator.js +3 -1
  299. package/dist/utils/diff-generator.js.map +1 -1
  300. package/dist/utils/graceful-shutdown.js +9 -9
  301. package/dist/utils/graceful-shutdown.js.map +1 -1
  302. package/dist/utils/head-tail-truncation.d.ts +18 -0
  303. package/dist/utils/head-tail-truncation.js +127 -0
  304. package/dist/utils/head-tail-truncation.js.map +1 -1
  305. package/dist/utils/history-manager.js +3 -2
  306. package/dist/utils/history-manager.js.map +1 -1
  307. package/dist/utils/logger.d.ts +2 -0
  308. package/dist/utils/logger.js +18 -3
  309. package/dist/utils/logger.js.map +1 -1
  310. package/dist/utils/performance.js +16 -15
  311. package/dist/utils/performance.js.map +1 -1
  312. package/dist/utils/stream-helpers.js +4 -2
  313. package/dist/utils/stream-helpers.js.map +1 -1
  314. package/dist/utils/update-notifier.js +2 -1
  315. package/dist/utils/update-notifier.js.map +1 -1
  316. package/dist/workflows/pipeline.d.ts +54 -1
  317. package/dist/workflows/pipeline.js +128 -7
  318. package/dist/workflows/pipeline.js.map +1 -1
  319. package/dist/workflows/step-manager.js +2 -1
  320. package/dist/workflows/step-manager.js.map +1 -1
  321. package/package.json +6 -3
@@ -0,0 +1,697 @@
1
+ /**
2
+ * WebChat Channel Adapter
3
+ *
4
+ * Built-in HTTP/WebSocket chat server that provides a browser-based
5
+ * chat interface. Serves a simple HTML UI and manages connected
6
+ * WebSocket clients for real-time bidirectional messaging.
7
+ *
8
+ * No external dependencies required -- uses Node.js built-in
9
+ * http module and the ws package (already a project dependency).
10
+ */
11
+ import http from 'http';
12
+ import { randomUUID } from 'crypto';
13
+ import { BaseChannel, getSessionKey, checkDMPairing } from '../index.js';
14
+ import { logger } from '../../utils/logger.js';
15
+ // ============================================================================
16
+ // Channel Implementation
17
+ // ============================================================================
18
+ /**
19
+ * WebChat channel -- built-in HTTP/WS chat server
20
+ */
21
+ export class WebChatChannel extends BaseChannel {
22
+ server = null;
23
+ wss = null;
24
+ clients = new Map();
25
+ messageHistory = [];
26
+ maxHistory = 100;
27
+ constructor(config) {
28
+ super('webchat', config);
29
+ // Apply defaults
30
+ const cfg = this.config;
31
+ if (cfg.port === undefined)
32
+ cfg.port = 3001;
33
+ if (!cfg.host)
34
+ cfg.host = '0.0.0.0';
35
+ if (!cfg.corsOrigins)
36
+ cfg.corsOrigins = ['*'];
37
+ if (!cfg.title)
38
+ cfg.title = 'Code Buddy WebChat';
39
+ if (cfg.maxMessageLength === undefined)
40
+ cfg.maxMessageLength = 4096;
41
+ }
42
+ get webChatConfig() {
43
+ return this.config;
44
+ }
45
+ // ==========================================================================
46
+ // Lifecycle
47
+ // ==========================================================================
48
+ /**
49
+ * Start the HTTP and WebSocket servers
50
+ */
51
+ async connect() {
52
+ let WebSocketModule;
53
+ try {
54
+ WebSocketModule = await import('ws');
55
+ }
56
+ catch {
57
+ throw new Error('WebChat channel requires the ws package. Install it with: npm install ws');
58
+ }
59
+ const port = this.webChatConfig.port ?? 3001;
60
+ const host = this.webChatConfig.host ?? '0.0.0.0';
61
+ return new Promise((resolve, reject) => {
62
+ try {
63
+ // Create HTTP server
64
+ this.server = http.createServer((req, res) => {
65
+ this.handleHttpRequest(req, res);
66
+ });
67
+ // Create WebSocket server attached to the HTTP server
68
+ const WebSocketServer = WebSocketModule.WebSocketServer;
69
+ this.wss = new WebSocketServer({ server: this.server });
70
+ this.wss.on('connection', (ws, req) => {
71
+ this.handleWsConnection(ws, req);
72
+ });
73
+ this.wss.on('error', (err) => {
74
+ logger.debug('WebChat WS server error', {
75
+ error: err instanceof Error ? err.message : String(err),
76
+ });
77
+ this.emit('error', 'webchat', err);
78
+ });
79
+ this.server.on('error', (err) => {
80
+ this.emit('error', 'webchat', err);
81
+ reject(err);
82
+ });
83
+ this.server.listen(port, host, () => {
84
+ this.status.connected = true;
85
+ this.status.authenticated = true;
86
+ this.status.info = {
87
+ port,
88
+ host,
89
+ url: `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`,
90
+ };
91
+ logger.debug('WebChat server started', { port, host });
92
+ this.emit('connected', 'webchat');
93
+ resolve();
94
+ });
95
+ }
96
+ catch (error) {
97
+ reject(error);
98
+ }
99
+ });
100
+ }
101
+ /**
102
+ * Stop the servers and disconnect all clients
103
+ */
104
+ async disconnect() {
105
+ // Close all WebSocket connections
106
+ for (const [id, client] of this.clients) {
107
+ try {
108
+ client.ws.close(1000, 'Server shutting down');
109
+ }
110
+ catch {
111
+ // Ignore close errors
112
+ }
113
+ }
114
+ this.clients.clear();
115
+ // Close WebSocket server
116
+ if (this.wss) {
117
+ this.wss.close();
118
+ this.wss = null;
119
+ }
120
+ // Close HTTP server
121
+ if (this.server) {
122
+ await new Promise((resolve) => {
123
+ this.server.close(() => resolve());
124
+ });
125
+ this.server = null;
126
+ }
127
+ this.status.connected = false;
128
+ this.status.authenticated = false;
129
+ this.emit('disconnected', 'webchat');
130
+ }
131
+ /**
132
+ * Send a message to a specific client or broadcast to all
133
+ */
134
+ async send(message) {
135
+ if (!this.status.connected) {
136
+ return { success: false, error: 'WebChat not connected', timestamp: new Date() };
137
+ }
138
+ const now = new Date();
139
+ const msgId = randomUUID();
140
+ const wsMsg = {
141
+ type: 'message',
142
+ id: msgId,
143
+ content: message.content,
144
+ user: { id: 'bot', username: 'assistant', displayName: 'Assistant', isBot: true },
145
+ timestamp: now.toISOString(),
146
+ replyTo: message.replyTo,
147
+ };
148
+ // Store in history
149
+ this.addToHistory({
150
+ id: msgId,
151
+ content: message.content,
152
+ user: wsMsg.user,
153
+ timestamp: now.toISOString(),
154
+ replyTo: message.replyTo,
155
+ });
156
+ const payload = JSON.stringify(wsMsg);
157
+ if (message.channelId === '*' || message.channelId === 'broadcast') {
158
+ // Broadcast to all connected clients
159
+ let sent = 0;
160
+ for (const [, client] of this.clients) {
161
+ try {
162
+ if (client.ws.readyState === 1) { // WebSocket.OPEN = 1
163
+ client.ws.send(payload);
164
+ sent++;
165
+ }
166
+ }
167
+ catch {
168
+ // Individual send failures are non-fatal
169
+ }
170
+ }
171
+ return {
172
+ success: sent > 0 || this.clients.size === 0,
173
+ messageId: msgId,
174
+ timestamp: now,
175
+ };
176
+ }
177
+ // Send to specific client
178
+ const client = this.clients.get(message.channelId);
179
+ if (!client) {
180
+ return {
181
+ success: false,
182
+ error: `Client ${message.channelId} not found`,
183
+ timestamp: now,
184
+ };
185
+ }
186
+ try {
187
+ if (client.ws.readyState === 1) {
188
+ client.ws.send(payload);
189
+ return { success: true, messageId: msgId, timestamp: now };
190
+ }
191
+ return { success: false, error: 'Client WebSocket not open', timestamp: now };
192
+ }
193
+ catch (error) {
194
+ return {
195
+ success: false,
196
+ error: error instanceof Error ? error.message : String(error),
197
+ timestamp: now,
198
+ };
199
+ }
200
+ }
201
+ /**
202
+ * Get number of connected clients
203
+ */
204
+ getClientCount() {
205
+ return this.clients.size;
206
+ }
207
+ /**
208
+ * Get connected client IDs
209
+ */
210
+ getClientIds() {
211
+ return Array.from(this.clients.keys());
212
+ }
213
+ /**
214
+ * Broadcast a system message to all clients
215
+ */
216
+ async broadcastSystem(content) {
217
+ const msg = {
218
+ type: 'system',
219
+ content,
220
+ timestamp: new Date().toISOString(),
221
+ };
222
+ const payload = JSON.stringify(msg);
223
+ for (const [, client] of this.clients) {
224
+ try {
225
+ if (client.ws.readyState === 1) {
226
+ client.ws.send(payload);
227
+ }
228
+ }
229
+ catch {
230
+ // Ignore individual failures
231
+ }
232
+ }
233
+ }
234
+ // ==========================================================================
235
+ // HTTP Handler
236
+ // ==========================================================================
237
+ /**
238
+ * Handle incoming HTTP requests
239
+ */
240
+ handleHttpRequest(req, res) {
241
+ const url = req.url ?? '/';
242
+ const corsOrigins = this.webChatConfig.corsOrigins ?? ['*'];
243
+ const origin = req.headers.origin ?? '*';
244
+ // CORS headers
245
+ const allowedOrigin = corsOrigins.includes('*') ? '*' : (corsOrigins.includes(origin) ? origin : '');
246
+ if (allowedOrigin) {
247
+ res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
248
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
249
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
250
+ }
251
+ if (req.method === 'OPTIONS') {
252
+ res.writeHead(204);
253
+ res.end();
254
+ return;
255
+ }
256
+ if (url === '/' || url === '/index.html') {
257
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
258
+ res.end(this.getChatHtml());
259
+ return;
260
+ }
261
+ if (url === '/api/health') {
262
+ res.writeHead(200, { 'Content-Type': 'application/json' });
263
+ res.end(JSON.stringify({
264
+ status: 'ok',
265
+ clients: this.clients.size,
266
+ uptime: process.uptime(),
267
+ }));
268
+ return;
269
+ }
270
+ if (url === '/api/history') {
271
+ res.writeHead(200, { 'Content-Type': 'application/json' });
272
+ res.end(JSON.stringify({ messages: this.messageHistory }));
273
+ return;
274
+ }
275
+ // 404 for everything else
276
+ res.writeHead(404, { 'Content-Type': 'application/json' });
277
+ res.end(JSON.stringify({ error: 'Not found' }));
278
+ }
279
+ // ==========================================================================
280
+ // WebSocket Handler
281
+ // ==========================================================================
282
+ /**
283
+ * Handle a new WebSocket connection
284
+ */
285
+ handleWsConnection(ws, req) {
286
+ const clientId = randomUUID();
287
+ const ip = req.headers['x-forwarded-for'] ?? req.socket.remoteAddress ?? 'unknown';
288
+ // If auth token is required, wait for auth message
289
+ const needsAuth = !!this.webChatConfig.authToken;
290
+ let authenticated = !needsAuth;
291
+ const client = {
292
+ id: clientId,
293
+ ws,
294
+ user: {
295
+ id: clientId,
296
+ username: `user-${clientId.slice(0, 8)}`,
297
+ displayName: `User ${clientId.slice(0, 8)}`,
298
+ isBot: false,
299
+ },
300
+ connectedAt: new Date(),
301
+ lastActivity: new Date(),
302
+ };
303
+ if (!needsAuth) {
304
+ this.clients.set(clientId, client);
305
+ this.sendWelcome(client);
306
+ }
307
+ ws.on('message', async (data) => {
308
+ try {
309
+ const msg = JSON.parse(data.toString());
310
+ client.lastActivity = new Date();
311
+ // Handle auth
312
+ if (msg.type === 'auth') {
313
+ if (needsAuth && msg.token !== this.webChatConfig.authToken) {
314
+ ws.send(JSON.stringify({ type: 'system', content: 'Authentication failed' }));
315
+ ws.close(4001, 'Unauthorized');
316
+ return;
317
+ }
318
+ authenticated = true;
319
+ // Update user info from auth message
320
+ if (msg.user) {
321
+ client.user = {
322
+ ...client.user,
323
+ ...msg.user,
324
+ id: msg.user.id ?? clientId,
325
+ };
326
+ }
327
+ this.clients.set(clientId, client);
328
+ this.sendWelcome(client);
329
+ return;
330
+ }
331
+ if (!authenticated) {
332
+ ws.send(JSON.stringify({ type: 'system', content: 'Please authenticate first' }));
333
+ return;
334
+ }
335
+ // Handle message
336
+ if (msg.type === 'message') {
337
+ await this.handleWsMessage(client, msg);
338
+ return;
339
+ }
340
+ // Handle typing
341
+ if (msg.type === 'typing') {
342
+ this.emit('typing', {
343
+ id: clientId,
344
+ type: 'webchat',
345
+ }, client.user);
346
+ // Broadcast typing to other clients
347
+ this.broadcastExcept(clientId, JSON.stringify({
348
+ type: 'typing',
349
+ user: client.user,
350
+ timestamp: new Date().toISOString(),
351
+ }));
352
+ return;
353
+ }
354
+ // Handle history request
355
+ if (msg.type === 'history') {
356
+ ws.send(JSON.stringify({
357
+ type: 'history',
358
+ messages: this.messageHistory,
359
+ }));
360
+ return;
361
+ }
362
+ }
363
+ catch (err) {
364
+ logger.debug('WebChat: error processing WS message', {
365
+ clientId,
366
+ error: err instanceof Error ? err.message : String(err),
367
+ });
368
+ }
369
+ });
370
+ ws.on('close', () => {
371
+ this.clients.delete(clientId);
372
+ logger.debug('WebChat: client disconnected', { clientId });
373
+ // Notify other clients
374
+ this.broadcastExcept(clientId, JSON.stringify({
375
+ type: 'system',
376
+ content: `${client.user.displayName ?? clientId} has left the chat`,
377
+ timestamp: new Date().toISOString(),
378
+ }));
379
+ });
380
+ ws.on('error', (err) => {
381
+ logger.debug('WebChat: client error', {
382
+ clientId,
383
+ error: err instanceof Error ? err.message : String(err),
384
+ });
385
+ });
386
+ ws.on('pong', () => {
387
+ client.lastActivity = new Date();
388
+ });
389
+ logger.debug('WebChat: client connected', { clientId, ip });
390
+ }
391
+ /**
392
+ * Handle an incoming WebSocket chat message
393
+ */
394
+ async handleWsMessage(client, msg) {
395
+ const content = (msg.content ?? '').trim();
396
+ if (!content)
397
+ return;
398
+ // Enforce max message length
399
+ const maxLen = this.webChatConfig.maxMessageLength ?? 4096;
400
+ if (content.length > maxLen) {
401
+ client.ws.send(JSON.stringify({
402
+ type: 'system',
403
+ content: `Message too long (max ${maxLen} characters)`,
404
+ }));
405
+ return;
406
+ }
407
+ // Check user allowlist
408
+ if (!this.isUserAllowed(client.id))
409
+ return;
410
+ const now = new Date();
411
+ const msgId = msg.id ?? randomUUID();
412
+ // Store in history
413
+ this.addToHistory({
414
+ id: msgId,
415
+ content,
416
+ user: client.user,
417
+ timestamp: now.toISOString(),
418
+ replyTo: msg.replyTo,
419
+ });
420
+ // Broadcast the message to all other clients (echo)
421
+ this.broadcastExcept(client.id, JSON.stringify({
422
+ type: 'message',
423
+ id: msgId,
424
+ content,
425
+ user: client.user,
426
+ timestamp: now.toISOString(),
427
+ replyTo: msg.replyTo,
428
+ }));
429
+ // Build InboundMessage
430
+ const inbound = {
431
+ id: msgId,
432
+ channel: {
433
+ id: client.id,
434
+ type: 'webchat',
435
+ name: 'WebChat',
436
+ isDM: true,
437
+ isGroup: false,
438
+ },
439
+ sender: client.user,
440
+ content,
441
+ contentType: this.determineContentType(content, msg.attachments),
442
+ attachments: msg.attachments,
443
+ replyTo: msg.replyTo,
444
+ timestamp: now,
445
+ raw: msg,
446
+ };
447
+ const parsed = this.parseCommand(inbound);
448
+ parsed.sessionKey = getSessionKey(parsed);
449
+ // DM pairing check
450
+ const pairingStatus = await checkDMPairing(parsed);
451
+ if (!pairingStatus.approved) {
452
+ const { getDMPairing } = await import('../dm-pairing.js');
453
+ const pairingMessage = getDMPairing().getPairingMessage(pairingStatus);
454
+ if (pairingMessage) {
455
+ await this.send({ channelId: client.id, content: pairingMessage });
456
+ }
457
+ return;
458
+ }
459
+ this.status.lastActivity = now;
460
+ this.emit('message', parsed);
461
+ if (parsed.isCommand) {
462
+ this.emit('command', parsed);
463
+ }
464
+ }
465
+ // ==========================================================================
466
+ // Helpers
467
+ // ==========================================================================
468
+ /**
469
+ * Send welcome message and history to a newly connected client
470
+ */
471
+ sendWelcome(client) {
472
+ // Send system welcome
473
+ client.ws.send(JSON.stringify({
474
+ type: 'system',
475
+ content: `Welcome to ${this.webChatConfig.title ?? 'Code Buddy WebChat'}! You are connected as ${client.user.displayName}.`,
476
+ timestamp: new Date().toISOString(),
477
+ }));
478
+ // Send recent history
479
+ if (this.messageHistory.length > 0) {
480
+ client.ws.send(JSON.stringify({
481
+ type: 'history',
482
+ messages: this.messageHistory,
483
+ }));
484
+ }
485
+ // Notify other clients
486
+ this.broadcastExcept(client.id, JSON.stringify({
487
+ type: 'system',
488
+ content: `${client.user.displayName ?? client.id} has joined the chat`,
489
+ timestamp: new Date().toISOString(),
490
+ }));
491
+ }
492
+ /**
493
+ * Broadcast a message to all clients except the sender
494
+ */
495
+ broadcastExcept(excludeId, payload) {
496
+ for (const [id, client] of this.clients) {
497
+ if (id === excludeId)
498
+ continue;
499
+ try {
500
+ if (client.ws.readyState === 1) {
501
+ client.ws.send(payload);
502
+ }
503
+ }
504
+ catch {
505
+ // Ignore individual send failures
506
+ }
507
+ }
508
+ }
509
+ /**
510
+ * Add a message to the history ring buffer
511
+ */
512
+ addToHistory(msg) {
513
+ this.messageHistory.push(msg);
514
+ if (this.messageHistory.length > this.maxHistory) {
515
+ this.messageHistory.shift();
516
+ }
517
+ }
518
+ /**
519
+ * Determine content type from message content
520
+ */
521
+ determineContentType(content, attachments) {
522
+ if (attachments && attachments.length > 0)
523
+ return attachments[0].type;
524
+ if (content.startsWith('/'))
525
+ return 'command';
526
+ return 'text';
527
+ }
528
+ /**
529
+ * Generate the HTML for the chat interface
530
+ */
531
+ getChatHtml() {
532
+ const title = this.webChatConfig.title ?? 'Code Buddy WebChat';
533
+ const wsProtocol = 'ws';
534
+ return `<!DOCTYPE html>
535
+ <html lang="en">
536
+ <head>
537
+ <meta charset="utf-8">
538
+ <meta name="viewport" content="width=device-width, initial-scale=1">
539
+ <title>${this.escapeHtml(title)}</title>
540
+ <style>
541
+ * { margin: 0; padding: 0; box-sizing: border-box; }
542
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column; }
543
+ #header { padding: 12px 20px; background: #16213e; border-bottom: 1px solid #0f3460; display: flex; align-items: center; justify-content: space-between; }
544
+ #header h1 { font-size: 16px; color: #e94560; }
545
+ #header .status { font-size: 12px; color: #888; }
546
+ #header .status.connected { color: #4caf50; }
547
+ #messages { flex: 1; overflow-y: auto; padding: 16px 20px; display: flex; flex-direction: column; gap: 8px; }
548
+ .message { max-width: 75%; padding: 10px 14px; border-radius: 12px; line-height: 1.5; word-wrap: break-word; white-space: pre-wrap; }
549
+ .message.user { align-self: flex-end; background: #0f3460; color: #e0e0e0; border-bottom-right-radius: 4px; }
550
+ .message.bot { align-self: flex-start; background: #1a1a3e; border: 1px solid #333; border-bottom-left-radius: 4px; }
551
+ .message.system { align-self: center; background: transparent; color: #666; font-size: 12px; font-style: italic; }
552
+ .message .sender { font-size: 11px; color: #888; margin-bottom: 4px; }
553
+ .message .time { font-size: 10px; color: #555; margin-top: 4px; text-align: right; }
554
+ #input-area { padding: 12px 20px; background: #16213e; border-top: 1px solid #0f3460; display: flex; gap: 8px; }
555
+ #input-area input { flex: 1; padding: 10px 14px; border: 1px solid #333; border-radius: 8px; background: #1a1a2e; color: #e0e0e0; font-size: 14px; outline: none; }
556
+ #input-area input:focus { border-color: #e94560; }
557
+ #input-area button { padding: 10px 20px; border: none; border-radius: 8px; background: #e94560; color: white; font-size: 14px; cursor: pointer; }
558
+ #input-area button:hover { background: #c73650; }
559
+ #input-area button:disabled { background: #555; cursor: not-allowed; }
560
+ </style>
561
+ </head>
562
+ <body>
563
+ <div id="header">
564
+ <h1>${this.escapeHtml(title)}</h1>
565
+ <span id="status" class="status">Connecting...</span>
566
+ </div>
567
+ <div id="messages"></div>
568
+ <div id="input-area">
569
+ <input id="msg-input" type="text" placeholder="Type a message..." autocomplete="off" disabled>
570
+ <button id="send-btn" disabled>Send</button>
571
+ </div>
572
+ <script>
573
+ (function() {
574
+ const messagesEl = document.getElementById('messages');
575
+ const inputEl = document.getElementById('msg-input');
576
+ const sendBtn = document.getElementById('send-btn');
577
+ const statusEl = document.getElementById('status');
578
+ let ws = null;
579
+ let reconnectTimer = null;
580
+ let typingTimer = null;
581
+
582
+ function connect() {
583
+ const proto = location.protocol === 'https:' ? 'wss' : 'ws';
584
+ ws = new WebSocket(proto + '://' + location.host);
585
+
586
+ ws.onopen = function() {
587
+ statusEl.textContent = 'Connected';
588
+ statusEl.className = 'status connected';
589
+ inputEl.disabled = false;
590
+ sendBtn.disabled = false;
591
+ inputEl.focus();
592
+ };
593
+
594
+ ws.onclose = function() {
595
+ statusEl.textContent = 'Disconnected';
596
+ statusEl.className = 'status';
597
+ inputEl.disabled = true;
598
+ sendBtn.disabled = true;
599
+ reconnectTimer = setTimeout(connect, 3000);
600
+ };
601
+
602
+ ws.onerror = function() {
603
+ statusEl.textContent = 'Error';
604
+ statusEl.className = 'status';
605
+ };
606
+
607
+ ws.onmessage = function(event) {
608
+ try {
609
+ const msg = JSON.parse(event.data);
610
+ if (msg.type === 'history' && msg.messages) {
611
+ msg.messages.forEach(function(m) { addMessage(m.content, m.user, m.timestamp); });
612
+ scrollBottom();
613
+ } else if (msg.type === 'message') {
614
+ addMessage(msg.content, msg.user, msg.timestamp);
615
+ scrollBottom();
616
+ } else if (msg.type === 'system') {
617
+ addSystemMessage(msg.content);
618
+ scrollBottom();
619
+ }
620
+ } catch(e) { console.error('Parse error', e); }
621
+ };
622
+ }
623
+
624
+ function addMessage(content, user, timestamp) {
625
+ const div = document.createElement('div');
626
+ const isBot = user && user.isBot;
627
+ div.className = 'message ' + (isBot ? 'bot' : 'user');
628
+ let html = '';
629
+ if (user && user.displayName) {
630
+ html += '<div class="sender">' + escapeHtml(user.displayName) + '</div>';
631
+ }
632
+ html += escapeHtml(content);
633
+ if (timestamp) {
634
+ const t = new Date(timestamp);
635
+ html += '<div class="time">' + t.toLocaleTimeString() + '</div>';
636
+ }
637
+ div.innerHTML = html;
638
+ messagesEl.appendChild(div);
639
+ }
640
+
641
+ function addSystemMessage(content) {
642
+ const div = document.createElement('div');
643
+ div.className = 'message system';
644
+ div.textContent = content;
645
+ messagesEl.appendChild(div);
646
+ }
647
+
648
+ function scrollBottom() {
649
+ messagesEl.scrollTop = messagesEl.scrollHeight;
650
+ }
651
+
652
+ function sendMessage() {
653
+ const text = inputEl.value.trim();
654
+ if (!text || !ws || ws.readyState !== 1) return;
655
+ ws.send(JSON.stringify({ type: 'message', content: text }));
656
+ addMessage(text, { displayName: 'You', isBot: false }, new Date().toISOString());
657
+ scrollBottom();
658
+ inputEl.value = '';
659
+ }
660
+
661
+ function escapeHtml(str) {
662
+ if (!str) return '';
663
+ return str.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
664
+ }
665
+
666
+ sendBtn.addEventListener('click', sendMessage);
667
+ inputEl.addEventListener('keydown', function(e) {
668
+ if (e.key === 'Enter') sendMessage();
669
+ // Send typing indicator
670
+ if (ws && ws.readyState === 1) {
671
+ clearTimeout(typingTimer);
672
+ typingTimer = setTimeout(function() {
673
+ ws.send(JSON.stringify({ type: 'typing' }));
674
+ }, 300);
675
+ }
676
+ });
677
+
678
+ connect();
679
+ })();
680
+ </script>
681
+ </body>
682
+ </html>`;
683
+ }
684
+ /**
685
+ * Escape HTML entities
686
+ */
687
+ escapeHtml(str) {
688
+ return str
689
+ .replace(/&/g, '&amp;')
690
+ .replace(/</g, '&lt;')
691
+ .replace(/>/g, '&gt;')
692
+ .replace(/"/g, '&quot;')
693
+ .replace(/'/g, '&#39;');
694
+ }
695
+ }
696
+ export default WebChatChannel;
697
+ //# sourceMappingURL=index.js.map