@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,244 @@
1
+ import { MsEdgeTTS, OUTPUT_FORMAT } from 'msedge-tts'
2
+ import { Readable } from 'stream'
3
+ import ytSearch from 'yt-search'
4
+ import { YoutubeTranscript } from 'youtube-transcript-plus'
5
+ import path from 'path'
6
+ import os from 'os'
7
+ import fs from 'fs'
8
+
9
+ // Patch MsEdgeTTS prototype to prevent unhandled crashes when streams close prematurely,
10
+ // when connections are rejected, or when WebSocket events throw without handler.
11
+ if (!MsEdgeTTS.__mark_patched) {
12
+ MsEdgeTTS.__mark_patched = true
13
+
14
+ MsEdgeTTS.prototype._pushAudioData = function (data, requestId) {
15
+ if (this._streams && this._streams[requestId] && this._streams[requestId].audio) {
16
+ try {
17
+ if (!this._streams[requestId].audio.destroyed) {
18
+ this._streams[requestId].audio.push(data)
19
+ }
20
+ } catch (_) {}
21
+ }
22
+ }
23
+
24
+ MsEdgeTTS.prototype._pushMetadata = function (data, requestId) {
25
+ if (this._streams && this._streams[requestId] && this._streams[requestId].metadata) {
26
+ try {
27
+ if (!this._streams[requestId].metadata.destroyed) {
28
+ this._streams[requestId].metadata.push(data)
29
+ }
30
+ } catch (_) {}
31
+ }
32
+ }
33
+
34
+ // Override _rawSSMLRequest with explicit catch handler on _send() to eliminate UnhandledPromiseRejection
35
+ MsEdgeTTS.prototype._rawSSMLRequest = function (requestSSML) {
36
+ this._metadataCheck()
37
+ const requestId = MsEdgeTTS.randomHex(16)
38
+ const request =
39
+ `X-RequestId:${requestId}\r\nContent-Type:application/ssml+xml\r\nPath:ssml\r\n\r\n` +
40
+ requestSSML.trim()
41
+ const self = this
42
+ const audioStream = new Readable({
43
+ read() {},
44
+ destroy(error, callback) {
45
+ delete self._streams[requestId]
46
+ if (typeof callback === 'function') callback(error)
47
+ }
48
+ })
49
+ const metadataStream = this._hasMetadataBoundaries()
50
+ ? new Readable({
51
+ read() {},
52
+ destroy(error, callback) {
53
+ delete self._streams[requestId]
54
+ if (typeof callback === 'function') callback(error)
55
+ }
56
+ })
57
+ : null
58
+
59
+ audioStream.on('error', () => {
60
+ audioStream.destroy()
61
+ metadataStream?.destroy()
62
+ })
63
+ audioStream.once('close', () => {
64
+ audioStream.destroy()
65
+ metadataStream?.destroy()
66
+ })
67
+
68
+ this._streams[requestId] = {
69
+ audio: audioStream,
70
+ metadata: metadataStream
71
+ }
72
+
73
+ // Attach catch handler directly to eliminate UnhandledPromiseRejection and pipe error to audioStream
74
+ this._send(request).catch((err) => {
75
+ const errMsg = typeof err === 'string' ? err : err?.message || 'TTS connection failed'
76
+ if (!audioStream.destroyed) {
77
+ audioStream.destroy(new Error(errMsg))
78
+ }
79
+ })
80
+
81
+ return { audioStream, metadataStream, requestId }
82
+ }
83
+
84
+ const originalInitClient = MsEdgeTTS.prototype._initClient
85
+ MsEdgeTTS.prototype._initClient = async function () {
86
+ try {
87
+ const res = await originalInitClient.apply(this, arguments)
88
+ if (this._ws) {
89
+ const origOnMessage = this._ws.onmessage
90
+ this._ws.onmessage = (m) => {
91
+ try {
92
+ const buffer = Buffer.from(m.data)
93
+ const message = buffer.toString()
94
+ const match = /X-RequestId:(.*?)\r\n/gm.exec(message)
95
+ const requestId = match ? match[1] : null
96
+
97
+ if (message.includes('Path:turn.end') && requestId) {
98
+ if (this._streams && this._streams[requestId] && this._streams[requestId].audio) {
99
+ try {
100
+ if (!this._streams[requestId].audio.destroyed) {
101
+ this._streams[requestId].audio.push(null)
102
+ }
103
+ } catch (_) {}
104
+ }
105
+ return
106
+ }
107
+
108
+ if (origOnMessage) {
109
+ origOnMessage.call(this._ws, m)
110
+ }
111
+ } catch (_) {
112
+ // Ignore unparseable or orphaned socket packets
113
+ }
114
+ }
115
+ }
116
+ return res
117
+ } catch (connectErr) {
118
+ const msg =
119
+ typeof connectErr === 'string' ? connectErr : connectErr?.message || 'TTS connection failed'
120
+ throw new Error(msg)
121
+ }
122
+ }
123
+ }
124
+
125
+ const TEMP_DIR = path.join(os.tmpdir(), 'mark-audio')
126
+ if (!fs.existsSync(TEMP_DIR)) {
127
+ fs.mkdirSync(TEMP_DIR, { recursive: true })
128
+ }
129
+
130
+ export const CUTE_ROBOT_PITCH = 55
131
+ export const CUTE_ROBOT_RATE = 10
132
+
133
+ /**
134
+ * Menghasilkan readable stream audio langsung untuk HTTP response streaming.
135
+ * Menggunakan instance terisolasi per permintaan dengan penutupan socket otomatis
136
+ * agar tahan terhadap idle timeout dan permintaan kalimat paralel.
137
+ * Default pitch +55Hz dan rate +10% menghasilkan nada suara robot kecil yang imut & ekspresif.
138
+ * @param {string} text
139
+ * @param {string} [voice='id-ID-ArdiNeural']
140
+ * @param {number|string} [rate=10]
141
+ * @param {number|string} [pitch=55]
142
+ */
143
+ export async function streamTTS(text, voice = 'id-ID-ArdiNeural', rate = 10, pitch = 55) {
144
+ if (!text || !text.trim()) throw new Error('Teks kosong untuk TTS')
145
+
146
+ let selectedVoice = 'id-ID-ArdiNeural'
147
+ if (typeof voice === 'string' && voice.trim() && !/^-?\d+$/.test(voice.trim())) {
148
+ selectedVoice = voice.trim()
149
+ }
150
+
151
+ const numRate = typeof rate === 'number' ? rate : (rate !== undefined && rate !== '' ? parseInt(rate, 10) : CUTE_ROBOT_RATE) || CUTE_ROBOT_RATE
152
+ const numPitch = typeof pitch === 'number' ? pitch : (pitch !== undefined && pitch !== '' ? parseInt(pitch, 10) : CUTE_ROBOT_PITCH) || CUTE_ROBOT_PITCH
153
+ const rateStr = numRate >= 0 ? `+${numRate}%` : `${numRate}%`
154
+ const pitchStr = numPitch >= 0 ? `+${numPitch}Hz` : `${numPitch}Hz`
155
+
156
+ const tts = new MsEdgeTTS()
157
+ await tts.setMetadata(selectedVoice, OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3)
158
+ const streamObj = tts.toStream(text, { rate: rateStr, pitch: pitchStr })
159
+
160
+ const cleanup = () => {
161
+ try {
162
+ tts.close()
163
+ } catch (_) {}
164
+ }
165
+
166
+ streamObj.audioStream.once('end', cleanup)
167
+ streamObj.audioStream.once('close', cleanup)
168
+ streamObj.audioStream.once('error', cleanup)
169
+
170
+ return streamObj.audioStream
171
+ }
172
+
173
+ /**
174
+ * Sintesis suara menggunakan Microsoft Edge TTS (Buffer RAM langsung tanpa disk write)
175
+ * @param {string} text
176
+ * @param {string} [voice='id-ID-ArdiNeural']
177
+ * @param {number|string} [rate=10]
178
+ * @param {number|string} [pitch=55]
179
+ * @returns {Promise<{ filePath: string, audioBase64: string }>}
180
+ */
181
+ export async function synthesizeTTS(text, voice = 'id-ID-ArdiNeural', rate = 10, pitch = 55) {
182
+ if (!text || !text.trim()) throw new Error('Teks kosong untuk TTS')
183
+
184
+ let selectedVoice = 'id-ID-ArdiNeural'
185
+ if (typeof voice === 'string' && voice.trim() && !/^-?\d+$/.test(voice.trim())) {
186
+ selectedVoice = voice.trim()
187
+ }
188
+
189
+ const audioStream = await streamTTS(text, selectedVoice, rate, pitch)
190
+ const chunks = []
191
+
192
+ await new Promise((resolve, reject) => {
193
+ audioStream.on('data', (chunk) => chunks.push(chunk))
194
+ audioStream.on('end', resolve)
195
+ audioStream.on('error', reject)
196
+ })
197
+
198
+ const buffer = Buffer.concat(chunks)
199
+ const audioBase64 = `data:audio/mp3;base64,${buffer.toString('base64')}`
200
+
201
+ return { filePath: '', audioBase64 }
202
+ }
203
+
204
+ /**
205
+ * Cari video atau musik YouTube
206
+ * @param {string} query
207
+ */
208
+ export async function searchYoutube(query) {
209
+ if (!query) return []
210
+ const result = await ytSearch(query)
211
+ return result.videos.slice(0, 10).map((v) => ({
212
+ id: v.videoId,
213
+ videoId: v.videoId,
214
+ title: v.title,
215
+ url: v.url,
216
+ timestamp: v.timestamp,
217
+ seconds: v.seconds,
218
+ views: v.views,
219
+ author: v.author?.name || 'Unknown Artist',
220
+ artist: v.author?.name || 'Unknown Artist',
221
+ thumbnail: v.thumbnail || v.image || `https://img.youtube.com/vi/${v.videoId}/hqdefault.jpg`
222
+ }))
223
+ }
224
+
225
+ /**
226
+ * Mengambil transkrip dari video YouTube
227
+ * @param {string} url
228
+ * @param {string} [lang='id']
229
+ */
230
+ export async function getTranscript(url, lang = 'id') {
231
+ if (!url || typeof url !== 'string') return ''
232
+ try {
233
+ const transcriptList = await YoutubeTranscript.fetchTranscript(url, { lang })
234
+ if (transcriptList && transcriptList.length > 0) {
235
+ return transcriptList.map((t) => t.text).join(' ')
236
+ }
237
+ // Fallback tanpa filter bahasa jika bahasa target tidak tersedia
238
+ const fallbackList = await YoutubeTranscript.fetchTranscript(url)
239
+ return fallbackList.map((t) => t.text).join(' ')
240
+ } catch (err) {
241
+ console.warn('[YouTube Transcript] Gagal mengambil transkrip:', err?.message || err)
242
+ return `Gagal mengambil transkrip video: ${err?.message || 'Video tidak memiliki transkrip atau dibatasi'}`
243
+ }
244
+ }
@@ -0,0 +1,327 @@
1
+ import { spawn } from 'child_process'
2
+ import path from 'path'
3
+ import fs from 'fs'
4
+ import { fileURLToPath } from 'url'
5
+ import { wsHub } from '../ws-hub.js'
6
+
7
+ const __filename = fileURLToPath(import.meta.url)
8
+ const __dirname = path.dirname(__filename)
9
+
10
+ let daemonProcess = null
11
+ let daemonReady = false
12
+ let pendingResolve = null
13
+ let daemonBuffer = ''
14
+ let lastReadResult = null
15
+ let lastReadTimestamp = 0
16
+ let stateChanged = false
17
+ const CACHE_TTL = 10000
18
+
19
+ let overlayProcess = null
20
+ let overlayReady = false
21
+
22
+ function getScriptPath(scriptName) {
23
+ const candidates = [
24
+ path.resolve(__dirname, `../../../src/main/pc-agent-scripts/${scriptName}`),
25
+ path.resolve(__dirname, `../../main/pc-agent-scripts/${scriptName}`),
26
+ path.resolve(__dirname, `../../../resources/scripts/${scriptName}`),
27
+ path.resolve(__dirname, `../../resources/scripts/${scriptName}`)
28
+ ]
29
+ for (const c of candidates) {
30
+ if (fs.existsSync(c)) return c
31
+ }
32
+ return candidates[0]
33
+ }
34
+
35
+ function getDaemonScriptPath() {
36
+ return getScriptPath('pc-daemon.ps1')
37
+ }
38
+
39
+ function getOverlayScriptPath() {
40
+ return getScriptPath('pc-overlay.ps1')
41
+ }
42
+
43
+ export function isOverlayAlive() {
44
+ return overlayProcess && !overlayProcess.killed
45
+ }
46
+
47
+ export function startOverlay() {
48
+ if (isOverlayAlive()) return
49
+
50
+ const scriptPath = getOverlayScriptPath()
51
+ if (!fs.existsSync(scriptPath)) {
52
+ console.warn('[PC-Agent] Skrip overlay tidak ditemukan:', scriptPath)
53
+ return
54
+ }
55
+
56
+ try {
57
+ overlayProcess = spawn('powershell.exe', [
58
+ '-NoProfile',
59
+ '-STA',
60
+ '-ExecutionPolicy',
61
+ 'Bypass',
62
+ '-File',
63
+ scriptPath
64
+ ])
65
+
66
+ overlayReady = false
67
+
68
+ overlayProcess.stdout.on('data', (chunk) => {
69
+ const text = chunk.toString().trim()
70
+ if (text.includes('READY')) {
71
+ overlayReady = true
72
+ }
73
+ if (text.includes('"event":"abort"') || text.includes('{"event":"abort"}')) {
74
+ console.warn('[PC-Agent] Sinyal ABORT diterima dari Overlay (Ctrl+Shift+S / Tombol Batal)!')
75
+ // Hentikan sesi dan picu abort di WebSocket hub
76
+ stopDaemon()
77
+ stopOverlay()
78
+ wsHub.broadcast('ai:abort', { source: 'pc-overlay', reason: 'User pressed Ctrl+Shift+S or clicked Cancel' })
79
+ }
80
+ })
81
+
82
+ overlayProcess.stderr.on('data', (chunk) => {
83
+ console.warn('[PC-Agent Overlay Error]:', chunk.toString())
84
+ })
85
+
86
+ overlayProcess.on('close', () => {
87
+ overlayProcess = null
88
+ overlayReady = false
89
+ })
90
+
91
+ overlayProcess.on('error', (err) => {
92
+ console.error('[PC-Agent] Gagal spawn overlay:', err)
93
+ overlayProcess = null
94
+ overlayReady = false
95
+ })
96
+ } catch (err) {
97
+ console.error('[PC-Agent] Error menjalankan overlay:', err)
98
+ }
99
+ }
100
+
101
+ export function stopOverlay() {
102
+ if (overlayProcess && !overlayProcess.killed) {
103
+ try {
104
+ overlayProcess.kill()
105
+ } catch (_) {}
106
+ overlayProcess = null
107
+ overlayReady = false
108
+ }
109
+ }
110
+
111
+ export function isDaemonAlive() {
112
+ return daemonProcess && !daemonProcess.killed && daemonReady
113
+ }
114
+
115
+ export function startDaemon() {
116
+ return new Promise((resolve, reject) => {
117
+ if (isDaemonAlive()) {
118
+ resolve()
119
+ return
120
+ }
121
+
122
+ const scriptPath = getDaemonScriptPath()
123
+
124
+ daemonProcess = spawn('powershell.exe', [
125
+ '-NoProfile',
126
+ '-WindowStyle',
127
+ 'Hidden',
128
+ '-ExecutionPolicy',
129
+ 'Bypass',
130
+ '-File',
131
+ scriptPath
132
+ ])
133
+
134
+ daemonBuffer = ''
135
+ daemonReady = false
136
+
137
+ daemonProcess.stdout.on('data', (chunk) => {
138
+ daemonBuffer += chunk.toString()
139
+
140
+ const delimiterIndex = daemonBuffer.indexOf('---MARK_DONE---')
141
+ if (delimiterIndex !== -1) {
142
+ const response = daemonBuffer.substring(0, delimiterIndex).trim()
143
+ daemonBuffer = daemonBuffer.substring(delimiterIndex + '---MARK_DONE---'.length).trimStart()
144
+
145
+ if (!daemonReady) {
146
+ daemonReady = true
147
+ resolve()
148
+ return
149
+ }
150
+
151
+ if (pendingResolve) {
152
+ const resolveFn = pendingResolve
153
+ pendingResolve = null
154
+ resolveFn(response)
155
+ }
156
+ }
157
+ })
158
+
159
+ daemonProcess.stderr.on('data', () => {})
160
+
161
+ daemonProcess.on('close', () => {
162
+ daemonProcess = null
163
+ daemonReady = false
164
+ if (pendingResolve) {
165
+ const resolveFn = pendingResolve
166
+ pendingResolve = null
167
+ resolveFn('')
168
+ }
169
+ })
170
+
171
+ daemonProcess.on('error', (err) => {
172
+ console.error('[PC-Agent] Gagal spawn daemon:', err)
173
+ daemonProcess = null
174
+ reject(err)
175
+ })
176
+
177
+ setTimeout(() => {
178
+ if (!daemonReady) {
179
+ console.warn('[PC-Agent] Timeout menunggu startup daemon (15s)')
180
+ resolve()
181
+ }
182
+ }, 15000)
183
+ })
184
+ }
185
+
186
+ export function stopDaemon() {
187
+ if (daemonProcess && !daemonProcess.killed) {
188
+ try {
189
+ daemonProcess.stdin.write(JSON.stringify({ cmd: 'exit' }) + '\n')
190
+ } catch (_) {}
191
+ setTimeout(() => {
192
+ if (daemonProcess && !daemonProcess.killed) {
193
+ try {
194
+ daemonProcess.kill()
195
+ } catch (_) {}
196
+ }
197
+ daemonProcess = null
198
+ daemonReady = false
199
+ }, 1000)
200
+ }
201
+ }
202
+
203
+ export function sendCommand(cmd) {
204
+ return new Promise((resolve) => {
205
+ if (!isDaemonAlive()) {
206
+ resolve(JSON.stringify({ status: 'error', message: 'Daemon Win32 belum berjalan' }))
207
+ return
208
+ }
209
+
210
+ pendingResolve = resolve
211
+ try {
212
+ daemonProcess.stdin.write(JSON.stringify(cmd) + '\n')
213
+ } catch (err) {
214
+ pendingResolve = null
215
+ resolve(JSON.stringify({ status: 'error', message: 'Gagal kirim ke daemon: ' + err.message }))
216
+ }
217
+
218
+ setTimeout(() => {
219
+ if (pendingResolve === resolve) {
220
+ pendingResolve = null
221
+ resolve(JSON.stringify({ status: 'error', message: 'Command timeout (30s)' }))
222
+ }
223
+ }, 30000)
224
+ })
225
+ }
226
+
227
+ export async function readDesktop(options = {}) {
228
+ const maxElements = options.maxElements || 60
229
+ if (!stateChanged && lastReadResult && Date.now() - lastReadTimestamp < CACHE_TTL) {
230
+ return { ...lastReadResult, method: 'cached' }
231
+ }
232
+
233
+ if (!isDaemonAlive()) {
234
+ await startDaemon()
235
+ }
236
+
237
+ const raw = await sendCommand({ cmd: 'read-ui', maxElements, roles: options.roles })
238
+ try {
239
+ const parsed = JSON.parse(raw)
240
+ lastReadResult = parsed
241
+ lastReadTimestamp = Date.now()
242
+ stateChanged = false
243
+ return parsed
244
+ } catch (_) {
245
+ return { status: 'error', message: raw }
246
+ }
247
+ }
248
+
249
+ export async function executeClick(x, y) {
250
+ startOverlay()
251
+ if (!isDaemonAlive()) await startDaemon()
252
+ stateChanged = true
253
+ const raw = await sendCommand({ cmd: 'click', x, y })
254
+ return raw
255
+ }
256
+
257
+ export async function executeType(text) {
258
+ startOverlay()
259
+ if (!isDaemonAlive()) await startDaemon()
260
+ stateChanged = true
261
+ const raw = await sendCommand({ cmd: 'type', text })
262
+ return raw
263
+ }
264
+
265
+ export async function executeKey(combo) {
266
+ startOverlay()
267
+ if (!isDaemonAlive()) await startDaemon()
268
+ stateChanged = true
269
+ const raw = await sendCommand({ cmd: 'key', combo })
270
+ return raw
271
+ }
272
+
273
+ export async function executeScroll(direction = 'down', amount = 3) {
274
+ startOverlay()
275
+ if (!isDaemonAlive()) await startDaemon()
276
+ stateChanged = true
277
+ const raw = await sendCommand({ cmd: 'scroll', direction, amount })
278
+ return raw
279
+ }
280
+
281
+ export async function openApp(target) {
282
+ startOverlay()
283
+ if (!isDaemonAlive()) await startDaemon()
284
+ stateChanged = true
285
+ const raw = await sendCommand({ cmd: 'open', target })
286
+ return raw
287
+ }
288
+
289
+ export async function listWindows() {
290
+ if (!isDaemonAlive()) await startDaemon()
291
+ const raw = await sendCommand({ cmd: 'list-windows' })
292
+ try {
293
+ return JSON.parse(raw)
294
+ } catch (_) {
295
+ return []
296
+ }
297
+ }
298
+
299
+ export async function focusWindow(title) {
300
+ startOverlay()
301
+ if (!isDaemonAlive()) await startDaemon()
302
+ stateChanged = true
303
+ const raw = await sendCommand({ cmd: 'focus-window', title })
304
+ return raw
305
+ }
306
+
307
+ export async function executeDoubleClick(x, y) {
308
+ await executeClick(x, y)
309
+ await new Promise((r) => setTimeout(r, 60))
310
+ return await executeClick(x, y)
311
+ }
312
+
313
+ export async function openPCSession() {
314
+ startOverlay()
315
+ await startDaemon()
316
+ return { success: true }
317
+ }
318
+
319
+ export async function closePCSession() {
320
+ stopOverlay()
321
+ stopDaemon()
322
+ return { success: true }
323
+ }
324
+
325
+ export function isPCSessionOpen() {
326
+ return isDaemonAlive()
327
+ }
@@ -0,0 +1,51 @@
1
+ import { exec } from 'child_process'
2
+ import path from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import util from 'util'
5
+ import fs from 'fs'
6
+
7
+ const execPromise = util.promisify(exec)
8
+ const __filename = fileURLToPath(import.meta.url)
9
+ const __dirname = path.dirname(__filename)
10
+
11
+ export async function captureDesktopScreenshotsBase64() {
12
+ if (process.platform !== 'win32') {
13
+ throw new Error('Screen capture hanya didukung di lingkungan Windows.')
14
+ }
15
+
16
+ try {
17
+ const scriptPath = path.resolve(__dirname, '../../main/pc-agent-scripts/take-screenshot.ps1')
18
+ if (!fs.existsSync(scriptPath)) {
19
+ throw new Error(`Skrip screenshot tidak ditemukan di ${scriptPath}`)
20
+ }
21
+
22
+ const { stdout, stderr } = await execPromise(
23
+ `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${scriptPath}"`,
24
+ { maxBuffer: 50 * 1024 * 1024 }
25
+ )
26
+
27
+ const rawOutput = (stdout || '').trim()
28
+ if (!rawOutput) {
29
+ throw new Error(stderr || 'Output screenshot kosong.')
30
+ }
31
+
32
+ const base64List = rawOutput
33
+ .split('|||')
34
+ .map((str) => str.trim())
35
+ .filter(Boolean)
36
+
37
+ if (base64List.length === 0) {
38
+ throw new Error('Tidak ada gambar layar yang berhasil diambil.')
39
+ }
40
+
41
+ return base64List.map((b64) => `data:image/jpeg;base64,${b64}`)
42
+ } catch (err) {
43
+ console.error('[Screen Capture Error]:', err)
44
+ throw new Error(`Gagal mengambil screenshot: ${err.message}`)
45
+ }
46
+ }
47
+
48
+ export async function captureDesktopScreenshotBase64() {
49
+ const all = await captureDesktopScreenshotsBase64()
50
+ return all[0]
51
+ }