kanbaii 0.3.2 → 0.3.4
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.
- package/dashboard/404.html +1 -1
- package/dashboard/index.html +1 -1
- package/dashboard/index.txt +1 -1
- package/dist/server/engines/coordinator.js +20 -0
- package/dist/server/engines/ralph.js +21 -2
- package/dist/server/engines/workerPool.js +9 -2
- package/dist/server/mcp/kanbaii-mcp-server.js +496 -0
- package/dist/server/services/costTracker.d.ts +3 -1
- package/dist/server/services/costTracker.js +3 -1
- package/package.json +4 -4
- /package/dashboard/_next/static/{A8eY-I1syJkHlELnPcf1s → PAWhPHPqRCFfVFpsqwO5u}/_buildManifest.js +0 -0
- /package/dashboard/_next/static/{A8eY-I1syJkHlELnPcf1s → PAWhPHPqRCFfVFpsqwO5u}/_ssgManifest.js +0 -0
package/dashboard/404.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/85ce8ef4f13f9778.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-5c1d03322d6ecf76.js"/><script src="/_next/static/chunks/fd9d1056-1f1f859026f5f0fa.js" async=""></script><script src="/_next/static/chunks/117-749dc8b5f56031ab.js" async=""></script><script src="/_next/static/chunks/main-app-2f313c3206a6e049.js" async=""></script><script src="/_next/static/chunks/184-15d72b1991c86ab9.js" async=""></script><script src="/_next/static/chunks/23-4a72ba4a90f008a6.js" async=""></script><script src="/_next/static/chunks/app/layout-5b89132efd82b0a2.js" async=""></script><meta name="robots" content="noindex"/><meta name="theme-color" content="#6366f1"/><title>KANBAII</title><meta name="description" content="Structure for your ideas. AI to move them forward."/><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="icon" href="/favicon.svg" type="image/svg+xml"/><link rel="icon" href="/favicon-16x16.svg" sizes="16x16" type="image/svg+xml"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><script src="/_next/static/chunks/webpack-5c1d03322d6ecf76.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/85ce8ef4f13f9778.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n4:I[4707,[],\"\"]\n5:I[6423,[],\"\"]\n6:I[792,[\"184\",\"static/chunks/184-15d72b1991c86ab9.js\",\"23\",\"static/chunks/23-4a72ba4a90f008a6.js\",\"185\",\"static/chunks/app/layout-5b89132efd82b0a2.js\"],\"AppShell\"]\nc:I[1060,[],\"\"]\n7:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n8:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n9:{\"display\":\"inline-block\"}\na:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nd:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/85ce8ef4f13f9778.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-5c1d03322d6ecf76.js"/><script src="/_next/static/chunks/fd9d1056-1f1f859026f5f0fa.js" async=""></script><script src="/_next/static/chunks/117-749dc8b5f56031ab.js" async=""></script><script src="/_next/static/chunks/main-app-2f313c3206a6e049.js" async=""></script><script src="/_next/static/chunks/184-15d72b1991c86ab9.js" async=""></script><script src="/_next/static/chunks/23-4a72ba4a90f008a6.js" async=""></script><script src="/_next/static/chunks/app/layout-5b89132efd82b0a2.js" async=""></script><meta name="robots" content="noindex"/><meta name="theme-color" content="#6366f1"/><title>KANBAII</title><meta name="description" content="Structure for your ideas. AI to move them forward."/><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="icon" href="/favicon.svg" type="image/svg+xml"/><link rel="icon" href="/favicon-16x16.svg" sizes="16x16" type="image/svg+xml"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><script src="/_next/static/chunks/webpack-5c1d03322d6ecf76.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/85ce8ef4f13f9778.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n4:I[4707,[],\"\"]\n5:I[6423,[],\"\"]\n6:I[792,[\"184\",\"static/chunks/184-15d72b1991c86ab9.js\",\"23\",\"static/chunks/23-4a72ba4a90f008a6.js\",\"185\",\"static/chunks/app/layout-5b89132efd82b0a2.js\"],\"AppShell\"]\nc:I[1060,[],\"\"]\n7:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n8:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n9:{\"display\":\"inline-block\"}\na:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nd:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"PAWhPHPqRCFfVFpsqwO5u\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/85ce8ef4f13f9778.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$8\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$9\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$a\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"name\":\"theme-color\",\"content\":\"#6366f1\"}],[\"$\",\"meta\",\"2\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"3\",{\"children\":\"KANBAII\"}],[\"$\",\"meta\",\"4\",{\"name\":\"description\",\"content\":\"Structure for your ideas. AI to move them forward.\"}],[\"$\",\"link\",\"5\",{\"rel\":\"manifest\",\"href\":\"/manifest.json\",\"crossOrigin\":\"use-credentials\"}],[\"$\",\"link\",\"6\",{\"rel\":\"icon\",\"href\":\"/favicon.svg\",\"type\":\"image/svg+xml\"}],[\"$\",\"link\",\"7\",{\"rel\":\"icon\",\"href\":\"/favicon-16x16.svg\",\"sizes\":\"16x16\",\"type\":\"image/svg+xml\"}]]\n3:null\n"])</script></body></html>
|
package/dashboard/index.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/85ce8ef4f13f9778.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-5c1d03322d6ecf76.js"/><script src="/_next/static/chunks/fd9d1056-1f1f859026f5f0fa.js" async=""></script><script src="/_next/static/chunks/117-749dc8b5f56031ab.js" async=""></script><script src="/_next/static/chunks/main-app-2f313c3206a6e049.js" async=""></script><script src="/_next/static/chunks/184-15d72b1991c86ab9.js" async=""></script><script src="/_next/static/chunks/759-3d8fc7952548ec08.js" async=""></script><script src="/_next/static/chunks/23-4a72ba4a90f008a6.js" async=""></script><script src="/_next/static/chunks/app/page-e96878de09e0c519.js" async=""></script><script src="/_next/static/chunks/app/layout-5b89132efd82b0a2.js" async=""></script><meta name="theme-color" content="#6366f1"/><title>KANBAII</title><meta name="description" content="Structure for your ideas. AI to move them forward."/><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="icon" href="/favicon.svg" type="image/svg+xml"/><link rel="icon" href="/favicon-16x16.svg" sizes="16x16" type="image/svg+xml"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><script src="/_next/static/chunks/webpack-5c1d03322d6ecf76.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/85ce8ef4f13f9778.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n4:I[9107,[],\"ClientPageRoot\"]\n5:I[2039,[\"184\",\"static/chunks/184-15d72b1991c86ab9.js\",\"759\",\"static/chunks/759-3d8fc7952548ec08.js\",\"23\",\"static/chunks/23-4a72ba4a90f008a6.js\",\"931\",\"static/chunks/app/page-e96878de09e0c519.js\"],\"default\",1]\n6:I[792,[\"184\",\"static/chunks/184-15d72b1991c86ab9.js\",\"23\",\"static/chunks/23-4a72ba4a90f008a6.js\",\"185\",\"static/chunks/app/layout-5b89132efd82b0a2.js\"],\"AppShell\"]\n7:I[4707,[],\"\"]\n8:I[6423,[],\"\"]\na:I[1060,[],\"\"]\nb:[]\n0:[\"$\",\"$L2\",null,{\"buildId\":\"
|
|
1
|
+
<!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/85ce8ef4f13f9778.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-5c1d03322d6ecf76.js"/><script src="/_next/static/chunks/fd9d1056-1f1f859026f5f0fa.js" async=""></script><script src="/_next/static/chunks/117-749dc8b5f56031ab.js" async=""></script><script src="/_next/static/chunks/main-app-2f313c3206a6e049.js" async=""></script><script src="/_next/static/chunks/184-15d72b1991c86ab9.js" async=""></script><script src="/_next/static/chunks/759-3d8fc7952548ec08.js" async=""></script><script src="/_next/static/chunks/23-4a72ba4a90f008a6.js" async=""></script><script src="/_next/static/chunks/app/page-e96878de09e0c519.js" async=""></script><script src="/_next/static/chunks/app/layout-5b89132efd82b0a2.js" async=""></script><meta name="theme-color" content="#6366f1"/><title>KANBAII</title><meta name="description" content="Structure for your ideas. AI to move them forward."/><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"/><link rel="icon" href="/favicon.svg" type="image/svg+xml"/><link rel="icon" href="/favicon-16x16.svg" sizes="16x16" type="image/svg+xml"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><script src="/_next/static/chunks/webpack-5c1d03322d6ecf76.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/85ce8ef4f13f9778.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[2846,[],\"\"]\n4:I[9107,[],\"ClientPageRoot\"]\n5:I[2039,[\"184\",\"static/chunks/184-15d72b1991c86ab9.js\",\"759\",\"static/chunks/759-3d8fc7952548ec08.js\",\"23\",\"static/chunks/23-4a72ba4a90f008a6.js\",\"931\",\"static/chunks/app/page-e96878de09e0c519.js\"],\"default\",1]\n6:I[792,[\"184\",\"static/chunks/184-15d72b1991c86ab9.js\",\"23\",\"static/chunks/23-4a72ba4a90f008a6.js\",\"185\",\"static/chunks/app/layout-5b89132efd82b0a2.js\"],\"AppShell\"]\n7:I[4707,[],\"\"]\n8:I[6423,[],\"\"]\na:I[1060,[],\"\"]\nb:[]\n0:[\"$\",\"$L2\",null,{\"buildId\":\"PAWhPHPqRCFfVFpsqwO5u\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"\"],\"initialTree\":[\"\",{\"children\":[\"__PAGE__\",{}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[\"$\",\"$L4\",null,{\"props\":{\"params\":{},\"searchParams\":{}},\"Component\":\"$5\"}],null],null],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/85ce8ef4f13f9778.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"children\":[\"$\",\"body\",null,{\"children\":[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$L7\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L8\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"pad"])</script><script>self.__next_f.push([1,"ding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[null,\"$L9\"],\"globalErrorComponent\":\"$a\",\"missingSlots\":\"$Wb\"}]\n"])</script><script>self.__next_f.push([1,"9:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"name\":\"theme-color\",\"content\":\"#6366f1\"}],[\"$\",\"meta\",\"2\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"3\",{\"children\":\"KANBAII\"}],[\"$\",\"meta\",\"4\",{\"name\":\"description\",\"content\":\"Structure for your ideas. AI to move them forward.\"}],[\"$\",\"link\",\"5\",{\"rel\":\"manifest\",\"href\":\"/manifest.json\",\"crossOrigin\":\"use-credentials\"}],[\"$\",\"link\",\"6\",{\"rel\":\"icon\",\"href\":\"/favicon.svg\",\"type\":\"image/svg+xml\"}],[\"$\",\"link\",\"7\",{\"rel\":\"icon\",\"href\":\"/favicon-16x16.svg\",\"sizes\":\"16x16\",\"type\":\"image/svg+xml\"}]]\n3:null\n"])</script></body></html>
|
package/dashboard/index.txt
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
4:I[792,["184","static/chunks/184-15d72b1991c86ab9.js","23","static/chunks/23-4a72ba4a90f008a6.js","185","static/chunks/app/layout-5b89132efd82b0a2.js"],"AppShell"]
|
|
4
4
|
5:I[4707,[],""]
|
|
5
5
|
6:I[6423,[],""]
|
|
6
|
-
0:["
|
|
6
|
+
0:["PAWhPHPqRCFfVFpsqwO5u",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/85ce8ef4f13f9778.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]}]],null],null],["$L7",null]]]]
|
|
7
7
|
7:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"name":"theme-color","content":"#6366f1"}],["$","meta","2",{"charSet":"utf-8"}],["$","title","3",{"children":"KANBAII"}],["$","meta","4",{"name":"description","content":"Structure for your ideas. AI to move them forward."}],["$","link","5",{"rel":"manifest","href":"/manifest.json","crossOrigin":"use-credentials"}],["$","link","6",{"rel":"icon","href":"/favicon.svg","type":"image/svg+xml"}],["$","link","7",{"rel":"icon","href":"/favicon-16x16.svg","sizes":"16x16","type":"image/svg+xml"}]]
|
|
8
8
|
1:null
|
|
@@ -56,6 +56,7 @@ const typedEmit_1 = require("../lib/typedEmit");
|
|
|
56
56
|
const workItemStore = __importStar(require("../services/workItemStore"));
|
|
57
57
|
const projectStore = __importStar(require("../services/projectStore"));
|
|
58
58
|
const pluginLoader_1 = require("../services/pluginLoader");
|
|
59
|
+
const costTracker_1 = require("../services/costTracker");
|
|
59
60
|
// ── Singleton state ───────────────────────────────────────────────────────
|
|
60
61
|
const INITIAL_STATE = {
|
|
61
62
|
status: 'idle',
|
|
@@ -346,6 +347,25 @@ function handleStreamEvent(event) {
|
|
|
346
347
|
costUsd: event.total_cost_usd,
|
|
347
348
|
usage: event.usage,
|
|
348
349
|
});
|
|
350
|
+
// Record coordinator's own cost
|
|
351
|
+
if (event.total_cost_usd && _state.projectSlug) {
|
|
352
|
+
try {
|
|
353
|
+
(0, costTracker_1.recordExecution)({
|
|
354
|
+
projectSlug: _state.projectSlug,
|
|
355
|
+
taskTitle: 'Coordinator (orchestration)',
|
|
356
|
+
model: 'sonnet',
|
|
357
|
+
duration: _state.stats.durationMs || 0,
|
|
358
|
+
inputTokens: event.usage?.input_tokens || 0,
|
|
359
|
+
outputTokens: event.usage?.output_tokens || 0,
|
|
360
|
+
cacheTokens: 0,
|
|
361
|
+
costUsd: event.total_cost_usd,
|
|
362
|
+
status: 'success',
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
console.error('[coordinator] Failed to record cost:', err.message);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
349
369
|
// Sync final stats from pool
|
|
350
370
|
syncStatsFromPool();
|
|
351
371
|
break;
|
|
@@ -129,10 +129,14 @@ async function startRalph(config) {
|
|
|
129
129
|
const runner = new claudeRunner_1.ClaudeRunner();
|
|
130
130
|
currentRunner = runner;
|
|
131
131
|
let output = '';
|
|
132
|
+
let costData = { costUsd: 0, inputTokens: 0, outputTokens: 0 };
|
|
132
133
|
runner.on('output', (chunk) => {
|
|
133
134
|
output += chunk;
|
|
134
135
|
(0, typedEmit_1.emit)('ralph:output', { taskId: task.id, message: chunk });
|
|
135
136
|
});
|
|
137
|
+
runner.on('cost', (data) => {
|
|
138
|
+
costData = data;
|
|
139
|
+
});
|
|
136
140
|
runner.on('escalation', (data) => {
|
|
137
141
|
console.log(`[ralph] Escalation detected (${data.tool}): ${data.question.slice(0, 80)}`);
|
|
138
142
|
(0, escalationService_1.createEscalation)({
|
|
@@ -169,10 +173,13 @@ async function startRalph(config) {
|
|
|
169
173
|
(0, costTracker_1.recordExecution)({
|
|
170
174
|
projectSlug, workItemSlug, taskId: task.id, taskTitle: task.title,
|
|
171
175
|
model: task.model || 'sonnet', duration: result.duration,
|
|
172
|
-
inputTokens:
|
|
176
|
+
inputTokens: costData.inputTokens, outputTokens: costData.outputTokens,
|
|
177
|
+
cacheTokens: 0, costUsd: costData.costUsd, status: 'success',
|
|
173
178
|
});
|
|
174
179
|
}
|
|
175
|
-
catch {
|
|
180
|
+
catch (err) {
|
|
181
|
+
console.error('[ralph] Failed to record execution cost:', err.message);
|
|
182
|
+
}
|
|
176
183
|
// Broadcast update
|
|
177
184
|
const updatedWI = workItemStore.getWorkItem(projectSlug, workItemSlug);
|
|
178
185
|
if (updatedWI)
|
|
@@ -185,6 +192,18 @@ async function startRalph(config) {
|
|
|
185
192
|
});
|
|
186
193
|
runStore_1.runStore.taskFailed();
|
|
187
194
|
consecutiveErrors++;
|
|
195
|
+
// Cost tracking for failed tasks too
|
|
196
|
+
try {
|
|
197
|
+
(0, costTracker_1.recordExecution)({
|
|
198
|
+
projectSlug, workItemSlug, taskId: task.id, taskTitle: task.title,
|
|
199
|
+
model: task.model || 'sonnet', duration: result.duration,
|
|
200
|
+
inputTokens: costData.inputTokens, outputTokens: costData.outputTokens,
|
|
201
|
+
cacheTokens: 0, costUsd: costData.costUsd, status: 'failed',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.error('[ralph] Failed to record execution cost:', err.message);
|
|
206
|
+
}
|
|
188
207
|
(0, typedEmit_1.emit)('ralph:error', { taskId: task.id, message: result.stderr || `Exit code ${result.exitCode}` });
|
|
189
208
|
// Circuit breaker
|
|
190
209
|
if (consecutiveErrors >= maxErrors) {
|
|
@@ -169,10 +169,14 @@ async function assignTask(opts) {
|
|
|
169
169
|
// Run async — don't await (coordinator will poll via check_workers/wait_for_completion)
|
|
170
170
|
(async () => {
|
|
171
171
|
let output = '';
|
|
172
|
+
let costData = { costUsd: 0, inputTokens: 0, outputTokens: 0 };
|
|
172
173
|
runner.on('output', (chunk) => {
|
|
173
174
|
output += chunk;
|
|
174
175
|
(0, typedEmit_1.emit)('live:output', { workerId, taskId: opts.taskId, message: chunk });
|
|
175
176
|
});
|
|
177
|
+
runner.on('cost', (data) => {
|
|
178
|
+
costData = data;
|
|
179
|
+
});
|
|
176
180
|
runner.on('escalation', (data) => {
|
|
177
181
|
(0, escalationService_1.createEscalation)({
|
|
178
182
|
source: 'teams', taskId: opts.taskId, taskTitle: foundTask.title,
|
|
@@ -207,11 +211,14 @@ async function assignTask(opts) {
|
|
|
207
211
|
projectSlug: _projectSlug, workItemSlug: foundWiSlug,
|
|
208
212
|
taskId: opts.taskId, taskTitle: foundTask.title,
|
|
209
213
|
model: effectiveModel, duration: result.duration,
|
|
210
|
-
inputTokens:
|
|
214
|
+
inputTokens: costData.inputTokens, outputTokens: costData.outputTokens,
|
|
215
|
+
cacheTokens: 0, costUsd: costData.costUsd,
|
|
211
216
|
status: result.exitCode === 0 ? 'success' : 'failed',
|
|
212
217
|
});
|
|
213
218
|
}
|
|
214
|
-
catch {
|
|
219
|
+
catch (err) {
|
|
220
|
+
console.error('[workerPool] Failed to record execution cost:', err.message);
|
|
221
|
+
}
|
|
215
222
|
const wiAfter = workItemStore.getWorkItem(_projectSlug, foundWiSlug);
|
|
216
223
|
if (wiAfter)
|
|
217
224
|
(0, typedEmit_1.emit)('workItem:updated', { projectSlug: _projectSlug, workItem: wiAfter });
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* KANBAII MCP Server — Orchestration tools for Claude agents.
|
|
5
|
+
*
|
|
6
|
+
* This runs as a stdio MCP server that Claude CLI connects to.
|
|
7
|
+
* Tools available:
|
|
8
|
+
* - escalate_to_human : blocking poll for human response via dashboard
|
|
9
|
+
* - send_notification : fire-and-forget message to dashboard
|
|
10
|
+
* - list_tasks : GET /api/teams/tasks → tasks grouped by column
|
|
11
|
+
* - assign_task : POST /api/teams/assign → spin up a worker
|
|
12
|
+
* - check_workers : GET /api/teams/workers → worker pool status
|
|
13
|
+
* - wait_for_completion : poll workers until taskIds complete (or timeout)
|
|
14
|
+
* - send_message : POST /api/escalation/create (teams source, non-blocking)
|
|
15
|
+
*
|
|
16
|
+
* Supports both transports:
|
|
17
|
+
* - JSONL (newline-delimited JSON) — used by Claude CLI 2.x+
|
|
18
|
+
* - Content-Length (LSP-style) — used by older MCP clients
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const http = require('http');
|
|
22
|
+
|
|
23
|
+
const KANBAII_PORT = process.env.KANBAII_PORT || '5555';
|
|
24
|
+
const KANBAII_HOST = process.env.KANBAII_HOST || 'localhost';
|
|
25
|
+
const POLL_INTERVAL = 3000;
|
|
26
|
+
|
|
27
|
+
// ─── HTTP helpers ───
|
|
28
|
+
|
|
29
|
+
function post(path, body) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const data = JSON.stringify(body);
|
|
32
|
+
const req = http.request({
|
|
33
|
+
hostname: KANBAII_HOST, port: KANBAII_PORT, path, method: 'POST',
|
|
34
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
|
|
35
|
+
}, (res) => {
|
|
36
|
+
let buf = '';
|
|
37
|
+
res.on('data', c => buf += c);
|
|
38
|
+
res.on('end', () => { try { resolve(JSON.parse(buf)); } catch { resolve({ ok: false }); } });
|
|
39
|
+
});
|
|
40
|
+
req.on('error', reject);
|
|
41
|
+
req.write(data);
|
|
42
|
+
req.end();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function get(path) {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const req = http.request({
|
|
49
|
+
hostname: KANBAII_HOST, port: KANBAII_PORT, path, method: 'GET',
|
|
50
|
+
}, (res) => {
|
|
51
|
+
let buf = '';
|
|
52
|
+
res.on('data', c => buf += c);
|
|
53
|
+
res.on('end', () => { try { resolve(JSON.parse(buf)); } catch { resolve({ ok: false }); } });
|
|
54
|
+
});
|
|
55
|
+
req.on('error', reject);
|
|
56
|
+
req.end();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── MCP Protocol (stdio JSON-RPC, auto-detect JSONL vs Content-Length) ───
|
|
61
|
+
|
|
62
|
+
let buffer = '';
|
|
63
|
+
let useJsonl = null; // Auto-detect on first message
|
|
64
|
+
|
|
65
|
+
process.stdin.setEncoding('utf8');
|
|
66
|
+
process.stdin.on('data', (chunk) => {
|
|
67
|
+
buffer += chunk;
|
|
68
|
+
processBuffer();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function processBuffer() {
|
|
72
|
+
// Auto-detect format on first meaningful data
|
|
73
|
+
if (useJsonl === null) {
|
|
74
|
+
const trimmed = buffer.trimStart();
|
|
75
|
+
if (trimmed.startsWith('{')) {
|
|
76
|
+
useJsonl = true;
|
|
77
|
+
} else if (trimmed.startsWith('Content-Length:')) {
|
|
78
|
+
useJsonl = false;
|
|
79
|
+
} else {
|
|
80
|
+
return; // Wait for more data
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (useJsonl) {
|
|
85
|
+
processJsonl();
|
|
86
|
+
} else {
|
|
87
|
+
processContentLength();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function processJsonl() {
|
|
92
|
+
const lines = buffer.split('\n');
|
|
93
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const trimmed = line.trim();
|
|
96
|
+
if (!trimmed) continue;
|
|
97
|
+
try {
|
|
98
|
+
handleMessage(JSON.parse(trimmed));
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function processContentLength() {
|
|
104
|
+
while (true) {
|
|
105
|
+
const headerEnd = buffer.indexOf('\r\n\r\n');
|
|
106
|
+
if (headerEnd === -1) return;
|
|
107
|
+
const header = buffer.substring(0, headerEnd);
|
|
108
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
109
|
+
if (!match) { buffer = buffer.substring(headerEnd + 4); continue; }
|
|
110
|
+
const contentLength = parseInt(match[1], 10);
|
|
111
|
+
const bodyStart = headerEnd + 4;
|
|
112
|
+
if (buffer.length < bodyStart + contentLength) return;
|
|
113
|
+
const body = buffer.substring(bodyStart, bodyStart + contentLength);
|
|
114
|
+
buffer = buffer.substring(bodyStart + contentLength);
|
|
115
|
+
try { handleMessage(JSON.parse(body)); } catch {}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function sendResponse(msg) {
|
|
120
|
+
const body = JSON.stringify(msg);
|
|
121
|
+
if (useJsonl) {
|
|
122
|
+
process.stdout.write(body + '\n');
|
|
123
|
+
} else {
|
|
124
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ─── Tool Definitions ───
|
|
129
|
+
|
|
130
|
+
const TOOLS = [
|
|
131
|
+
{
|
|
132
|
+
name: 'escalate_to_human',
|
|
133
|
+
description: 'Escalate a question or decision to the human operator. Posts the question to the KANBAII dashboard and Telegram. If blocking=true (default), waits for the human response before returning. Use this whenever you need human input, approval, or a decision.',
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {
|
|
137
|
+
question: { type: 'string', description: 'The question to present to the human' },
|
|
138
|
+
options: { type: 'array', items: { type: 'string' }, description: 'Optional response options' },
|
|
139
|
+
blocking: { type: 'boolean', description: 'If true (default), wait for response. If false, fire-and-forget.' },
|
|
140
|
+
timeout_seconds: { type: 'number', description: 'Timeout in seconds (default 1800 = 30 min)' },
|
|
141
|
+
},
|
|
142
|
+
required: ['question'],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'send_notification',
|
|
147
|
+
description: 'Send a notification message to the KANBAII dashboard. Use for progress updates or important messages that do not require a response.',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
message: { type: 'string', description: 'The message to display' },
|
|
152
|
+
type: { type: 'string', enum: ['info', 'warning', 'error', 'success'], description: 'Message type' },
|
|
153
|
+
},
|
|
154
|
+
required: ['message'],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'list_tasks',
|
|
159
|
+
description: 'List all tasks in the Teams queue, grouped by column (Backlog, Todo, In Progress, Review, Done). Use this to understand what work is available or in progress before assigning tasks.',
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: 'object',
|
|
162
|
+
properties: {
|
|
163
|
+
projectSlug: { type: 'string', description: 'Optional project slug to filter tasks' },
|
|
164
|
+
workItemSlug: { type: 'string', description: 'Optional work item slug to filter tasks' },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'assign_task',
|
|
170
|
+
description: 'Assign a task to an agent worker. Posts to the Teams engine to spin up a worker for the given task. Returns the workerId that can be tracked via check_workers or wait_for_completion.',
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
taskId: { type: 'string', description: 'The task ID to assign' },
|
|
175
|
+
agent: { type: 'string', description: 'Optional agent name to assign the task to' },
|
|
176
|
+
model: { type: 'string', description: 'Optional model override (e.g. claude-opus-4-5)' },
|
|
177
|
+
additionalContext: { type: 'string', description: 'Optional additional context or instructions for the worker' },
|
|
178
|
+
},
|
|
179
|
+
required: ['taskId'],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: 'check_workers',
|
|
184
|
+
description: 'Check the current worker pool status. Returns all active, completed, and failed workers with their task assignments and output summaries.',
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: 'object',
|
|
187
|
+
properties: {},
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'wait_for_completion',
|
|
192
|
+
description: 'Poll the worker pool every 3 seconds until the specified taskIds appear in completedResults, or until timeout. If no taskIds are provided, waits for the next ANY new completion. Returns the completed worker results.',
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
taskIds: {
|
|
197
|
+
type: 'array',
|
|
198
|
+
items: { type: 'string' },
|
|
199
|
+
description: 'Task IDs to wait for. If omitted, waits for any new completion.',
|
|
200
|
+
},
|
|
201
|
+
timeout_seconds: {
|
|
202
|
+
type: 'number',
|
|
203
|
+
description: 'Max seconds to wait (default 900 = 15 min)',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'send_message',
|
|
210
|
+
description: 'Send a simple message or status update to the KANBAII dashboard from a teams agent. Unlike escalate_to_human, this never blocks — it is purely informational.',
|
|
211
|
+
inputSchema: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
message: { type: 'string', description: 'The message to send to the dashboard' },
|
|
215
|
+
},
|
|
216
|
+
required: ['message'],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'report_work_item',
|
|
221
|
+
description: 'Register a discovered work item (feature, bug, or refactor) in the KANBAII planner dashboard. The item appears on the discovery board for the user to see. Call this as soon as you identify a work item.',
|
|
222
|
+
inputSchema: {
|
|
223
|
+
type: 'object',
|
|
224
|
+
properties: {
|
|
225
|
+
id: { type: 'string', description: 'Unique ID for this item (e.g. disc-1, disc-2)' },
|
|
226
|
+
title: { type: 'string', description: 'Short descriptive title' },
|
|
227
|
+
category: { type: 'string', enum: ['feature', 'bug', 'refactor'], description: 'Type of work item' },
|
|
228
|
+
},
|
|
229
|
+
required: ['id', 'title', 'category'],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'update_work_item',
|
|
234
|
+
description: 'Update a work item with plan and/or tasks. Use status "planning" when you start working on it, and "ready" when plan + tasks are complete. The user can then approve it from the dashboard.',
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: 'object',
|
|
237
|
+
properties: {
|
|
238
|
+
id: { type: 'string', description: 'The item ID (e.g. disc-1) from report_work_item' },
|
|
239
|
+
status: { type: 'string', enum: ['planning', 'ready'], description: '"planning" = working on it, "ready" = plan + tasks complete' },
|
|
240
|
+
plan: { type: 'string', description: 'Markdown plan content (Objective, Approach, Key Decisions)' },
|
|
241
|
+
tasks: {
|
|
242
|
+
type: 'array',
|
|
243
|
+
description: 'Array of tasks (required when status is "ready")',
|
|
244
|
+
items: {
|
|
245
|
+
type: 'object',
|
|
246
|
+
properties: {
|
|
247
|
+
title: { type: 'string' },
|
|
248
|
+
description: { type: 'string' },
|
|
249
|
+
model: { type: 'string', description: 'Claude model: sonnet, opus, or haiku' },
|
|
250
|
+
priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
|
|
251
|
+
tags: { type: 'array', items: { type: 'string' } },
|
|
252
|
+
},
|
|
253
|
+
required: ['title', 'description'],
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
required: ['id', 'status'],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
// ─── Message Handler ───
|
|
263
|
+
|
|
264
|
+
async function handleMessage(msg) {
|
|
265
|
+
if (msg.method === 'initialize') {
|
|
266
|
+
// Mirror the protocol version the client sends
|
|
267
|
+
const clientVersion = msg.params?.protocolVersion || '2024-11-05';
|
|
268
|
+
sendResponse({
|
|
269
|
+
jsonrpc: '2.0', id: msg.id,
|
|
270
|
+
result: {
|
|
271
|
+
protocolVersion: clientVersion,
|
|
272
|
+
serverInfo: { name: 'kanbaii', version: '1.0.0' },
|
|
273
|
+
capabilities: { tools: { listChanged: false } },
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
} else if (msg.method === 'notifications/initialized') {
|
|
277
|
+
// No response needed
|
|
278
|
+
} else if (msg.method === 'tools/list') {
|
|
279
|
+
sendResponse({
|
|
280
|
+
jsonrpc: '2.0', id: msg.id,
|
|
281
|
+
result: { tools: TOOLS },
|
|
282
|
+
});
|
|
283
|
+
} else if (msg.method === 'tools/call') {
|
|
284
|
+
const { name, arguments: args } = msg.params;
|
|
285
|
+
try {
|
|
286
|
+
const result = await dispatchTool(name, args || {});
|
|
287
|
+
sendResponse({
|
|
288
|
+
jsonrpc: '2.0', id: msg.id,
|
|
289
|
+
result: { content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }] },
|
|
290
|
+
});
|
|
291
|
+
} catch (e) {
|
|
292
|
+
sendResponse({
|
|
293
|
+
jsonrpc: '2.0', id: msg.id,
|
|
294
|
+
result: { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
} else if (msg.id) {
|
|
298
|
+
sendResponse({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: `Unknown method: ${msg.method}` } });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ─── Tool Dispatcher ───
|
|
303
|
+
|
|
304
|
+
async function dispatchTool(name, args) {
|
|
305
|
+
switch (name) {
|
|
306
|
+
case 'escalate_to_human': return handleEscalation(args);
|
|
307
|
+
case 'send_notification': return handleSendNotification(args);
|
|
308
|
+
case 'list_tasks': return handleListTasks(args);
|
|
309
|
+
case 'assign_task': return handleAssignTask(args);
|
|
310
|
+
case 'check_workers': return handleCheckWorkers();
|
|
311
|
+
case 'wait_for_completion': return handleWaitForCompletion(args);
|
|
312
|
+
case 'send_message': return handleSendMessage(args);
|
|
313
|
+
case 'report_work_item': return handleReportWorkItem(args);
|
|
314
|
+
case 'update_work_item': return handleUpdateWorkItem(args);
|
|
315
|
+
default:
|
|
316
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ─── Tool Implementations ───
|
|
321
|
+
|
|
322
|
+
async function handleEscalation(args) {
|
|
323
|
+
const { question, options, blocking, timeout_seconds } = args;
|
|
324
|
+
const isBlocking = blocking !== false;
|
|
325
|
+
|
|
326
|
+
const createRes = await post('/api/escalation/create', {
|
|
327
|
+
source: 'ralph', taskId: '', taskTitle: '',
|
|
328
|
+
question, options: options || [], timeoutSeconds: timeout_seconds || 1800,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (!createRes.ok) throw new Error('Failed to create escalation');
|
|
332
|
+
|
|
333
|
+
const escalationId = createRes.data.escalationId;
|
|
334
|
+
|
|
335
|
+
if (!isBlocking) {
|
|
336
|
+
return { escalationId, status: 'sent', message: 'Escalation sent (non-blocking)' };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Poll for response
|
|
340
|
+
const deadline = Date.now() + (timeout_seconds || 1800) * 1000;
|
|
341
|
+
|
|
342
|
+
while (Date.now() < deadline) {
|
|
343
|
+
await sleep(POLL_INTERVAL);
|
|
344
|
+
|
|
345
|
+
const status = await get('/api/escalation/status');
|
|
346
|
+
if (status.ok && status.data?.responded && status.data.escalation?.response) {
|
|
347
|
+
const response = status.data.escalation.response;
|
|
348
|
+
await post('/api/escalation/clear', {});
|
|
349
|
+
return { escalationId, status: 'responded', response, question };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (status.ok && status.data?.escalation?.status === 'timed_out') {
|
|
353
|
+
return { escalationId, status: 'timed_out', message: 'No response received', question };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return { escalationId, status: 'timed_out', message: 'Timeout', question };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function handleSendNotification(args) {
|
|
361
|
+
await post('/api/escalation/create', {
|
|
362
|
+
source: 'ralph', question: args.message, options: [],
|
|
363
|
+
taskId: '', taskTitle: 'Notification', timeoutSeconds: 5,
|
|
364
|
+
});
|
|
365
|
+
return 'Notification sent';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function handleListTasks(args) {
|
|
369
|
+
let path = '/api/teams/tasks';
|
|
370
|
+
const params = [];
|
|
371
|
+
if (args.projectSlug) params.push(`projectSlug=${encodeURIComponent(args.projectSlug)}`);
|
|
372
|
+
if (args.workItemSlug) params.push(`workItemSlug=${encodeURIComponent(args.workItemSlug)}`);
|
|
373
|
+
if (params.length) path += '?' + params.join('&');
|
|
374
|
+
|
|
375
|
+
const res = await get(path);
|
|
376
|
+
if (!res.ok) throw new Error('Failed to fetch tasks');
|
|
377
|
+
return res.data || res;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function handleAssignTask(args) {
|
|
381
|
+
const { taskId, agent, model, additionalContext } = args;
|
|
382
|
+
|
|
383
|
+
const body = { taskId };
|
|
384
|
+
if (agent !== undefined) body.agent = agent;
|
|
385
|
+
if (model !== undefined) body.model = model;
|
|
386
|
+
if (additionalContext !== undefined) body.additionalContext = additionalContext;
|
|
387
|
+
|
|
388
|
+
const res = await post('/api/teams/assign', body);
|
|
389
|
+
if (!res.ok) throw new Error(res.error || 'Failed to assign task');
|
|
390
|
+
return res.data || res;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function handleCheckWorkers() {
|
|
394
|
+
const res = await get('/api/teams/workers');
|
|
395
|
+
if (!res.ok) throw new Error('Failed to fetch workers');
|
|
396
|
+
return res.data || res;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function handleWaitForCompletion(args) {
|
|
400
|
+
const { taskIds, timeout_seconds } = args;
|
|
401
|
+
const timeoutMs = (timeout_seconds || 900) * 1000;
|
|
402
|
+
const deadline = Date.now() + timeoutMs;
|
|
403
|
+
const targetIds = taskIds && taskIds.length > 0 ? new Set(taskIds) : null;
|
|
404
|
+
|
|
405
|
+
// Snapshot initial completed count for "any new completion" mode
|
|
406
|
+
let initialCompletedCount = null;
|
|
407
|
+
if (!targetIds) {
|
|
408
|
+
try {
|
|
409
|
+
const snapshot = await get('/api/teams/workers');
|
|
410
|
+
if (snapshot.ok) {
|
|
411
|
+
const data = snapshot.data || snapshot;
|
|
412
|
+
const completed = data.completedResults || data.completed || [];
|
|
413
|
+
initialCompletedCount = completed.length;
|
|
414
|
+
}
|
|
415
|
+
} catch {
|
|
416
|
+
initialCompletedCount = 0;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
while (Date.now() < deadline) {
|
|
421
|
+
await sleep(POLL_INTERVAL);
|
|
422
|
+
|
|
423
|
+
const res = await get('/api/teams/workers');
|
|
424
|
+
if (!res.ok) continue;
|
|
425
|
+
|
|
426
|
+
const data = res.data || res;
|
|
427
|
+
const completed = data.completedResults || data.completed || [];
|
|
428
|
+
|
|
429
|
+
if (targetIds) {
|
|
430
|
+
// Wait for all specified taskIds to appear in completedResults
|
|
431
|
+
const completedIds = new Set(completed.map(r => r.taskId || r.id).filter(Boolean));
|
|
432
|
+
const allDone = [...targetIds].every(id => completedIds.has(id));
|
|
433
|
+
if (allDone) {
|
|
434
|
+
const matching = completed.filter(r => targetIds.has(r.taskId || r.id));
|
|
435
|
+
return { status: 'completed', results: matching, allWorkers: data };
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
// Wait for any new completion
|
|
439
|
+
if (completed.length > initialCompletedCount) {
|
|
440
|
+
const newResults = completed.slice(initialCompletedCount);
|
|
441
|
+
return { status: 'completed', results: newResults, allWorkers: data };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Timeout — return current state
|
|
447
|
+
const finalRes = await get('/api/teams/workers');
|
|
448
|
+
const finalData = finalRes.ok ? (finalRes.data || finalRes) : {};
|
|
449
|
+
return {
|
|
450
|
+
status: 'timed_out',
|
|
451
|
+
message: `No completion after ${timeout_seconds || 900}s`,
|
|
452
|
+
allWorkers: finalData,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function handleSendMessage(args) {
|
|
457
|
+
await post('/api/escalation/create', {
|
|
458
|
+
source: 'teams', question: args.message, options: [],
|
|
459
|
+
taskId: '', taskTitle: 'Message', timeoutSeconds: 5,
|
|
460
|
+
});
|
|
461
|
+
return 'Message sent';
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function handleReportWorkItem(args) {
|
|
465
|
+
const { id, title, category } = args;
|
|
466
|
+
if (!id || !title) throw new Error('id and title are required');
|
|
467
|
+
const res = await post('/api/planner/report-item', { id, title, category: category || 'feature' });
|
|
468
|
+
if (!res.ok) throw new Error(res.error || 'Failed to report item');
|
|
469
|
+
return res.data || { message: `Item "${title}" registered on the planner dashboard.` };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async function handleUpdateWorkItem(args) {
|
|
473
|
+
const { id, status, plan, tasks } = args;
|
|
474
|
+
if (!id || !status) throw new Error('id and status are required');
|
|
475
|
+
const body = { id, status };
|
|
476
|
+
if (plan) body.plan = plan;
|
|
477
|
+
if (tasks) body.tasks = tasks.map(t => ({
|
|
478
|
+
title: t.title,
|
|
479
|
+
description: t.description || '',
|
|
480
|
+
model: t.model || 'sonnet',
|
|
481
|
+
priority: t.priority || 'medium',
|
|
482
|
+
tags: t.tags || [],
|
|
483
|
+
}));
|
|
484
|
+
const res = await post('/api/planner/update-item', body);
|
|
485
|
+
if (!res.ok) throw new Error(res.error || 'Failed to update item');
|
|
486
|
+
return res.data || { message: `Item updated to "${status}".` };
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ─── Utilities ───
|
|
490
|
+
|
|
491
|
+
function sleep(ms) {
|
|
492
|
+
return new Promise(r => setTimeout(r, ms));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Keep process alive
|
|
496
|
+
process.stdin.resume();
|
|
@@ -33,7 +33,9 @@ export interface CostSummary {
|
|
|
33
33
|
monthlyExecutions: number;
|
|
34
34
|
}
|
|
35
35
|
export declare function estimateCost(model: string, inputTokens: number, outputTokens: number, cacheTokens?: number): number;
|
|
36
|
-
export declare function recordExecution(data: Omit<ExecutionRecord, 'id' | 'timestamp' | 'costUsd'>
|
|
36
|
+
export declare function recordExecution(data: Omit<ExecutionRecord, 'id' | 'timestamp' | 'costUsd'> & {
|
|
37
|
+
costUsd?: number;
|
|
38
|
+
}): ExecutionRecord;
|
|
37
39
|
export declare function listExecutions(opts?: {
|
|
38
40
|
projectSlug?: string;
|
|
39
41
|
days?: number;
|
|
@@ -47,7 +47,9 @@ function recordExecution(data) {
|
|
|
47
47
|
...data,
|
|
48
48
|
id: `exec-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
49
49
|
timestamp: new Date().toISOString(),
|
|
50
|
-
costUsd:
|
|
50
|
+
costUsd: data.costUsd && data.costUsd > 0
|
|
51
|
+
? data.costUsd
|
|
52
|
+
: estimateCost(data.model, data.inputTokens, data.outputTokens, data.cacheTokens),
|
|
51
53
|
};
|
|
52
54
|
usage.executions.push(record);
|
|
53
55
|
// Keep last 1000 records
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kanbaii",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "The organization layer that Claude Code doesn't have. Plan visually, track progress, let AI execute.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kanban",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"dev:server": "tsx watch src/server/index.ts",
|
|
53
53
|
"dev:frontend": "cd frontend && npm run dev",
|
|
54
54
|
"dev": "concurrently \"npm run dev:server\" \"npm run dev:frontend\"",
|
|
55
|
-
"build:server": "tsc -p tsconfig.server.json",
|
|
56
|
-
"build:cli": "tsc -p tsconfig.cli.json",
|
|
55
|
+
"build:server": "tsc -p tsconfig.server.json && node -e \"const fs=require('fs'),p=require('path');const s='src/server/mcp',d='dist/server/mcp';fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).filter(f=>f.endsWith('.js')).forEach(f=>fs.copyFileSync(p.join(s,f),p.join(d,f)));\"",
|
|
56
|
+
"build:cli": "tsc -p tsconfig.cli.json && node -e \"const fs=require('fs'),p=require('path');const s='src/server/mcp',d='dist/server/mcp';fs.mkdirSync(d,{recursive:true});fs.readdirSync(s).filter(f=>f.endsWith('.js')).forEach(f=>fs.copyFileSync(p.join(s,f),p.join(d,f)));\"",
|
|
57
57
|
"build:frontend": "cd frontend && npm ci && npm run build && node -e \"const fs=require('fs');fs.rmSync('../dashboard',{recursive:true,force:true});fs.cpSync('out','../dashboard',{recursive:true});\"",
|
|
58
|
-
"build": "
|
|
58
|
+
"build": "npm run build:cli && npm run build:frontend",
|
|
59
59
|
"postbuild": "node -e \"require('fs').chmodSync('dist/cli/index.js', '755')\"",
|
|
60
60
|
"prepublishOnly": "echo 'Build handled by CI'",
|
|
61
61
|
"start": "node dist/server/index.js",
|
/package/dashboard/_next/static/{A8eY-I1syJkHlELnPcf1s → PAWhPHPqRCFfVFpsqwO5u}/_buildManifest.js
RENAMED
|
File without changes
|
/package/dashboard/_next/static/{A8eY-I1syJkHlELnPcf1s → PAWhPHPqRCFfVFpsqwO5u}/_ssgManifest.js
RENAMED
|
File without changes
|