web-a2e 1.0.0

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 (295) hide show
  1. package/.clangd +5 -0
  2. package/.mcp.json +12 -0
  3. package/CLAUDE.md +362 -0
  4. package/CMakeLists.txt +774 -0
  5. package/LICENSE +21 -0
  6. package/README.md +392 -0
  7. package/build-wasm/generated/roms.cpp +2447 -0
  8. package/docker-compose.staging.yml +9 -0
  9. package/docs/basic-rom-disassembly.md +6663 -0
  10. package/docs/softswitch-comparison.md +273 -0
  11. package/docs/thunderclock-debug.md +89 -0
  12. package/examples/cube.bas +72 -0
  13. package/examples/hello.s +55 -0
  14. package/examples/scroll.s +140 -0
  15. package/package.json +18 -0
  16. package/public/assets/apple-logo-old.png +0 -0
  17. package/public/assets/apple-logo.png +0 -0
  18. package/public/assets/drive-closed-light-on.png +0 -0
  19. package/public/assets/drive-closed.png +0 -0
  20. package/public/assets/drive-open-light-on.png +0 -0
  21. package/public/assets/drive-open.png +0 -0
  22. package/public/audio-worklet.js +82 -0
  23. package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
  24. package/public/disks/ProDOS 2.4.3.po +0 -0
  25. package/public/disks/h32mb.2mg +0 -0
  26. package/public/disks/library.json +26 -0
  27. package/public/docs/llms/llm-assembler.txt +90 -0
  28. package/public/docs/llms/llm-basic-program.txt +256 -0
  29. package/public/docs/llms/llm-disk-drives.txt +72 -0
  30. package/public/docs/llms/llm-file-explorer.txt +50 -0
  31. package/public/docs/llms/llm-hard-drives.txt +80 -0
  32. package/public/docs/llms/llm-main.txt +51 -0
  33. package/public/docs/llms/llm-slot-configuration.txt +66 -0
  34. package/public/icons/icon-192.svg +4 -0
  35. package/public/icons/icon-512.svg +4 -0
  36. package/public/index.html +661 -0
  37. package/public/llms.txt +49 -0
  38. package/public/manifest.json +29 -0
  39. package/public/shaders/burnin.glsl +22 -0
  40. package/public/shaders/crt.glsl +706 -0
  41. package/public/shaders/edge.glsl +109 -0
  42. package/public/shaders/vertex.glsl +8 -0
  43. package/public/sw.js +186 -0
  44. package/roms/341-0027.bin +0 -0
  45. package/roms/341-0160-A-US-UK.bin +0 -0
  46. package/roms/341-0160-A.bin +0 -0
  47. package/roms/342-0273-A-US-UK.bin +0 -0
  48. package/roms/342-0349-B-C0-FF.bin +0 -0
  49. package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
  50. package/roms/Thunderclock Plus ROM.bin +0 -0
  51. package/scripts/generate_roms.sh +69 -0
  52. package/src/bindings/wasm_interface.cpp +1940 -0
  53. package/src/core/assembler/assembler.cpp +1239 -0
  54. package/src/core/assembler/assembler.hpp +115 -0
  55. package/src/core/audio/audio.cpp +160 -0
  56. package/src/core/audio/audio.hpp +81 -0
  57. package/src/core/basic/basic_detokenizer.cpp +436 -0
  58. package/src/core/basic/basic_detokenizer.hpp +41 -0
  59. package/src/core/basic/basic_tokenizer.cpp +286 -0
  60. package/src/core/basic/basic_tokenizer.hpp +26 -0
  61. package/src/core/basic/basic_tokens.hpp +295 -0
  62. package/src/core/cards/disk2_card.cpp +568 -0
  63. package/src/core/cards/disk2_card.hpp +316 -0
  64. package/src/core/cards/expansion_card.hpp +185 -0
  65. package/src/core/cards/mockingboard/ay8910.cpp +616 -0
  66. package/src/core/cards/mockingboard/ay8910.hpp +159 -0
  67. package/src/core/cards/mockingboard/via6522.cpp +530 -0
  68. package/src/core/cards/mockingboard/via6522.hpp +163 -0
  69. package/src/core/cards/mockingboard_card.cpp +312 -0
  70. package/src/core/cards/mockingboard_card.hpp +159 -0
  71. package/src/core/cards/mouse_card.cpp +654 -0
  72. package/src/core/cards/mouse_card.hpp +190 -0
  73. package/src/core/cards/smartport/block_device.cpp +202 -0
  74. package/src/core/cards/smartport/block_device.hpp +60 -0
  75. package/src/core/cards/smartport/smartport_card.cpp +603 -0
  76. package/src/core/cards/smartport/smartport_card.hpp +120 -0
  77. package/src/core/cards/thunderclock_card.cpp +237 -0
  78. package/src/core/cards/thunderclock_card.hpp +122 -0
  79. package/src/core/cpu/cpu6502.cpp +1609 -0
  80. package/src/core/cpu/cpu6502.hpp +203 -0
  81. package/src/core/debug/condition_evaluator.cpp +470 -0
  82. package/src/core/debug/condition_evaluator.hpp +87 -0
  83. package/src/core/disassembler/disassembler.cpp +552 -0
  84. package/src/core/disassembler/disassembler.hpp +171 -0
  85. package/src/core/disk-image/disk_image.hpp +267 -0
  86. package/src/core/disk-image/dsk_disk_image.cpp +827 -0
  87. package/src/core/disk-image/dsk_disk_image.hpp +204 -0
  88. package/src/core/disk-image/gcr_encoding.cpp +147 -0
  89. package/src/core/disk-image/gcr_encoding.hpp +78 -0
  90. package/src/core/disk-image/woz_disk_image.cpp +1049 -0
  91. package/src/core/disk-image/woz_disk_image.hpp +343 -0
  92. package/src/core/emulator.cpp +2126 -0
  93. package/src/core/emulator.hpp +434 -0
  94. package/src/core/filesystem/dos33.cpp +178 -0
  95. package/src/core/filesystem/dos33.hpp +66 -0
  96. package/src/core/filesystem/pascal.cpp +262 -0
  97. package/src/core/filesystem/pascal.hpp +87 -0
  98. package/src/core/filesystem/prodos.cpp +369 -0
  99. package/src/core/filesystem/prodos.hpp +119 -0
  100. package/src/core/input/keyboard.cpp +227 -0
  101. package/src/core/input/keyboard.hpp +111 -0
  102. package/src/core/mmu/mmu.cpp +1387 -0
  103. package/src/core/mmu/mmu.hpp +236 -0
  104. package/src/core/types.hpp +196 -0
  105. package/src/core/video/video.cpp +680 -0
  106. package/src/core/video/video.hpp +156 -0
  107. package/src/css/assembler-editor.css +1617 -0
  108. package/src/css/base.css +470 -0
  109. package/src/css/basic-debugger.css +791 -0
  110. package/src/css/basic-editor.css +792 -0
  111. package/src/css/controls.css +783 -0
  112. package/src/css/cpu-debugger.css +1413 -0
  113. package/src/css/debug-base.css +160 -0
  114. package/src/css/debug-windows.css +6455 -0
  115. package/src/css/disk-drives.css +406 -0
  116. package/src/css/documentation.css +392 -0
  117. package/src/css/file-explorer.css +867 -0
  118. package/src/css/hard-drive.css +180 -0
  119. package/src/css/layout.css +217 -0
  120. package/src/css/memory-windows.css +798 -0
  121. package/src/css/modals.css +510 -0
  122. package/src/css/monitor.css +425 -0
  123. package/src/css/release-notes.css +101 -0
  124. package/src/css/responsive.css +400 -0
  125. package/src/css/rule-builder.css +340 -0
  126. package/src/css/save-states.css +201 -0
  127. package/src/css/settings-windows.css +1231 -0
  128. package/src/css/window-switcher.css +150 -0
  129. package/src/js/agent/agent-manager.js +643 -0
  130. package/src/js/agent/agent-tools.js +293 -0
  131. package/src/js/agent/agent-version-tools.js +131 -0
  132. package/src/js/agent/assembler-tools.js +357 -0
  133. package/src/js/agent/basic-program-tools.js +894 -0
  134. package/src/js/agent/disk-tools.js +417 -0
  135. package/src/js/agent/file-explorer-tools.js +269 -0
  136. package/src/js/agent/index.js +13 -0
  137. package/src/js/agent/main-tools.js +222 -0
  138. package/src/js/agent/slot-tools.js +303 -0
  139. package/src/js/agent/smartport-tools.js +257 -0
  140. package/src/js/agent/window-tools.js +80 -0
  141. package/src/js/audio/audio-driver.js +417 -0
  142. package/src/js/audio/audio-worklet.js +85 -0
  143. package/src/js/audio/index.js +8 -0
  144. package/src/js/config/default-layout.js +34 -0
  145. package/src/js/config/version.js +8 -0
  146. package/src/js/data/apple2-rom-routines.js +577 -0
  147. package/src/js/debug/assembler-editor-window.js +2993 -0
  148. package/src/js/debug/basic-breakpoint-manager.js +529 -0
  149. package/src/js/debug/basic-program-parser.js +436 -0
  150. package/src/js/debug/basic-program-window.js +2594 -0
  151. package/src/js/debug/basic-variable-inspector.js +447 -0
  152. package/src/js/debug/breakpoint-manager.js +472 -0
  153. package/src/js/debug/cpu-debugger-window.js +2396 -0
  154. package/src/js/debug/index.js +22 -0
  155. package/src/js/debug/label-manager.js +238 -0
  156. package/src/js/debug/memory-browser-window.js +416 -0
  157. package/src/js/debug/memory-heat-map-window.js +481 -0
  158. package/src/js/debug/memory-map-window.js +206 -0
  159. package/src/js/debug/mockingboard-window.js +882 -0
  160. package/src/js/debug/mouse-card-window.js +355 -0
  161. package/src/js/debug/rule-builder-window.js +648 -0
  162. package/src/js/debug/soft-switch-window.js +458 -0
  163. package/src/js/debug/stack-viewer-window.js +221 -0
  164. package/src/js/debug/symbols.js +416 -0
  165. package/src/js/debug/trace-panel.js +291 -0
  166. package/src/js/debug/zero-page-watch-window.js +297 -0
  167. package/src/js/disk-manager/disk-drives-window.js +212 -0
  168. package/src/js/disk-manager/disk-operations.js +284 -0
  169. package/src/js/disk-manager/disk-persistence.js +301 -0
  170. package/src/js/disk-manager/disk-surface-renderer.js +388 -0
  171. package/src/js/disk-manager/drive-sounds.js +139 -0
  172. package/src/js/disk-manager/hard-drive-manager.js +481 -0
  173. package/src/js/disk-manager/hard-drive-persistence.js +187 -0
  174. package/src/js/disk-manager/hard-drive-window.js +57 -0
  175. package/src/js/disk-manager/index.js +890 -0
  176. package/src/js/display/display-settings-window.js +383 -0
  177. package/src/js/display/index.js +10 -0
  178. package/src/js/display/screen-window.js +342 -0
  179. package/src/js/display/webgl-renderer.js +705 -0
  180. package/src/js/file-explorer/disassembler.js +574 -0
  181. package/src/js/file-explorer/dos33.js +266 -0
  182. package/src/js/file-explorer/file-viewer.js +359 -0
  183. package/src/js/file-explorer/index.js +1261 -0
  184. package/src/js/file-explorer/prodos.js +549 -0
  185. package/src/js/file-explorer/utils.js +67 -0
  186. package/src/js/help/documentation-window.js +1096 -0
  187. package/src/js/help/index.js +10 -0
  188. package/src/js/help/release-notes-window.js +85 -0
  189. package/src/js/help/release-notes.js +612 -0
  190. package/src/js/input/gamepad-handler.js +176 -0
  191. package/src/js/input/index.js +12 -0
  192. package/src/js/input/input-handler.js +396 -0
  193. package/src/js/input/joystick-window.js +404 -0
  194. package/src/js/input/mouse-handler.js +99 -0
  195. package/src/js/input/text-selection.js +462 -0
  196. package/src/js/main.js +653 -0
  197. package/src/js/state/index.js +15 -0
  198. package/src/js/state/save-states-window.js +393 -0
  199. package/src/js/state/state-manager.js +409 -0
  200. package/src/js/state/state-persistence.js +218 -0
  201. package/src/js/ui/confirm.js +43 -0
  202. package/src/js/ui/disk-drive-positioner.js +347 -0
  203. package/src/js/ui/reminder-controller.js +129 -0
  204. package/src/js/ui/slot-configuration-window.js +560 -0
  205. package/src/js/ui/theme-manager.js +61 -0
  206. package/src/js/ui/toast.js +44 -0
  207. package/src/js/ui/ui-controller.js +897 -0
  208. package/src/js/ui/window-switcher.js +275 -0
  209. package/src/js/utils/basic-autocomplete.js +832 -0
  210. package/src/js/utils/basic-highlighting.js +473 -0
  211. package/src/js/utils/basic-tokenizer.js +153 -0
  212. package/src/js/utils/basic-tokens.js +117 -0
  213. package/src/js/utils/constants.js +28 -0
  214. package/src/js/utils/indexeddb-helper.js +225 -0
  215. package/src/js/utils/merlin-editor-support.js +905 -0
  216. package/src/js/utils/merlin-highlighting.js +551 -0
  217. package/src/js/utils/storage.js +125 -0
  218. package/src/js/utils/string-utils.js +19 -0
  219. package/src/js/utils/wasm-memory.js +54 -0
  220. package/src/js/windows/base-window.js +690 -0
  221. package/src/js/windows/index.js +9 -0
  222. package/src/js/windows/window-manager.js +375 -0
  223. package/tests/catch2/catch.hpp +17976 -0
  224. package/tests/common/basic_program_builder.cpp +119 -0
  225. package/tests/common/basic_program_builder.hpp +209 -0
  226. package/tests/common/disk_image_builder.cpp +444 -0
  227. package/tests/common/disk_image_builder.hpp +141 -0
  228. package/tests/common/test_helpers.hpp +118 -0
  229. package/tests/gcr/gcr-test.cpp +142 -0
  230. package/tests/integration/check-rom.js +70 -0
  231. package/tests/integration/compare-boot.js +239 -0
  232. package/tests/integration/crash-trace.js +102 -0
  233. package/tests/integration/disk-boot-test.js +264 -0
  234. package/tests/integration/memory-crash.js +108 -0
  235. package/tests/integration/nibble-read-test.js +249 -0
  236. package/tests/integration/phase-test.js +159 -0
  237. package/tests/integration/test_emulator.cpp +291 -0
  238. package/tests/integration/test_emulator_basic.cpp +91 -0
  239. package/tests/integration/test_emulator_debug.cpp +344 -0
  240. package/tests/integration/test_emulator_disk.cpp +153 -0
  241. package/tests/integration/test_emulator_state.cpp +163 -0
  242. package/tests/klaus/6502_functional_test.bin +0 -0
  243. package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
  244. package/tests/klaus/klaus_6502_test.cpp +184 -0
  245. package/tests/klaus/klaus_65c02_test.cpp +197 -0
  246. package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
  247. package/tests/thunderclock/thunderclock_test.cpp +550 -0
  248. package/tests/unit/test_assembler.cpp +521 -0
  249. package/tests/unit/test_audio.cpp +196 -0
  250. package/tests/unit/test_ay8910.cpp +311 -0
  251. package/tests/unit/test_basic_detokenizer.cpp +265 -0
  252. package/tests/unit/test_basic_tokenizer.cpp +382 -0
  253. package/tests/unit/test_block_device.cpp +259 -0
  254. package/tests/unit/test_condition_evaluator.cpp +219 -0
  255. package/tests/unit/test_cpu6502.cpp +1301 -0
  256. package/tests/unit/test_cpu_addressing.cpp +361 -0
  257. package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
  258. package/tests/unit/test_cpu_decimal.cpp +166 -0
  259. package/tests/unit/test_cpu_interrupts.cpp +285 -0
  260. package/tests/unit/test_disassembler.cpp +323 -0
  261. package/tests/unit/test_disk2_card.cpp +330 -0
  262. package/tests/unit/test_dos33.cpp +273 -0
  263. package/tests/unit/test_dsk_disk_image.cpp +315 -0
  264. package/tests/unit/test_expansion_card.cpp +178 -0
  265. package/tests/unit/test_gcr_encoding.cpp +232 -0
  266. package/tests/unit/test_keyboard.cpp +262 -0
  267. package/tests/unit/test_mmu.cpp +555 -0
  268. package/tests/unit/test_mmu_slots.cpp +323 -0
  269. package/tests/unit/test_mockingboard.cpp +352 -0
  270. package/tests/unit/test_mouse_card.cpp +386 -0
  271. package/tests/unit/test_pascal.cpp +248 -0
  272. package/tests/unit/test_prodos.cpp +259 -0
  273. package/tests/unit/test_smartport_card.cpp +321 -0
  274. package/tests/unit/test_thunderclock.cpp +354 -0
  275. package/tests/unit/test_via6522.cpp +323 -0
  276. package/tests/unit/test_video.cpp +319 -0
  277. package/tests/unit/test_woz_disk_image.cpp +257 -0
  278. package/vite.config.js +96 -0
  279. package/wiki/AI-Agent.md +372 -0
  280. package/wiki/Architecture-Overview.md +303 -0
  281. package/wiki/Audio-System.md +449 -0
  282. package/wiki/CPU-Emulation.md +477 -0
  283. package/wiki/Debugger.md +516 -0
  284. package/wiki/Disk-Drives.md +161 -0
  285. package/wiki/Disk-System-Internals.md +547 -0
  286. package/wiki/Display-Settings.md +88 -0
  287. package/wiki/Expansion-Slots.md +187 -0
  288. package/wiki/File-Explorer.md +259 -0
  289. package/wiki/Getting-Started.md +156 -0
  290. package/wiki/Home.md +69 -0
  291. package/wiki/Input-Devices.md +183 -0
  292. package/wiki/Keyboard-Shortcuts.md +158 -0
  293. package/wiki/Memory-System.md +364 -0
  294. package/wiki/Save-States.md +172 -0
  295. package/wiki/Video-Rendering.md +658 -0
