@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,168 @@
1
+ import { fetchAI, cleanAndParse } from './core'
2
+ import { saveLearnedSkill, getAllLearnedSkills } from '../db'
3
+
4
+ /**
5
+ * Filter apakah eksekusi tools layak untuk disintesis menjadi sebuah skill.
6
+ * Mencegah membuat skill sampah untuk aksi trivial/sederhana (misal cuma read-file 1x atau search biasa).
7
+ */
8
+ function isWorthLearning(executedTools = []) {
9
+ if (!executedTools || executedTools.length === 0) return false
10
+
11
+ const toolNames = executedTools
12
+ .filter((t) => t && !t.type && (t.tool || t.task))
13
+ .map((t) => t.tool || t.task || '')
14
+
15
+ // Abaikan jika hanya memanggil read-skill atau hanya 1 tool sepele
16
+ const trivialSingleTools = [
17
+ 'read-skill',
18
+ 'read-tools',
19
+ 'read-memory',
20
+ 'camera-look',
21
+ 'analyze-screen'
22
+ ]
23
+ const nonTrivialTools = toolNames.filter((name) => !trivialSingleTools.includes(name))
24
+
25
+ if (nonTrivialTools.length === 0) return false
26
+
27
+ // Layak jika:
28
+ // 1. Mengeksekusi >= 2 langkah tool yang saling berkaitan, ATAU
29
+ // 2. Mengeksekusi tool kompleks/high-impact (seperti run-powershell, replace-content, git-commit, browser multi-step, subagent)
30
+ const highImpactTools = [
31
+ 'run-powershell',
32
+ 'replace-content',
33
+ 'replace-lines',
34
+ 'write-file',
35
+ 'spawn_subagent',
36
+ 'git-commit'
37
+ ]
38
+ const hasHighImpact = nonTrivialTools.some((name) => highImpactTools.includes(name))
39
+
40
+ return nonTrivialTools.length >= 2 || hasHighImpact
41
+ }
42
+
43
+ /**
44
+ * Dedicated Skill Synthesizer (MARK Meta-Learning Engine)
45
+ * Dieksekusi secara asinkron di background setelah Mark menyelesaikan tugas bermakna.
46
+ * Menghasilkan objek skill murni { name, description, content, should_save } dan menyimpannya ke SQLite / db.
47
+ * Jika skill serupa sudah ada, akan memperbarui (update) isi prosedurnya secara cerdas.
48
+ */
49
+ export async function synthesizeSkillAndSave({
50
+ userPrompt = '',
51
+ executedTools = [],
52
+ finalAnswer = '',
53
+ thought = ''
54
+ }) {
55
+ try {
56
+ if (!isWorthLearning(executedTools)) {
57
+ return null
58
+ }
59
+
60
+ const existingSkills = await getAllLearnedSkills()
61
+ const existingListStr =
62
+ existingSkills.length > 0
63
+ ? existingSkills.map((s) => `- ${s.name}: ${s.description}`).join('\n')
64
+ : '(Belum ada skill terdaftar)'
65
+
66
+ // Susun ringkasan riwayat aksi & tool yang berhasil
67
+ const toolsTrajectory = executedTools
68
+ .filter((t) => t && !t.type && (t.tool || t.task))
69
+ .slice(0, 12)
70
+ .map((t, idx) => {
71
+ const toolName = t.tool || t.task || 'unknown_tool'
72
+ const queryStr = t.query ? `\n Query: ${JSON.stringify(t.query).slice(0, 200)}` : ''
73
+ return `${idx + 1}. [Tool: ${toolName}]${queryStr}`
74
+ })
75
+ .join('\n')
76
+
77
+ const promptText = `Berikut adalah sesi kerja Mark yang berhasil:
78
+
79
+ [PERMINTAAN USER]:
80
+ ${userPrompt ? userPrompt.slice(0, 500) : '(Tidak ada teks permintaan)'}
81
+
82
+ [ANALISIS & LOGIKA (THOUGHT)]:
83
+ ${thought ? thought.slice(0, 500) : '(Tidak ada thought)'}
84
+
85
+ [LANGKAH ALAT YANG SUKSES DIEKSEKUSI]:
86
+ ${toolsTrajectory}
87
+
88
+ [HASIL / JAWABAN AKHIR]:
89
+ ${finalAnswer ? finalAnswer.slice(0, 600) : '(Tidak ada jawaban akhir)'}
90
+
91
+ [DAFTAR SKILL YANG SUDAH ADA SEBELUMNYA]:
92
+ ${existingListStr}
93
+
94
+ Tugasmu: Evaluasi apakah alur kerja di atas memiliki nilai prosedur yang berharga untuk dipelajari. Jika ya, buatkan atau perbarui panduan skill yang tepat.`
95
+
96
+ const systemPrompt = `Kamu adalah MARK Meta-Learning Synthesizer Engine — Inti Metakognitif & Pusat Pembelajaran Otonom MARK.
97
+ Sebagai otak pembelajar, kamu memiliki dorongan kuat untuk SELALU BELAJAR, mengekstraksi kebijaksanaan teknis, dan mengabadikan setiap alur kerja sukses menjadi aset keahlian (Skill Prosedural) yang permanen, rapi, dan siap dieksekusi ulang secara otomatis di masa depan.
98
+
99
+ # FILOSOFI & JIWA PEMBELAJAR (CONTINUOUS META-LEARNING):
100
+ 1. **HAUS PENGETAHUAN & AKUMULASI PENGALAMAN:**
101
+ - Tugas sucimu adalah memastikan Mark tidak pernah "membuang pengalaman". Setiap kali Mark memecahkan masalah kompleks, mengeksekusi serangkaian perintah terminal, memodifikasi file, atau mengorkestrasi tools, kamu bertindak sebagai analis metakognitif yang merumuskan "resep sukses" tersebut menjadi Standard Operating Procedure (SOP).
102
+ 2. **KUALITAS TINGGI VS ANTI-SAMPAH (SELEKTIF & BERBOBOT):**
103
+ - **Haus belajar BUKAN berarti mencatat hal sepele.** Jika sesi hanya berisi obrolan kasual, sapaan, lelucon, atau tindakan satu langkah yang tidak memiliki nilai prosedur teknis berulang, dengan tegas set \`"should_save": false\`.
104
+ - Simpan HANYA alur kerja yang mengandung nilai taktis: otomasi PowerShell/Bash, manipulasi berkas presisi, scraping/navigasi browser mendalam, konfigurasi lingkungan kerja, perbaikan bug (self-healing), atau pipeline multi-tool.
105
+ 3. **PENGEMBANGAN SKILL BERKELANJUTAN & UPDATE DUPLIKAT (CONTINUAL MASTERY):**
106
+ - Periksa daftar [DAFTAR SKILL YANG SUDAH ADA SEBELUMNYA].
107
+ - Jika alur kerja ini melengkapi, menyempurnakan, atau merupakan variasi baru dari skill yang sudah ada, GUNAKAN \`"name"\` yang SAMA persis dengan skill tersebut!
108
+ - Gabungkan wawasan baru ke dalam konten Markdown agar skill tersebut berevolusi menjadi lebih komprehensif dan matang.
109
+ 4. **FORMAT NAMA SKILL:**
110
+ - Gunakan format \`kebab-case\` ringkas dan deskriptif (huruf kecil, pisahkan dengan strip '-', contoh: \`setup-wsl-node\`, \`fix-powershell-policy\`, \`scrape-dynamic-table\`, \`debug-vite-build\`, \`git-sync-workflow\`).
111
+ 5. **STRUKTUR DESKRIPSI & KONTEN MARKDOWN YANG ACTIONABLE:**
112
+ - \`"description"\`: 1-2 kalimat padat yang menjelaskan tujuan skill dan kata kunci pemicu kapan Mark harus memanggilnya via 'read-skill'.
113
+ - \`"content"\`: Tulis panduan teknis Markdown yang sangat terstruktur, jelas, dan siap pakai:
114
+ * \`## Gambaran Umum & Tujuan\`: Apa yang dicapai oleh prosedur ini.
115
+ * \`## Prasyarat & Alat yang Digunakan\`: Daftar tool terkait (misal: \`run-powershell\`, \`replace-content\`, dsb).
116
+ * \`## Langkah Kerja Teruji (SOP)\`: Urutan aksi presisi beserta contoh parameter/query yang benar.
117
+ * \`## Tips, Pencegahan Error & Edge Cases\`: Hambatan apa yang sempat terjadi dan bagaimana cara menghindarinya.
118
+
119
+ # FORMAT OUTPUT JSON WAJIB:
120
+ {
121
+ "should_save": true,
122
+ "name": "nama-skill-kebab-case",
123
+ "description": "Deskripsi singkat fungsi dan kondisi pemanggilan",
124
+ "content": "# Judul Panduan Prosedur\\n\\n## Gambaran Umum & Tujuan\\n...\\n\\n## Prasyarat & Alat yang Digunakan\\n- ...\\n\\n## Langkah Kerja Teruji (SOP)\\n1. ...\\n2. ...\\n\\n## Tips, Pencegahan Error & Edge Cases\\n- ..."
125
+ }`
126
+
127
+ const messages = [
128
+ { role: 'system', content: systemPrompt },
129
+ { role: 'user', content: promptText }
130
+ ]
131
+
132
+ const response = await fetchAI(messages, false, {
133
+ isSmallTask: true,
134
+ jsonSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ should_save: { type: 'boolean' },
138
+ name: { type: 'string' },
139
+ description: { type: 'string' },
140
+ content: { type: 'string' }
141
+ },
142
+ required: ['should_save', 'name', 'description', 'content']
143
+ }
144
+ })
145
+
146
+ const parsed = cleanAndParse(response)
147
+ if (!parsed || parsed.should_save === false || !parsed.name || !parsed.content) {
148
+ return null
149
+ }
150
+
151
+ const savedSkill = await saveLearnedSkill({
152
+ name: parsed.name.toLowerCase().trim(),
153
+ description: parsed.description || 'Prosedur teknis teruji buatan Mark',
154
+ content: parsed.content
155
+ })
156
+
157
+ if (savedSkill) {
158
+ console.log(
159
+ `[Meta-Learner] ✨ Keahlian berhasil disintesis/diupdate di Database: /${savedSkill.name}`
160
+ )
161
+ }
162
+
163
+ return savedSkill
164
+ } catch (err) {
165
+ console.error('[Meta-Learner] Error in synthesizeSkillAndSave:', err)
166
+ return null
167
+ }
168
+ }
@@ -0,0 +1,167 @@
1
+ import { fetchAI, cleanAndParse } from './core'
2
+
3
+ export const getYoutubeSummary = async (url, data, signal) => {
4
+ try {
5
+ const transcript = await window.api.getYoutubeTranscript(url)
6
+ if (!transcript) return 'Gagal mengambil transkrip video.'
7
+
8
+ const MAX_CHARS = 4000
9
+
10
+ // Jika transkrip pendek, langsung proses tanpa chunking
11
+ if (transcript.length <= MAX_CHARS) {
12
+ const prompts = `
13
+ # ROLE
14
+ Kamu adalah Mark, asisten AI yang ahli dalam menganalisis konten video. Tugasmu adalah memberikan ringkasan yang akurat, padat, dan mudah dipahami dari transkrip video YouTube yang diberikan. Langsung berikan hasil ringkasannya tanpa basa-basi!
15
+
16
+ # FORMAT OUTPUT (WAJIB)
17
+ 1. **Ringkasan Singkat**: 1-2 kalimat tentang inti video.
18
+ 2. **Poin-Poin Penting**: Daftar 3-5 poin utama yang dibahas (ceritakan dengan mengalir dan informatif).
19
+ 3. **Kesimpulan**: Penutup dan kesimpulan dari seluruh video.
20
+ 4. Gunakan bahasa indonesia, jangan gunakan bahasa inggris atau bahasa lainnya
21
+
22
+ # ATURAN MAIN
23
+ - Gunakan bahasa yang santai tapi informatif (seperti peer/teman).
24
+ - Jika ada istilah teknis jelaskan secara singkat.
25
+ - Fokus HANYA pada isi transkrip. Jangan berikan informasi di luar teks yang diberikan.
26
+ - Gunakan bahasa indonesia, jangan gunakan bahasa inggris atau bahasa lainnya
27
+
28
+ # VIDEO META DATA
29
+ judul: ${data.judul},
30
+ author: ${data.author}
31
+
32
+ # TRANSCRIPT
33
+ ${transcript}
34
+ `
35
+ console.log('--- PROMPT YOUTUBE SHORT ---')
36
+ console.log(prompts)
37
+
38
+ const response = await fetchAI([{ role: 'user', content: prompts }], false, {
39
+ signal,
40
+ isSmallTask: true
41
+ })
42
+ return response.content
43
+ }
44
+
45
+ // --- SISTEM CHUNKING UNTUK VIDEO PANJANG ---
46
+ const chunks = []
47
+ let currentChunk = ''
48
+ const lines = transcript.split('\n')
49
+
50
+ for (let line of lines) {
51
+ // Jika ada satu baris yang sangat panjang melebihi batas (misal tidak ada newline)
52
+ while (line.length > MAX_CHARS) {
53
+ if (currentChunk.length > 0) {
54
+ chunks.push(currentChunk)
55
+ currentChunk = ''
56
+ }
57
+ chunks.push(line.substring(0, MAX_CHARS))
58
+ line = line.substring(MAX_CHARS)
59
+ }
60
+
61
+ if (currentChunk.length + line.length > MAX_CHARS) {
62
+ if (currentChunk.length > 0) chunks.push(currentChunk)
63
+ currentChunk = line + '\n'
64
+ } else {
65
+ currentChunk += line + '\n'
66
+ }
67
+ }
68
+ if (currentChunk.trim().length > 0) {
69
+ chunks.push(currentChunk)
70
+ }
71
+
72
+ let finalSummary = ''
73
+ // Mulai proses tiap chunk
74
+ for (let i = 0; i < chunks.length; i++) {
75
+ if (signal?.aborted) throw new Error('AbortError')
76
+
77
+ const chunkPrompt = `
78
+ # ROLE
79
+ Kamu adalah Mark, asisten AI yang ahli merangkum konten video secara naratif dan mengalir layaknya sebuah artikel atau cerita. Ini adalah instruksi langsung, BUKAN percakapan. DILARANG meminta input tambahan. LANGSUNG berikan ringkasan dari teks transkrip di bawah ini!
80
+
81
+ Ini adalah bagian ${i + 1} dari ${chunks.length} dari transkrip video YouTube yang panjang.
82
+
83
+ # FORMAT OUTPUT (WAJIB)
84
+ - Berikan ringkasan isi video secara kreatif, bebas, dan asik (boleh menggunakan paragraf naratif atau poin-poin penting yang mengalir).
85
+ - DILARANG KERAS menyertakan atau menggunakan [timestamp] dalam bentuk apapun! Cukup ceritakan saja isi informasinya.
86
+ - Gunakan bahasa Indonesia yang santai tapi tetap padat dan informatif.
87
+
88
+ # VIDEO META DATA
89
+ judul: ${data.judul || 'Tidak diketahui'},
90
+ author: ${data.author || 'Tidak diketahui'}
91
+
92
+ # TRANSCRIPT BAGIAN ${i + 1}
93
+ ${chunks[i]}
94
+ `
95
+ console.log(`--- PROMPT YOUTUBE CHUNK ${i + 1}/${chunks.length} ---`)
96
+ console.log(chunkPrompt)
97
+
98
+ const response = await fetchAI([{ role: 'user', content: chunkPrompt }], false, {
99
+ signal,
100
+ isSmallTask: true
101
+ })
102
+ finalSummary += `${response.content}\n\n`
103
+
104
+ // Cooldown 12 detik jika bukan chunk terakhir (Menghindari TPM limit Groq)
105
+ if (i < chunks.length - 1) {
106
+ await new Promise((resolve) => setTimeout(resolve, 12000))
107
+ }
108
+ }
109
+
110
+ return finalSummary.trim()
111
+ } catch (error) {
112
+ console.error('Error in youtubeSummary:', error)
113
+ throw error
114
+ }
115
+ }
116
+
117
+ export const getBestMusicMatch = async (userInput, musicList, signal) => {
118
+ try {
119
+ const systemPrompt = `
120
+ Kamu adalah asisten kurator musik. Tugasmu adalah memilih SATU lagu yang paling sesuai dengan niat pengguna dari daftar hasil pencarian YouTube Music.
121
+ Gunakan logikamu:
122
+ - Jika user meminta lagu secara spesifik (misal versi cover, live, atau karaoke), carilah judul yang mengandung unsur tersebut.
123
+ - Jika user menyebutkan nama artis, prioritaskan artis tersebut.
124
+ - Jika user hanya menyebutkan judul secara umum, pilih versi original atau official track yang paling populer/masuk akal (hindari live/cover/karaoke jika tidak diminta).
125
+
126
+ # OUTPUT RULES
127
+ Output HANYA boleh berupa valid JSON berisi ID lagu terpilih:
128
+ \`\`\`json
129
+ { "selectedId": "id_lagu_pilihan" }
130
+ \`\`\`
131
+ `
132
+ const userPrompt = `
133
+ Instruksi User: "${userInput}"
134
+
135
+ Daftar Hasil Pencarian:
136
+ ${JSON.stringify(
137
+ musicList.map((m) => ({ id: m.id, title: m.title, artist: m.artist, duration: m.duration })),
138
+ null,
139
+ 2
140
+ )}
141
+ `
142
+ const messages = [
143
+ { role: 'system', content: systemPrompt },
144
+ { role: 'user', content: userPrompt }
145
+ ]
146
+
147
+ const schema = {
148
+ type: 'object',
149
+ properties: {
150
+ selectedId: { type: 'string' }
151
+ },
152
+ required: ['selectedId'],
153
+ additionalProperties: false
154
+ }
155
+
156
+ const response = await fetchAI(messages, false, {
157
+ signal,
158
+ isSmallTask: true,
159
+ jsonSchema: schema
160
+ })
161
+ const data = cleanAndParse(response.content)
162
+ return data
163
+ } catch (error) {
164
+ console.error('Error in getBestMusicMatch:', error)
165
+ return { selectedId: musicList[0]?.id }
166
+ }
167
+ }
@@ -0,0 +1,359 @@
1
+ import { getAllConfig } from '../db'
2
+ import { stripMarkTags } from '@shared/parsers/mark-tag-parser.js'
3
+
4
+ export const getCurrentTimeInfo = (dateObj = new Date()) => {
5
+ const options = {
6
+ weekday: 'long',
7
+ year: 'numeric',
8
+ month: 'long',
9
+ day: 'numeric',
10
+ hour: '2-digit',
11
+ minute: '2-digit',
12
+ second: '2-digit',
13
+ timeZoneName: 'short'
14
+ }
15
+ return dateObj.toLocaleDateString('id-ID', options)
16
+ }
17
+
18
+ let ttsAudioContext = null
19
+ let currentAudioElement = null
20
+
21
+ // Bersihkan tag kontrol (<mark ... />, legacy mood), tag <mic> / (Mikrofon), format markdown berlebih, dan tag teknis agar tidak terbaca oleh TTS
22
+ export const cleanTextForTTS = (text) => {
23
+ if (!text || typeof text !== 'string') return ''
24
+ return stripMarkTags(text)
25
+ .replace(/(?:<mic>|\(Mikrofon\))/gi, '')
26
+ .replace(/```[\s\S]*?```/g, '') // Hapus blok kode
27
+ .replace(/`([^`]+)`/g, '$1') // Bersihkan inline code
28
+ .replace(/\*\*([^*]+)\*\*/g, '$1') // Bersihkan bold
29
+ .replace(/\*([^*]+)\*/g, '$1') // Bersihkan italic
30
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Bersihkan link
31
+ .replace(/^[\s>#-]+/gm, '') // Bersihkan simbol quote/heading/list di awal baris
32
+ .trim()
33
+ }
34
+
35
+ /**
36
+ * Speech Queue Manager untuk memutar audio per kalimat secara berurutan,
37
+ * mulus, dan gapless dengan audio pre-buffering.
38
+ */
39
+ class SpeechQueueManager {
40
+ constructor() {
41
+ this.queue = []
42
+ this.isPlaying = false
43
+ this.currentAudio = null
44
+ this.activeSessionId = 0
45
+ }
46
+
47
+ reset() {
48
+ this.activeSessionId++
49
+ // Bersihkan dan pause semua item di antrean yang mungkin sudah di-preload
50
+ for (const item of this.queue) {
51
+ if (item.audio) {
52
+ try {
53
+ item.audio.pause()
54
+ item.audio.currentTime = 0
55
+ item.audio.src = ''
56
+ } catch (_) {}
57
+ }
58
+ }
59
+ this.queue = []
60
+
61
+ if (this.currentAudio) {
62
+ try {
63
+ this.currentAudio.pause()
64
+ this.currentAudio.currentTime = 0
65
+ this.currentAudio.src = ''
66
+ } catch (_) {}
67
+ this.currentAudio = null
68
+ }
69
+ if (currentAudioElement) {
70
+ try {
71
+ currentAudioElement.pause()
72
+ currentAudioElement.currentTime = 0
73
+ currentAudioElement.src = ''
74
+ } catch (_) {}
75
+ currentAudioElement = null
76
+ }
77
+
78
+ this.isPlaying = false
79
+ window.isMarkSpeaking = false
80
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: 0 }))
81
+ }
82
+
83
+ async preloadItem(item) {
84
+ if (item.audioPromise) return item.audioPromise
85
+ item.audioPromise = (async () => {
86
+ try {
87
+ const config = await getAllConfig()
88
+ const rate = config[0]?.ttsRate ?? 10
89
+ const pitch = config[0]?.ttsPitch ?? 55
90
+
91
+ const audioSrc = await window.api.textToSpeech(item.text, rate, pitch)
92
+ if (!audioSrc || item.sessionId !== this.activeSessionId) return null
93
+
94
+ const audio = new Audio(audioSrc)
95
+ audio.crossOrigin = 'anonymous'
96
+ audio.preload = 'auto'
97
+ // Picu buffer awal audio di latar belakang
98
+ audio.load()
99
+ item.audio = audio
100
+ return audio
101
+ } catch (err) {
102
+ console.warn('[SpeechQueue] Preload audio failed:', err)
103
+ return null
104
+ }
105
+ })()
106
+ return item.audioPromise
107
+ }
108
+
109
+ enqueue(text) {
110
+ const clean = cleanTextForTTS(text)
111
+ if (!clean) return
112
+ const sessionId = this.activeSessionId
113
+ const item = {
114
+ text: clean,
115
+ sessionId,
116
+ audio: null,
117
+ audioPromise: null
118
+ }
119
+
120
+ // Segera mulai preload audio di latar belakang
121
+ this.preloadItem(item)
122
+ this.queue.push(item)
123
+
124
+ if (!this.isPlaying) {
125
+ this.playNext()
126
+ }
127
+ }
128
+
129
+ async playNext() {
130
+ if (this.queue.length === 0) {
131
+ this.isPlaying = false
132
+ window.isMarkSpeaking = false
133
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: 0 }))
134
+ return
135
+ }
136
+
137
+ const item = this.queue.shift()
138
+ if (item.sessionId !== this.activeSessionId) {
139
+ // Abaikan jika sesi bicara sudah di-reset
140
+ this.playNext()
141
+ return
142
+ }
143
+
144
+ // Pastikan item berikutnya di queue (jika ada) sedang di-preload
145
+ if (this.queue.length > 0) {
146
+ this.preloadItem(this.queue[0])
147
+ }
148
+
149
+ this.isPlaying = true
150
+ try {
151
+ // Dapatkan audio yang sudah di-preload atau tunggu sampai siap
152
+ let audio = item.audio
153
+ if (!audio && item.audioPromise) {
154
+ audio = await item.audioPromise
155
+ } else if (!audio) {
156
+ audio = await this.preloadItem(item)
157
+ }
158
+
159
+ if (!audio || item.sessionId !== this.activeSessionId) {
160
+ this.playNext()
161
+ return
162
+ }
163
+
164
+ this.currentAudio = audio
165
+ currentAudioElement = audio
166
+
167
+ let animationId = null
168
+ let analyser = null
169
+ let dataArray = null
170
+ let bufferLength = 0
171
+
172
+ try {
173
+ if (!ttsAudioContext || ttsAudioContext.state === 'closed') {
174
+ ttsAudioContext = new (window.AudioContext || window.webkitAudioContext)()
175
+ }
176
+ if (ttsAudioContext.state === 'suspended') {
177
+ await ttsAudioContext.resume()
178
+ }
179
+
180
+ const source = ttsAudioContext.createMediaElementSource(audio)
181
+ analyser = ttsAudioContext.createAnalyser()
182
+ analyser.fftSize = 256
183
+ analyser.smoothingTimeConstant = 0.4
184
+ source.connect(analyser)
185
+ analyser.connect(ttsAudioContext.destination)
186
+
187
+ bufferLength = analyser.frequencyBinCount
188
+ dataArray = new Uint8Array(bufferLength)
189
+ } catch (_) {}
190
+
191
+ const updateIntensity = () => {
192
+ if (!window.isMarkSpeaking || !analyser) return
193
+ analyser.getByteFrequencyData(dataArray)
194
+ let sum = 0
195
+ for (let i = 0; i < bufferLength; i++) {
196
+ sum += dataArray[i]
197
+ }
198
+ const avg = sum / bufferLength
199
+ const normalized = Math.min(1, Math.max(0, (avg - 8) / 55))
200
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: normalized }))
201
+ animationId = requestAnimationFrame(updateIntensity)
202
+ }
203
+
204
+ const cleanupAndNext = () => {
205
+ if (animationId) cancelAnimationFrame(animationId)
206
+ if (this.currentAudio === audio) {
207
+ this.currentAudio = null
208
+ }
209
+ this.playNext()
210
+ }
211
+
212
+ audio.onended = cleanupAndNext
213
+ audio.onerror = cleanupAndNext
214
+
215
+ window.isMarkSpeaking = true
216
+ await audio.play()
217
+ if (analyser) updateIntensity()
218
+ } catch (err) {
219
+ console.warn('[SpeechQueue] Error playing segment:', err)
220
+ this.playNext()
221
+ }
222
+ }
223
+ }
224
+
225
+ export const speechQueue = new SpeechQueueManager()
226
+
227
+ export const playVoice = async (text, onStart, onEnd) => {
228
+ try {
229
+ if (!text || typeof text !== 'string') {
230
+ if (onStart) onStart()
231
+ if (onEnd) onEnd()
232
+ return
233
+ }
234
+
235
+ speechQueue.reset()
236
+
237
+ // Hentikan pemutaran sebelumnya jika sedang ada suara aktif
238
+ if (currentAudioElement) {
239
+ try {
240
+ currentAudioElement.pause()
241
+ currentAudioElement.currentTime = 0
242
+ } catch (_) {}
243
+ currentAudioElement = null
244
+ }
245
+
246
+ const cleanText = cleanTextForTTS(text)
247
+ if (!cleanText) {
248
+ if (onStart) onStart()
249
+ if (onEnd) onEnd()
250
+ return
251
+ }
252
+
253
+ const config = await getAllConfig()
254
+ const rate = config[0]?.ttsRate ?? 10
255
+ const pitch = config[0]?.ttsPitch ?? 55
256
+
257
+ // 1. Minta stream URL audio ke backend
258
+ const audioSrc = await window.api.textToSpeech(cleanText, rate, pitch)
259
+
260
+ if (audioSrc) {
261
+ // 2. Bikin object Audio baru dari stream URL
262
+ const audio = new Audio(audioSrc)
263
+ audio.crossOrigin = 'anonymous'
264
+ currentAudioElement = audio
265
+
266
+ let animationId = null
267
+ let analyser = null
268
+ let dataArray = null
269
+ let bufferLength = 0
270
+
271
+ // Setup Web Audio API for Intensity Extraction
272
+ try {
273
+ if (!ttsAudioContext || ttsAudioContext.state === 'closed') {
274
+ ttsAudioContext = new (window.AudioContext || window.webkitAudioContext)()
275
+ }
276
+ if (ttsAudioContext.state === 'suspended') {
277
+ await ttsAudioContext.resume()
278
+ }
279
+
280
+ const source = ttsAudioContext.createMediaElementSource(audio)
281
+ analyser = ttsAudioContext.createAnalyser()
282
+ analyser.fftSize = 256
283
+ analyser.smoothingTimeConstant = 0.4
284
+ source.connect(analyser)
285
+ analyser.connect(ttsAudioContext.destination)
286
+
287
+ bufferLength = analyser.frequencyBinCount
288
+ dataArray = new Uint8Array(bufferLength)
289
+ } catch (_) {
290
+ // Fallback jika createMediaElementSource terhalang: audio tetap play secara mandiri
291
+ }
292
+
293
+ const updateIntensity = () => {
294
+ if (!window.isMarkSpeaking || !analyser) return
295
+ analyser.getByteFrequencyData(dataArray)
296
+ let sum = 0
297
+ for (let i = 0; i < bufferLength; i++) {
298
+ sum += dataArray[i]
299
+ }
300
+ const avg = sum / bufferLength
301
+ // Normalisasi 0 - 255 menjadi 0.0 - 1.0 dengan respons dinamis
302
+ const normalized = Math.min(1, Math.max(0, (avg - 8) / 55))
303
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: normalized }))
304
+ animationId = requestAnimationFrame(updateIntensity)
305
+ }
306
+
307
+ audio.onended = () => {
308
+ window.isMarkSpeaking = false
309
+ if (currentAudioElement === audio) {
310
+ currentAudioElement = null
311
+ }
312
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: 0 }))
313
+ if (animationId) cancelAnimationFrame(animationId)
314
+ if (onEnd) onEnd()
315
+ }
316
+
317
+ audio.onerror = (e) => {
318
+ console.warn('[playVoice] Audio playback warning:', e)
319
+ window.isMarkSpeaking = false
320
+ if (currentAudioElement === audio) {
321
+ currentAudioElement = null
322
+ }
323
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: 0 }))
324
+ if (animationId) cancelAnimationFrame(animationId)
325
+ if (onEnd) onEnd()
326
+ }
327
+
328
+ // 3. Mainkan audio seketika
329
+ window.isMarkSpeaking = true
330
+ await audio.play()
331
+ if (analyser) updateIntensity()
332
+ if (onStart) onStart()
333
+ } else {
334
+ if (onStart) onStart()
335
+ if (onEnd) onEnd()
336
+ }
337
+ } catch (error) {
338
+ console.error('Gagal memutar suara:', error)
339
+ if (window.api && window.api.showNotification) {
340
+ window.api.showNotification('Error TTS', String(error.message || error))
341
+ }
342
+ window.isMarkSpeaking = false
343
+ window.dispatchEvent(new CustomEvent('mark-intensity', { detail: 0 }))
344
+ if (onStart) onStart()
345
+ if (onEnd) onEnd()
346
+ }
347
+ }
348
+
349
+ // ==========================================
350
+ // TELEGRAM UTILS
351
+ // ==========================================
352
+ export const formatForTelegram = (text) => {
353
+ if (!text) return ''
354
+ return text.trim()
355
+ }
356
+
357
+ // ==========================================
358
+ // PLANNING (AGENTIC) FUNCTIONS
359
+ // ==========================================