@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,454 @@
1
+ import { useEffect, useRef, useCallback } from 'react'
2
+ import { useYoutubeMusic } from '../contexts/YoutubeMusicContext'
3
+ import { useApproval } from '../contexts/ApprovalContext'
4
+ import { saveSession, getChatData, saveMainThread, getMainThread } from '../api/db'
5
+ import { useMarkState, useMarkYoutube, useMarkMusic, useMarkPlan } from './agent'
6
+ import { useAwareness } from './useAwareness'
7
+ import { useRelationalGrowth } from './agent/useRelationalGrowth'
8
+ import { useChatArchiver } from './useChatArchiver'
9
+ import { useVAD } from './useVAD'
10
+ import { formatForTelegram, getCurrentTimeInfo } from '../api/ai/utils'
11
+
12
+ export const useMarkAgent = () => {
13
+ const { requestApproval } = useApproval()
14
+ const youtubeMusicTools = useYoutubeMusic()
15
+
16
+ const state = useMarkState()
17
+ const {
18
+ chatData,
19
+ setChatData,
20
+ clearChat,
21
+ config,
22
+ message,
23
+ setMessage,
24
+ isLoading,
25
+ isAgentBusy,
26
+ runningSessionId,
27
+ setRunningSessionId,
28
+ runningSessionIds,
29
+ setRunningSessionIds,
30
+ addRunningSessionId,
31
+ removeRunningSessionId,
32
+ isSpeak,
33
+ setIsSpeak,
34
+ abortControllerRef,
35
+ handleStop,
36
+ orbStatus,
37
+ setOrbStatus,
38
+ currentResponse,
39
+ setCurrentResponse,
40
+ notifications,
41
+ pushNotification,
42
+ activeProcesses,
43
+ setActiveProcesses,
44
+ pushProcess,
45
+ dismissProcess,
46
+ inputSource,
47
+ setInputSource,
48
+ activeTopic,
49
+ currentActiveSessionId,
50
+ setCurrentActiveSessionId,
51
+ isChatLoaded,
52
+ isBooting,
53
+ setIsBooting
54
+ } = state
55
+
56
+ const { handleYoutubeSearch, handleYoutubeSummary, getYoutubeData } = useMarkYoutube(setChatData)
57
+ const { handleMusic } = useMarkMusic(setChatData, abortControllerRef, youtubeMusicTools)
58
+
59
+ const tools = {
60
+ handleYoutubeSearch,
61
+ handleYoutubeSummary,
62
+ handleMusic,
63
+ getYoutubeData,
64
+ currentMusicTrack: youtubeMusicTools.isPlaying ? youtubeMusicTools.currentTrack : null
65
+ }
66
+
67
+ const requestCameraCaptureRef = useRef(null)
68
+
69
+ const {
70
+ handlePlanningCommand,
71
+ handleIntervention,
72
+ handleStop: planHandleStop
73
+ } = useMarkPlan({
74
+ ...state,
75
+ ...tools,
76
+ requestApproval,
77
+ requestCameraCapture: async (args) => {
78
+ console.log(
79
+ '[useMarkAgent] requestCameraCapture called, ref.current:',
80
+ !!requestCameraCaptureRef.current
81
+ )
82
+ if (requestCameraCaptureRef.current) {
83
+ return await requestCameraCaptureRef.current(args)
84
+ }
85
+ console.warn(
86
+ '[useMarkAgent] requestCameraCaptureRef.current is null! MarkHome belum set callback.'
87
+ )
88
+ return null
89
+ }
90
+ })
91
+
92
+ useAwareness({
93
+ isLoading,
94
+ isAgentBusy,
95
+ setChatData,
96
+ setOrbStatus,
97
+ config,
98
+ chatData,
99
+ handlePlanningCommand,
100
+ currentMusicTrack: youtubeMusicTools.isPlaying ? youtubeMusicTools.currentTrack : null
101
+ })
102
+
103
+ useRelationalGrowth({ chatData, currentActiveSessionId, isLoading })
104
+
105
+ useChatArchiver({ chatData, activeTopic, config, pushNotification, isLoading })
106
+
107
+ const activeTgRequestRef = useRef(null)
108
+ const hasGreetedRef = useRef(false)
109
+
110
+ // Welcome Greeting on Startup
111
+ useEffect(() => {
112
+ if (isChatLoaded && !hasGreetedRef.current) {
113
+ hasGreetedRef.current = true
114
+ console.log('[useMarkAgent] Memicu pesan sambutan (Boot sequence)...')
115
+
116
+ const bootSequence = async () => {
117
+ let timeContext = ''
118
+ let topicContext = ''
119
+
120
+ if (chatData && chatData.length > 0) {
121
+ const lastMsg = chatData[chatData.length - 1]
122
+ let lastTimeMs = null
123
+
124
+ if (lastMsg) {
125
+ if (
126
+ typeof lastMsg.created_at === 'number' &&
127
+ !isNaN(lastMsg.created_at) &&
128
+ lastMsg.created_at > 0
129
+ ) {
130
+ lastTimeMs = lastMsg.created_at
131
+ } else if (
132
+ typeof lastMsg.timestamp === 'number' &&
133
+ !isNaN(lastMsg.timestamp) &&
134
+ lastMsg.timestamp > 0
135
+ ) {
136
+ lastTimeMs = lastMsg.timestamp
137
+ }
138
+ }
139
+
140
+ if (lastTimeMs && lastTimeMs > 0) {
141
+ const diffMs = Date.now() - lastTimeMs
142
+ const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
143
+ const diffDays = Math.floor(diffHours / 24)
144
+
145
+ if (diffDays >= 365 || diffDays < 0) {
146
+ timeContext = `\n[KONTEKS WAKTU & RIWAYAT]: Pengguna baru saja membuka kembali aplikasi.`
147
+ } else if (diffDays >= 3) {
148
+ timeContext = `\n[KONTEKS WAKTU & RIWAYAT]: Pengguna sudah tidak membuka aplikasi/ngobrol selama ${diffDays} hari! Sapa dengan nada kaget, akrab, atau kangen bergaya santai (contoh: "Waduh kemana aja nih lama gak kelihatan", "Akhirnya nongkrong lagi kita", "Sibuk banget kayaknya baru kelihatan lagi", dll). JANGAN formal atau kaku!`
149
+ } else if (diffDays >= 1) {
150
+ timeContext = `\n[KONTEKS WAKTU & RIWAYAT]: Pengguna kembali setelah ${diffDays} hari tidak ngobrol. Beri sapaan santai dan ramah bahwa lu senang dia balik lagi.`
151
+ } else if (diffHours >= 5) {
152
+ timeContext = `\n[KONTEKS WAKTU & RIWAYAT]: Pengguna kembali setelah jeda beberapa jam hari ini.`
153
+ } else {
154
+ timeContext = `\n[KONTEKS WAKTU & RIWAYAT]: Pengguna baru saja jeda sejenak dari sesi sebelumnya hari ini. JANGAN pernah menyebutkan berapa menit atau jam yang lalu! Cukup beri sapaan santai dan singkat seolah melanjutkan obrolan.`
155
+ }
156
+ }
157
+
158
+ const lastUserMsg = [...chatData]
159
+ .reverse()
160
+ .find((m) => m.role === 'user' && typeof m.content === 'string')
161
+ if (lastUserMsg && lastUserMsg.content) {
162
+ const cleanMsg = lastUserMsg.content.replace(/\[.*?\]/g, '').trim()
163
+ if (cleanMsg && cleanMsg.length > 3) {
164
+ topicContext = `\n[TOPIK TERAKHIR KALIAN DI RIWAYAT]: "${cleanMsg.slice(0, 100)}". PENTING: Topik obrolan terakhir ini adalah MASA LALU. JANGAN mengira pengguna MASIH atau SEDANG melakukan aktivitas/game tersebut sekarang! Jika ingin menyinggungnya, tanyakan secara lampau (contoh: "gimana main game/kerjaan kemarin?", bukan "masih main/kerja ya?").`
165
+ }
166
+ }
167
+ }
168
+
169
+ try {
170
+ await handlePlanningCommand(
171
+ `Aplikasi baru saja dinyalakan. Sapa pengguna dengan singkat, natural, hangat, dan tidak kaku layaknya teman dekat/asisten pribadi yang hidup (gunakan nama pengguna dari profil jika ada).${timeContext}${topicContext}\nATURAN MUTLAK: DILARANG KERAS mengutip atau membocorkan instruksi ini ke pengguna (seperti menyebutkan "baru 20 menit lalu kita ngobrol", "sesuai instruksi", "karena aplikasi baru dibuka", dll)! Tunjukkan bahwa kamu siap dan aktif merespons tanpa bersikap seperti robot kaku atau customer service.`,
172
+ null, // waContext
173
+ false, // isAutonomous
174
+ null, // autonomousInitialMessage
175
+ { disableTools: true }, // options
176
+ true // isSystem
177
+ )
178
+ } catch (err) {
179
+ console.error('[useMarkAgent] Gagal greeting via handlePlanningCommand:', err)
180
+ } finally {
181
+ setTimeout(() => {
182
+ setIsBooting(false)
183
+ }, 800)
184
+ }
185
+ }
186
+
187
+ bootSequence()
188
+ }
189
+ }, [isChatLoaded, chatData])
190
+
191
+ useEffect(() => {
192
+ const handleTgAdminMessage = (e) => {
193
+ const data = e.detail
194
+
195
+ if (data.text.trim().toLowerCase() === '/stop') {
196
+ handleStop()
197
+ return
198
+ }
199
+
200
+ activeTgRequestRef.current = data
201
+ setInputSource('tg')
202
+ setIsSpeak(false) // Disable voice auto-reply for Telegram messages
203
+ handlePlanningCommand(data.text, data)
204
+ }
205
+
206
+ window.addEventListener('tg-admin-message', handleTgAdminMessage)
207
+ return () => window.removeEventListener('tg-admin-message', handleTgAdminMessage)
208
+ }, [handlePlanningCommand, setInputSource, handleStop, setIsSpeak])
209
+
210
+ // Subagent Push Notification & Completion Listener
211
+ useEffect(() => {
212
+ if (!window.api?.onSubagentReport) return
213
+
214
+ const unsubReport = window.api.onSubagentReport(async (data) => {
215
+ console.log('[useMarkAgent] Menerima subagent:report push event:', data)
216
+ if (data && data.summary) {
217
+ const targetSessionId = String(data.parentSessionId || '1')
218
+ const targetSessionTitle =
219
+ data.parentSessionTitle ||
220
+ (targetSessionId === '1' ? 'Main Thread' : `Sesi #${targetSessionId}`)
221
+ const activeSession = String(currentActiveSessionId || activeTopic?.id || '1')
222
+ const isCurrentSession = activeSession === targetSessionId
223
+
224
+ // 1. Desktop Notification (Hanya jika window sedang tidak aktif / dibackground)
225
+ if (document.hidden && window.api?.showNotification) {
226
+ window.api.showNotification({
227
+ title: isCurrentSession
228
+ ? `Laporan @${data.subagentName || 'Sub-Agent'}`
229
+ : `Laporan @${data.subagentName || 'Sub-Agent'} [${targetSessionTitle}]`,
230
+ body: data.summary
231
+ })
232
+ }
233
+
234
+ // 2. Jika sesi yang menerima laporan sedang dibuka aktif oleh user
235
+ if (isCurrentSession) {
236
+ if (!isAgentBusy) {
237
+ handlePlanningCommand(
238
+ `[SUB-AGENT REPORT RECEIVED]: Sub-agent @${data.subagentName || 'Specialist'} telah menyelesaikan tugasnya dan melaporkan hasil berikut:\n"${data.summary}"\n${data.artifact ? `Artefak: ${data.artifact}` : ''}\nBeri tanggapan atau rangkumkan secara singkat kepada user.`,
239
+ null,
240
+ false,
241
+ null,
242
+ {
243
+ sessionId: targetSessionId,
244
+ disableTools: false,
245
+ customUserMessage: {
246
+ role: 'user',
247
+ source: 'subagent',
248
+ sender: `@${data.subagentName || 'Sub-Agent'}`,
249
+ content: `[SUB-AGENT REPORT RECEIVED]: Sub-agent @${data.subagentName || 'Specialist'} telah menyelesaikan tugasnya dan melaporkan hasil berikut:\n"${data.summary}"\n${data.artifact ? `Artefak: ${data.artifact}` : ''}`
250
+ }
251
+ },
252
+ true
253
+ ).catch((err) => {
254
+ console.error(
255
+ '[useMarkAgent] Error handling active session subagent report turn:',
256
+ err
257
+ )
258
+ })
259
+ }
260
+ } else {
261
+ // 3. Jika user sedang berada di sesi lain / background delivery
262
+ try {
263
+ const timestampStr = getCurrentTimeInfo()
264
+ const existingData =
265
+ targetSessionId === '1'
266
+ ? (await getMainThread()) || []
267
+ : (await getChatData(targetSessionId)) || []
268
+
269
+ const userReportMessage = {
270
+ role: 'user',
271
+ content: `[SUB-AGENT REPORT RECEIVED]: Sub-agent @${data.subagentName || 'Specialist'} telah menyelesaikan tugasnya dan melaporkan hasil berikut:\n"${data.summary}"\n${data.artifact ? `Artefak: ${data.artifact}` : ''}`,
272
+ timestamp: timestampStr,
273
+ created_at: Date.now(),
274
+ source: 'subagent',
275
+ sender: `@${data.subagentName || 'Sub-Agent'}`
276
+ }
277
+
278
+ const aiSummaryMessage = {
279
+ role: 'ai',
280
+ content: `Laporan dari @${data.subagentName || 'Specialist'} telah diterima dan diarsipkan ke sesi ini:\n\n${data.summary}${data.artifact ? `\n\n**Artefak:**\n${data.artifact}` : ''}`,
281
+ timestamp: timestampStr,
282
+ created_at: Date.now() + 1,
283
+ isThinking: false
284
+ }
285
+
286
+ const updatedHistory = [...existingData, userReportMessage, aiSummaryMessage]
287
+
288
+ if (targetSessionId === '1') {
289
+ await saveMainThread(updatedHistory)
290
+ } else {
291
+ await saveSession(targetSessionId, updatedHistory)
292
+ }
293
+
294
+ // Pancarkan event reactive update agar UI sesi yang sedang di background otomatis ter-update
295
+ window.dispatchEvent(
296
+ new CustomEvent('session-updated', {
297
+ detail: { sessionId: targetSessionId, data: updatedHistory }
298
+ })
299
+ )
300
+ } catch (dbErr) {
301
+ console.error('[useMarkAgent] Gagal menyimpan background subagent report:', dbErr)
302
+ }
303
+ }
304
+ }
305
+ })
306
+
307
+ return () => {
308
+ if (unsubReport) unsubReport()
309
+ }
310
+ }, [handlePlanningCommand, isAgentBusy, pushNotification, currentActiveSessionId, activeTopic])
311
+
312
+ const isInitialSyncDoneRef = useRef(false)
313
+ const lastSyncedMsgIdRef = useRef(null)
314
+
315
+ useEffect(() => {
316
+ if (!isChatLoaded) return
317
+
318
+ // Pada render pertama setelah chat DB dimuat, tandai pesan AI terakhir sebagai "sudah tersinkron" agar pesan histori tidak terkirim ulang
319
+ if (!isInitialSyncDoneRef.current) {
320
+ isInitialSyncDoneRef.current = true
321
+ if (chatData && chatData.length > 0) {
322
+ const lastAiMsg = [...chatData]
323
+ .reverse()
324
+ .find((m) => m.role === 'ai' && !m.isThinking && !m.isSearching && !m.isSummarizing)
325
+ if (lastAiMsg) {
326
+ lastSyncedMsgIdRef.current = lastAiMsg.timestamp || lastAiMsg.content
327
+ }
328
+ }
329
+ return
330
+ }
331
+
332
+ if (!isAgentBusy && activeTgRequestRef.current && chatData.length > 0) {
333
+ const lastAiMsg = [...chatData]
334
+ .reverse()
335
+ .find((m) => m.role === 'ai' && !m.isThinking && !m.isSearching && !m.isSummarizing)
336
+ if (lastAiMsg) {
337
+ const currentReq = activeTgRequestRef.current
338
+ activeTgRequestRef.current = null
339
+ setInputSource('pc')
340
+ lastSyncedMsgIdRef.current = lastAiMsg.timestamp || lastAiMsg.content
341
+
342
+ window.api?.sendTgAgentExecutionDone({
343
+ chatId: currentReq.chatId,
344
+ result: { answer: formatForTelegram(lastAiMsg.content) },
345
+ msgId: currentReq.msgId
346
+ })
347
+ }
348
+ } else if (
349
+ !isAgentBusy &&
350
+ chatData.length > 0 &&
351
+ inputSource !== 'tg' &&
352
+ !activeTgRequestRef.current
353
+ ) {
354
+ const lastAiMsg = [...chatData]
355
+ .reverse()
356
+ .find((m) => m.role === 'ai' && !m.isThinking && !m.isSearching && !m.isSummarizing)
357
+ const msgKey = lastAiMsg ? lastAiMsg.timestamp || lastAiMsg.content : null
358
+ if (lastAiMsg && lastAiMsg.content && lastSyncedMsgIdRef.current !== msgKey) {
359
+ lastSyncedMsgIdRef.current = msgKey
360
+ if (
361
+ window.api?.tgBroadcastToAdmins &&
362
+ !lastAiMsg.isProactive &&
363
+ lastAiMsg.source !== 'telegram'
364
+ ) {
365
+ window.api.tgBroadcastToAdmins(`*Mark (PC)*:\n${lastAiMsg.content}`)
366
+ }
367
+ }
368
+ }
369
+ }, [isAgentBusy, chatData, isChatLoaded, setInputSource])
370
+
371
+ const handleSubmit = (e, textPrompt, sendOptions = {}) => {
372
+ if (e && typeof e.preventDefault === 'function') e.preventDefault()
373
+ const textToSend =
374
+ typeof textPrompt === 'string' ? textPrompt.trim() : typeof e === 'string' ? e.trim() : ''
375
+ if (!textToSend) return
376
+
377
+ const targetSessionId = sendOptions?.sessionId || 1
378
+
379
+ const isTargetRunning =
380
+ (runningSessionIds && runningSessionIds.map(String).includes(String(targetSessionId))) ||
381
+ (String(targetSessionId) === '1' && isLoading)
382
+
383
+ if (isTargetRunning) {
384
+ if (handleIntervention) {
385
+ handleIntervention(textToSend, targetSessionId, sendOptions)
386
+ }
387
+ } else {
388
+ handlePlanningCommand(textToSend, false, false, sendOptions)
389
+ }
390
+ }
391
+
392
+ const handleVoiceTranscript = useCallback(
393
+ (text, meta = {}) => {
394
+ if (!text || !text.trim()) return
395
+ const wakePrefix = meta?.isWakeWord && meta?.wakePhrase ? `${meta.wakePhrase} ` : ''
396
+ const prefixedText = `<mic> ${wakePrefix}${text}`.trim()
397
+ setMessage(prefixedText)
398
+ setIsSpeak(true)
399
+ handlePlanningCommand(prefixedText, null, false, null, { forceSpeak: true })
400
+ },
401
+ [setMessage, setIsSpeak, handlePlanningCommand]
402
+ )
403
+
404
+ const vad = useVAD({
405
+ onTranscript: handleVoiceTranscript
406
+ })
407
+
408
+ return {
409
+ chatData,
410
+ setChatData,
411
+ clearChat,
412
+ isSpeak,
413
+ setIsSpeak,
414
+ config,
415
+ isLoading,
416
+ isAgentBusy,
417
+ runningSessionId,
418
+ setRunningSessionId,
419
+ runningSessionIds,
420
+ setRunningSessionIds,
421
+ addRunningSessionId,
422
+ removeRunningSessionId,
423
+ message,
424
+ setMessage,
425
+ orbStatus,
426
+ setOrbStatus,
427
+ currentResponse,
428
+ setCurrentResponse,
429
+ notifications,
430
+ pushNotification,
431
+ activeProcesses,
432
+ setActiveProcesses,
433
+ pushProcess,
434
+ dismissProcess,
435
+ inputSource,
436
+ setInputSource,
437
+ handlePlanningCommand,
438
+ handleIntervention,
439
+ handleStop: planHandleStop || handleStop,
440
+ handleSubmit,
441
+ isBooting,
442
+ requestCameraCaptureRef,
443
+ currentActiveSessionId,
444
+ setCurrentActiveSessionId,
445
+ // VAD & Voice Engine
446
+ isRecording: vad.isRecording,
447
+ isProcessing: vad.isProcessing,
448
+ audioIntensity: vad.audioIntensity,
449
+ startRecording: vad.startRecording,
450
+ stopRecording: vad.stopRecording,
451
+ toggleRecording: vad.toggleRecording,
452
+ toastMessage: vad.toastMessage
453
+ }
454
+ }
@@ -0,0 +1,152 @@
1
+ import { useState, useEffect, useRef, useCallback } from 'react'
2
+ import { findSimilarMemoryClusters } from '../api/oramaStore'
3
+ import { runBatchConsolidation } from '../api/ai/memoryGroomer'
4
+ import { updateMemory, deleteMemory } from '../api/db'
5
+
6
+ // Shared in-memory event listener so any component using useMemoryGroomer stays in sync
7
+ const groomListeners = new Set()
8
+ let isAutoGroomScheduled = false
9
+
10
+ function notifyListeners(state) {
11
+ groomListeners.forEach(listener => listener(state))
12
+ }
13
+
14
+ export function useMemoryGroomer(enableAutoOnStartup = false) {
15
+ const [isGrooming, setIsGrooming] = useState(false)
16
+ const [groomResult, setGroomResult] = useState(() => {
17
+ try {
18
+ const saved = localStorage.getItem('mark_last_groom_result')
19
+ return saved ? JSON.parse(saved) : {
20
+ timestamp: null,
21
+ mergedCount: 0,
22
+ deletedCount: 0,
23
+ details: [],
24
+ lastChecked: null
25
+ }
26
+ } catch {
27
+ return {
28
+ timestamp: null,
29
+ mergedCount: 0,
30
+ deletedCount: 0,
31
+ details: [],
32
+ lastChecked: null
33
+ }
34
+ }
35
+ })
36
+
37
+ const hasAutoGroomedRef = useRef(false)
38
+
39
+ useEffect(() => {
40
+ const listener = (nextState) => {
41
+ if (nextState.isGrooming !== undefined) setIsGrooming(nextState.isGrooming)
42
+ if (nextState.groomResult !== undefined) setGroomResult(nextState.groomResult)
43
+ }
44
+ groomListeners.add(listener)
45
+ return () => groomListeners.delete(listener)
46
+ }, [])
47
+
48
+ const triggerGrooming = useCallback(async (force = false) => {
49
+ if (isGrooming) return null
50
+ setIsGrooming(true)
51
+ notifyListeners({ isGrooming: true })
52
+
53
+ try {
54
+ console.log('[Hippocampus Engine] Memulai scan konsolidasi memori...')
55
+ const clusters = await findSimilarMemoryClusters(0.60)
56
+
57
+ if (!clusters || clusters.length === 0) {
58
+ console.log('[Hippocampus Engine] Ingatan sudah bersih & terstruktur. Tidak ada cluster yang perlu digabung.')
59
+ const currentRes = {
60
+ ...groomResult,
61
+ lastChecked: Date.now()
62
+ }
63
+ setGroomResult(currentRes)
64
+ localStorage.setItem('mark_last_groom_result', JSON.stringify(currentRes))
65
+ notifyListeners({ groomResult: currentRes, isGrooming: false })
66
+ setIsGrooming(false)
67
+ return currentRes
68
+ }
69
+
70
+ console.log(`[Hippocampus Engine] Ditemukan ${clusters.length} cluster. Mengirim ke LLM...`)
71
+ const consolidations = await runBatchConsolidation(clusters)
72
+
73
+ let mergedCount = 0
74
+ let deletedCount = 0
75
+ const details = []
76
+
77
+ for (const item of consolidations) {
78
+ const { keep_id, merged_text, delete_ids } = item
79
+ if (!keep_id || !merged_text) continue
80
+
81
+ try {
82
+ // 1. Update memori utama dengan narasi kronologis yang sudah digabungkan
83
+ await updateMemory(keep_id, merged_text)
84
+ mergedCount++
85
+
86
+ // 2. Hapus duplikat setelah merge sukses
87
+ if (Array.isArray(delete_ids)) {
88
+ for (const delId of delete_ids) {
89
+ if (delId && delId !== keep_id) {
90
+ await deleteMemory(delId)
91
+ deletedCount++
92
+ }
93
+ }
94
+ }
95
+
96
+ details.push({
97
+ keep_id,
98
+ merged_text,
99
+ delete_ids: delete_ids || []
100
+ })
101
+ } catch (err) {
102
+ console.error(`[Hippocampus Engine] Gagal mengaplikasikan konsolidasi untuk id ${keep_id}:`, err)
103
+ }
104
+ }
105
+
106
+ const updatedResult = {
107
+ timestamp: Date.now(),
108
+ lastChecked: Date.now(),
109
+ mergedCount,
110
+ deletedCount,
111
+ details
112
+ }
113
+
114
+ setGroomResult(updatedResult)
115
+ localStorage.setItem('mark_last_groom_result', JSON.stringify(updatedResult))
116
+ notifyListeners({ groomResult: updatedResult, isGrooming: false })
117
+ setIsGrooming(false)
118
+
119
+ console.log(`[Hippocampus Engine] Selesai! ${mergedCount} ingatan dikonsolidasi kronologis, ${deletedCount} duplikat dihapus.`)
120
+ return updatedResult
121
+ } catch (err) {
122
+ console.error('[Hippocampus Engine] Error selama proses grooming:', err)
123
+ setIsGrooming(false)
124
+ notifyListeners({ isGrooming: false })
125
+ return null
126
+ }
127
+ }, [isGrooming, groomResult])
128
+
129
+ const triggerGroomingRef = useRef(triggerGrooming)
130
+ useEffect(() => {
131
+ triggerGroomingRef.current = triggerGrooming
132
+ }, [triggerGrooming])
133
+
134
+ useEffect(() => {
135
+ if (!enableAutoOnStartup || isAutoGroomScheduled) return
136
+ isAutoGroomScheduled = true
137
+
138
+ console.log('[Hippocampus Engine] Timer otomatis grooming dijadwalkan dalam 10 detik setelah startup...')
139
+ setTimeout(() => {
140
+ console.log('[Hippocampus Engine] Menjalankan automatic background memory grooming...')
141
+ if (triggerGroomingRef.current) {
142
+ triggerGroomingRef.current(false)
143
+ }
144
+ }, 10000)
145
+ }, [enableAutoOnStartup])
146
+
147
+ return {
148
+ isGrooming,
149
+ groomResult,
150
+ triggerGrooming
151
+ }
152
+ }