@@ -0,0 +1,643 @@
1
+ /*
2
+ * agent-manager.js - AG-UI protocol client for MCP server communication
3
+ *
4
+ * Written by
5
+ * Shawn Bullock <shawn@agenticexpert.ai>
6
+ */
7
+
8
+ import { executeAgentTool } from "./agent-tools.js";
9
+ import { showConfirm } from "../ui/confirm.js";
10
+
11
+ /**
12
+ * Manages connection to MCP server via AG-UI protocol
13
+ */
14
+ export class AgentManager {
15
+ constructor() {
16
+ this.serverUrl = "http://localhost:3033";
17
+ this.serverUrlHttps = "https://localhost:3033";
18
+ this.currentProtocol = "http"; // Will be auto-detected
19
+ this.eventSource = null;
20
+ this.connected = false;
21
+ this.reconnectAttempts = 0;
22
+ this.maxReconnectAttempts = 36; // 3 minutes at 5 second intervals
23
+ this.reconnectDelay = 5000; // 5 seconds
24
+ this.onConnectionChange = null;
25
+ this.onServerAvailable = null; // Callback when heartbeat detected
26
+ this.onServerUnavailable = null; // Callback when server goes away
27
+
28
+ // Tool call state
29
+ this.activeToolCalls = new Map();
30
+
31
+ // Heartbeat polling
32
+ this.heartbeatInterval = null;
33
+ this.heartbeatCheckInterval = 15000; // 15 seconds
34
+ this.serverAvailable = false;
35
+
36
+ // Reconnection timeout
37
+ this.reconnectTimeout = null;
38
+ this.reconnectStartTime = null;
39
+ this.maxReconnectDuration = 3 * 60 * 1000; // 3 minutes
40
+ }
41
+
42
+ /**
43
+ * Start heartbeat polling to detect when MCP server becomes available
44
+ */
45
+ startHeartbeatPolling() {
46
+ if (this.heartbeatInterval) {
47
+ return; // Already polling
48
+ }
49
+
50
+ console.log("[AgentManager] Starting heartbeat polling");
51
+
52
+ // Initial check
53
+ this._checkHeartbeat();
54
+
55
+ // Then check every 15 seconds
56
+ this.heartbeatInterval = setInterval(() => {
57
+ this._checkHeartbeat();
58
+ }, this.heartbeatCheckInterval);
59
+ }
60
+
61
+ /**
62
+ * Stop heartbeat polling
63
+ */
64
+ stopHeartbeatPolling() {
65
+ if (this.heartbeatInterval) {
66
+ clearInterval(this.heartbeatInterval);
67
+ this.heartbeatInterval = null;
68
+ console.log("[AgentManager] Stopped heartbeat polling");
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Check if MCP server is available via heartbeat endpoint
74
+ */
75
+ async _checkHeartbeat() {
76
+ // Try HTTPS first, then HTTP
77
+ const protocols = [
78
+ { url: this.serverUrlHttps, protocol: "https" },
79
+ { url: this.serverUrl, protocol: "http" },
80
+ ];
81
+
82
+ for (const { url, protocol } of protocols) {
83
+ try {
84
+ const response = await fetch(`${url}/heartbeat`, {
85
+ method: "GET",
86
+ signal: AbortSignal.timeout(2000), // 2 second timeout
87
+ });
88
+
89
+ if (response.ok) {
90
+ const wasAvailable = this.serverAvailable;
91
+ this.serverAvailable = true;
92
+ this.currentProtocol = protocol;
93
+ this.serverUrl = url;
94
+
95
+ // Only log when server becomes available, not on every heartbeat
96
+ if (!wasAvailable) {
97
+ console.log(`[AgentManager] MCP server available at ${url}`);
98
+ if (this.onServerAvailable) {
99
+ this.onServerAvailable();
100
+ }
101
+ }
102
+
103
+ return; // Success, stop trying
104
+ }
105
+ } catch (error) {
106
+ // Server not reachable on this protocol, try next
107
+ continue;
108
+ }
109
+ }
110
+
111
+ // If we get here, server is not available on either protocol
112
+ const wasAvailable = this.serverAvailable;
113
+ this.serverAvailable = false;
114
+
115
+ if (wasAvailable && this.onServerUnavailable) {
116
+ console.log("[AgentManager] MCP server no longer available");
117
+ this.onServerUnavailable();
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Connect to MCP server SSE stream
123
+ */
124
+ async connect() {
125
+ // Check if already connected (EventSource is OPEN)
126
+ if (this.eventSource && this.eventSource.readyState === EventSource.OPEN) {
127
+ console.warn("[AgentManager] Already connected");
128
+ return;
129
+ }
130
+
131
+ // Clean up any existing EventSource that's not OPEN (stale connection)
132
+ if (this.eventSource) {
133
+ console.log("[AgentManager] Cleaning up stale EventSource");
134
+ this.eventSource.close();
135
+ this.eventSource = null;
136
+ }
137
+
138
+ // Detect current domain (where the emulator is running)
139
+ const domain = `${window.location.protocol}//${window.location.host}`;
140
+
141
+ console.log(`[AgentManager] Connecting to ${this.serverUrl}/events`);
142
+ console.log(`[AgentManager] Emulator domain: ${domain}`);
143
+
144
+ // Check if connection is allowed (detect 409 for single-client mode)
145
+ try {
146
+ const testResponse = await fetch(`${this.serverUrl}/events?domain=${encodeURIComponent(domain)}`, {
147
+ method: "HEAD",
148
+ signal: AbortSignal.timeout(2000),
149
+ });
150
+
151
+ // If 409, another client is connected
152
+ if (testResponse.status === 409) {
153
+ const message = await testResponse.text();
154
+ console.warn(`[AgentManager] ${message}`);
155
+
156
+ // Show confirm dialog with option to disconnect other client
157
+ const shouldDisconnect = await this._showConnectionConflictDialog(message);
158
+
159
+ if (shouldDisconnect) {
160
+ // User chose to disconnect other client
161
+ console.log("[AgentManager] Disconnecting other client and retrying...");
162
+ try {
163
+ await this.callMCPTool("disconnect_clients", {});
164
+ // Retry connection after brief delay
165
+ setTimeout(() => this.connect(), 500);
166
+ } catch (error) {
167
+ console.error("[AgentManager] Failed to disconnect other client:", error);
168
+ }
169
+ } else {
170
+ // User chose not to connect
171
+ console.log("[AgentManager] Connection cancelled by user");
172
+ }
173
+ return;
174
+ }
175
+ } catch (error) {
176
+ // HEAD request not supported or other error, proceed with EventSource anyway
177
+ console.log("[AgentManager] Connection check failed, proceeding with EventSource:", error.message);
178
+ }
179
+
180
+ // Check agent version compatibility before connecting
181
+ try {
182
+ const versionCheck = await this._checkVersionCompatibility();
183
+ if (!versionCheck.compatible) {
184
+ console.warn(`[AgentManager] Agent version ${versionCheck.agent.version} is incompatible (requires >= ${versionCheck.required.minVersion})`);
185
+
186
+ // Show warning dialog and prevent connection
187
+ await this._showVersionIncompatibleDialog(versionCheck);
188
+ console.log("[AgentManager] Connection blocked due to version incompatibility");
189
+ return;
190
+ } else {
191
+ console.log(`[AgentManager] Agent version ${versionCheck.agent.version} is compatible`);
192
+ }
193
+ } catch (error) {
194
+ console.warn("[AgentManager] Version check failed, proceeding with connection:", error.message);
195
+ }
196
+
197
+ try {
198
+ // Send domain as query parameter so MCP server can fetch llms.txt
199
+ this.eventSource = new EventSource(`${this.serverUrl}/events?domain=${encodeURIComponent(domain)}`);
200
+
201
+ this.eventSource.onopen = () => {
202
+ console.log("[AgentManager] Connected to MCP server");
203
+ this.connected = true;
204
+ this.reconnectAttempts = 0;
205
+ this.reconnectStartTime = null; // Reset reconnection window
206
+
207
+ // Stop heartbeat polling while connected
208
+ this.stopHeartbeatPolling();
209
+
210
+ if (this.onConnectionChange) {
211
+ this.onConnectionChange(true);
212
+ }
213
+ };
214
+
215
+ this.eventSource.onmessage = (e) => {
216
+ this._handleEvent(e);
217
+ };
218
+
219
+ this.eventSource.onerror = (error) => {
220
+ console.error("[AgentManager] Connection error:", error);
221
+ this.connected = false;
222
+ if (this.onConnectionChange) {
223
+ this.onConnectionChange(false);
224
+ }
225
+ this._handleConnectionError();
226
+ };
227
+
228
+ // Check connection state after a brief delay
229
+ setTimeout(() => {
230
+ if (this.eventSource && this.eventSource.readyState === EventSource.OPEN) {
231
+ if (!this.connected) {
232
+ console.log("[AgentManager] Connection established (via readyState check)");
233
+ this.connected = true;
234
+ if (this.onConnectionChange) {
235
+ this.onConnectionChange(true);
236
+ }
237
+ }
238
+ }
239
+ }, 500);
240
+
241
+ } catch (error) {
242
+ console.error("[AgentManager] Failed to create EventSource:", error);
243
+ this._scheduleReconnect();
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Show dialog when connection is rejected due to another client being connected
249
+ * @param {string} message - The rejection message from server
250
+ * @returns {Promise<boolean>} True if user wants to disconnect other client, false otherwise
251
+ */
252
+ async _showConnectionConflictDialog(message) {
253
+ return await showConfirm(
254
+ message + "\n\nWould you like to disconnect the other client and connect?",
255
+ "Disconnect and Connect"
256
+ );
257
+ }
258
+
259
+ /**
260
+ * Check if agent version is compatible with app requirements
261
+ * @returns {Promise<Object>} Version compatibility check result
262
+ */
263
+ async _checkVersionCompatibility() {
264
+ const versionInfo = await this.callMCPTool("get_version", {});
265
+
266
+ if (!versionInfo.success) {
267
+ throw new Error("Failed to get agent version");
268
+ }
269
+
270
+ // Parse versions
271
+ const parseVersion = (version) => {
272
+ const parts = version.split('.').map(p => parseInt(p, 10));
273
+ if (parts.length !== 3 || parts.some(isNaN)) {
274
+ throw new Error(`Invalid version format: ${version}`);
275
+ }
276
+ return { major: parts[0], minor: parts[1], patch: parts[2] };
277
+ };
278
+
279
+ const minVersion = "1.0.5"; // Required minimum version
280
+ const agentVersion = parseVersion(versionInfo.version);
281
+ const requiredVersion = parseVersion(minVersion);
282
+
283
+ // Compare versions
284
+ const compareVersions = (v1, v2) => {
285
+ if (v1.major !== v2.major) return v1.major - v2.major;
286
+ if (v1.minor !== v2.minor) return v1.minor - v2.minor;
287
+ return v1.patch - v2.patch;
288
+ };
289
+
290
+ const comparison = compareVersions(agentVersion, requiredVersion);
291
+ const compatible = comparison >= 0;
292
+
293
+ return {
294
+ success: true,
295
+ agent: {
296
+ name: versionInfo.name,
297
+ version: versionInfo.version,
298
+ versionNumeric: agentVersion
299
+ },
300
+ required: {
301
+ minVersion: minVersion,
302
+ minVersionNumeric: requiredVersion
303
+ },
304
+ compatible: compatible,
305
+ comparison: comparison
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Show dialog when agent version is incompatible
311
+ * @param {Object} versionCheck - Version check result
312
+ * @returns {Promise<boolean>} Always returns false (connection not allowed)
313
+ */
314
+ async _showVersionIncompatibleDialog(versionCheck) {
315
+ await showConfirm("Agent out of date. Please update the Agent to latest version to continue.", "OK");
316
+ return false; // Never allow connection with incompatible version
317
+ }
318
+
319
+ /**
320
+ * Disconnect from MCP server and abort any reconnection attempts
321
+ */
322
+ disconnect() {
323
+ if (this.eventSource) {
324
+ console.log("[AgentManager] Disconnecting from MCP server");
325
+ this.eventSource.close();
326
+ this.eventSource = null;
327
+ this.connected = false;
328
+ if (this.onConnectionChange) {
329
+ this.onConnectionChange(false);
330
+ }
331
+ }
332
+
333
+ // Clear reconnection timeout if active
334
+ if (this.reconnectTimeout) {
335
+ clearTimeout(this.reconnectTimeout);
336
+ this.reconnectTimeout = null;
337
+ }
338
+
339
+ // Reset reconnection state completely
340
+ this.reconnectAttempts = 0;
341
+ this.reconnectStartTime = null;
342
+ }
343
+
344
+ /**
345
+ * Handle incoming AG-UI event
346
+ */
347
+ _handleEvent(e) {
348
+ try {
349
+ const event = JSON.parse(e.data);
350
+
351
+ // Route event to appropriate handler
352
+ switch (event.type) {
353
+ case "TOOL_CALL_START":
354
+ this._handleToolCallStart(event);
355
+ break;
356
+ case "TOOL_CALL_ARGS":
357
+ this._handleToolCallArgs(event);
358
+ break;
359
+ case "TOOL_CALL_END":
360
+ this._handleToolCallEnd(event);
361
+ break;
362
+ case "TEXT_MESSAGE_START":
363
+ case "TEXT_MESSAGE_CONTENT":
364
+ case "TEXT_MESSAGE_END":
365
+ this._handleTextMessage(event);
366
+ break;
367
+ case "RUN_STARTED":
368
+ case "RUN_FINISHED":
369
+ case "RUN_ERROR":
370
+ this._handleRunEvent(event);
371
+ break;
372
+ case "DISCONNECT":
373
+ this._handleGracefulDisconnect(event);
374
+ break;
375
+ default:
376
+ console.log("[AgentManager] Unhandled event type:", event.type);
377
+ }
378
+ } catch (error) {
379
+ console.error("[AgentManager] Error parsing event:", error);
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Handle TOOL_CALL_START event
385
+ */
386
+ _handleToolCallStart(event) {
387
+ const { tool_call_id, tool_call_name } = event;
388
+
389
+ console.log(`[AgentManager] Tool call started: ${tool_call_name} (${tool_call_id})`);
390
+
391
+ this.activeToolCalls.set(tool_call_id, {
392
+ name: tool_call_name,
393
+ argsBuffer: "",
394
+ startTime: Date.now(),
395
+ });
396
+ }
397
+
398
+ /**
399
+ * Handle TOOL_CALL_ARGS event (streaming arguments)
400
+ */
401
+ _handleToolCallArgs(event) {
402
+ const { tool_call_id, delta } = event;
403
+
404
+ const toolCall = this.activeToolCalls.get(tool_call_id);
405
+ if (!toolCall) {
406
+ console.warn(`[AgentManager] Received args for unknown tool call: ${tool_call_id}`);
407
+ return;
408
+ }
409
+
410
+ // Accumulate arguments
411
+ toolCall.argsBuffer += delta;
412
+ }
413
+
414
+ /**
415
+ * Handle TOOL_CALL_END event (execute tool)
416
+ */
417
+ async _handleToolCallEnd(event) {
418
+ const { tool_call_id } = event;
419
+
420
+ const toolCall = this.activeToolCalls.get(tool_call_id);
421
+ if (!toolCall) {
422
+ console.warn(`[AgentManager] Received end for unknown tool call: ${tool_call_id}`);
423
+ return;
424
+ }
425
+
426
+ try {
427
+ // Parse accumulated arguments
428
+ const args = toolCall.argsBuffer ? JSON.parse(toolCall.argsBuffer) : {};
429
+
430
+ console.log(`[AgentManager] Executing tool: ${toolCall.name}`, args);
431
+
432
+ // Execute the tool
433
+ const result = await executeAgentTool(toolCall.name, args);
434
+
435
+ // Send result back to MCP server
436
+ await this._sendToolResult(tool_call_id, result);
437
+
438
+ console.log(`[AgentManager] Tool completed: ${toolCall.name} (${Date.now() - toolCall.startTime}ms)`);
439
+
440
+ } catch (error) {
441
+ console.error(`[AgentManager] Tool execution failed:`, error);
442
+
443
+ // Send error result
444
+ await this._sendToolResult(tool_call_id, {
445
+ error: error.message,
446
+ success: false,
447
+ });
448
+ } finally {
449
+ // Clean up
450
+ this.activeToolCalls.delete(tool_call_id);
451
+ }
452
+ }
453
+
454
+ /**
455
+ * Handle text message events (optional logging)
456
+ */
457
+ _handleTextMessage(event) {
458
+ // For now, just log text messages
459
+ if (event.type === "TEXT_MESSAGE_CONTENT") {
460
+ console.log(`[AgentManager] Message: ${event.delta}`);
461
+ }
462
+ }
463
+
464
+ /**
465
+ * Handle run lifecycle events
466
+ */
467
+ _handleRunEvent(event) {
468
+ console.log(`[AgentManager] ${event.type}:`, event.run_id || event.error || "");
469
+ }
470
+
471
+ /**
472
+ * Handle graceful disconnect from server
473
+ */
474
+ _handleGracefulDisconnect(event) {
475
+ console.log(`[AgentManager] Graceful disconnect: ${event.reason}`);
476
+
477
+ // Close connection cleanly
478
+ if (this.eventSource) {
479
+ this.eventSource.close();
480
+ this.eventSource = null;
481
+ }
482
+
483
+ this.connected = false;
484
+
485
+ // Don't trigger reconnection - this was intentional
486
+ this.reconnectStartTime = null;
487
+ this.reconnectAttempts = 0;
488
+
489
+ // Update UI to show disconnected (not broken)
490
+ if (this.onConnectionChange) {
491
+ this.onConnectionChange(false);
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Send TOOL_CALL_RESULT back to MCP server
497
+ */
498
+ async _sendToolResult(toolCallId, content) {
499
+ try {
500
+ const response = await fetch(`${this.serverUrl}/tool-result`, {
501
+ method: "POST",
502
+ headers: {
503
+ "Content-Type": "application/json",
504
+ },
505
+ body: JSON.stringify({
506
+ type: "TOOL_CALL_RESULT",
507
+ tool_call_id: toolCallId,
508
+ content: typeof content === "string" ? content : JSON.stringify(content),
509
+ }),
510
+ });
511
+
512
+ if (!response.ok) {
513
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
514
+ }
515
+
516
+ console.log(`[AgentManager] Tool result sent: ${toolCallId}`);
517
+
518
+ } catch (error) {
519
+ console.error("[AgentManager] Failed to send tool result:", error);
520
+ throw error;
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Handle connection errors
526
+ */
527
+ _handleConnectionError() {
528
+ this.disconnect();
529
+
530
+ // Start reconnection window if not already started
531
+ if (!this.reconnectStartTime) {
532
+ this.reconnectStartTime = Date.now();
533
+ console.log("[AgentManager] Starting 3-minute reconnection window");
534
+ }
535
+
536
+ this._scheduleReconnect();
537
+ }
538
+
539
+ /**
540
+ * Schedule reconnection attempt
541
+ */
542
+ _scheduleReconnect() {
543
+ // Check if we've exceeded the 3-minute reconnection window
544
+ if (this.reconnectStartTime) {
545
+ const elapsed = Date.now() - this.reconnectStartTime;
546
+ if (elapsed >= this.maxReconnectDuration) {
547
+ console.error("[AgentManager] 3-minute reconnection window expired. Hiding button.");
548
+ this.reconnectStartTime = null;
549
+ this.reconnectAttempts = 0;
550
+
551
+ // Mark server as unavailable and notify UI to hide button
552
+ this.serverAvailable = false;
553
+ if (this.onServerUnavailable) {
554
+ this.onServerUnavailable();
555
+ }
556
+
557
+ // Resume heartbeat polling to detect when server comes back
558
+ this.startHeartbeatPolling();
559
+ return;
560
+ }
561
+ }
562
+
563
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
564
+ console.error("[AgentManager] Max reconnect attempts reached within window.");
565
+ this.reconnectStartTime = null;
566
+ this.reconnectAttempts = 0;
567
+
568
+ // Mark server as unavailable and hide button
569
+ this.serverAvailable = false;
570
+ if (this.onServerUnavailable) {
571
+ this.onServerUnavailable();
572
+ }
573
+
574
+ // Resume heartbeat polling
575
+ this.startHeartbeatPolling();
576
+ return;
577
+ }
578
+
579
+ this.reconnectAttempts++;
580
+
581
+ console.log(`[AgentManager] Reconnecting in ${this.reconnectDelay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
582
+
583
+ this.reconnectTimeout = setTimeout(() => {
584
+ this.connect();
585
+ }, this.reconnectDelay);
586
+ }
587
+
588
+ /**
589
+ * Get connection status
590
+ */
591
+ isConnected() {
592
+ return this.eventSource && this.eventSource.readyState === EventSource.OPEN;
593
+ }
594
+
595
+ /**
596
+ * Get current state for UI
597
+ * @returns {Object} State object with serverAvailable, connected, reconnecting flags
598
+ */
599
+ getState() {
600
+ return {
601
+ serverAvailable: this.serverAvailable,
602
+ connected: this.isConnected(),
603
+ reconnecting: this.reconnectStartTime !== null,
604
+ protocol: this.currentProtocol,
605
+ };
606
+ }
607
+
608
+ /**
609
+ * Call an MCP tool on the server
610
+ * @param {string} toolName - Name of the tool to call
611
+ * @param {Object} args - Tool arguments
612
+ * @returns {Promise<any>} Tool result
613
+ */
614
+ async callMCPTool(toolName, args = {}) {
615
+ if (!this.serverAvailable) {
616
+ throw new Error("MCP server not available");
617
+ }
618
+
619
+ try {
620
+ const response = await fetch(`${this.serverUrl}/call-tool`, {
621
+ method: "POST",
622
+ headers: {
623
+ "Content-Type": "application/json",
624
+ },
625
+ body: JSON.stringify({
626
+ tool: toolName,
627
+ args: args,
628
+ }),
629
+ });
630
+
631
+ if (!response.ok) {
632
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
633
+ }
634
+
635
+ const result = await response.json();
636
+ return result;
637
+
638
+ } catch (error) {
639
+ console.error(`[AgentManager] Failed to call MCP tool ${toolName}:`, error);
640
+ throw error;
641
+ }
642
+ }
643
+ }