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.
- package/.clangd +5 -0
- package/.mcp.json +12 -0
- package/CLAUDE.md +362 -0
- package/CMakeLists.txt +774 -0
- package/LICENSE +21 -0
- package/README.md +392 -0
- package/build-wasm/generated/roms.cpp +2447 -0
- package/docker-compose.staging.yml +9 -0
- package/docs/basic-rom-disassembly.md +6663 -0
- package/docs/softswitch-comparison.md +273 -0
- package/docs/thunderclock-debug.md +89 -0
- package/examples/cube.bas +72 -0
- package/examples/hello.s +55 -0
- package/examples/scroll.s +140 -0
- package/package.json +18 -0
- package/public/assets/apple-logo-old.png +0 -0
- package/public/assets/apple-logo.png +0 -0
- package/public/assets/drive-closed-light-on.png +0 -0
- package/public/assets/drive-closed.png +0 -0
- package/public/assets/drive-open-light-on.png +0 -0
- package/public/assets/drive-open.png +0 -0
- package/public/audio-worklet.js +82 -0
- package/public/disks/Apple DOS 3.3 January 1983.dsk +0 -0
- package/public/disks/ProDOS 2.4.3.po +0 -0
- package/public/disks/h32mb.2mg +0 -0
- package/public/disks/library.json +26 -0
- package/public/docs/llms/llm-assembler.txt +90 -0
- package/public/docs/llms/llm-basic-program.txt +256 -0
- package/public/docs/llms/llm-disk-drives.txt +72 -0
- package/public/docs/llms/llm-file-explorer.txt +50 -0
- package/public/docs/llms/llm-hard-drives.txt +80 -0
- package/public/docs/llms/llm-main.txt +51 -0
- package/public/docs/llms/llm-slot-configuration.txt +66 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/index.html +661 -0
- package/public/llms.txt +49 -0
- package/public/manifest.json +29 -0
- package/public/shaders/burnin.glsl +22 -0
- package/public/shaders/crt.glsl +706 -0
- package/public/shaders/edge.glsl +109 -0
- package/public/shaders/vertex.glsl +8 -0
- package/public/sw.js +186 -0
- package/roms/341-0027.bin +0 -0
- package/roms/341-0160-A-US-UK.bin +0 -0
- package/roms/341-0160-A.bin +0 -0
- package/roms/342-0273-A-US-UK.bin +0 -0
- package/roms/342-0349-B-C0-FF.bin +0 -0
- package/roms/Apple Mouse Interface Card ROM - 342-0270-C.bin +0 -0
- package/roms/Thunderclock Plus ROM.bin +0 -0
- package/scripts/generate_roms.sh +69 -0
- package/src/bindings/wasm_interface.cpp +1940 -0
- package/src/core/assembler/assembler.cpp +1239 -0
- package/src/core/assembler/assembler.hpp +115 -0
- package/src/core/audio/audio.cpp +160 -0
- package/src/core/audio/audio.hpp +81 -0
- package/src/core/basic/basic_detokenizer.cpp +436 -0
- package/src/core/basic/basic_detokenizer.hpp +41 -0
- package/src/core/basic/basic_tokenizer.cpp +286 -0
- package/src/core/basic/basic_tokenizer.hpp +26 -0
- package/src/core/basic/basic_tokens.hpp +295 -0
- package/src/core/cards/disk2_card.cpp +568 -0
- package/src/core/cards/disk2_card.hpp +316 -0
- package/src/core/cards/expansion_card.hpp +185 -0
- package/src/core/cards/mockingboard/ay8910.cpp +616 -0
- package/src/core/cards/mockingboard/ay8910.hpp +159 -0
- package/src/core/cards/mockingboard/via6522.cpp +530 -0
- package/src/core/cards/mockingboard/via6522.hpp +163 -0
- package/src/core/cards/mockingboard_card.cpp +312 -0
- package/src/core/cards/mockingboard_card.hpp +159 -0
- package/src/core/cards/mouse_card.cpp +654 -0
- package/src/core/cards/mouse_card.hpp +190 -0
- package/src/core/cards/smartport/block_device.cpp +202 -0
- package/src/core/cards/smartport/block_device.hpp +60 -0
- package/src/core/cards/smartport/smartport_card.cpp +603 -0
- package/src/core/cards/smartport/smartport_card.hpp +120 -0
- package/src/core/cards/thunderclock_card.cpp +237 -0
- package/src/core/cards/thunderclock_card.hpp +122 -0
- package/src/core/cpu/cpu6502.cpp +1609 -0
- package/src/core/cpu/cpu6502.hpp +203 -0
- package/src/core/debug/condition_evaluator.cpp +470 -0
- package/src/core/debug/condition_evaluator.hpp +87 -0
- package/src/core/disassembler/disassembler.cpp +552 -0
- package/src/core/disassembler/disassembler.hpp +171 -0
- package/src/core/disk-image/disk_image.hpp +267 -0
- package/src/core/disk-image/dsk_disk_image.cpp +827 -0
- package/src/core/disk-image/dsk_disk_image.hpp +204 -0
- package/src/core/disk-image/gcr_encoding.cpp +147 -0
- package/src/core/disk-image/gcr_encoding.hpp +78 -0
- package/src/core/disk-image/woz_disk_image.cpp +1049 -0
- package/src/core/disk-image/woz_disk_image.hpp +343 -0
- package/src/core/emulator.cpp +2126 -0
- package/src/core/emulator.hpp +434 -0
- package/src/core/filesystem/dos33.cpp +178 -0
- package/src/core/filesystem/dos33.hpp +66 -0
- package/src/core/filesystem/pascal.cpp +262 -0
- package/src/core/filesystem/pascal.hpp +87 -0
- package/src/core/filesystem/prodos.cpp +369 -0
- package/src/core/filesystem/prodos.hpp +119 -0
- package/src/core/input/keyboard.cpp +227 -0
- package/src/core/input/keyboard.hpp +111 -0
- package/src/core/mmu/mmu.cpp +1387 -0
- package/src/core/mmu/mmu.hpp +236 -0
- package/src/core/types.hpp +196 -0
- package/src/core/video/video.cpp +680 -0
- package/src/core/video/video.hpp +156 -0
- package/src/css/assembler-editor.css +1617 -0
- package/src/css/base.css +470 -0
- package/src/css/basic-debugger.css +791 -0
- package/src/css/basic-editor.css +792 -0
- package/src/css/controls.css +783 -0
- package/src/css/cpu-debugger.css +1413 -0
- package/src/css/debug-base.css +160 -0
- package/src/css/debug-windows.css +6455 -0
- package/src/css/disk-drives.css +406 -0
- package/src/css/documentation.css +392 -0
- package/src/css/file-explorer.css +867 -0
- package/src/css/hard-drive.css +180 -0
- package/src/css/layout.css +217 -0
- package/src/css/memory-windows.css +798 -0
- package/src/css/modals.css +510 -0
- package/src/css/monitor.css +425 -0
- package/src/css/release-notes.css +101 -0
- package/src/css/responsive.css +400 -0
- package/src/css/rule-builder.css +340 -0
- package/src/css/save-states.css +201 -0
- package/src/css/settings-windows.css +1231 -0
- package/src/css/window-switcher.css +150 -0
- package/src/js/agent/agent-manager.js +643 -0
- package/src/js/agent/agent-tools.js +293 -0
- package/src/js/agent/agent-version-tools.js +131 -0
- package/src/js/agent/assembler-tools.js +357 -0
- package/src/js/agent/basic-program-tools.js +894 -0
- package/src/js/agent/disk-tools.js +417 -0
- package/src/js/agent/file-explorer-tools.js +269 -0
- package/src/js/agent/index.js +13 -0
- package/src/js/agent/main-tools.js +222 -0
- package/src/js/agent/slot-tools.js +303 -0
- package/src/js/agent/smartport-tools.js +257 -0
- package/src/js/agent/window-tools.js +80 -0
- package/src/js/audio/audio-driver.js +417 -0
- package/src/js/audio/audio-worklet.js +85 -0
- package/src/js/audio/index.js +8 -0
- package/src/js/config/default-layout.js +34 -0
- package/src/js/config/version.js +8 -0
- package/src/js/data/apple2-rom-routines.js +577 -0
- package/src/js/debug/assembler-editor-window.js +2993 -0
- package/src/js/debug/basic-breakpoint-manager.js +529 -0
- package/src/js/debug/basic-program-parser.js +436 -0
- package/src/js/debug/basic-program-window.js +2594 -0
- package/src/js/debug/basic-variable-inspector.js +447 -0
- package/src/js/debug/breakpoint-manager.js +472 -0
- package/src/js/debug/cpu-debugger-window.js +2396 -0
- package/src/js/debug/index.js +22 -0
- package/src/js/debug/label-manager.js +238 -0
- package/src/js/debug/memory-browser-window.js +416 -0
- package/src/js/debug/memory-heat-map-window.js +481 -0
- package/src/js/debug/memory-map-window.js +206 -0
- package/src/js/debug/mockingboard-window.js +882 -0
- package/src/js/debug/mouse-card-window.js +355 -0
- package/src/js/debug/rule-builder-window.js +648 -0
- package/src/js/debug/soft-switch-window.js +458 -0
- package/src/js/debug/stack-viewer-window.js +221 -0
- package/src/js/debug/symbols.js +416 -0
- package/src/js/debug/trace-panel.js +291 -0
- package/src/js/debug/zero-page-watch-window.js +297 -0
- package/src/js/disk-manager/disk-drives-window.js +212 -0
- package/src/js/disk-manager/disk-operations.js +284 -0
- package/src/js/disk-manager/disk-persistence.js +301 -0
- package/src/js/disk-manager/disk-surface-renderer.js +388 -0
- package/src/js/disk-manager/drive-sounds.js +139 -0
- package/src/js/disk-manager/hard-drive-manager.js +481 -0
- package/src/js/disk-manager/hard-drive-persistence.js +187 -0
- package/src/js/disk-manager/hard-drive-window.js +57 -0
- package/src/js/disk-manager/index.js +890 -0
- package/src/js/display/display-settings-window.js +383 -0
- package/src/js/display/index.js +10 -0
- package/src/js/display/screen-window.js +342 -0
- package/src/js/display/webgl-renderer.js +705 -0
- package/src/js/file-explorer/disassembler.js +574 -0
- package/src/js/file-explorer/dos33.js +266 -0
- package/src/js/file-explorer/file-viewer.js +359 -0
- package/src/js/file-explorer/index.js +1261 -0
- package/src/js/file-explorer/prodos.js +549 -0
- package/src/js/file-explorer/utils.js +67 -0
- package/src/js/help/documentation-window.js +1096 -0
- package/src/js/help/index.js +10 -0
- package/src/js/help/release-notes-window.js +85 -0
- package/src/js/help/release-notes.js +612 -0
- package/src/js/input/gamepad-handler.js +176 -0
- package/src/js/input/index.js +12 -0
- package/src/js/input/input-handler.js +396 -0
- package/src/js/input/joystick-window.js +404 -0
- package/src/js/input/mouse-handler.js +99 -0
- package/src/js/input/text-selection.js +462 -0
- package/src/js/main.js +653 -0
- package/src/js/state/index.js +15 -0
- package/src/js/state/save-states-window.js +393 -0
- package/src/js/state/state-manager.js +409 -0
- package/src/js/state/state-persistence.js +218 -0
- package/src/js/ui/confirm.js +43 -0
- package/src/js/ui/disk-drive-positioner.js +347 -0
- package/src/js/ui/reminder-controller.js +129 -0
- package/src/js/ui/slot-configuration-window.js +560 -0
- package/src/js/ui/theme-manager.js +61 -0
- package/src/js/ui/toast.js +44 -0
- package/src/js/ui/ui-controller.js +897 -0
- package/src/js/ui/window-switcher.js +275 -0
- package/src/js/utils/basic-autocomplete.js +832 -0
- package/src/js/utils/basic-highlighting.js +473 -0
- package/src/js/utils/basic-tokenizer.js +153 -0
- package/src/js/utils/basic-tokens.js +117 -0
- package/src/js/utils/constants.js +28 -0
- package/src/js/utils/indexeddb-helper.js +225 -0
- package/src/js/utils/merlin-editor-support.js +905 -0
- package/src/js/utils/merlin-highlighting.js +551 -0
- package/src/js/utils/storage.js +125 -0
- package/src/js/utils/string-utils.js +19 -0
- package/src/js/utils/wasm-memory.js +54 -0
- package/src/js/windows/base-window.js +690 -0
- package/src/js/windows/index.js +9 -0
- package/src/js/windows/window-manager.js +375 -0
- package/tests/catch2/catch.hpp +17976 -0
- package/tests/common/basic_program_builder.cpp +119 -0
- package/tests/common/basic_program_builder.hpp +209 -0
- package/tests/common/disk_image_builder.cpp +444 -0
- package/tests/common/disk_image_builder.hpp +141 -0
- package/tests/common/test_helpers.hpp +118 -0
- package/tests/gcr/gcr-test.cpp +142 -0
- package/tests/integration/check-rom.js +70 -0
- package/tests/integration/compare-boot.js +239 -0
- package/tests/integration/crash-trace.js +102 -0
- package/tests/integration/disk-boot-test.js +264 -0
- package/tests/integration/memory-crash.js +108 -0
- package/tests/integration/nibble-read-test.js +249 -0
- package/tests/integration/phase-test.js +159 -0
- package/tests/integration/test_emulator.cpp +291 -0
- package/tests/integration/test_emulator_basic.cpp +91 -0
- package/tests/integration/test_emulator_debug.cpp +344 -0
- package/tests/integration/test_emulator_disk.cpp +153 -0
- package/tests/integration/test_emulator_state.cpp +163 -0
- package/tests/klaus/6502_functional_test.bin +0 -0
- package/tests/klaus/65C02_extended_opcodes_test.bin +0 -0
- package/tests/klaus/klaus_6502_test.cpp +184 -0
- package/tests/klaus/klaus_65c02_test.cpp +197 -0
- package/tests/thunderclock/thunderclock_mmu_test.cpp +304 -0
- package/tests/thunderclock/thunderclock_test.cpp +550 -0
- package/tests/unit/test_assembler.cpp +521 -0
- package/tests/unit/test_audio.cpp +196 -0
- package/tests/unit/test_ay8910.cpp +311 -0
- package/tests/unit/test_basic_detokenizer.cpp +265 -0
- package/tests/unit/test_basic_tokenizer.cpp +382 -0
- package/tests/unit/test_block_device.cpp +259 -0
- package/tests/unit/test_condition_evaluator.cpp +219 -0
- package/tests/unit/test_cpu6502.cpp +1301 -0
- package/tests/unit/test_cpu_addressing.cpp +361 -0
- package/tests/unit/test_cpu_cycle_counts.cpp +409 -0
- package/tests/unit/test_cpu_decimal.cpp +166 -0
- package/tests/unit/test_cpu_interrupts.cpp +285 -0
- package/tests/unit/test_disassembler.cpp +323 -0
- package/tests/unit/test_disk2_card.cpp +330 -0
- package/tests/unit/test_dos33.cpp +273 -0
- package/tests/unit/test_dsk_disk_image.cpp +315 -0
- package/tests/unit/test_expansion_card.cpp +178 -0
- package/tests/unit/test_gcr_encoding.cpp +232 -0
- package/tests/unit/test_keyboard.cpp +262 -0
- package/tests/unit/test_mmu.cpp +555 -0
- package/tests/unit/test_mmu_slots.cpp +323 -0
- package/tests/unit/test_mockingboard.cpp +352 -0
- package/tests/unit/test_mouse_card.cpp +386 -0
- package/tests/unit/test_pascal.cpp +248 -0
- package/tests/unit/test_prodos.cpp +259 -0
- package/tests/unit/test_smartport_card.cpp +321 -0
- package/tests/unit/test_thunderclock.cpp +354 -0
- package/tests/unit/test_via6522.cpp +323 -0
- package/tests/unit/test_video.cpp +319 -0
- package/tests/unit/test_woz_disk_image.cpp +257 -0
- package/vite.config.js +96 -0
- package/wiki/AI-Agent.md +372 -0
- package/wiki/Architecture-Overview.md +303 -0
- package/wiki/Audio-System.md +449 -0
- package/wiki/CPU-Emulation.md +477 -0
- package/wiki/Debugger.md +516 -0
- package/wiki/Disk-Drives.md +161 -0
- package/wiki/Disk-System-Internals.md +547 -0
- package/wiki/Display-Settings.md +88 -0
- package/wiki/Expansion-Slots.md +187 -0
- package/wiki/File-Explorer.md +259 -0
- package/wiki/Getting-Started.md +156 -0
- package/wiki/Home.md +69 -0
- package/wiki/Input-Devices.md +183 -0
- package/wiki/Keyboard-Shortcuts.md +158 -0
- package/wiki/Memory-System.md +364 -0
- package/wiki/Save-States.md +172 -0
- 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
|
+
}
|