@mazees/mark 5.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 (227) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +209 -0
  3. package/bin/mark.js +27 -0
  4. package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
  5. package/out/renderer/assets/index-G2fADWO-.js +4768 -0
  6. package/out/renderer/assets/index-f3mVse3c.css +1 -0
  7. package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
  8. package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
  9. package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
  10. package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
  11. package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
  12. package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
  13. package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
  14. package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
  15. package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
  16. package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
  17. package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
  18. package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
  19. package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
  20. package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
  21. package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
  22. package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
  23. package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
  24. package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
  25. package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
  26. package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
  27. package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
  28. package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
  29. package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
  30. package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
  31. package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
  32. package/out/renderer/favicon.ico +0 -0
  33. package/out/renderer/icon-256.png +0 -0
  34. package/out/renderer/icon.svg +1 -0
  35. package/out/renderer/index.html +25 -0
  36. package/out/renderer/manifest.json +25 -0
  37. package/out/renderer/robot-simulator.html +1640 -0
  38. package/package.json +106 -0
  39. package/src/cli/index.js +332 -0
  40. package/src/cli/monitor.js +413 -0
  41. package/src/cli/theme.js +347 -0
  42. package/src/launcher/MarkTray.cs +227 -0
  43. package/src/main/browser-agent.js +1132 -0
  44. package/src/main/git-service.js +90 -0
  45. package/src/main/google/google-calendar.js +69 -0
  46. package/src/main/google/google-drive.js +263 -0
  47. package/src/main/google/google-gmail.js +131 -0
  48. package/src/main/google/google-service.js +241 -0
  49. package/src/main/node-tools.js +23 -0
  50. package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
  51. package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
  52. package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
  53. package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
  54. package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
  55. package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
  56. package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
  57. package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
  58. package/src/main/plugins/plugin-loader.js +306 -0
  59. package/src/main/skills/skill-manager.js +265 -0
  60. package/src/main/syntax-validator.js +455 -0
  61. package/src/main/task-daemon.js +117 -0
  62. package/src/main/telegram/telegram-service.js +835 -0
  63. package/src/main/tools/browser-tools.js +516 -0
  64. package/src/main/tools/file-tools.js +1126 -0
  65. package/src/main/tools/git-tools.js +79 -0
  66. package/src/main/tools/google-tools.js +566 -0
  67. package/src/main/tools/index.js +36 -0
  68. package/src/main/tools/media-tools.js +42 -0
  69. package/src/main/tools/system-tools.js +295 -0
  70. package/src/main/tools/task-tools.js +73 -0
  71. package/src/main/tools/telegram-tools.js +101 -0
  72. package/src/renderer/index.html +24 -0
  73. package/src/renderer/public/favicon.ico +0 -0
  74. package/src/renderer/public/icon-256.png +0 -0
  75. package/src/renderer/public/icon.svg +1 -0
  76. package/src/renderer/public/manifest.json +25 -0
  77. package/src/renderer/public/robot-simulator.html +1640 -0
  78. package/src/renderer/src/App.jsx +304 -0
  79. package/src/renderer/src/api/ai/awareness.js +294 -0
  80. package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
  81. package/src/renderer/src/api/ai/contextCompactor.js +129 -0
  82. package/src/renderer/src/api/ai/contextManager.js +1130 -0
  83. package/src/renderer/src/api/ai/core.js +167 -0
  84. package/src/renderer/src/api/ai/index.js +4 -0
  85. package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
  86. package/src/renderer/src/api/ai/persona.js +115 -0
  87. package/src/renderer/src/api/ai/planning.js +317 -0
  88. package/src/renderer/src/api/ai/relationship.js +177 -0
  89. package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
  90. package/src/renderer/src/api/ai/tools.js +167 -0
  91. package/src/renderer/src/api/ai/utils.js +359 -0
  92. package/src/renderer/src/api/db.js +1013 -0
  93. package/src/renderer/src/api/oramaStore.js +592 -0
  94. package/src/renderer/src/api/ragPipeline.js +87 -0
  95. package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
  96. package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
  97. package/src/renderer/src/api/subagent/subagentStore.js +173 -0
  98. package/src/renderer/src/api/taskExecutor.js +18 -0
  99. package/src/renderer/src/api/taskStore.js +315 -0
  100. package/src/renderer/src/api/tools/core-tools.js +707 -0
  101. package/src/renderer/src/api/tools/index.js +151 -0
  102. package/src/renderer/src/api/turnPairMigrator.js +200 -0
  103. package/src/renderer/src/api/vectorMemory.js +136 -0
  104. package/src/renderer/src/api/wakeWord.js +101 -0
  105. package/src/renderer/src/api/web-bridge.js +885 -0
  106. package/src/renderer/src/api/webSpeech.js +178 -0
  107. package/src/renderer/src/api/workspaceRag.js +76 -0
  108. package/src/renderer/src/assets/electron.svg +10 -0
  109. package/src/renderer/src/assets/icon.svg +1 -0
  110. package/src/renderer/src/assets/main.css +589 -0
  111. package/src/renderer/src/assets/music-cover.png +0 -0
  112. package/src/renderer/src/assets/wavy-lines.svg +25 -0
  113. package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
  114. package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
  115. package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
  116. package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
  117. package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
  118. package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
  119. package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
  120. package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
  121. package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
  122. package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
  123. package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
  124. package/src/renderer/src/components/Chat/index.js +11 -0
  125. package/src/renderer/src/components/ChatList.jsx +434 -0
  126. package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
  127. package/src/renderer/src/components/Versions.jsx +15 -0
  128. package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
  129. package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
  130. package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
  131. package/src/renderer/src/components/config/CameraSection.jsx +100 -0
  132. package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
  133. package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
  134. package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
  135. package/src/renderer/src/components/config/index.js +5 -0
  136. package/src/renderer/src/components/core/Avatar.jsx +2197 -0
  137. package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
  138. package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
  139. package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
  140. package/src/renderer/src/components/core/HoloCard.jsx +64 -0
  141. package/src/renderer/src/components/core/InputBar.jsx +1346 -0
  142. package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
  143. package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
  144. package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
  145. package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
  146. package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
  147. package/src/renderer/src/components/core/native-skills.js +43 -0
  148. package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
  149. package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
  150. package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
  151. package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
  152. package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
  153. package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
  154. package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
  155. package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
  156. package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
  157. package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
  158. package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
  159. package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
  160. package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
  161. package/src/renderer/src/contexts/ChatContext.jsx +61 -0
  162. package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
  163. package/src/renderer/src/data/faqData.js +202 -0
  164. package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
  165. package/src/renderer/src/hooks/agent/index.js +4 -0
  166. package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
  167. package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
  168. package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
  169. package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
  170. package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
  171. package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
  172. package/src/renderer/src/hooks/useAwareness.js +289 -0
  173. package/src/renderer/src/hooks/useChatArchiver.js +52 -0
  174. package/src/renderer/src/hooks/useConfirm.jsx +68 -0
  175. package/src/renderer/src/hooks/useMarkAgent.js +454 -0
  176. package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
  177. package/src/renderer/src/hooks/useVAD.js +511 -0
  178. package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
  179. package/src/renderer/src/main.jsx +12 -0
  180. package/src/renderer/src/pages/ChatStudio.jsx +922 -0
  181. package/src/renderer/src/pages/Configuration.jsx +360 -0
  182. package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
  183. package/src/renderer/src/pages/Guidebook.jsx +917 -0
  184. package/src/renderer/src/pages/Knowledge.jsx +241 -0
  185. package/src/renderer/src/pages/MarkHome.jsx +491 -0
  186. package/src/renderer/src/pages/NeuralCore.jsx +786 -0
  187. package/src/renderer/src/pages/Plugins.jsx +630 -0
  188. package/src/renderer/src/pages/SkillEditor.jsx +380 -0
  189. package/src/renderer/src/pages/Skills.jsx +219 -0
  190. package/src/renderer/src/pages/Subagents.jsx +342 -0
  191. package/src/renderer/src/pages/TelegramBot.jsx +343 -0
  192. package/src/renderer/src/utils/chatContentParser.js +203 -0
  193. package/src/renderer/src/utils/toolClusters.js +201 -0
  194. package/src/server/bin/sha3_wasm_bg.wasm +0 -0
  195. package/src/server/config-manager.js +57 -0
  196. package/src/server/index.js +195 -0
  197. package/src/server/launcher.js +87 -0
  198. package/src/server/memory/db-store.js +758 -0
  199. package/src/server/memory/orama-store.js +177 -0
  200. package/src/server/memory/vector-engine.js +109 -0
  201. package/src/server/routes/ai.routes.js +280 -0
  202. package/src/server/routes/awareness.routes.js +62 -0
  203. package/src/server/routes/chat.routes.js +61 -0
  204. package/src/server/routes/config.routes.js +41 -0
  205. package/src/server/routes/index.js +35 -0
  206. package/src/server/routes/integrations.routes.js +116 -0
  207. package/src/server/routes/memory.routes.js +256 -0
  208. package/src/server/routes/plugins.routes.js +72 -0
  209. package/src/server/routes/skills.routes.js +139 -0
  210. package/src/server/routes/subagents.routes.js +70 -0
  211. package/src/server/routes/tasks.routes.js +175 -0
  212. package/src/server/routes/workspace.routes.js +127 -0
  213. package/src/server/services/ai/ai-utils.js +161 -0
  214. package/src/server/services/ai/openai-provider.js +757 -0
  215. package/src/server/services/ai/web-provider.js +729 -0
  216. package/src/server/services/ai-bridge.js +46 -0
  217. package/src/server/services/deepseek-web.js +956 -0
  218. package/src/server/services/gemini-web.js +170 -0
  219. package/src/server/services/notification-service.js +91 -0
  220. package/src/server/services/updater.js +125 -0
  221. package/src/server/tools/awareness-tracker.js +292 -0
  222. package/src/server/tools/group-tools.js +1164 -0
  223. package/src/server/tools/media-tools.js +244 -0
  224. package/src/server/tools/pc-agent.js +327 -0
  225. package/src/server/tools/screen-service.js +51 -0
  226. package/src/server/ws-hub.js +168 -0
  227. package/src/shared/parsers/mark-tag-parser.js +275 -0
