@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 { WebSocketServer, WebSocket } from 'ws'
2
+
3
+ /**
4
+ * WebSocket Hub untuk MARK Core.
5
+ * Mengelola koneksi real-time antara Node.js Core Backend, WebUI Client, dan CLI Streamer.
6
+ */
7
+ class WebSocketHub {
8
+ constructor() {
9
+ this.wss = null
10
+ this.clients = new Set()
11
+ this.eventHandlers = new Map()
12
+ }
13
+
14
+ /**
15
+ * Inisialisasi WebSocket Server pada HTTP Server yang ada
16
+ * @param {import('http').Server} server
17
+ */
18
+ init(server) {
19
+ this.wss = new WebSocketServer({ server, path: '/stream' })
20
+ this.uiClients = new Set()
21
+
22
+ this.wss.on('error', (err) => {
23
+ // Delegasikan error EADDRINUSE ke HTTP Server listener agar auto-fallback berjalan
24
+ if (err.code !== 'EADDRINUSE') {
25
+ console.error('[WebSocket Hub Error]:', err)
26
+ }
27
+ })
28
+
29
+ this.wss.on('connection', (ws) => {
30
+ this.clients.add(ws)
31
+
32
+ // Kirim event sambutan ready dan status presence UI terkini
33
+ this.send(ws, 'core:ready', {
34
+ version: '5.0.0',
35
+ timestamp: Date.now()
36
+ })
37
+ this.send(ws, 'ui:status', {
38
+ active: this.uiClients.size > 0,
39
+ count: this.uiClients.size
40
+ })
41
+
42
+ ws.on('message', (data) => {
43
+ try {
44
+ const parsed = JSON.parse(data.toString())
45
+ const { event, id, payload } = parsed
46
+
47
+ if (event === 'ui:presence') {
48
+ this.uiClients.add(ws)
49
+ this.broadcast('ui:status', {
50
+ active: true,
51
+ count: this.uiClients.size
52
+ })
53
+ } else if (event === 'ui:goodbye') {
54
+ this.uiClients.delete(ws)
55
+ this.broadcast('ui:status', {
56
+ active: this.uiClients.size > 0,
57
+ count: this.uiClients.size
58
+ })
59
+ } else if (event) {
60
+ this.handleIncomingEvent(ws, event, payload, id)
61
+ }
62
+ } catch (_) {}
63
+ })
64
+
65
+ const cleanupClient = () => {
66
+ this.clients.delete(ws)
67
+ if (this.uiClients.has(ws)) {
68
+ this.uiClients.delete(ws)
69
+ this.broadcast('ui:status', {
70
+ active: this.uiClients.size > 0,
71
+ count: this.uiClients.size
72
+ })
73
+ }
74
+ }
75
+
76
+ ws.on('close', cleanupClient)
77
+ ws.on('error', cleanupClient)
78
+ })
79
+ }
80
+
81
+ /**
82
+ * Daftarkan handler event masuk dari client
83
+ * @param {string} event
84
+ * @param {Function} handler
85
+ */
86
+ on(event, handler) {
87
+ if (!this.eventHandlers.has(event)) {
88
+ this.eventHandlers.set(event, [])
89
+ }
90
+ this.eventHandlers.get(event).push(handler)
91
+ }
92
+
93
+ /**
94
+ * Handle event yang dikirim dari WebUI / CLI
95
+ */
96
+ async handleIncomingEvent(ws, event, payload, id) {
97
+ const handlers = this.eventHandlers.get(event)
98
+ if (handlers && handlers.length > 0) {
99
+ for (const handler of handlers) {
100
+ try {
101
+ const result = await handler(payload, ws)
102
+ if (id) {
103
+ this.send(ws, `${event}:response`, { id, success: true, data: result })
104
+ }
105
+ } catch (err) {
106
+ if (id) {
107
+ this.send(ws, `${event}:response`, { id, success: false, error: err.message })
108
+ }
109
+ }
110
+ }
111
+ } else {
112
+ if (id) {
113
+ this.send(ws, `${event}:response`, {
114
+ id,
115
+ success: false,
116
+ error: `No handler for event ${event}`
117
+ })
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Kirim pesan ke client tertentu
124
+ * @param {WebSocket} ws
125
+ * @param {string} event
126
+ * @param {any} payload
127
+ */
128
+ send(ws, event, payload) {
129
+ if (ws && ws.readyState === WebSocket.OPEN) {
130
+ ws.send(JSON.stringify({ event, payload }))
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Broadcast event ke SEMUA client yang terhubung (WebUI, CLI, dll)
136
+ * @param {string} event
137
+ * @param {any} payload
138
+ */
139
+ broadcast(event, payload) {
140
+ const message = JSON.stringify({ event, payload })
141
+ for (const client of this.clients) {
142
+ if (client.readyState === WebSocket.OPEN) {
143
+ client.send(message)
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Streaming token AI langsung ke semua client
150
+ * @param {string} token
151
+ * @param {string} [type='answer'] 'thought' | 'answer'
152
+ */
153
+ streamToken(token, type = 'answer') {
154
+ this.broadcast('ai:token', { token, type, timestamp: Date.now() })
155
+ }
156
+
157
+ /**
158
+ * Emit status tool execution
159
+ * @param {string} toolName
160
+ * @param {string} status 'start' | 'running' | 'done' | 'error'
161
+ * @param {any} [data=null]
162
+ */
163
+ emitToolStatus(toolName, status, data = null) {
164
+ this.broadcast('tool:status', { tool: toolName, status, data, timestamp: Date.now() })
165
+ }
166
+ }
167
+
168
+ export const wsHub = new WebSocketHub()
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Unified Mark Tag Parser (V5)
3
+ * Modul isomorphic murni (kompatibel dengan Node.js backend dan browser frontend).
4
+ * Mendukung fixed schema (<mark mood="..." done="..." />), CoT reasoning (<think>),
5
+ * serta sanitasi tag legacy (<mood:...>, [mood:...]) dari teks output.
6
+ */
7
+
8
+ export const VALID_MOODS = [
9
+ 'neutral',
10
+ 'joy',
11
+ 'happy',
12
+ 'sadness',
13
+ 'sad',
14
+ 'fear',
15
+ 'anger',
16
+ 'angry',
17
+ 'disgust',
18
+ 'anxiety',
19
+ 'envy',
20
+ 'embarrassment',
21
+ 'ennui',
22
+ 'thinking',
23
+ 'sarcasm',
24
+ 'focused',
25
+ 'excited',
26
+ 'confused',
27
+ 'annoyed',
28
+ 'calm'
29
+ ]
30
+
31
+ export const MOOD_NORMALIZATION = {
32
+ happy: 'joy',
33
+ sad: 'sadness',
34
+ angry: 'anger',
35
+ bored: 'ennui',
36
+ anxious: 'anxiety'
37
+ }
38
+
39
+ export const MARK_TAG_SCHEMA = {
40
+ mood: {
41
+ type: 'string',
42
+ default: 'neutral',
43
+ validate: (val) => {
44
+ if (!val) return 'neutral'
45
+ const clean = String(val).trim().toLowerCase()
46
+ const normalized = MOOD_NORMALIZATION[clean] || clean
47
+ return VALID_MOODS.includes(normalized) ? normalized : 'neutral'
48
+ }
49
+ },
50
+ done: {
51
+ type: 'boolean',
52
+ default: false,
53
+ validate: (val) => {
54
+ if (val === true || val === 'true' || val === 1 || val === '1') return true
55
+ return false
56
+ }
57
+ }
58
+ }
59
+
60
+ const MARK_TAG_REGEX = /<mark\b([^>]*)\/?>/i
61
+ const THINK_BLOCK_REGEX = /<think>([\s\S]*?)<\/think>/i
62
+ const LEGACY_MOOD_REGEX = /(?:<|\[)mood:[a-zA-Z0-9_-]+(?:>|\])/gi
63
+ const LEGACY_DONE_REGEX = /<\/?done\s*\/?>/gi
64
+
65
+ /**
66
+ * Parsing atribut XML dari tag string ke objek JavaScript sesuai fixed schema.
67
+ * @param {string} attrString - String atribut di dalam tag <mark ...>
68
+ * @returns {Record<string, any>} Objek metadata yang divalidasi
69
+ */
70
+ export function parseAttributes(attrString) {
71
+ const result = {
72
+ mood: MARK_TAG_SCHEMA.mood.default,
73
+ done: MARK_TAG_SCHEMA.done.default,
74
+ hasExplicitDone: false
75
+ }
76
+
77
+ if (!attrString || typeof attrString !== 'string') {
78
+ return result
79
+ }
80
+
81
+ const attrPattern = /([a-zA-Z0-9_-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/g
82
+ let match
83
+
84
+ while ((match = attrPattern.exec(attrString)) !== null) {
85
+ const rawKey = match[1].toLowerCase()
86
+ const rawValue = match[2] ?? match[3] ?? match[4] ?? ''
87
+
88
+ if (rawKey in MARK_TAG_SCHEMA) {
89
+ result[rawKey] = MARK_TAG_SCHEMA[rawKey].validate(rawValue)
90
+ if (rawKey === 'done') {
91
+ result.hasExplicitDone = true
92
+ }
93
+ }
94
+ }
95
+
96
+ return result
97
+ }
98
+
99
+ /**
100
+ * Membersihkan semua tag kontrol (V5 dan legacy) dari teks agar riwayat dan tampilan bersih.
101
+ * @param {string} text - Teks mentah dari AI
102
+ * @returns {string} Teks bersih bebas dari tag kontrol
103
+ */
104
+ export function stripMarkTags(text) {
105
+ if (!text || typeof text !== 'string') return ''
106
+
107
+ return text
108
+ .replace(THINK_BLOCK_REGEX, '')
109
+ .replace(MARK_TAG_REGEX, '')
110
+ .replace(/<\/mark>/gi, '')
111
+ .replace(LEGACY_MOOD_REGEX, '')
112
+ .replace(LEGACY_DONE_REGEX, '')
113
+ .replace(/^\s+/, '')
114
+ }
115
+
116
+ /**
117
+ * Parsing teks respons AI untuk mengekstrak metadata <mark>, CoT reasoning, dan cleanContent.
118
+ * @param {string} rawText - Teks mentah dari AI
119
+ * @returns {{ meta: { mood: string, done: boolean, hasTag: boolean, hasExplicitDone: boolean }, reasoning: string, cleanContent: string }}
120
+ */
121
+ export function parseMarkTag(rawText) {
122
+ if (!rawText || typeof rawText !== 'string') {
123
+ return {
124
+ meta: {
125
+ mood: MARK_TAG_SCHEMA.mood.default,
126
+ done: MARK_TAG_SCHEMA.done.default,
127
+ hasTag: false,
128
+ hasExplicitDone: false
129
+ },
130
+ reasoning: '',
131
+ cleanContent: ''
132
+ }
133
+ }
134
+
135
+ // 1. Ekstraksi CoT Reasoning (<think>...</think>)
136
+ let reasoning = ''
137
+ const thinkMatch = rawText.match(THINK_BLOCK_REGEX)
138
+ if (thinkMatch) {
139
+ reasoning = thinkMatch[1].trim()
140
+ }
141
+
142
+ // 2. Ekstraksi Tag <mark ... />
143
+ let meta = {
144
+ mood: MARK_TAG_SCHEMA.mood.default,
145
+ done: MARK_TAG_SCHEMA.done.default,
146
+ hasTag: false,
147
+ hasExplicitDone: false
148
+ }
149
+
150
+ const markMatch = rawText.match(MARK_TAG_REGEX)
151
+ if (markMatch) {
152
+ const attrString = markMatch[1]
153
+ const parsedAttrs = parseAttributes(attrString)
154
+ meta = {
155
+ ...parsedAttrs,
156
+ hasTag: true
157
+ }
158
+ }
159
+
160
+ // 3. Sanitasi teks untuk tampilan dan penyimpanan
161
+ const cleanContent = stripMarkTags(rawText)
162
+
163
+ return {
164
+ meta,
165
+ reasoning,
166
+ cleanContent
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Factory untuk membuat stream filter stateful.
172
+ * Mem-buffer awal stream untuk mendeteksi <mark ... />, memicu onMeta, dan menyalurkan chunk bersih.
173
+ * @param {{ onMeta?: (meta: { mood: string, done: boolean }) => void, onChunk?: (chunk: string) => void }} callbacks
174
+ */
175
+ export function createMarkStreamFilter({ onMeta, onChunk } = {}) {
176
+ let flushed = false
177
+ let buffer = ''
178
+ const MAX_BUFFER_LENGTH = 160
179
+
180
+ const emitMeta = (meta) => {
181
+ if (typeof onMeta === 'function') {
182
+ try {
183
+ onMeta(meta)
184
+ } catch (err) {
185
+ console.error('[MarkStreamFilter] Error in onMeta callback:', err)
186
+ }
187
+ }
188
+ }
189
+
190
+ const emitChunk = (chunk) => {
191
+ if (chunk && typeof onChunk === 'function') {
192
+ onChunk(chunk)
193
+ }
194
+ }
195
+
196
+ return {
197
+ /**
198
+ * Memproses potongan token baru dari stream.
199
+ * @param {string} chunk - Potongan teks token
200
+ */
201
+ write(chunk) {
202
+ if (typeof chunk !== 'string') return
203
+
204
+ if (flushed) {
205
+ emitChunk(chunk)
206
+ return
207
+ }
208
+
209
+ buffer += chunk
210
+
211
+ // Cari apakah tag <mark ... /> sudah lengkap di dalam buffer
212
+ const markMatch = buffer.match(MARK_TAG_REGEX)
213
+ if (markMatch) {
214
+ const meta = parseAttributes(markMatch[1])
215
+ emitMeta(meta)
216
+
217
+ // Hapus tag <mark ... /> dari buffer dan trim newline awal jika ada
218
+ const tagIndex = buffer.indexOf(markMatch[0])
219
+ const afterTag = buffer.slice(tagIndex + markMatch[0].length).replace(/^[\r\n]+/, '')
220
+ flushed = true
221
+ buffer = ''
222
+
223
+ if (afterTag.length > 0) {
224
+ emitChunk(afterTag)
225
+ }
226
+ return
227
+ }
228
+
229
+ // Jika buffer melebihi batas atau ada baris baru tanpa awalan tag, flush default
230
+ const trimmed = buffer.trimStart()
231
+ const hasNewlineWithoutTag = trimmed.includes('\n') && !trimmed.startsWith('<')
232
+ if (buffer.length >= MAX_BUFFER_LENGTH || hasNewlineWithoutTag) {
233
+ emitMeta({
234
+ mood: MARK_TAG_SCHEMA.mood.default,
235
+ done: MARK_TAG_SCHEMA.done.default
236
+ })
237
+ flushed = true
238
+ const out = buffer
239
+ buffer = ''
240
+ emitChunk(out)
241
+ }
242
+ },
243
+
244
+ /**
245
+ * Menuntaskan stream jika stream berakhir sebelum buffer mencapai batas.
246
+ */
247
+ flush() {
248
+ if (flushed) return
249
+
250
+ const markMatch = buffer.match(MARK_TAG_REGEX)
251
+ if (markMatch) {
252
+ const meta = parseAttributes(markMatch[1])
253
+ emitMeta(meta)
254
+ const tagIndex = buffer.indexOf(markMatch[0])
255
+ const afterTag = buffer.slice(tagIndex + markMatch[0].length).replace(/^[\r\n]+/, '')
256
+ flushed = true
257
+ buffer = ''
258
+ if (afterTag.length > 0) {
259
+ emitChunk(afterTag)
260
+ }
261
+ } else {
262
+ emitMeta({
263
+ mood: MARK_TAG_SCHEMA.mood.default,
264
+ done: MARK_TAG_SCHEMA.done.default
265
+ })
266
+ flushed = true
267
+ const out = buffer
268
+ buffer = ''
269
+ if (out.length > 0) {
270
+ emitChunk(out)
271
+ }
272
+ }
273
+ }
274
+ }
275
+ }