quadwork 2.0.0 → 2.1.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.
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +2 -2
- package/out/__next._full.txt +3 -3
- package/out/__next._head.txt +1 -1
- package/out/__next._index.txt +2 -2
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/{13xk0vgfbrcld.css → 0pfyuhd8ccue..css} +1 -1
- package/out/_next/static/chunks/{0_lyyn..t63bc.js → 0ud0uv.699had.js} +1 -1
- package/out/_next/static/chunks/{11khe5i7gu158.js → 0z.9wnba-t6z8.js} +1 -1
- package/out/_next/static/chunks/0zk4tzycn0w4g.js +25 -0
- package/out/_not-found/__next._full.txt +2 -2
- package/out/_not-found/__next._head.txt +1 -1
- package/out/_not-found/__next._index.txt +2 -2
- package/out/_not-found/__next._not-found.__PAGE__.txt +1 -1
- package/out/_not-found/__next._not-found.txt +1 -1
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +2 -2
- package/out/app-shell/__next._full.txt +2 -2
- package/out/app-shell/__next._head.txt +1 -1
- package/out/app-shell/__next._index.txt +2 -2
- package/out/app-shell/__next._tree.txt +2 -2
- package/out/app-shell/__next.app-shell.__PAGE__.txt +1 -1
- package/out/app-shell/__next.app-shell.txt +1 -1
- package/out/app-shell.html +1 -1
- package/out/app-shell.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +3 -3
- package/out/project/_/__next._full.txt +3 -3
- package/out/project/_/__next._head.txt +1 -1
- package/out/project/_/__next._index.txt +2 -2
- package/out/project/_/__next._tree.txt +2 -2
- package/out/project/_/__next.project.$d$id.__PAGE__.txt +2 -2
- package/out/project/_/__next.project.$d$id.txt +1 -1
- package/out/project/_/__next.project.txt +1 -1
- package/out/project/_/queue/__next._full.txt +2 -2
- package/out/project/_/queue/__next._head.txt +1 -1
- package/out/project/_/queue/__next._index.txt +2 -2
- package/out/project/_/queue/__next._tree.txt +2 -2
- package/out/project/_/queue/__next.project.$d$id.queue.__PAGE__.txt +1 -1
- package/out/project/_/queue/__next.project.$d$id.queue.txt +1 -1
- package/out/project/_/queue/__next.project.$d$id.txt +1 -1
- package/out/project/_/queue/__next.project.txt +1 -1
- package/out/project/_/queue.html +1 -1
- package/out/project/_/queue.txt +2 -2
- package/out/project/_.html +1 -1
- package/out/project/_.txt +3 -3
- package/out/settings/__next._full.txt +2 -2
- package/out/settings/__next._head.txt +1 -1
- package/out/settings/__next._index.txt +2 -2
- package/out/settings/__next._tree.txt +2 -2
- package/out/settings/__next.settings.__PAGE__.txt +1 -1
- package/out/settings/__next.settings.txt +1 -1
- package/out/settings.html +1 -1
- package/out/settings.txt +2 -2
- package/out/setup/__next._full.txt +2 -2
- package/out/setup/__next._head.txt +1 -1
- package/out/setup/__next._index.txt +2 -2
- package/out/setup/__next._tree.txt +2 -2
- package/out/setup/__next.setup.__PAGE__.txt +1 -1
- package/out/setup/__next.setup.txt +1 -1
- package/out/setup.html +1 -1
- package/out/setup.txt +2 -2
- package/package.json +1 -1
- package/server/bridges/discord.js +63 -2
- package/server/bridges/telegram.js +51 -3
- package/server/index.js +22 -1
- package/server/routes.js +73 -12
- package/templates/seeds/head.AGENTS.md +9 -5
- package/out/_next/static/chunks/14k3bfe537f9_.js +0 -25
- /package/out/_next/static/{479UD5Kit4YvCmtgO25VT → vvtpLPTwziTD3klXH46MU}/_buildManifest.js +0 -0
- /package/out/_next/static/{479UD5Kit4YvCmtgO25VT → vvtpLPTwziTD3klXH46MU}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{479UD5Kit4YvCmtgO25VT → vvtpLPTwziTD3klXH46MU}/_ssgManifest.js +0 -0
package/out/setup.html
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><html lang="en" class="geist_mono_8d43a2aa-module__8Li5zG__variable h-full"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/chunks/13xk0vgfbrcld.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0ze4gu236oq96.js"/><script src="/_next/static/chunks/0.bbxho1vnxin.js" async=""></script><script src="/_next/static/chunks/07lhk_q6pmm3r.js" async=""></script><script src="/_next/static/chunks/0oxv9vrvc17to.js" async=""></script><script src="/_next/static/chunks/0pqt~8bl3ukh4.js" async=""></script><script src="/_next/static/chunks/turbopack-0y2u-q0l2m67w.js" async=""></script><script src="/_next/static/chunks/0q4bm04c1jl_3.js" async=""></script><script src="/_next/static/chunks/0d3shmwh5_nmn.js" async=""></script><script src="/_next/static/chunks/0py7102i226n5.js" async=""></script><meta name="next-size-adjust" content=""/><title>QuadWork</title><meta name="description" content="Unified dashboard for multi-agent coding teams"/><link rel="icon" href="/favicon.ico?favicon.05o2q2p4kvnq_.ico" sizes="256x256" type="image/x-icon"/><script src="/_next/static/chunks/03~yq9q893hmn.js" noModule=""></script></head><body class="h-full flex flex-col"><div hidden=""><!--$--><!--/$--></div><header class="sticky top-0 z-40 flex h-12 items-center justify-between border-b border-white/10 bg-neutral-950/90 px-4 backdrop-blur" aria-hidden="true"></header><div class="flex flex-1 min-h-0"><button type="button" class="fixed top-14 left-2 z-30 lg:hidden w-10 h-10 flex items-center justify-center bg-bg-surface border border-border text-text-muted hover:text-accent"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M3 5h14M3 10h14M3 15h14"></path></svg></button><aside class="fixed inset-y-0 left-0 z-50 w-52 bg-bg-surface border-r border-border flex flex-col py-3 px-2 items-stretch overflow-y-auto transition-transform duration-200 ease-in-out lg:hidden -translate-x-full"><button type="button" class="self-end shrink-0 w-10 h-10 flex items-center justify-center text-text-muted hover:text-accent mb-1"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 5l10 10M15 5L5 15"></path></svg></button><a class="flex items-center gap-2 rounded-sm transition-colors px-2 py-2 text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Home" href="/"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10L10 3l7 7"></path><path d="M5 8.5V16h3.5v-4h3v4H15V8.5"></path></svg><span class="text-xs">Home</span></a><div class="h-px bg-border my-2 "></div><div class="flex-1 flex flex-col gap-2 overflow-y-auto min-h-0 "><a class="flex items-center gap-2 rounded-full transition-colors px-2 py-2 border border-dashed border-border text-text-muted hover:text-text hover:bg-[#1a1a1a] rounded-sm" title="Add project" href="/setup"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 3v10M3 8h10"></path></svg><span class="text-xs text-text-muted">New Project</span></a></div><div class="h-px bg-border my-2 "></div><a class="flex items-center gap-2 rounded-sm transition-colors px-2 py-2 text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Settings" href="/settings"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="2.5"></circle><path d="M7.5 1.5h3l.4 2.1a5.5 5.5 0 011.3.7l2-.8 1.5 2.6-1.6 1.3a5.5 5.5 0 010 1.5l1.6 1.3-1.5 2.6-2-.8a5.5 5.5 0 01-1.3.7l-.4 2.1h-3l-.4-2.1a5.5 5.5 0 01-1.3-.7l-2 .8-1.5-2.6 1.6-1.3a5.5 5.5 0 010-1.5L2.3 6.1l1.5-2.6 2 .8a5.5 5.5 0 011.3-.7z"></path></svg><span class="text-xs">Settings</span></a></aside><aside class="hidden lg:flex shrink-0 h-full border-r border-border bg-bg-surface flex-col py-3 transition-[width] duration-200 ease-in-out overflow-hidden w-16 items-center"><a class="flex items-center gap-2 rounded-sm transition-colors w-10 h-10 justify-center self-center text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Home" href="/"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10L10 3l7 7"></path><path d="M5 8.5V16h3.5v-4h3v4H15V8.5"></path></svg></a><div class="h-px bg-border my-2 w-6 self-center"></div><div class="flex-1 flex flex-col gap-2 overflow-y-auto min-h-0 items-center"><a class="flex items-center gap-2 rounded-full transition-colors w-10 h-10 justify-center border border-dashed border-border text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Add project" href="/setup"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 3v10M3 8h10"></path></svg></a></div><div class="h-px bg-border my-2 w-6 self-center"></div><a class="flex items-center gap-2 rounded-sm transition-colors w-10 h-10 justify-center self-center text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Settings" href="/settings"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="2.5"></circle><path d="M7.5 1.5h3l.4 2.1a5.5 5.5 0 011.3.7l2-.8 1.5 2.6-1.6 1.3a5.5 5.5 0 010 1.5l1.6 1.3-1.5 2.6-2-.8a5.5 5.5 0 01-1.3.7l-.4 2.1h-3l-.4-2.1a5.5 5.5 0 01-1.3-.7l-2 .8-1.5-2.6 1.6-1.3a5.5 5.5 0 010-1.5L2.3 6.1l1.5-2.6 2 .8a5.5 5.5 0 011.3-.7z"></path></svg></a><div class="h-1"></div><button class="flex shrink-0 items-center justify-center w-10 h-10 rounded-sm border border-border text-text-muted hover:text-accent hover:border-accent/50 transition-colors self-center" title="Expand sidebar"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"></path></svg></button></aside><main class="flex-1 min-w-0 overflow-auto"><div class="h-full overflow-y-auto"></div><!--$--><!--/$--></main></div><script src="/_next/static/chunks/0ze4gu236oq96.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[52368,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"LocaleProvider\"]\n3:I[43688,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n4:I[26704,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n5:I[22140,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n6:I[39756,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n7:I[37457,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n8:I[94810,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\",\"/_next/static/chunks/0py7102i226n5.js\"],\"default\"]\n9:I[97367,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"OutletBoundary\"]\na:\"$Sreact.suspense\"\nd:I[97367,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"ViewportBoundary\"]\nf:I[97367,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"MetadataBoundary\"]\n11:I[68027,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/13xk0vgfbrcld.css\",\"style\"]\n:HL[\"/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"setup\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"setup\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",16],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/13xk0vgfbrcld.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/0d3shmwh5_nmn.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"geist_mono_8d43a2aa-module__8Li5zG__variable h-full\",\"children\":[\"$\",\"body\",null,{\"className\":\"h-full flex flex-col\",\"children\":[\"$\",\"$L2\",null,{\"children\":[[\"$\",\"$L3\",null,{}],[\"$\",\"$L4\",null,{}],[\"$\",\"div\",null,{\"className\":\"flex flex-1 min-h-0\",\"children\":[[\"$\",\"$L5\",null,{}],[\"$\",\"main\",null,{\"className\":\"flex-1 min-w-0 overflow-auto\",\"children\":[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",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.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]]}]]}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L8\",null,{}],[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0py7102i226n5.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L9\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@b\"}]}]]}],{},null,false,null]},null,false,\"$@c\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.Metadata\",\"children\":\"$L10\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$11\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/13xk0vgfbrcld.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"479UD5Kit4YvCmtgO25VT\"}\n"])</script><script>self.__next_f.push([1,"12:[]\nc:\"$W12\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"13:I[27201,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"IconMark\"]\nb:null\n10:[[\"$\",\"title\",\"0\",{\"children\":\"QuadWork\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Unified dashboard for multi-agent coding teams\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico?favicon.05o2q2p4kvnq_.ico\",\"sizes\":\"256x256\",\"type\":\"image/x-icon\"}],[\"$\",\"$L13\",\"3\",{}]]\n"])</script></body></html>
|
|
1
|
+
<!DOCTYPE html><html lang="en" class="geist_mono_8d43a2aa-module__8Li5zG__variable h-full"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" href="/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/chunks/0pfyuhd8ccue..css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/0ze4gu236oq96.js"/><script src="/_next/static/chunks/0.bbxho1vnxin.js" async=""></script><script src="/_next/static/chunks/07lhk_q6pmm3r.js" async=""></script><script src="/_next/static/chunks/0oxv9vrvc17to.js" async=""></script><script src="/_next/static/chunks/0pqt~8bl3ukh4.js" async=""></script><script src="/_next/static/chunks/turbopack-0y2u-q0l2m67w.js" async=""></script><script src="/_next/static/chunks/0q4bm04c1jl_3.js" async=""></script><script src="/_next/static/chunks/0d3shmwh5_nmn.js" async=""></script><script src="/_next/static/chunks/0py7102i226n5.js" async=""></script><meta name="next-size-adjust" content=""/><title>QuadWork</title><meta name="description" content="Unified dashboard for multi-agent coding teams"/><link rel="icon" href="/favicon.ico?favicon.05o2q2p4kvnq_.ico" sizes="256x256" type="image/x-icon"/><script src="/_next/static/chunks/03~yq9q893hmn.js" noModule=""></script></head><body class="h-full flex flex-col"><div hidden=""><!--$--><!--/$--></div><header class="sticky top-0 z-40 flex h-12 items-center justify-between border-b border-white/10 bg-neutral-950/90 px-4 backdrop-blur" aria-hidden="true"></header><div class="flex flex-1 min-h-0"><button type="button" class="fixed top-14 left-2 z-30 lg:hidden w-10 h-10 flex items-center justify-center bg-bg-surface border border-border text-text-muted hover:text-accent"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M3 5h14M3 10h14M3 15h14"></path></svg></button><aside class="fixed inset-y-0 left-0 z-50 w-52 bg-bg-surface border-r border-border flex flex-col py-3 px-2 items-stretch overflow-y-auto transition-transform duration-200 ease-in-out lg:hidden -translate-x-full"><button type="button" class="self-end shrink-0 w-10 h-10 flex items-center justify-center text-text-muted hover:text-accent mb-1"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M5 5l10 10M15 5L5 15"></path></svg></button><a class="flex items-center gap-2 rounded-sm transition-colors px-2 py-2 text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Home" href="/"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10L10 3l7 7"></path><path d="M5 8.5V16h3.5v-4h3v4H15V8.5"></path></svg><span class="text-xs">Home</span></a><div class="h-px bg-border my-2 "></div><div class="flex-1 flex flex-col gap-2 overflow-y-auto min-h-0 "><a class="flex items-center gap-2 rounded-full transition-colors px-2 py-2 border border-dashed border-border text-text-muted hover:text-text hover:bg-[#1a1a1a] rounded-sm" title="Add project" href="/setup"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 3v10M3 8h10"></path></svg><span class="text-xs text-text-muted">New Project</span></a></div><div class="h-px bg-border my-2 "></div><a class="flex items-center gap-2 rounded-sm transition-colors px-2 py-2 text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Settings" href="/settings"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="2.5"></circle><path d="M7.5 1.5h3l.4 2.1a5.5 5.5 0 011.3.7l2-.8 1.5 2.6-1.6 1.3a5.5 5.5 0 010 1.5l1.6 1.3-1.5 2.6-2-.8a5.5 5.5 0 01-1.3.7l-.4 2.1h-3l-.4-2.1a5.5 5.5 0 01-1.3-.7l-2 .8-1.5-2.6 1.6-1.3a5.5 5.5 0 010-1.5L2.3 6.1l1.5-2.6 2 .8a5.5 5.5 0 011.3-.7z"></path></svg><span class="text-xs">Settings</span></a></aside><aside class="hidden lg:flex shrink-0 h-full border-r border-border bg-bg-surface flex-col py-3 transition-[width] duration-200 ease-in-out overflow-hidden w-16 items-center"><a class="flex items-center gap-2 rounded-sm transition-colors w-10 h-10 justify-center self-center text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Home" href="/"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 10L10 3l7 7"></path><path d="M5 8.5V16h3.5v-4h3v4H15V8.5"></path></svg></a><div class="h-px bg-border my-2 w-6 self-center"></div><div class="flex-1 flex flex-col gap-2 overflow-y-auto min-h-0 items-center"><a class="flex items-center gap-2 rounded-full transition-colors w-10 h-10 justify-center border border-dashed border-border text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Add project" href="/setup"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 3v10M3 8h10"></path></svg></a></div><div class="h-px bg-border my-2 w-6 self-center"></div><a class="flex items-center gap-2 rounded-sm transition-colors w-10 h-10 justify-center self-center text-text-muted hover:text-text hover:bg-[#1a1a1a]" title="Settings" href="/settings"><svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="9" r="2.5"></circle><path d="M7.5 1.5h3l.4 2.1a5.5 5.5 0 011.3.7l2-.8 1.5 2.6-1.6 1.3a5.5 5.5 0 010 1.5l1.6 1.3-1.5 2.6-2-.8a5.5 5.5 0 01-1.3.7l-.4 2.1h-3l-.4-2.1a5.5 5.5 0 01-1.3-.7l-2 .8-1.5-2.6 1.6-1.3a5.5 5.5 0 010-1.5L2.3 6.1l1.5-2.6 2 .8a5.5 5.5 0 011.3-.7z"></path></svg></a><div class="h-1"></div><button class="flex shrink-0 items-center justify-center w-10 h-10 rounded-sm border border-border text-text-muted hover:text-accent hover:border-accent/50 transition-colors self-center" title="Expand sidebar"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"></path></svg></button></aside><main class="flex-1 min-w-0 overflow-auto"><div class="h-full overflow-y-auto"></div><!--$--><!--/$--></main></div><script src="/_next/static/chunks/0ze4gu236oq96.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[52368,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"LocaleProvider\"]\n3:I[43688,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n4:I[26704,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n5:I[22140,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n6:I[39756,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n7:I[37457,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n8:I[94810,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\",\"/_next/static/chunks/0py7102i226n5.js\"],\"default\"]\n9:I[97367,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"OutletBoundary\"]\na:\"$Sreact.suspense\"\nd:I[97367,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"ViewportBoundary\"]\nf:I[97367,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"MetadataBoundary\"]\n11:I[68027,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/0pfyuhd8ccue..css\",\"style\"]\n:HL[\"/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"setup\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"setup\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",16],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/0pfyuhd8ccue..css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"async\":true,\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-1\",{\"src\":\"/_next/static/chunks/0d3shmwh5_nmn.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"geist_mono_8d43a2aa-module__8Li5zG__variable h-full\",\"children\":[\"$\",\"body\",null,{\"className\":\"h-full flex flex-col\",\"children\":[\"$\",\"$L2\",null,{\"children\":[[\"$\",\"$L3\",null,{}],[\"$\",\"$L4\",null,{}],[\"$\",\"div\",null,{\"className\":\"flex flex-1 min-h-0\",\"children\":[[\"$\",\"$L5\",null,{}],[\"$\",\"main\",null,{\"className\":\"flex-1 min-w-0 overflow-auto\",\"children\":[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",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.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]]}]]}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L6\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L7\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"$L8\",null,{}],[[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0py7102i226n5.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"$L9\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@b\"}]}]]}],{},null,false,null]},null,false,\"$@c\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$Ld\",null,{\"children\":\"$Le\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Lf\",null,{\"children\":[\"$\",\"$a\",null,{\"name\":\"Next.Metadata\",\"children\":\"$L10\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$11\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/0pfyuhd8ccue..css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"vvtpLPTwziTD3klXH46MU\"}\n"])</script><script>self.__next_f.push([1,"12:[]\nc:\"$W12\"\n"])</script><script>self.__next_f.push([1,"e:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"13:I[27201,[\"/_next/static/chunks/0q4bm04c1jl_3.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"IconMark\"]\nb:null\n10:[[\"$\",\"title\",\"0\",{\"children\":\"QuadWork\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Unified dashboard for multi-agent coding teams\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico?favicon.05o2q2p4kvnq_.ico\",\"sizes\":\"256x256\",\"type\":\"image/x-icon\"}],[\"$\",\"$L13\",\"3\",{}]]\n"])</script></body></html>
|
package/out/setup.txt
CHANGED
|
@@ -11,9 +11,9 @@ a:"$Sreact.suspense"
|
|
|
11
11
|
d:I[97367,["/_next/static/chunks/0q4bm04c1jl_3.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"ViewportBoundary"]
|
|
12
12
|
f:I[97367,["/_next/static/chunks/0q4bm04c1jl_3.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"MetadataBoundary"]
|
|
13
13
|
11:I[68027,["/_next/static/chunks/0q4bm04c1jl_3.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"default",1]
|
|
14
|
-
:HL["/_next/static/chunks/
|
|
14
|
+
:HL["/_next/static/chunks/0pfyuhd8ccue..css","style"]
|
|
15
15
|
:HL["/_next/static/media/797e433ab948586e-s.p.09zddjkbdep5a.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
|
|
16
|
-
0:{"P":null,"c":["","setup"],"q":"","i":false,"f":[[["",{"children":["setup",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/
|
|
16
|
+
0:{"P":null,"c":["","setup"],"q":"","i":false,"f":[[["",{"children":["setup",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0pfyuhd8ccue..css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0q4bm04c1jl_3.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/_next/static/chunks/0d3shmwh5_nmn.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","className":"geist_mono_8d43a2aa-module__8Li5zG__variable h-full","children":["$","body",null,{"className":"h-full flex flex-col","children":["$","$L2",null,{"children":[["$","$L3",null,{}],["$","$L4",null,{}],["$","div",null,{"className":"flex flex-1 min-h-0","children":[["$","$L5",null,{}],["$","main",null,{"className":"flex-1 min-w-0 overflow-auto","children":["$","$L6",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",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."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]]}]]}]}]}]]}],{"children":[["$","$1","c",{"children":[null,["$","$L6",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L7",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","forbidden":"$undefined","unauthorized":"$undefined"}]]}],{"children":[["$","$1","c",{"children":[["$","$L8",null,{}],[["$","script","script-0",{"src":"/_next/static/chunks/0py7102i226n5.js","async":true,"nonce":"$undefined"}]],["$","$L9",null,{"children":["$","$a",null,{"name":"Next.MetadataOutlet","children":"$@b"}]}]]}],{},null,false,null]},null,false,"$@c"]},null,false,null],["$","$1","h",{"children":[null,["$","$Ld",null,{"children":"$Le"}],["$","div",null,{"hidden":true,"children":["$","$Lf",null,{"children":["$","$a",null,{"name":"Next.Metadata","children":"$L10"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$11",[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/0pfyuhd8ccue..css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"vvtpLPTwziTD3klXH46MU"}
|
|
17
17
|
12:[]
|
|
18
18
|
c:"$W12"
|
|
19
19
|
e:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
|
package/package.json
CHANGED
|
@@ -6,6 +6,16 @@ const CONFIG_DIR = path.join(os.homedir(), ".quadwork");
|
|
|
6
6
|
|
|
7
7
|
const instances = new Map();
|
|
8
8
|
|
|
9
|
+
// #782: cursor-lag threshold beyond which a valid cursor is treated as
|
|
10
|
+
// stale (bridge stopped mid-backlog) and reseeded to latest on start.
|
|
11
|
+
// Small lags within this window keep continuity for graceful restarts.
|
|
12
|
+
//
|
|
13
|
+
// #786: intentional trade-off — if the bridge was down while 11+ messages
|
|
14
|
+
// arrived, those messages are skipped (not replayed to Discord). This
|
|
15
|
+
// prevents the "old message flood" bug. If continuity is needed for long
|
|
16
|
+
// downtimes, increase this threshold or remove the stale-cursor check.
|
|
17
|
+
const STALE_CURSOR_THRESHOLD = 10;
|
|
18
|
+
|
|
9
19
|
function cursorPath(projectId) {
|
|
10
20
|
return path.join(CONFIG_DIR, `dc-bridge-cursor-${projectId}.json`);
|
|
11
21
|
}
|
|
@@ -134,7 +144,11 @@ async function start(projectId, botToken, channelId, qwPort) {
|
|
|
134
144
|
if (message.channel.id !== channelId) return;
|
|
135
145
|
|
|
136
146
|
const from = message.author.username || "unknown";
|
|
137
|
-
|
|
147
|
+
console.log(`[bridge] discord ${projectId}: received message from ${from}`);
|
|
148
|
+
if (!message.content) {
|
|
149
|
+
console.warn(`[bridge] discord ${projectId}: empty message content — check MESSAGE_CONTENT intent`);
|
|
150
|
+
}
|
|
151
|
+
const res = await fetch(`http://127.0.0.1:${qwPort}/api/chat?project=${encodeURIComponent(projectId)}`, {
|
|
138
152
|
method: "POST",
|
|
139
153
|
headers: {
|
|
140
154
|
"Content-Type": "application/json",
|
|
@@ -147,11 +161,58 @@ async function start(projectId, botToken, channelId, qwPort) {
|
|
|
147
161
|
}),
|
|
148
162
|
signal: AbortSignal.timeout(5000),
|
|
149
163
|
});
|
|
164
|
+
if (!res.ok) {
|
|
165
|
+
console.error(`[bridge] discord ${projectId} inbound POST failed: ${res.status}`);
|
|
166
|
+
}
|
|
150
167
|
} catch (err) {
|
|
151
|
-
if (!inst.stopping)
|
|
168
|
+
if (!inst.stopping) {
|
|
169
|
+
console.error(`[bridge] discord ${projectId} inbound error: ${err.message}`);
|
|
170
|
+
inst.lastError = err.message;
|
|
171
|
+
}
|
|
152
172
|
}
|
|
153
173
|
});
|
|
154
174
|
|
|
175
|
+
client.on("error", (err) => {
|
|
176
|
+
console.error(`[bridge] discord ${projectId} client error: ${err?.message || err}`);
|
|
177
|
+
});
|
|
178
|
+
client.on("warn", (msg) => {
|
|
179
|
+
console.warn(`[bridge] discord ${projectId} client warn: ${msg}`);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// #782: seed cursor to latest on first enable (no cursor file, or
|
|
183
|
+
// cursor=0) AND on stale-cursor restarts where the cursor lags the
|
|
184
|
+
// latest chat message by more than STALE_CURSOR_THRESHOLD. The stale
|
|
185
|
+
// case covers bridges stopped mid-backlog (e.g. the plottoon scenario:
|
|
186
|
+
// cursor=83 with 127 messages — without this guard the next start
|
|
187
|
+
// replays 44 old messages to Discord). Small lags (graceful restart
|
|
188
|
+
// mid-conversation) keep continuity.
|
|
189
|
+
const cursorFileExists = fs.existsSync(cursorPath(projectId));
|
|
190
|
+
try {
|
|
191
|
+
const r = await fetch(
|
|
192
|
+
`http://127.0.0.1:${qwPort}/api/chat?project=${encodeURIComponent(projectId)}&limit=1`,
|
|
193
|
+
{ signal: AbortSignal.timeout(5000) }
|
|
194
|
+
);
|
|
195
|
+
if (r.ok) {
|
|
196
|
+
const msgs = await r.json();
|
|
197
|
+
if (msgs.length > 0) {
|
|
198
|
+
const latestId = msgs[msgs.length - 1].id;
|
|
199
|
+
const stale = !cursorFileExists
|
|
200
|
+
|| inst.cursor === 0
|
|
201
|
+
|| (latestId - inst.cursor) > STALE_CURSOR_THRESHOLD;
|
|
202
|
+
if (stale) {
|
|
203
|
+
inst.cursor = latestId;
|
|
204
|
+
writeCursor(projectId, inst.cursor);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// #786: surface HTTP failures so non-OK responses don't fall
|
|
209
|
+
// through silently (the catch only fires on thrown errors).
|
|
210
|
+
console.warn(`[bridge] discord ${projectId}: cursor seed fetch returned ${r.status}`);
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.warn(`[bridge] discord ${projectId}: cursor seed failed (${err.message})`);
|
|
214
|
+
}
|
|
215
|
+
|
|
155
216
|
pollLoop(projectId, channel, qwPort).catch((err) => {
|
|
156
217
|
console.error(`[bridge] discord ${projectId} poll crashed: ${err.message}`);
|
|
157
218
|
inst.lastError = err.message;
|
|
@@ -6,6 +6,16 @@ const CONFIG_DIR = path.join(os.homedir(), ".quadwork");
|
|
|
6
6
|
|
|
7
7
|
const instances = new Map();
|
|
8
8
|
|
|
9
|
+
// #782: cursor-lag threshold beyond which a valid cursor is treated as
|
|
10
|
+
// stale (bridge stopped mid-backlog) and reseeded to latest on start.
|
|
11
|
+
// Small lags within this window keep continuity for graceful restarts.
|
|
12
|
+
//
|
|
13
|
+
// #786: intentional trade-off — if the bridge was down while 11+ messages
|
|
14
|
+
// arrived, those messages are skipped (not replayed to Telegram). This
|
|
15
|
+
// prevents the "old message flood" bug. If continuity is needed for long
|
|
16
|
+
// downtimes, increase this threshold or remove the stale-cursor check.
|
|
17
|
+
const STALE_CURSOR_THRESHOLD = 10;
|
|
18
|
+
|
|
9
19
|
function cursorPath(projectId) {
|
|
10
20
|
return path.join(CONFIG_DIR, `tg-bridge-cursor-${projectId}.json`);
|
|
11
21
|
}
|
|
@@ -123,7 +133,7 @@ async function startTelegramUpdates(projectId, botToken, chatId, qwPort) {
|
|
|
123
133
|
if (!text || msgChatId !== String(chatId)) continue;
|
|
124
134
|
|
|
125
135
|
try {
|
|
126
|
-
await fetch(`http://127.0.0.1:${qwPort}/api/chat?project=${encodeURIComponent(projectId)}`, {
|
|
136
|
+
const r = await fetch(`http://127.0.0.1:${qwPort}/api/chat?project=${encodeURIComponent(projectId)}`, {
|
|
127
137
|
method: "POST",
|
|
128
138
|
headers: {
|
|
129
139
|
"Content-Type": "application/json",
|
|
@@ -136,7 +146,12 @@ async function startTelegramUpdates(projectId, botToken, chatId, qwPort) {
|
|
|
136
146
|
}),
|
|
137
147
|
signal: AbortSignal.timeout(5000),
|
|
138
148
|
});
|
|
139
|
-
|
|
149
|
+
if (!r.ok) {
|
|
150
|
+
console.error(`[bridge] telegram ${projectId} inbound POST failed: ${r.status}`);
|
|
151
|
+
}
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error(`[bridge] telegram ${projectId} inbound error: ${err.message}`);
|
|
154
|
+
}
|
|
140
155
|
}
|
|
141
156
|
}
|
|
142
157
|
} catch (err) {
|
|
@@ -154,7 +169,7 @@ async function startTelegramUpdates(projectId, botToken, chatId, qwPort) {
|
|
|
154
169
|
tick();
|
|
155
170
|
}
|
|
156
171
|
|
|
157
|
-
function start(projectId, botToken, chatId, qwPort) {
|
|
172
|
+
async function start(projectId, botToken, chatId, qwPort) {
|
|
158
173
|
if (instances.has(projectId)) return;
|
|
159
174
|
|
|
160
175
|
const oldCursor = path.join(CONFIG_DIR, `telegram-bridge-cursor-${projectId}.json`);
|
|
@@ -174,6 +189,39 @@ function start(projectId, botToken, chatId, qwPort) {
|
|
|
174
189
|
};
|
|
175
190
|
instances.set(projectId, inst);
|
|
176
191
|
|
|
192
|
+
// #782: seed cursor to latest on first enable (no cursor file, or
|
|
193
|
+
// cursor=0) AND on stale-cursor restarts where the cursor lags the
|
|
194
|
+
// latest chat message by more than STALE_CURSOR_THRESHOLD. The stale
|
|
195
|
+
// case covers bridges stopped mid-backlog so the next start does not
|
|
196
|
+
// replay old conversations to Telegram. Small lags (graceful restart
|
|
197
|
+
// mid-conversation) keep continuity.
|
|
198
|
+
const cursorFileExists = fs.existsSync(cursorPath(projectId));
|
|
199
|
+
try {
|
|
200
|
+
const r = await fetch(
|
|
201
|
+
`http://127.0.0.1:${qwPort}/api/chat?project=${encodeURIComponent(projectId)}&limit=1`,
|
|
202
|
+
{ signal: AbortSignal.timeout(5000) }
|
|
203
|
+
);
|
|
204
|
+
if (r.ok) {
|
|
205
|
+
const msgs = await r.json();
|
|
206
|
+
if (msgs.length > 0) {
|
|
207
|
+
const latestId = msgs[msgs.length - 1].id;
|
|
208
|
+
const stale = !cursorFileExists
|
|
209
|
+
|| inst.cursor === 0
|
|
210
|
+
|| (latestId - inst.cursor) > STALE_CURSOR_THRESHOLD;
|
|
211
|
+
if (stale) {
|
|
212
|
+
inst.cursor = latestId;
|
|
213
|
+
writeCursor(projectId, inst.cursor);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// #786: surface HTTP failures so non-OK responses don't fall
|
|
218
|
+
// through silently (the catch only fires on thrown errors).
|
|
219
|
+
console.warn(`[bridge] telegram ${projectId}: cursor seed fetch returned ${r.status}`);
|
|
220
|
+
}
|
|
221
|
+
} catch (err) {
|
|
222
|
+
console.warn(`[bridge] telegram ${projectId}: cursor seed failed (${err.message})`);
|
|
223
|
+
}
|
|
224
|
+
|
|
177
225
|
pollLoop(projectId, botToken, chatId, qwPort).catch((err) => {
|
|
178
226
|
console.error(`[bridge] telegram ${projectId} poll crashed: ${err.message}`);
|
|
179
227
|
inst.lastError = err.message;
|
package/server/index.js
CHANGED
|
@@ -1183,6 +1183,13 @@ app.get("/api/triggers", (_req, res) => {
|
|
|
1183
1183
|
res.json(result);
|
|
1184
1184
|
});
|
|
1185
1185
|
|
|
1186
|
+
// #812: a parked (idle) project gets no trigger starts/pulses. Read
|
|
1187
|
+
// the live config so a config-write that sets idle takes effect at once.
|
|
1188
|
+
function isProjectIdleId(projectId) {
|
|
1189
|
+
try { return !!readConfig().projects?.find((p) => p.id === projectId)?.idle; }
|
|
1190
|
+
catch { return false; }
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1186
1193
|
function stopTrigger(project) {
|
|
1187
1194
|
const existing = triggers.get(project);
|
|
1188
1195
|
if (existing) {
|
|
@@ -1194,6 +1201,11 @@ function stopTrigger(project) {
|
|
|
1194
1201
|
|
|
1195
1202
|
app.post("/api/triggers/:project/start", (req, res) => {
|
|
1196
1203
|
const { project } = req.params;
|
|
1204
|
+
// #812: refuse to start a trigger for a parked (idle) project — no
|
|
1205
|
+
// timer created, no agents pulsed. Toggle the project off idle first.
|
|
1206
|
+
if (isProjectIdleId(project)) {
|
|
1207
|
+
return res.json({ ok: false, idle: true, enabled: false });
|
|
1208
|
+
}
|
|
1197
1209
|
// #418 / quadwork#306: sendImmediately was an always-true
|
|
1198
1210
|
// send-and-start flag from the original #210 button; operators
|
|
1199
1211
|
// asked for a pure scheduler (the button is now just "Start
|
|
@@ -1265,6 +1277,10 @@ app.post("/api/triggers/:project/stop", (req, res) => {
|
|
|
1265
1277
|
|
|
1266
1278
|
app.post("/api/triggers/:project/send-now", (req, res) => {
|
|
1267
1279
|
const { project } = req.params;
|
|
1280
|
+
// #812: parked (idle) project — do not pulse agents.
|
|
1281
|
+
if (isProjectIdleId(project)) {
|
|
1282
|
+
return res.json({ ok: false, idle: true, sent: false });
|
|
1283
|
+
}
|
|
1268
1284
|
sendTriggerMessage(project);
|
|
1269
1285
|
res.json({ ok: true, sent: true });
|
|
1270
1286
|
});
|
|
@@ -1593,7 +1609,11 @@ function syncTriggersFromConfig() {
|
|
|
1593
1609
|
|
|
1594
1610
|
if (cfg.projects) {
|
|
1595
1611
|
for (const project of cfg.projects) {
|
|
1596
|
-
|
|
1612
|
+
// #812: idle (parked) projects get no trigger. Excluding them from
|
|
1613
|
+
// activeIds also makes the cleanup loop below clear any timer they
|
|
1614
|
+
// had — so writing idle:true via PUT /api/config (which calls this)
|
|
1615
|
+
// stops a running trigger with no separate stop call.
|
|
1616
|
+
if (project.trigger_enabled && !project.idle) {
|
|
1597
1617
|
activeIds.add(project.id);
|
|
1598
1618
|
const ms = (project.trigger_interval || 30) * 60 * 1000;
|
|
1599
1619
|
const existing = triggers.get(project.id);
|
|
@@ -1631,6 +1651,7 @@ async function autoStopPollingTick() {
|
|
|
1631
1651
|
if (!cfg.projects) return;
|
|
1632
1652
|
|
|
1633
1653
|
for (const project of cfg.projects) {
|
|
1654
|
+
if (project.idle) continue; // #812: parked project — no batch-progress polling
|
|
1634
1655
|
const hasTriggerAuto = project.trigger_auto && triggers.has(project.id);
|
|
1635
1656
|
const hasBridgeAuto = project.telegram_auto || project.discord_auto;
|
|
1636
1657
|
if (!hasTriggerAuto && !hasBridgeAuto) continue;
|
package/server/routes.js
CHANGED
|
@@ -120,9 +120,17 @@ function _ghEnqueueRefresh(cacheKey, ghArgs, transform) {
|
|
|
120
120
|
_ghDrainQueue();
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
function cachedGhEndpoint(cacheKey, ghArgs, res, { transform } = {}) {
|
|
124
|
-
const ttl = adaptiveTTL(GH_ENDPOINT_CACHE_TTL);
|
|
123
|
+
function cachedGhEndpoint(cacheKey, ghArgs, res, { transform, idle } = {}) {
|
|
125
124
|
const cached = _ghEndpointCache.get(cacheKey);
|
|
125
|
+
// #812: a parked (idle) project must never initiate a gh fetch. Serve
|
|
126
|
+
// whatever we last cached, or an empty list — never call gh. The list
|
|
127
|
+
// endpoints return a bare array (client contract), so signal idle via a
|
|
128
|
+
// response header rather than mutating the JSON shape.
|
|
129
|
+
if (idle) {
|
|
130
|
+
res.set("X-QuadWork-Idle", "1");
|
|
131
|
+
return res.json(cached ? cached.data : []);
|
|
132
|
+
}
|
|
133
|
+
const ttl = adaptiveTTL(GH_ENDPOINT_CACHE_TTL);
|
|
126
134
|
if (cached && Date.now() - cached.ts < ttl) {
|
|
127
135
|
return res.json(cached.stale ? { ...cached.data, _stale: true } : cached.data);
|
|
128
136
|
}
|
|
@@ -268,11 +276,10 @@ router.get("/api/chat", (req, res) => {
|
|
|
268
276
|
since_id: sinceId,
|
|
269
277
|
limit: Number(req.query.limit) || 50,
|
|
270
278
|
});
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return res.json(normalized);
|
|
279
|
+
// #783: return raw ISO `ts` only. The previous server-side `time`
|
|
280
|
+
// field used the server's local time, which on UTC VPS hosts gave
|
|
281
|
+
// wrong-timezone display. The frontend formats `ts` in the browser.
|
|
282
|
+
return res.json(messages);
|
|
276
283
|
});
|
|
277
284
|
|
|
278
285
|
// #693: Auto-normalize bare agent names to @mentions in outbound messages.
|
|
@@ -851,6 +858,20 @@ router.get("/api/projects", async (req, res) => {
|
|
|
851
858
|
async function fetchProjectGhData(p) {
|
|
852
859
|
let openPrs = 0;
|
|
853
860
|
let lastActivity = null;
|
|
861
|
+
// #812: parked (idle) project — no gh calls; return zero/last-known metadata.
|
|
862
|
+
if (p.idle) {
|
|
863
|
+
const hasAgentsIdle = p.agents && Object.keys(p.agents).length > 0;
|
|
864
|
+
return {
|
|
865
|
+
id: p.id,
|
|
866
|
+
name: p.name,
|
|
867
|
+
repo: p.repo,
|
|
868
|
+
agentCount: hasAgentsIdle ? Object.keys(p.agents).length : 0,
|
|
869
|
+
openPrs: 0,
|
|
870
|
+
state: "idle",
|
|
871
|
+
lastActivity: null,
|
|
872
|
+
_idle: true,
|
|
873
|
+
};
|
|
874
|
+
}
|
|
854
875
|
if (REPO_RE.test(p.repo)) {
|
|
855
876
|
try {
|
|
856
877
|
const [prs, recentPrs] = await Promise.allSettled([
|
|
@@ -891,7 +912,7 @@ router.get("/api/projects", async (req, res) => {
|
|
|
891
912
|
}
|
|
892
913
|
if (projectName) {
|
|
893
914
|
recentEvents.push({
|
|
894
|
-
|
|
915
|
+
ts: m.ts,
|
|
895
916
|
text: m.text.length > 120 ? m.text.slice(0, 120) + "…" : m.text,
|
|
896
917
|
actor: m.sender,
|
|
897
918
|
projectName,
|
|
@@ -938,6 +959,20 @@ function getRepo(projectId) {
|
|
|
938
959
|
}
|
|
939
960
|
}
|
|
940
961
|
|
|
962
|
+
// #812: per-project Idle toggle. When a project is idle, QuadWork must
|
|
963
|
+
// initiate ZERO project-specific GitHub/API activity for it. Callers
|
|
964
|
+
// (board fetch, per-endpoint handlers, batch-progress, /api/projects)
|
|
965
|
+
// check this before issuing any gh call.
|
|
966
|
+
function isProjectIdle(projectId) {
|
|
967
|
+
if (!projectId) return false;
|
|
968
|
+
try {
|
|
969
|
+
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
970
|
+
return !!cfg.projects?.find((p) => p.id === projectId)?.idle;
|
|
971
|
+
} catch {
|
|
972
|
+
return false;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
941
976
|
// ─── #703: Batched GraphQL layer ──────────────────────────────────────────
|
|
942
977
|
// Instead of spawning individual `gh issue list` / `gh pr list` subprocesses
|
|
943
978
|
// per project per endpoint, we fetch ALL configured projects' GitHub data in
|
|
@@ -960,7 +995,7 @@ async function fetchAllProjectsGraphQL() {
|
|
|
960
995
|
} catch {
|
|
961
996
|
return null;
|
|
962
997
|
}
|
|
963
|
-
const projects = (cfg.projects || []).filter((p) => p.repo && REPO_RE.test(p.repo));
|
|
998
|
+
const projects = (cfg.projects || []).filter((p) => p.repo && REPO_RE.test(p.repo) && !p.idle); // #812: skip idle (parked) projects
|
|
964
999
|
if (projects.length === 0) return null;
|
|
965
1000
|
|
|
966
1001
|
// Build aliased repository fields — one per project.
|
|
@@ -1239,7 +1274,7 @@ router.get("/api/github/all", async (req, res) => {
|
|
|
1239
1274
|
const anyStale = (() => {
|
|
1240
1275
|
let cfg;
|
|
1241
1276
|
try { cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); } catch { return true; }
|
|
1242
|
-
const projects = (cfg.projects || []).filter((p) => p.repo && REPO_RE.test(p.repo));
|
|
1277
|
+
const projects = (cfg.projects || []).filter((p) => p.repo && REPO_RE.test(p.repo) && !p.idle); // #812: skip idle (parked) projects
|
|
1243
1278
|
for (const p of projects) {
|
|
1244
1279
|
const cached = _graphqlCache.get(p.repo);
|
|
1245
1280
|
if (!cached || Date.now() - cached.ts > adaptiveTTL(GRAPHQL_CACHE_TTL)) return true;
|
|
@@ -1251,13 +1286,21 @@ router.get("/api/github/all", async (req, res) => {
|
|
|
1251
1286
|
// Build response.
|
|
1252
1287
|
let cfg;
|
|
1253
1288
|
try { cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8")); } catch { return res.status(500).json({ error: "Config unreadable" }); }
|
|
1254
|
-
const projects = (cfg.projects || []).filter((p) => p.repo && REPO_RE.test(p.repo));
|
|
1289
|
+
const projects = (cfg.projects || []).filter((p) => p.repo && REPO_RE.test(p.repo)); // #812: include idle projects — served stale below, never fetched
|
|
1255
1290
|
|
|
1256
1291
|
const result = {};
|
|
1257
1292
|
const fallbackNeeded = [];
|
|
1258
1293
|
for (const p of projects) {
|
|
1259
1294
|
if (projectFilter && p.id !== projectFilter) continue;
|
|
1260
1295
|
const cached = _graphqlCache.get(p.repo);
|
|
1296
|
+
// #812: idle (parked) project — serve last-known data flagged _idle,
|
|
1297
|
+
// never trigger a fetch or fall back to gh.
|
|
1298
|
+
if (p.idle) {
|
|
1299
|
+
result[p.id] = cached
|
|
1300
|
+
? { issues: cached.issues, prs: cached.prs, closedIssues: cached.closedIssues, mergedPrs: cached.mergedPrs, _idle: true }
|
|
1301
|
+
: { issues: [], prs: [], closedIssues: [], mergedPrs: [], _idle: true };
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1261
1304
|
if (cached) {
|
|
1262
1305
|
result[p.id] = {
|
|
1263
1306
|
issues: cached.issues,
|
|
@@ -1321,6 +1364,7 @@ router.get("/api/github/issues", (req, res) => {
|
|
|
1321
1364
|
`issues:${repo}`,
|
|
1322
1365
|
["issue", "list", "-R", repo, "--json", "number,title,state,assignees,labels,createdAt,url", "--limit", "50"],
|
|
1323
1366
|
res,
|
|
1367
|
+
{ idle: isProjectIdle(req.query.project || "") },
|
|
1324
1368
|
);
|
|
1325
1369
|
});
|
|
1326
1370
|
|
|
@@ -1331,6 +1375,7 @@ router.get("/api/github/prs", (req, res) => {
|
|
|
1331
1375
|
`prs:${repo}`,
|
|
1332
1376
|
["pr", "list", "-R", repo, "--json", "number,title,state,author,assignees,reviewDecision,reviews,statusCheckRollup,url,createdAt", "--limit", "50"],
|
|
1333
1377
|
res,
|
|
1378
|
+
{ idle: isProjectIdle(req.query.project || "") },
|
|
1334
1379
|
);
|
|
1335
1380
|
});
|
|
1336
1381
|
|
|
@@ -1352,6 +1397,7 @@ router.get("/api/github/closed-issues", (req, res) => {
|
|
|
1352
1397
|
["issue", "list", "-R", repo, "--state", "closed", "--json", "number,title,state,url,closedAt", "--limit", String(RECENT_FETCH_LIMIT)],
|
|
1353
1398
|
res,
|
|
1354
1399
|
{
|
|
1400
|
+
idle: isProjectIdle(req.query.project || ""),
|
|
1355
1401
|
transform: (items) =>
|
|
1356
1402
|
Array.isArray(items)
|
|
1357
1403
|
? items
|
|
@@ -1379,6 +1425,7 @@ router.get("/api/github/merged-prs", (req, res) => {
|
|
|
1379
1425
|
["pr", "list", "-R", repo, "--state", "merged", "--json", "number,title,state,url,mergedAt,author", "--limit", String(RECENT_FETCH_LIMIT)],
|
|
1380
1426
|
res,
|
|
1381
1427
|
{
|
|
1428
|
+
idle: isProjectIdle(req.query.project || ""),
|
|
1382
1429
|
transform: (items) =>
|
|
1383
1430
|
Array.isArray(items)
|
|
1384
1431
|
? items
|
|
@@ -1804,6 +1851,14 @@ router.get("/api/batch-progress", async (req, res) => {
|
|
|
1804
1851
|
if (!projectId) return res.status(400).json({ error: "Missing project" });
|
|
1805
1852
|
|
|
1806
1853
|
const cached = _batchProgressCache.get(projectId);
|
|
1854
|
+
// #812: parked (idle) project — never run batch-progress gh/GraphQL calls,
|
|
1855
|
+
// and ALWAYS flag the payload _idle (even on a fresh cache hit), so the
|
|
1856
|
+
// endpoint contract is consistent regardless of cache freshness. This must
|
|
1857
|
+
// precede the fresh-cache and rate-limit returns below.
|
|
1858
|
+
if (isProjectIdle(projectId)) {
|
|
1859
|
+
if (cached) return res.json({ ...cached.data, _idle: true });
|
|
1860
|
+
return res.json({ batch_number: null, items: [], summary: "", complete: false, _idle: true });
|
|
1861
|
+
}
|
|
1807
1862
|
const batchTTL = adaptiveTTL(BATCH_PROGRESS_TTL_MS);
|
|
1808
1863
|
if (cached && Date.now() - cached.ts < batchTTL) {
|
|
1809
1864
|
return res.json(cached.data);
|
|
@@ -2202,6 +2257,12 @@ router.post("/api/setup", (req, res) => {
|
|
|
2202
2257
|
ensureSecureDir(dir);
|
|
2203
2258
|
writeConfig(cfg);
|
|
2204
2259
|
|
|
2260
|
+
// #775: initialize file-chat for the new project so the first
|
|
2261
|
+
// chat send doesn't error with "Project not initialized" before
|
|
2262
|
+
// the next server restart. Boot init only runs for projects
|
|
2263
|
+
// already in config.json at startup.
|
|
2264
|
+
fileChat.initProject(id);
|
|
2265
|
+
|
|
2205
2266
|
// Batch 25 / #204: seed the per-project OVERNIGHT-QUEUE.md at
|
|
2206
2267
|
// ~/.quadwork/{id}/OVERNIGHT-QUEUE.md.
|
|
2207
2268
|
writeOvernightQueueFileSafe(id, name || id, repo);
|
|
@@ -2436,7 +2497,7 @@ router.post("/api/telegram", async (req, res) => {
|
|
|
2436
2497
|
try {
|
|
2437
2498
|
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
2438
2499
|
const qwPort = cfg.port || 8400;
|
|
2439
|
-
telegramBridge.start(projectId, tg.bot_token, tg.chat_id, qwPort);
|
|
2500
|
+
await telegramBridge.start(projectId, tg.bot_token, tg.chat_id, qwPort);
|
|
2440
2501
|
emitSystemMessage(projectId, "Telegram bridge connected");
|
|
2441
2502
|
return res.json({ ok: true, running: true });
|
|
2442
2503
|
} catch (err) {
|
|
@@ -96,13 +96,17 @@ When the operator asks you in chat to start a task or batch:
|
|
|
96
96
|
|
|
97
97
|
When you move a batch to Done, **preserve its `Batch: N` line** so the next batch's number computation stays correct.
|
|
98
98
|
3. Reply in chat to confirm what you wrote to the queue file (issue numbers + which section).
|
|
99
|
-
4. **Tell the operator the queue is ready
|
|
99
|
+
4. **Tell the operator the queue is ready.** Send a chat message like:
|
|
100
100
|
|
|
101
|
-
>
|
|
101
|
+
> Batch N is ready with tickets #X, #Y, #Z. Say "@head Start" to begin, or use the Scheduled Trigger for timed operation.
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
5. **
|
|
105
|
-
|
|
103
|
+
Always send this after step 3, even if the operator only asked for a single ticket.
|
|
104
|
+
5. **Start assigning when EITHER:**
|
|
105
|
+
- The operator says "Start", "Go", "Begin", or similar in chat addressed to `@head`
|
|
106
|
+
- The Scheduled Trigger fires
|
|
107
|
+
|
|
108
|
+
Do NOT require the Scheduled Trigger — the operator's direct chat command is sufficient. Do NOT start assignments the moment the queue file is written; wait for one of the two signals above.
|
|
109
|
+
6. Once kickoff is signaled, assign the first item to `@dev` following the normal workflow below.
|
|
106
110
|
|
|
107
111
|
### After each merge
|
|
108
112
|
1. Move the merged item from **Active Batch** to **Done** in `OVERNIGHT-QUEUE.md`.
|