@@ -0,0 +1,885 @@
1
+ /**
2
+ * Web Bridge Adapter untuk MARK WebUI.
3
+ * Menghubungkan antarmuka React secara langsung ke Node.js Core Backend via REST API & WebSocket.
4
+ */
5
+
6
+ const isBrowser =
7
+ typeof window !== 'undefined' &&
8
+ window.location?.origin &&
9
+ !window.location.origin.startsWith('file:')
10
+
11
+ export const SERVER_CONFIG = {
12
+ host:
13
+ typeof window !== 'undefined' && window.location?.hostname
14
+ ? window.location.hostname
15
+ : 'localhost',
16
+ port:
17
+ typeof window !== 'undefined' && window.location?.port
18
+ ? Number(window.location.port) || 3000
19
+ : 3000,
20
+ apiBase: isBrowser ? window.location.origin : 'http://localhost:3000',
21
+ wsProtocol: isBrowser && window.location.protocol === 'https:' ? 'wss:' : 'ws:',
22
+ wsHost: isBrowser ? window.location.host : 'localhost:3000',
23
+ get wsBase() {
24
+ return `${this.wsProtocol}//${this.wsHost}/stream`
25
+ }
26
+ }
27
+
28
+ export const SERVER_HOST = SERVER_CONFIG.host
29
+ export const SERVER_PORT = SERVER_CONFIG.port
30
+ export const API_BASE = SERVER_CONFIG.apiBase
31
+ export const WS_PROTOCOL = SERVER_CONFIG.wsProtocol
32
+ export const WS_HOST = SERVER_CONFIG.wsHost
33
+ export const WS_BASE = SERVER_CONFIG.wsBase
34
+
35
+ let ws = null
36
+ const listeners = new Map()
37
+
38
+ function getWebSocket() {
39
+ if (typeof window === 'undefined') return null
40
+ if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
41
+ return ws
42
+ }
43
+
44
+ try {
45
+ ws = new WebSocket(WS_BASE)
46
+
47
+ ws.onopen = () => {
48
+ console.log('[WebBridge] Terhubung ke WebSocket MARK Core di', WS_BASE)
49
+ try {
50
+ ws.send(JSON.stringify({ event: 'ui:presence', payload: { client: 'webui' } }))
51
+ } catch (_) {}
52
+ }
53
+
54
+ ws.onmessage = (event) => {
55
+ try {
56
+ const data = JSON.parse(event.data)
57
+ const { event: evtName, payload } = data
58
+
59
+ if (evtName && listeners.has(evtName)) {
60
+ listeners.get(evtName).forEach((cb) => {
61
+ try {
62
+ cb(payload)
63
+ } catch (err) {
64
+ console.error(`[WebBridge] Error pada listener event '${evtName}':`, err)
65
+ }
66
+ })
67
+ }
68
+ } catch (err) {
69
+ console.error('[WebBridge] Gagal parse pesan WebSocket:', err)
70
+ }
71
+ }
72
+
73
+ ws.onclose = () => {
74
+ console.warn('[WebBridge] Koneksi WebSocket terputus. Mencoba reconnect dalam 3 detik...')
75
+ setTimeout(getWebSocket, 3000)
76
+ }
77
+
78
+ ws.onerror = (err) => {
79
+ console.error('[WebBridge] WebSocket error:', err)
80
+ }
81
+ } catch (err) {
82
+ console.error('[WebBridge] Gagal membuat WebSocket:', err)
83
+ }
84
+
85
+ return ws
86
+ }
87
+
88
+ export function addWebListener(event, callback) {
89
+ if (!listeners.has(event)) {
90
+ listeners.set(event, new Set())
91
+ }
92
+ listeners.get(event).add(callback)
93
+ getWebSocket()
94
+ }
95
+
96
+ export function removeWebListener(event, callback) {
97
+ if (listeners.has(event)) {
98
+ listeners.get(event).delete(callback)
99
+ }
100
+ }
101
+
102
+ export function removeAllWebListeners(event) {
103
+ if (listeners.has(event)) {
104
+ listeners.get(event).clear()
105
+ }
106
+ }
107
+
108
+ export function resolveAbortSignal(sig) {
109
+ if (!sig) return undefined
110
+ if (typeof AbortSignal !== 'undefined' && sig instanceof AbortSignal) {
111
+ return sig
112
+ }
113
+ if (
114
+ sig &&
115
+ sig.signal &&
116
+ typeof AbortSignal !== 'undefined' &&
117
+ sig.signal instanceof AbortSignal
118
+ ) {
119
+ return sig.signal
120
+ }
121
+ if (sig && typeof sig === 'object' && sig.current) {
122
+ return resolveAbortSignal(sig.current)
123
+ }
124
+ return undefined
125
+ }
126
+
127
+ export const webApi = {
128
+ // 1. Health & Config
129
+ getHealth: async () => {
130
+ const res = await fetch(`${API_BASE}/api/health`)
131
+ return await res.json()
132
+ },
133
+
134
+ getConfig: async () => {
135
+ const res = await fetch(`${API_BASE}/api/config`)
136
+ const json = await res.json()
137
+ return json.data
138
+ },
139
+
140
+ syncConfig: async (config) => {
141
+ const res = await fetch(`${API_BASE}/api/config`, {
142
+ method: 'POST',
143
+ headers: { 'Content-Type': 'application/json' },
144
+ body: JSON.stringify(config)
145
+ })
146
+ const json = await res.json()
147
+ return json.data
148
+ },
149
+
150
+ // 2. Chat & AI
151
+ fetchAI: async (params, signal = null) => {
152
+ const endpoint = params?.stream ? `${API_BASE}/api/ai/stream` : `${API_BASE}/api/ai/fetch`
153
+ const effectiveSignal = resolveAbortSignal(signal) || resolveAbortSignal(params?.signal)
154
+ const res = await fetch(endpoint, {
155
+ method: 'POST',
156
+ headers: { 'Content-Type': 'application/json' },
157
+ body: JSON.stringify(params),
158
+ signal: effectiveSignal
159
+ })
160
+ const json = await res.json()
161
+ if (!res.ok || json.error) {
162
+ const err = new Error(json.error?.message || `HTTP error ${res.status}`)
163
+ err.code = json.error?.code
164
+ throw err
165
+ }
166
+ return json
167
+ },
168
+
169
+ resetAiSession: async () => {
170
+ try {
171
+ const res = await fetch(`${API_BASE}/api/ai/reset-session`, { method: 'POST' })
172
+ return await res.json()
173
+ } catch {
174
+ return { success: false }
175
+ }
176
+ },
177
+
178
+ onAiToken: (callback) => {
179
+ addWebListener('ai:token', callback)
180
+ return () => removeWebListener('ai:token', callback)
181
+ },
182
+
183
+ onAiMood: (callback) => {
184
+ addWebListener('ai:mood', callback)
185
+ return () => removeWebListener('ai:mood', callback)
186
+ },
187
+
188
+ onAiMarkMeta: (callback) => {
189
+ addWebListener('ai:mark_meta', callback)
190
+ return () => removeWebListener('ai:mark_meta', callback)
191
+ },
192
+
193
+ onToolStatus: (callback) => {
194
+ addWebListener('tool:status', callback)
195
+ return () => removeWebListener('tool:status', callback)
196
+ },
197
+
198
+ abortFetchAI: () => {
199
+ const socket = getWebSocket()
200
+ if (socket && socket.readyState === WebSocket.OPEN) {
201
+ socket.send(JSON.stringify({ event: 'ai:abort' }))
202
+ }
203
+ fetch(`${API_BASE}/api/ai/abort`, { method: 'POST' }).catch(() => {})
204
+ },
205
+
206
+ onAiStatus: (callback) => {
207
+ addWebListener('ai:status', callback)
208
+ return () => removeWebListener('ai:status', callback)
209
+ },
210
+
211
+ onAiAbort: (callback) => {
212
+ addWebListener('ai:abort', callback)
213
+ return () => removeWebListener('ai:abort', callback)
214
+ },
215
+
216
+ // 3. Audio & Voice (Default imut seperti robot kecil: rate +10%, pitch +55Hz)
217
+ textToSpeech: async (text, rate = 10, pitch = 55, voice = 'id-ID-ArdiNeural') => {
218
+ // Normalisasi jika pemanggil mempassing (text, voice) atau (text, rate, pitch, voice)
219
+ let finalVoice = 'id-ID-ArdiNeural'
220
+ let finalRate = 10
221
+ let finalPitch = 55
222
+
223
+ if (typeof rate === 'string' && !/^-?\d+$/.test(rate.trim())) {
224
+ finalVoice = rate
225
+ } else {
226
+ finalRate = rate !== undefined && rate !== null && rate !== 0 ? rate : 10
227
+ finalPitch = pitch !== undefined && pitch !== null && pitch !== 0 ? pitch : 55
228
+ if (typeof voice === 'string' && voice.trim()) {
229
+ finalVoice = voice
230
+ }
231
+ }
232
+
233
+ // Gunakan direct streaming endpoint untuk latensi instan tanpa base64 overhead
234
+ try {
235
+ const url = `${API_BASE}/api/tts/stream?text=${encodeURIComponent(text)}&voice=${encodeURIComponent(finalVoice)}&rate=${encodeURIComponent(finalRate)}&pitch=${encodeURIComponent(finalPitch)}`
236
+ return url
237
+ } catch (err) {
238
+ console.error('[webBridge] textToSpeech stream URL error:', err)
239
+ return null
240
+ }
241
+ },
242
+
243
+ onAudioPlay: (callback) => {
244
+ addWebListener('audio:play', callback)
245
+ },
246
+
247
+ // 4. Memory API
248
+ getMemories: async () => {
249
+ const res = await fetch(`${API_BASE}/api/memories`)
250
+ const json = await res.json()
251
+ return json.data || []
252
+ },
253
+
254
+ saveMemory: async (item) => {
255
+ const res = await fetch(`${API_BASE}/api/memories`, {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify(item)
259
+ })
260
+ const json = await res.json()
261
+ return json.data
262
+ },
263
+
264
+ getTurns: async () => {
265
+ const res = await fetch(`${API_BASE}/api/turns`)
266
+ const json = await res.json()
267
+ return json.data || []
268
+ },
269
+
270
+ onDatabaseRestored: (callback) => {
271
+ addWebListener('db:restored', callback)
272
+ return () => removeWebListener('db:restored', callback)
273
+ },
274
+
275
+ // 5. System Notifications & Windows
276
+ showNotification: (title, body) => {
277
+ // Kirim ke backend Node.js untuk Native Windows Toast Notification (tanpa web permission prompt)
278
+ fetch(`${API_BASE}/api/system/notify`, {
279
+ method: 'POST',
280
+ headers: { 'Content-Type': 'application/json' },
281
+ body: JSON.stringify({ title, body })
282
+ }).catch(() => {
283
+ // Fallback lokal ke browser Notification jika server belum siap
284
+ if (typeof Notification !== 'undefined' && Notification.permission === 'granted') {
285
+ new Notification(title, { body })
286
+ }
287
+ })
288
+ },
289
+
290
+ openExternal: (url) => {
291
+ if (typeof window !== 'undefined') {
292
+ window.open(url, '_blank')
293
+ }
294
+ },
295
+
296
+ // 6. Tools Execution
297
+ executeNativeTool: async (tool, query, config) => {
298
+ try {
299
+ const res = await fetch(`${API_BASE}/api/tools/execute`, {
300
+ method: 'POST',
301
+ headers: { 'Content-Type': 'application/json' },
302
+ body: JSON.stringify({ tool, query, config })
303
+ })
304
+ const json = await res.json()
305
+ return json
306
+ } catch (err) {
307
+ return { success: false, error: err.message }
308
+ }
309
+ },
310
+
311
+ _groupToolsCache: null,
312
+ getGroupTools: async () => {
313
+ if (webApi._groupToolsCache) return webApi._groupToolsCache
314
+ try {
315
+ const res = await fetch(`${API_BASE}/api/tools/groups`)
316
+ const json = await res.json()
317
+ if (json && json.success && json.data) {
318
+ webApi._groupToolsCache = json.data
319
+ return webApi._groupToolsCache
320
+ }
321
+ } catch (err) {
322
+ console.error('Gagal memuat skema group tools dari server:', err)
323
+ }
324
+ return { schema: {}, names: [], definition: {}, flat: {} }
325
+ },
326
+
327
+ getWorkspaceFiles: async (query = '', root = '') => {
328
+ try {
329
+ const q = encodeURIComponent(query || '')
330
+ const r = encodeURIComponent(root || '')
331
+ const res = await fetch(`${API_BASE}/api/workspace/files?query=${q}&root=${r}`)
332
+ const json = await res.json()
333
+ return json?.data || []
334
+ } catch (err) {
335
+ console.error('[webBridge] getWorkspaceFiles error:', err)
336
+ return []
337
+ }
338
+ },
339
+
340
+ checkToolApproval: async (tool, query) => {
341
+ try {
342
+ const res = await fetch(`${API_BASE}/api/tools/needs-approval`, {
343
+ method: 'POST',
344
+ headers: { 'Content-Type': 'application/json' },
345
+ body: JSON.stringify({ tool, query })
346
+ })
347
+ return await res.json()
348
+ } catch (_) {
349
+ return { needsApproval: false }
350
+ }
351
+ },
352
+
353
+ needsApproval: async (tool, query) => {
354
+ return webApi.checkToolApproval(tool, query)
355
+ },
356
+
357
+ selectDirectory: async (description = 'Pilih Folder Workspace Proyek') => {
358
+ try {
359
+ const res = await webApi.executeNativeTool('select-directory', description)
360
+ if (res && res.success && res.path) {
361
+ return res.path
362
+ }
363
+ return null
364
+ } catch (_) {
365
+ return null
366
+ }
367
+ },
368
+
369
+ // Browser Automation Bridges
370
+ browserNavigate: async (url) => webApi.executeNativeTool('browser-navigate', url),
371
+ browserReadDom: async () => webApi.executeNativeTool('browser-read-dom', ''),
372
+ browserAction: async (data) =>
373
+ webApi.executeNativeTool(
374
+ 'browser-action',
375
+ typeof data === 'string' ? data : JSON.stringify(data)
376
+ ),
377
+ browserClose: async (sessionId = 'default') =>
378
+ webApi.executeNativeTool(
379
+ 'browser-close',
380
+ typeof sessionId === 'string' ? sessionId : sessionId?.sessionId || 'default'
381
+ ),
382
+ showBrowserWindow: async (sessionId = 'default') =>
383
+ webApi.executeNativeTool('browser-show', typeof sessionId === 'string' ? sessionId : 'default'),
384
+ hideBrowserWindow: async (sessionId = 'default') =>
385
+ webApi.executeNativeTool('browser-hide', typeof sessionId === 'string' ? sessionId : 'default'),
386
+ onBrowserPreview: (cb) => {
387
+ addWebListener('browser:preview', cb)
388
+ },
389
+
390
+ // Windows Desktop OS Tools Bridges
391
+ osRead: async () => webApi.executeNativeTool('os-read', ''),
392
+ osClick: async (query) => webApi.executeNativeTool('os-click', query),
393
+ osType: async (query) => webApi.executeNativeTool('os-type', query),
394
+ osKey: async (combo) => webApi.executeNativeTool('os-key', combo),
395
+ osScroll: async (query) => webApi.executeNativeTool('os-scroll', query),
396
+ osOpen: async (target) => webApi.executeNativeTool('os-open', target),
397
+ osListWindows: async () => webApi.executeNativeTool('os-list-windows', ''),
398
+ osFocusWindow: async (title) => webApi.executeNativeTool('os-focus-window', title),
399
+
400
+ // 7. Pure Node.js Skills Engine
401
+ getSkills: async () => {
402
+ try {
403
+ const res = await fetch(`${API_BASE}/api/skills`)
404
+ const json = await res.json()
405
+ return json.data || []
406
+ } catch (_) {
407
+ return []
408
+ }
409
+ },
410
+
411
+ getSkillTree: async (name) => {
412
+ try {
413
+ const res = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(name)}/tree`)
414
+ const json = await res.json()
415
+ return json.data || []
416
+ } catch (_) {
417
+ return []
418
+ }
419
+ },
420
+
421
+ readSkillFile: async (name, filePath = 'SKILL.md') => {
422
+ try {
423
+ const res = await fetch(
424
+ `${API_BASE}/api/skills/${encodeURIComponent(name)}/file?filePath=${encodeURIComponent(filePath)}`
425
+ )
426
+ const json = await res.json()
427
+ return json.data?.content || ''
428
+ } catch (_) {
429
+ return ''
430
+ }
431
+ },
432
+
433
+ readSkill: async (name) => {
434
+ const fileRes = await webApi.readSkillFile(name, 'SKILL.md')
435
+ if (fileRes) return fileRes
436
+ try {
437
+ const serverRes = await webApi.executeNativeTool('read-skill', { skill_name: name })
438
+ if (serverRes && serverRes.success && (serverRes.content || serverRes.data)) {
439
+ return serverRes.content || serverRes.data
440
+ }
441
+ } catch {
442
+ // ignore fallback error
443
+ }
444
+ return ''
445
+ },
446
+
447
+ saveSkillFile: async (name, filePath, content) => {
448
+ const res = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(name)}/file`, {
449
+ method: 'POST',
450
+ headers: { 'Content-Type': 'application/json' },
451
+ body: JSON.stringify({ filePath, content })
452
+ })
453
+ return await res.json()
454
+ },
455
+
456
+ saveSkill: async (name, content) => {
457
+ return await webApi.saveSkillFile(name, 'SKILL.md', content)
458
+ },
459
+
460
+ createSkillItem: async (name, itemPath, isFolder = false) => {
461
+ const res = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(name)}/item`, {
462
+ method: 'POST',
463
+ headers: { 'Content-Type': 'application/json' },
464
+ body: JSON.stringify({ itemPath, isFolder })
465
+ })
466
+ return await res.json()
467
+ },
468
+
469
+ renameSkillItem: async (name, oldPath, newPath) => {
470
+ const res = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(name)}/rename`, {
471
+ method: 'POST',
472
+ headers: { 'Content-Type': 'application/json' },
473
+ body: JSON.stringify({ oldPath, newPath })
474
+ })
475
+ return await res.json()
476
+ },
477
+
478
+ deleteSkillItem: async (name, itemPath) => {
479
+ const res = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(name)}/item`, {
480
+ method: 'DELETE',
481
+ headers: { 'Content-Type': 'application/json' },
482
+ body: JSON.stringify({ itemPath })
483
+ })
484
+ return await res.json()
485
+ },
486
+
487
+ deleteSkill: async (name) => {
488
+ const res = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(name)}`, {
489
+ method: 'DELETE'
490
+ })
491
+ return await res.json()
492
+ },
493
+
494
+ installSkill: async (fileOrBuffer, overrideName = null) => {
495
+ const body =
496
+ fileOrBuffer instanceof File || fileOrBuffer instanceof Blob
497
+ ? fileOrBuffer
498
+ : new Blob([fileOrBuffer])
499
+ const url = overrideName
500
+ ? `${API_BASE}/api/skills/install?name=${encodeURIComponent(overrideName)}`
501
+ : `${API_BASE}/api/skills/install`
502
+ const res = await fetch(url, {
503
+ method: 'POST',
504
+ headers: { 'Content-Type': 'application/zip' },
505
+ body
506
+ })
507
+ return await res.json()
508
+ },
509
+
510
+ onSkillsUpdated: (cb) => {
511
+ addWebListener('skills:updated', cb)
512
+ return () => removeWebListener('skills:updated', cb)
513
+ },
514
+
515
+ // 8. Dynamic Plugins Engine
516
+ getPlugins: async () => {
517
+ try {
518
+ const res = await fetch(`${API_BASE}/api/plugins`)
519
+ const json = await res.json()
520
+ return json.data || []
521
+ } catch (_) {
522
+ return []
523
+ }
524
+ },
525
+
526
+ reloadPlugins: async () => {
527
+ const res = await fetch(`${API_BASE}/api/plugins/reload`, { method: 'POST' })
528
+ const json = await res.json()
529
+ return json.data || []
530
+ },
531
+
532
+ createPlugin: async (payload) => {
533
+ const res = await fetch(`${API_BASE}/api/plugins/save`, {
534
+ method: 'POST',
535
+ headers: { 'Content-Type': 'application/json' },
536
+ body: JSON.stringify(payload)
537
+ })
538
+ return await res.json()
539
+ },
540
+
541
+ togglePlugin: async (name, isEnabled) => {
542
+ const res = await fetch(`${API_BASE}/api/plugins/toggle`, {
543
+ method: 'POST',
544
+ headers: { 'Content-Type': 'application/json' },
545
+ body: JSON.stringify({ name, isEnabled })
546
+ })
547
+ return await res.json()
548
+ },
549
+
550
+ deletePlugin: async (name) => {
551
+ const res = await fetch(`${API_BASE}/api/plugins/${encodeURIComponent(name)}`, {
552
+ method: 'DELETE'
553
+ })
554
+ return await res.json()
555
+ },
556
+
557
+ executePluginAction: async (action, query) => {
558
+ const res = await fetch(`${API_BASE}/api/plugins/execute`, {
559
+ method: 'POST',
560
+ headers: { 'Content-Type': 'application/json' },
561
+ body: JSON.stringify({ action, query })
562
+ })
563
+ return await res.json()
564
+ },
565
+
566
+ executePlugin: async (action, query) => {
567
+ return webApi.executePluginAction(action, query)
568
+ },
569
+
570
+ openPluginFolder: async () => {
571
+ return webApi.executeNativeTool(
572
+ 'run-powershell',
573
+ 'explorer.exe "$HOME\\Documents\\Mark Plugins"'
574
+ )
575
+ },
576
+
577
+ openSpecificFolder: async (folderPath) => {
578
+ return webApi.executeNativeTool('run-powershell', `explorer.exe "${folderPath}"`)
579
+ },
580
+
581
+ onPluginsUpdated: (cb) => {
582
+ addWebListener('plugins:updated', cb)
583
+ return () => removeWebListener('plugins:updated', cb)
584
+ },
585
+
586
+ // 9. Document RAG Binary Parser
587
+ parseDocument: async (arrayBuffer, isDocx = false) => {
588
+ const res = await fetch(`${API_BASE}/api/documents/parse?isDocx=${isDocx}`, {
589
+ method: 'POST',
590
+ headers: { 'Content-Type': 'application/octet-stream' },
591
+ body: arrayBuffer
592
+ })
593
+ const json = await res.json()
594
+ if (!res.ok || !json.success) throw new Error(json.error || 'Parse error')
595
+ return json.text
596
+ },
597
+
598
+ // 10. Awareness & OS Vision
599
+ getActivityBuffer: async () => {
600
+ try {
601
+ const res = await fetch(`${API_BASE}/api/awareness/activity-buffer`)
602
+ const json = await res.json()
603
+ const list = Array.isArray(json.data) ? [...json.data] : []
604
+ if (json.telemetry) {
605
+ list.telemetry = json.telemetry
606
+ }
607
+ return list
608
+ } catch (_) {
609
+ return []
610
+ }
611
+ },
612
+
613
+ getSystemTelemetry: async () => {
614
+ try {
615
+ const res = await fetch(`${API_BASE}/api/awareness/telemetry`)
616
+ const json = await res.json()
617
+ return json.telemetry || null
618
+ } catch (_) {
619
+ return null
620
+ }
621
+ },
622
+
623
+ clearActivityBuffer: async () => {
624
+ try {
625
+ const res = await fetch(`${API_BASE}/api/awareness/clear-buffer`, { method: 'POST' })
626
+ return await res.json()
627
+ } catch (_) {
628
+ return { success: false }
629
+ }
630
+ },
631
+
632
+ getSystemIdleSeconds: async () => {
633
+ try {
634
+ const res = await fetch(`${API_BASE}/api/awareness/idle-time`)
635
+ const json = await res.json()
636
+ return json.idleSeconds || 0
637
+ } catch (_) {
638
+ return 0
639
+ }
640
+ },
641
+
642
+ takeScreenshot: async () => {
643
+ const res = await fetch(`${API_BASE}/api/os/screenshot`, { method: 'POST' })
644
+ const json = await res.json()
645
+ if (!json.success) throw new Error(json.error || 'Failed to capture screenshot')
646
+ return json.data
647
+ },
648
+
649
+ // 11. Google Workspace API
650
+ googleStatus: async () => {
651
+ try {
652
+ const res = await fetch(`${API_BASE}/api/google/status`)
653
+ const json = await res.json()
654
+ return json
655
+ } catch (_) {
656
+ return { success: false, isConnected: false }
657
+ }
658
+ },
659
+ googleConnect: async (clientId, clientSecret) => {
660
+ const res = await fetch(`${API_BASE}/api/google/connect`, {
661
+ method: 'POST',
662
+ headers: { 'Content-Type': 'application/json' },
663
+ body: JSON.stringify({ clientId, clientSecret })
664
+ })
665
+ return await res.json()
666
+ },
667
+ googleDisconnect: async () => {
668
+ const res = await fetch(`${API_BASE}/api/google/disconnect`, { method: 'POST' })
669
+ return await res.json()
670
+ },
671
+
672
+ // 12. Telegram Bot API & Listeners
673
+ tgGetStatus: async () => {
674
+ try {
675
+ const res = await fetch(`${API_BASE}/api/telegram/status`)
676
+ return await res.json()
677
+ } catch (_) {
678
+ return { status: 'disconnected' }
679
+ }
680
+ },
681
+ tgGetHistory: async () => {
682
+ try {
683
+ const res = await fetch(`${API_BASE}/api/telegram/history`)
684
+ const json = await res.json()
685
+ return json.data || []
686
+ } catch (_) {
687
+ return []
688
+ }
689
+ },
690
+ tgStart: async (token) => {
691
+ const res = await fetch(`${API_BASE}/api/telegram/start`, {
692
+ method: 'POST',
693
+ headers: { 'Content-Type': 'application/json' },
694
+ body: JSON.stringify({ token })
695
+ })
696
+ return await res.json()
697
+ },
698
+ tgStop: async () => {
699
+ const res = await fetch(`${API_BASE}/api/telegram/stop`, { method: 'POST' })
700
+ return await res.json()
701
+ },
702
+ tgSendMessage: async (chatId, message) => {
703
+ const res = await fetch(`${API_BASE}/api/telegram/send`, {
704
+ method: 'POST',
705
+ headers: { 'Content-Type': 'application/json' },
706
+ body: JSON.stringify({ chatId, message })
707
+ })
708
+ return await res.json()
709
+ },
710
+ tgAgentExecutionDone: async (payload) => {
711
+ const res = await fetch(`${API_BASE}/api/telegram/agent-done`, {
712
+ method: 'POST',
713
+ headers: { 'Content-Type': 'application/json' },
714
+ body: JSON.stringify(payload)
715
+ })
716
+ return await res.json()
717
+ },
718
+ sendTgAgentExecutionDone: async (payload) => {
719
+ return await webApi.tgAgentExecutionDone(payload)
720
+ },
721
+ tgBroadcastToAdmins: async (message) => {
722
+ const res = await fetch(`${API_BASE}/api/telegram/broadcast`, {
723
+ method: 'POST',
724
+ headers: { 'Content-Type': 'application/json' },
725
+ body: JSON.stringify({ message })
726
+ })
727
+ return await res.json()
728
+ },
729
+ tgTakeScreenshot: async (chatId = null) => {
730
+ const res = await fetch(`${API_BASE}/api/telegram/screenshot`, {
731
+ method: 'POST',
732
+ headers: { 'Content-Type': 'application/json' },
733
+ body: JSON.stringify({ chatId })
734
+ })
735
+ return await res.json()
736
+ },
737
+ onTgCommandAccept: (cb) => {
738
+ addWebListener('tg:command-accept', cb)
739
+ return () => removeWebListener('tg:command-accept', cb)
740
+ },
741
+ onTgCommandAlways: (cb) => {
742
+ addWebListener('tg:command-always', cb)
743
+ return () => removeWebListener('tg:command-always', cb)
744
+ },
745
+ onTgCommandReject: (cb) => {
746
+ addWebListener('tg:command-reject', cb)
747
+ return () => removeWebListener('tg:command-reject', cb)
748
+ },
749
+ onTgCommandSession: (cb) => {
750
+ addWebListener('tg:command-session', cb)
751
+ return () => removeWebListener('tg:command-session', cb)
752
+ },
753
+ onTgConnection: (cb) => {
754
+ addWebListener('tg:connection', cb)
755
+ return () => removeWebListener('tg:connection', cb)
756
+ },
757
+ onTgThinking: (cb) => {
758
+ addWebListener('tg:thinking', cb)
759
+ return () => removeWebListener('tg:thinking', cb)
760
+ },
761
+ onTgMessage: (cb) => {
762
+ addWebListener('tg:message', cb)
763
+ return () => removeWebListener('tg:message', cb)
764
+ },
765
+ onTgReplySent: (cb) => {
766
+ addWebListener('tg:reply-sent', cb)
767
+ return () => removeWebListener('tg:reply-sent', cb)
768
+ },
769
+ onTgRequestAgentExecution: (cb) => {
770
+ addWebListener('tg:request-agent-execution', cb)
771
+ return () => removeWebListener('tg:request-agent-execution', cb)
772
+ },
773
+
774
+ // 13. Background Task Daemon API
775
+ spawnBackgroundTask: async (taskId, command, cwd) => {
776
+ const res = await fetch(`${API_BASE}/api/tasks/daemon/spawn`, {
777
+ method: 'POST',
778
+ headers: { 'Content-Type': 'application/json' },
779
+ body: JSON.stringify({ taskId, command, cwd })
780
+ })
781
+ return await res.json()
782
+ },
783
+ readBackgroundTaskOutput: async (taskId, lines = 40) => {
784
+ const res = await fetch(
785
+ `${API_BASE}/api/tasks/daemon/${encodeURIComponent(taskId)}/output?lines=${lines}`
786
+ )
787
+ return await res.json()
788
+ },
789
+ killBackgroundTask: async (taskId) => {
790
+ const res = await fetch(`${API_BASE}/api/tasks/daemon/${encodeURIComponent(taskId)}/kill`, {
791
+ method: 'POST'
792
+ })
793
+ return await res.json()
794
+ },
795
+ listBackgroundTasks: async () => {
796
+ const res = await fetch(`${API_BASE}/api/tasks/daemon/list`)
797
+ return await res.json()
798
+ },
799
+
800
+ // 14. Database Backup & Restore API
801
+ exportDatabase: async () => {
802
+ const res = await fetch(`${API_BASE}/api/db/export`)
803
+ const json = await res.json()
804
+ return json.data || json
805
+ },
806
+ restoreDatabase: async (dumpData, overwrite = true) => {
807
+ const res = await fetch(`${API_BASE}/api/db/restore`, {
808
+ method: 'POST',
809
+ headers: { 'Content-Type': 'application/json' },
810
+ body: JSON.stringify({ dumpData, overwrite })
811
+ })
812
+ const json = await res.json()
813
+ if (!res.ok || !json.success) {
814
+ throw new Error(json.error || 'Gagal memulihkan database.')
815
+ }
816
+ return json
817
+ },
818
+ saveTempFile: async (dataUrlOrBuffer, fileName = 'file.png') => {
819
+ try {
820
+ let dataUrl = typeof dataUrlOrBuffer === 'string' ? dataUrlOrBuffer : null
821
+ if (!dataUrl && dataUrlOrBuffer instanceof ArrayBuffer) {
822
+ const bytes = new Uint8Array(dataUrlOrBuffer)
823
+ let binary = ''
824
+ for (let i = 0; i < bytes.byteLength; i++) {
825
+ binary += String.fromCharCode(bytes[i])
826
+ }
827
+ dataUrl = `data:application/octet-stream;base64,${btoa(binary)}`
828
+ }
829
+ const res = await fetch(`${API_BASE}/api/chat/upload-temp`, {
830
+ method: 'POST',
831
+ headers: { 'Content-Type': 'application/json' },
832
+ body: JSON.stringify({ fileName, dataUrl })
833
+ })
834
+ const json = await res.json()
835
+ if (json && json.success) return json.path
836
+ } catch (e) {
837
+ console.error('[webBridge] saveTempFile error:', e)
838
+ }
839
+ return null
840
+ },
841
+
842
+ // Window Controls (WebUI App Mode)
843
+ windowMinimize: () => {},
844
+ windowMaximize: () => {},
845
+ windowClose: () => {
846
+ if (typeof window !== 'undefined') window.close()
847
+ },
848
+ onWindowMaximized: () => {},
849
+
850
+ getYoutubeTranscript: async (url) => {
851
+ const res = await webApi.executeNativeTool('youtube-transcript', url)
852
+ return res.data || ''
853
+ },
854
+ searchYoutube: async (query) => {
855
+ const res = await webApi.executeNativeTool('search-youtube', query)
856
+ return res.data || []
857
+ },
858
+ searchMusic: async (query) => {
859
+ const res = await webApi.executeNativeTool('search-youtube', query)
860
+ return res.data || []
861
+ },
862
+
863
+ // 13. Inter-Agent & WebSocket Broadcast Bridge
864
+ broadcastWsEvent: (event, data) => {
865
+ if (typeof window === 'undefined') return
866
+ const socket = getWebSocket()
867
+ if (socket && socket.readyState === WebSocket.OPEN) {
868
+ socket.send(JSON.stringify({ event: 'ws:broadcast', payload: { event, data } }))
869
+ }
870
+ },
871
+ onSubagentReport: (cb) => {
872
+ addWebListener('subagent:report', cb)
873
+ return () => removeWebListener('subagent:report', cb)
874
+ },
875
+ onSubagentCompleted: (cb) => {
876
+ addWebListener('subagent:completed', cb)
877
+ return () => removeWebListener('subagent:completed', cb)
878
+ }
879
+ }
880
+
881
+ // Inisialisasi otomatis WebSocket saat dimuat di browser
882
+ if (typeof window !== 'undefined') {
883
+ window.api = { ...webApi, ...(window.api || {}) }
884
+ getWebSocket()
885
+ }