@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,127 @@
1
+ /* eslint-disable react/prop-types */
2
+ import { useState } from 'react'
3
+ import { useApproval, extractToolTarget } from '../../contexts/ApprovalContext'
4
+ import FileDiffModal from './FileDiffModal'
5
+
6
+ const FILE_TOOLS = ['write-file', 'replace-content', 'replace-lines', 'delete-file']
7
+
8
+ export const ApprovalBubble = ({
9
+ approvalId,
10
+ tool = '',
11
+ message = '',
12
+ query = null,
13
+ status = 'pending'
14
+ }) => {
15
+ const { resolveApproval } = useApproval()
16
+ const [showDiffModal, setShowDiffModal] = useState(false)
17
+
18
+ // Hilang seketika setelah di-acc atau ditolak
19
+ if (status !== 'pending') {
20
+ return null
21
+ }
22
+
23
+ const targetInfo = extractToolTarget(tool, query)
24
+ const isFileTool = FILE_TOOLS.includes(tool)
25
+ const previewValue =
26
+ targetInfo.value ||
27
+ message ||
28
+ (typeof query === 'string' ? query : JSON.stringify(query, null, 2))
29
+
30
+ const handleDecision = (decisionType) => {
31
+ setShowDiffModal(false)
32
+ if (resolveApproval) {
33
+ resolveApproval(approvalId, decisionType)
34
+ }
35
+ }
36
+
37
+ return (
38
+ <div className="my-2 p-3 rounded-xl bg-base-300/50 border border-white/5 space-y-2.5 text-xs text-base-content select-text shadow-sm">
39
+ {/* Header persetujuan */}
40
+ <div className="flex items-center justify-between gap-2">
41
+ <div className="flex items-center gap-2 min-w-0">
42
+ <span className="font-semibold text-white/90 text-xs tracking-wide">
43
+ Persetujuan Eksekusi
44
+ </span>
45
+ {tool && (
46
+ <span className="bg-warning/10 text-warning px-2 py-0.5 rounded text-[11px] font-mono font-medium">
47
+ {tool}
48
+ </span>
49
+ )}
50
+ </div>
51
+
52
+ {isFileTool && (
53
+ <button
54
+ type="button"
55
+ onClick={() => setShowDiffModal(true)}
56
+ className="px-2.5 py-1 rounded-lg bg-primary/10 hover:bg-primary/20 text-primary font-mono text-xs transition-colors cursor-pointer"
57
+ >
58
+ Review
59
+ </button>
60
+ )}
61
+ </div>
62
+
63
+ <p className="text-white/60 text-[11px]">
64
+ Mark meminta izin untuk mengeksekusi operasi berikut:
65
+ </p>
66
+
67
+ {/* Parameter / Command Preview */}
68
+ <div className="bg-black/30 rounded-lg p-2.5 font-mono text-[11px] text-white/90 space-y-1">
69
+ {targetInfo.value && (
70
+ <div className="text-[10px] uppercase font-bold text-warning/70 select-none">
71
+ {targetInfo.type === 'command' ? 'Perintah Shell' : 'Target Berkas'}
72
+ </div>
73
+ )}
74
+ <div className="text-white/90 whitespace-pre-wrap break-all leading-relaxed">
75
+ {previewValue}
76
+ </div>
77
+ </div>
78
+
79
+ {/* Action Buttons */}
80
+ <div className="flex flex-wrap items-center justify-between gap-2 pt-0.5">
81
+ <button
82
+ type="button"
83
+ onClick={() => handleDecision('reject')}
84
+ className="btn btn-ghost btn-xs text-error/80 hover:text-error hover:bg-error/10 rounded-lg px-3"
85
+ >
86
+ Tolak
87
+ </button>
88
+ <div className="flex flex-wrap items-center gap-1.5">
89
+ <button
90
+ type="button"
91
+ onClick={() => handleDecision('approve_session')}
92
+ className="btn btn-ghost btn-xs text-warning/80 hover:text-warning hover:bg-warning/10 rounded-lg px-2.5"
93
+ >
94
+ Izinkan Sesi Ini
95
+ </button>
96
+ <button
97
+ type="button"
98
+ onClick={() => handleDecision('approve_always')}
99
+ className="btn btn-ghost btn-xs text-white/40 hover:text-error hover:bg-error/10 rounded-lg px-2.5"
100
+ >
101
+ Izinkan Selamanya
102
+ </button>
103
+ <button
104
+ type="button"
105
+ onClick={() => handleDecision('approve_once')}
106
+ className="btn btn-primary btn-xs text-black font-semibold rounded-lg px-3 shadow-sm"
107
+ >
108
+ Izinkan Sekali
109
+ </button>
110
+ </div>
111
+ </div>
112
+
113
+ {/* Floating Diff Modal */}
114
+ {isFileTool && (
115
+ <FileDiffModal
116
+ isOpen={showDiffModal}
117
+ onClose={() => setShowDiffModal(false)}
118
+ tool={tool}
119
+ query={query}
120
+ onResolve={handleDecision}
121
+ />
122
+ )}
123
+ </div>
124
+ )
125
+ }
126
+
127
+ export default ApprovalBubble
@@ -0,0 +1,83 @@
1
+ import React, { useState } from 'react'
2
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
3
+ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
4
+
5
+ export const CodeBlock = React.memo(({ node, inline, className, children, ...props }) => {
6
+ const match = /language-(\w+)/.exec(className || '')
7
+ const [isCopied, setIsCopied] = useState(false)
8
+
9
+ const handleCopy = () => {
10
+ navigator.clipboard.writeText(String(children).replace(/\n$/, ''))
11
+ setIsCopied(true)
12
+ setTimeout(() => setIsCopied(false), 2000)
13
+ }
14
+
15
+ if (!inline && match) {
16
+ return (
17
+ <div className="relative group my-4 rounded-xl overflow-hidden border border-base-300 shadow-sm bg-base-200/50">
18
+ <div className="flex items-center justify-between px-4 py-1.5 bg-base-300/50 text-[10px] uppercase tracking-wider font-bold text-white/50 border-b border-base-300">
19
+ <span>{match[1]}</span>
20
+ <button
21
+ onClick={handleCopy}
22
+ className="flex items-center gap-1.5 hover:text-white transition-colors cursor-pointer"
23
+ >
24
+ {isCopied ? (
25
+ <>
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ width="12"
29
+ height="12"
30
+ viewBox="0 0 24 24"
31
+ fill="none"
32
+ stroke="currentColor"
33
+ strokeWidth="2"
34
+ strokeLinecap="round"
35
+ strokeLinejoin="round"
36
+ className="text-success"
37
+ >
38
+ <polyline points="20 6 9 17 4 12"></polyline>
39
+ </svg>
40
+ <span className="text-success">Copied!</span>
41
+ </>
42
+ ) : (
43
+ <>
44
+ <svg
45
+ xmlns="http://www.w3.org/2000/svg"
46
+ width="12"
47
+ height="12"
48
+ viewBox="0 0 24 24"
49
+ fill="none"
50
+ stroke="currentColor"
51
+ strokeWidth="2"
52
+ strokeLinecap="round"
53
+ strokeLinejoin="round"
54
+ >
55
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
56
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
57
+ </svg>
58
+ <span>Copy</span>
59
+ </>
60
+ )}
61
+ </button>
62
+ </div>
63
+ <SyntaxHighlighter
64
+ {...props}
65
+ style={oneDark}
66
+ language={match[1]}
67
+ PreTag="div"
68
+ className="!m-0 !bg-transparent text-[12px] no-scrollbar"
69
+ >
70
+ {String(children).replace(/\n$/, '')}
71
+ </SyntaxHighlighter>
72
+ </div>
73
+ )
74
+ }
75
+ return (
76
+ <code
77
+ {...props}
78
+ className={`${className} bg-white/10 px-1.5 py-0.5 rounded-md text-[12px] font-mono`}
79
+ >
80
+ {children}
81
+ </code>
82
+ )
83
+ })
@@ -0,0 +1,455 @@
1
+ /* eslint-disable react/prop-types */
2
+ import { useState } from 'react'
3
+ import Markdown from 'react-markdown'
4
+ import remarkGfm from 'remark-gfm'
5
+ import {
6
+ Check,
7
+ FolderOpen,
8
+ FileText,
9
+ Square,
10
+ ChevronRight,
11
+ XCircle,
12
+ Brain,
13
+ Terminal,
14
+ Ban,
15
+ Loader2
16
+ } from 'lucide-react'
17
+ import { useApproval } from '../../contexts/ApprovalContext'
18
+ import { ApprovalBubble } from './ApprovalBubble'
19
+ import { FileDiffModal } from './FileDiffModal'
20
+
21
+ export const DurableTaskBubble = ({
22
+ plan = [],
23
+ resolvedCurrentStep = 0,
24
+ reasoning = null,
25
+ taskId = null,
26
+ taskTitle = null,
27
+ taskStatus = null,
28
+ artifactRoot = null,
29
+ onStop = null,
30
+ executedTools = [],
31
+ activeLiveTools = null,
32
+ activeThinkingContent = null
33
+ }) => {
34
+ const [selectedArtifactPath, setSelectedArtifactPath] = useState(null)
35
+ const [isProcessOpen, setIsProcessOpen] = useState(true)
36
+ const { activeApproval } = useApproval() || {}
37
+
38
+ const totalSteps = plan.length
39
+ const completedCount = plan.filter(
40
+ (s, idx) =>
41
+ s.status === 'completed' || plan.slice(idx + 1).some((later) => later.status === 'completed')
42
+ ).length
43
+
44
+ const isStopped = taskStatus === 'stopped' || taskStatus === 'cancelled'
45
+ const isFailed = taskStatus === 'failed'
46
+ const isAllDone =
47
+ taskStatus === 'completed' ||
48
+ (!isStopped && !isFailed && totalSteps > 0 && completedCount >= totalSteps)
49
+ const isRunning = !isAllDone && !isFailed && !isStopped
50
+
51
+ const toolsToDisplay =
52
+ activeLiveTools && activeLiveTools.length > 0 ? activeLiveTools : executedTools || []
53
+ const displayReasoning = reasoning || activeThinkingContent
54
+
55
+ const handleOpenFolder = (folderPath) => {
56
+ if (!folderPath) return
57
+ fetch('/api/tasks/ensure-dir', {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify({ dirPath: folderPath })
61
+ })
62
+ .then((r) => r.json())
63
+ .then((res) => {
64
+ const target = res?.data || folderPath
65
+ if (window.api?.executeNativeTool) {
66
+ window.api.executeNativeTool('open-folder', { path: target }).catch(() => {
67
+ window.api.executeNativeTool('open', target).catch(() => {})
68
+ })
69
+ } else if (window.api?.openPath) {
70
+ window.api.openPath(target)
71
+ }
72
+ })
73
+ .catch(() => {
74
+ if (window.api?.executeNativeTool) {
75
+ window.api.executeNativeTool('open-folder', { path: folderPath }).catch(() => {
76
+ window.api.executeNativeTool('open', folderPath).catch(() => {})
77
+ })
78
+ } else if (window.api?.openPath) {
79
+ window.api.openPath(folderPath)
80
+ }
81
+ })
82
+ }
83
+
84
+ const renderToolItem = (t, idx) => {
85
+ const isToolRunning = t.status === 'running'
86
+ const isPendingApproval = Boolean(
87
+ activeApproval &&
88
+ activeApproval.status === 'pending' &&
89
+ isToolRunning &&
90
+ (activeApproval.tool === t.tool || !t.tool)
91
+ )
92
+ const hasQuery = t.query !== undefined && t.query !== null && t.query !== ''
93
+ const queryString = typeof t.query === 'string' ? t.query : JSON.stringify(t.query, null, 2)
94
+ const textResult = String(t.fullResult || t.resultSummary || t.result || '')
95
+ const hasResult = Boolean(textResult.trim())
96
+ const isStoppedTool = t.status === 'stopped' || t.status === 'cancelled'
97
+ const hasError =
98
+ !isStoppedTool &&
99
+ (t.status === 'failed' ||
100
+ t.status === 'error' ||
101
+ textResult.startsWith('[ERROR]') ||
102
+ textResult.includes(' crash:') ||
103
+ textResult.toLowerCase().includes(' gagal:'))
104
+ const isSuccessful =
105
+ !isStoppedTool &&
106
+ !hasError &&
107
+ (t.status === 'done' || t.status === 'success' || t.status !== 'error')
108
+
109
+ const StatusIcon = isStoppedTool ? Ban : isSuccessful ? Check : XCircle
110
+ const statusClass = isStoppedTool
111
+ ? 'text-warning'
112
+ : isSuccessful
113
+ ? 'text-success'
114
+ : 'text-error'
115
+
116
+ let parsedArgs = null
117
+ if (hasQuery) {
118
+ try {
119
+ parsedArgs =
120
+ typeof t.query === 'object' && t.query !== null ? t.query : JSON.parse(queryString)
121
+ } catch (_) {}
122
+ }
123
+
124
+ const toolLabel = t.tool || t.task || 'tool'
125
+ const displayReason =
126
+ t.reason ||
127
+ parsedArgs?.reason ||
128
+ parsedArgs?.description ||
129
+ parsedArgs?.task ||
130
+ parsedArgs?.title ||
131
+ null
132
+
133
+ let shortSummary = ''
134
+ if (parsedArgs && typeof parsedArgs === 'object') {
135
+ shortSummary =
136
+ parsedArgs.command ||
137
+ parsedArgs.path ||
138
+ parsedArgs.file ||
139
+ parsedArgs.filePath ||
140
+ parsedArgs.url ||
141
+ parsedArgs.query ||
142
+ parsedArgs.prompt ||
143
+ parsedArgs.message ||
144
+ parsedArgs.skill_name ||
145
+ parsedArgs.text ||
146
+ (parsedArgs.script ? parsedArgs.script.trim().replace(/\s+/g, ' ').slice(0, 80) : '') ||
147
+ ''
148
+ }
149
+ if (!shortSummary && hasQuery) {
150
+ shortSummary = queryString
151
+ }
152
+
153
+ if (!hasQuery && !hasResult) {
154
+ return (
155
+ <div key={idx} className="flex flex-col py-0.5">
156
+ <div className="flex items-center gap-2 text-xs font-mono text-white/60">
157
+ {isPendingApproval ? (
158
+ <Loader2 className="w-3.5 h-3.5 animate-spin text-warning shrink-0" />
159
+ ) : isToolRunning ? (
160
+ <Loader2 className="w-3.5 h-3.5 animate-spin text-primary/80 shrink-0" />
161
+ ) : isStoppedTool ? (
162
+ <Ban className="w-3.5 h-3.5 text-warning shrink-0" />
163
+ ) : hasError ? (
164
+ <XCircle className="w-3.5 h-3.5 text-error shrink-0" />
165
+ ) : (
166
+ <Check className="w-3.5 h-3.5 text-success/80 shrink-0" />
167
+ )}
168
+ <span className="font-semibold text-white/90">{displayReason || toolLabel}</span>
169
+ {isPendingApproval ? (
170
+ <span className="text-[10px] text-warning font-normal animate-pulse">
171
+ (menunggu persetujuan...)
172
+ </span>
173
+ ) : isStoppedTool ? (
174
+ <span className="text-[10px] text-warning/80 font-normal">(dihentikan)</span>
175
+ ) : null}
176
+ </div>
177
+ {isPendingApproval && (
178
+ <div className="pl-3 pr-1">
179
+ <ApprovalBubble
180
+ approvalId={activeApproval.id}
181
+ tool={activeApproval.tool}
182
+ query={activeApproval.query}
183
+ message={activeApproval.message}
184
+ status={activeApproval.status}
185
+ />
186
+ </div>
187
+ )}
188
+ </div>
189
+ )
190
+ }
191
+
192
+ return (
193
+ <div key={idx} className="flex flex-col py-0.5">
194
+ <details className="group/toolitem outline-none text-xs font-mono" open={isToolRunning}>
195
+ <summary className="list-none flex items-center gap-2 cursor-pointer text-white/60 hover:text-white select-none py-0.5 transition-colors">
196
+ {isPendingApproval ? (
197
+ <Loader2 className="w-3.5 h-3.5 animate-spin text-warning shrink-0" />
198
+ ) : isToolRunning ? (
199
+ <Loader2 className="w-3.5 h-3.5 animate-spin text-primary/80 shrink-0" />
200
+ ) : isStoppedTool ? (
201
+ <Ban className="w-3.5 h-3.5 text-warning shrink-0" />
202
+ ) : hasError ? (
203
+ <XCircle className="w-3.5 h-3.5 text-error shrink-0" />
204
+ ) : (
205
+ <Check className="w-3.5 h-3.5 text-success/80 shrink-0" />
206
+ )}
207
+ <span className="font-semibold text-white/90">{displayReason || toolLabel}</span>
208
+ {isPendingApproval ? (
209
+ <span className="text-[10px] text-warning font-normal animate-pulse">
210
+ (menunggu persetujuan...)
211
+ </span>
212
+ ) : isStoppedTool ? (
213
+ <span className="text-[10px] text-warning/80 font-normal">(dihentikan)</span>
214
+ ) : null}
215
+ {!displayReason && shortSummary && (
216
+ <span className="text-white/40 truncate max-w-md">
217
+ {String(shortSummary).slice(0, 80)}
218
+ </span>
219
+ )}
220
+ <ChevronRight className="w-3 h-3 text-white/40 transition-transform duration-150 group-open/toolitem:rotate-90 ml-auto shrink-0" />
221
+ </summary>
222
+ <div className="mt-1 pl-3 my-1.5 text-[11px] font-mono border-l-2 border-white/20 text-white/80 whitespace-pre-wrap break-all max-h-56 overflow-y-auto custom-scrollbar bg-base-300/40 p-2.5 rounded-lg space-y-1.5 select-text">
223
+ {t.tool && (
224
+ <div>
225
+ <div className="text-primary/70 font-semibold mb-0.5">Tool:</div>
226
+ <div className="text-white/90 font-mono text-[11px]">{t.tool}</div>
227
+ </div>
228
+ )}
229
+ {displayReason && (
230
+ <div>
231
+ <div className="text-info/80 font-semibold mb-0.5">Tujuan:</div>
232
+ <div className="text-white/90 font-sans">{displayReason}</div>
233
+ </div>
234
+ )}
235
+ {hasQuery && (
236
+ <div>
237
+ <div className="text-primary/70 font-semibold mb-0.5">Input:</div>
238
+ <div className="text-white/90">{queryString}</div>
239
+ </div>
240
+ )}
241
+ {hasResult && (
242
+ <div>
243
+ <div className="text-success/70 font-semibold mb-0.5">Output:</div>
244
+ <div className="text-white/80">{textResult}</div>
245
+ </div>
246
+ )}
247
+ </div>
248
+ </details>
249
+ {isPendingApproval && (
250
+ <div className="pl-3 pr-1 my-1">
251
+ <ApprovalBubble
252
+ approvalId={activeApproval.id}
253
+ tool={activeApproval.tool}
254
+ query={activeApproval.query}
255
+ message={activeApproval.message}
256
+ status={activeApproval.status}
257
+ />
258
+ </div>
259
+ )}
260
+ </div>
261
+ )
262
+ }
263
+
264
+ return (
265
+ <div className="flex flex-col gap-2 w-full text-xs text-white/90 my-1 py-1">
266
+ {/* ── 1. HEADER ALUR KERJA MINIMALIS ── */}
267
+ <div className="flex items-center justify-between gap-2 pb-1.5 border-b border-white/10 text-xs select-none">
268
+ <div className="flex items-center gap-2 min-w-0">
269
+ <span className="font-semibold text-primary">Workflow :</span>
270
+ <span className="text-white/90 font-medium truncate">{taskTitle || 'Task Workflow'}</span>
271
+ <span className="text-white/40 text-[11px] font-mono shrink-0">
272
+ ({completedCount}/{totalSteps})
273
+ </span>
274
+ </div>
275
+
276
+ <div className="flex items-center gap-2 shrink-0">
277
+ <span
278
+ className={`text-[10px] font-mono px-1.5 py-0.5 rounded ${
279
+ isAllDone
280
+ ? 'text-primary bg-primary/10'
281
+ : isStopped
282
+ ? 'text-amber-400 bg-amber-400/10'
283
+ : isFailed
284
+ ? 'text-rose-400 bg-rose-400/10'
285
+ : 'text-primary bg-primary/10'
286
+ }`}
287
+ >
288
+ {isAllDone ? 'Selesai' : isStopped ? 'Dihentikan' : isFailed ? 'Gagal' : 'Berjalan'}
289
+ </span>
290
+ </div>
291
+ </div>
292
+
293
+ {/* ── 2. DAFTAR CHECKBOX STEPS BIASA (FLAT MINIMALIS) ── */}
294
+ <div className="flex flex-col gap-1 py-0.5">
295
+ {plan.map((step, idx) => {
296
+ const hasLaterDone = plan.slice(idx + 1).some((later) => later.status === 'completed')
297
+ const isStepDone = step.status === 'completed' || hasLaterDone
298
+ const isStepRunning =
299
+ !isStepDone && isRunning && (step.status === 'running' || idx === resolvedCurrentStep)
300
+
301
+ let title = typeof step === 'string' ? step : step.title || step.task || ''
302
+ if (!title || /^((langkah|tahap|step)\s*\d+[:.-]?\s*)$/i.test(title.trim())) {
303
+ title = step.objective || step.deliverable || `Tahap ${idx + 1}`
304
+ }
305
+
306
+ const artifactPath = typeof step === 'object' ? step.artifactPath : null
307
+
308
+ return (
309
+ <div
310
+ key={step.id || `step-${idx}`}
311
+ className="flex items-center justify-between gap-2.5 py-1 select-none"
312
+ >
313
+ <div className="flex items-center gap-2.5 min-w-0 flex-1">
314
+ {/* Checkbox Icon / Loading Spinner */}
315
+ {isStepDone ? (
316
+ <div className="w-3.5 h-3.5 rounded bg-primary/20 border border-primary flex items-center justify-center text-primary shrink-0">
317
+ <Check className="w-2.5 h-2.5 stroke-[3]" />
318
+ </div>
319
+ ) : isStepRunning ? (
320
+ <div className="w-3.5 h-3.5 rounded flex items-center justify-center text-primary shrink-0">
321
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
322
+ </div>
323
+ ) : (
324
+ <div className="w-3.5 h-3.5 rounded border border-white/25 shrink-0" />
325
+ )}
326
+
327
+ {/* Step Title & Deliverable Info */}
328
+ <div className="flex items-baseline gap-1.5 min-w-0 truncate">
329
+ <span
330
+ className={`text-xs truncate ${
331
+ isStepDone
332
+ ? 'text-white/90'
333
+ : isStepRunning
334
+ ? 'text-primary font-medium'
335
+ : 'text-white/70'
336
+ }`}
337
+ >
338
+ {idx + 1}. {title}
339
+ </span>
340
+ {isStepRunning && (
341
+ <span className="text-[10px] font-mono text-primary/80 animate-pulse shrink-0">
342
+ (sedang berjalan...)
343
+ </span>
344
+ )}
345
+ {step.deliverable && !isStepRunning && (
346
+ <span className="text-[10px] font-mono text-white/30 truncate hidden sm:inline">
347
+ ({step.deliverable})
348
+ </span>
349
+ )}
350
+ </div>
351
+ </div>
352
+
353
+ {/* Action Button: Lihat Hasil */}
354
+ {isStepDone && artifactPath && (
355
+ <button
356
+ type="button"
357
+ onClick={() => setSelectedArtifactPath(artifactPath)}
358
+ className="btn btn-ghost btn-xs text-primary hover:underline gap-1 font-mono text-[10.5px] h-5 min-h-0 px-1.5 shrink-0"
359
+ title="Buka pratinjau artefak dokumen"
360
+ >
361
+ <FileText className="w-3 h-3 text-primary" />
362
+ <span>Lihat Hasil</span>
363
+ </button>
364
+ )}
365
+ </div>
366
+ )
367
+ })}
368
+ </div>
369
+
370
+ {/* ── 3. PROSES SAMA PERSIS SEPERTI BUBBLE BIASA ── */}
371
+ {((toolsToDisplay && toolsToDisplay.length > 0) || displayReasoning) && (
372
+ <div className="my-2 space-y-1.5 select-none pt-1.5 border-t border-white/10">
373
+ {/* Thinking / Reasoning Accordion */}
374
+ {displayReasoning && (
375
+ <details className="group/thought outline-none" open={isRunning}>
376
+ <summary className="list-none flex items-center gap-2 cursor-pointer text-xs font-mono text-white/60 hover:text-white select-none py-0.5 transition-colors">
377
+ <Brain className="w-3.5 h-3.5 text-primary/80 shrink-0" />
378
+ <span className="font-medium text-white/80">Proses Pemikiran AI</span>
379
+ <ChevronRight className="w-3 h-3 text-white/40 transition-transform duration-150 group-open/thought:rotate-90 ml-auto shrink-0" />
380
+ </summary>
381
+ <div className="mt-1 pl-3 my-2 text-[11px] font-mono border-l-2 border-primary/30 text-white/80 leading-relaxed max-h-64 overflow-y-auto custom-scrollbar bg-base-300/30 p-2.5 rounded-lg select-text [&_p]:my-1 [&_p]:leading-relaxed [&_h1]:text-xs [&_h1]:font-bold [&_h1]:my-1.5 [&_h2]:text-[11px] [&_h2]:font-bold [&_h2]:my-1 [&_h3]:text-[11px] [&_h3]:font-bold [&_h3]:my-1 [&_ul]:my-1 [&_ul]:ml-3.5 [&_ol]:my-1 [&_ol]:ml-3.5 [&_li]:my-0.5 [&_code]:text-[10px] [&_pre]:my-1.5 [&_hr]:my-2 [&_hr]:border-white/10">
382
+ <Markdown remarkPlugins={[remarkGfm]}>
383
+ {typeof displayReasoning === 'string'
384
+ ? displayReasoning
385
+ : JSON.stringify(displayReasoning, null, 2)}
386
+ </Markdown>
387
+ </div>
388
+ </details>
389
+ )}
390
+
391
+ {/* Executed Tools Folded Process Accordion */}
392
+ {toolsToDisplay && toolsToDisplay.length > 0 && (
393
+ <details
394
+ className="group/process outline-none"
395
+ open={isRunning || isProcessOpen}
396
+ onToggle={(e) => setIsProcessOpen(e.currentTarget.open)}
397
+ >
398
+ <summary className="list-none flex items-center gap-2 cursor-pointer text-xs font-mono text-white/70 hover:text-white select-none py-1 transition-colors">
399
+ <Terminal className="w-3.5 h-3.5 text-primary/80 shrink-0" />
400
+ <span className="font-semibold text-white/90">Proses</span>
401
+ <span className="text-[10px] text-white/40 font-normal">
402
+ ({toolsToDisplay.length} langkah)
403
+ </span>
404
+ <ChevronRight className="w-3.5 h-3.5 text-white/40 transition-transform duration-150 group-open/process:rotate-90 ml-auto shrink-0" />
405
+ </summary>
406
+ <div className="mt-1 pl-2.5 space-y-1 border-l-2 border-white/15 ml-1.5 my-1">
407
+ {toolsToDisplay.map((t, idx) => renderToolItem(t, idx))}
408
+ </div>
409
+ </details>
410
+ )}
411
+ </div>
412
+ )}
413
+
414
+ {/* Indikator Menunggu ketika masih berjalan tanpa tool/reasoning */}
415
+ {isRunning && (!toolsToDisplay || toolsToDisplay.length === 0) && !displayReasoning && (
416
+ <div className="flex items-center gap-2 text-xs text-primary font-medium py-1">
417
+ <Loader2 className="w-3.5 h-3.5 animate-spin" />
418
+ <span className="text-white/80 animate-pulse">
419
+ Mark sedang menganalisis & mengeksekusi...
420
+ </span>
421
+ </div>
422
+ )}
423
+
424
+ {/* ── 4. FOOTER FOLDER ARTEFAK ── */}
425
+ {artifactRoot && (
426
+ <div className="flex items-center justify-between pt-1 border-t border-white/5 text-[10px] font-mono text-white/40 select-none">
427
+ <span className="truncate max-w-[200px]">
428
+ Folder: {artifactRoot.split(/[\\/]/).pop()}
429
+ </span>
430
+ <button
431
+ type="button"
432
+ onClick={() => handleOpenFolder(artifactRoot)}
433
+ className="text-[10px] text-primary hover:underline flex items-center gap-1"
434
+ >
435
+ <FolderOpen className="w-3 h-3" />
436
+ <span>Buka Folder</span>
437
+ </button>
438
+ </div>
439
+ )}
440
+
441
+ {/* ── 5. MODAL PRATINJAU ARTEFAK DOKUMEN VIA FILEDIFFMODAL ── */}
442
+ {selectedArtifactPath && (
443
+ <FileDiffModal
444
+ isOpen={Boolean(selectedArtifactPath)}
445
+ onClose={() => setSelectedArtifactPath(null)}
446
+ tool="read-file"
447
+ query={{ path: selectedArtifactPath }}
448
+ />
449
+ )}
450
+ </div>
451
+ )
452
+ }
453
+
454
+ export const PlanningBubble = DurableTaskBubble
455
+ export default DurableTaskBubble