quadwork 1.15.0 → 1.16.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/README.md +1 -0
- package/bin/quadwork.js +22 -0
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +1 -1
- package/out/__next._full.txt +2 -2
- 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/0-w6bd-n2t2n~.css +2 -0
- package/out/_next/static/chunks/{0crp2e6jp_uau.js → 0a-o_h2xhfjpa.js} +19 -15
- package/out/_next/static/chunks/{136v4hoh5wgyv.js → 0b0p-uvdx~p32.js} +1 -1
- package/out/_next/static/chunks/0ocyu-i-3tr3t.js +1 -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 +2 -2
- 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 +3 -3
- 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 +2 -2
- package/out/settings/__next.settings.txt +1 -1
- package/out/settings.html +1 -1
- package/out/settings.txt +3 -3
- 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/index.js +251 -27
- package/server/install-agentchattr.js +28 -0
- package/server/install-agentchattr.patchCrashTimeout.test.js +71 -0
- package/templates/seeds/butler.AGENTS.md +425 -0
- package/out/_next/static/chunks/0khv6othabbrd.js +0 -1
- package/out/_next/static/chunks/0yjmb1lbyzaa1.css +0 -2
- /package/out/_next/static/{yi31oGNueSNboTtRFQmfN → TYz7xYDKn1qut8gR2RrXw}/_buildManifest.js +0 -0
- /package/out/_next/static/{yi31oGNueSNboTtRFQmfN → TYz7xYDKn1qut8gR2RrXw}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{yi31oGNueSNboTtRFQmfN → TYz7xYDKn1qut8gR2RrXw}/_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.0.q-h669a_dqa.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/chunks/0yjmb1lbyzaa1.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/0n~dq4kpx9xxx.js" async=""></script><script src="/_next/static/chunks/0zfotsowwll1x.js" async=""></script><script src="/_next/static/chunks/0pqt~8bl3ukh4.js" async=""></script><script src="/_next/static/chunks/turbopack-0qm-e3ifrz~2u.js" async=""></script><script src="/_next/static/chunks/0m48m-8no_mew.js" async=""></script><script src="/_next/static/chunks/0d3shmwh5_nmn.js" async=""></script><script src="/_next/static/chunks/0uz5svjlo9dwl.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/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"LocaleProvider\"]\n3:I[43688,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n4:I[26704,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n5:I[22140,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n6:I[39756,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n7:I[37457,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n8:I[94810,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\",\"/_next/static/chunks/0uz5svjlo9dwl.js\"],\"default\"]\n9:I[97367,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"OutletBoundary\"]\na:\"$Sreact.suspense\"\nd:I[97367,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"ViewportBoundary\"]\nf:I[97367,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"MetadataBoundary\"]\n11:I[68027,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/0yjmb1lbyzaa1.css\",\"style\"]\n:HL[\"/_next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.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/0yjmb1lbyzaa1.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0m48m-8no_mew.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/0uz5svjlo9dwl.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/0yjmb1lbyzaa1.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"yi31oGNueSNboTtRFQmfN\"}\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/0m48m-8no_mew.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.0.q-h669a_dqa.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/chunks/0-w6bd-n2t2n~.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/0n~dq4kpx9xxx.js" async=""></script><script src="/_next/static/chunks/0zfotsowwll1x.js" async=""></script><script src="/_next/static/chunks/0pqt~8bl3ukh4.js" async=""></script><script src="/_next/static/chunks/turbopack-0qm-e3ifrz~2u.js" async=""></script><script src="/_next/static/chunks/0m48m-8no_mew.js" async=""></script><script src="/_next/static/chunks/0d3shmwh5_nmn.js" async=""></script><script src="/_next/static/chunks/0uz5svjlo9dwl.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/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"LocaleProvider\"]\n3:I[43688,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n4:I[26704,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n5:I[22140,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n6:I[39756,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n7:I[37457,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\"]\n8:I[94810,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\",\"/_next/static/chunks/0uz5svjlo9dwl.js\"],\"default\"]\n9:I[97367,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"OutletBoundary\"]\na:\"$Sreact.suspense\"\nd:I[97367,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"ViewportBoundary\"]\nf:I[97367,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"MetadataBoundary\"]\n11:I[68027,[\"/_next/static/chunks/0m48m-8no_mew.js\",\"/_next/static/chunks/0d3shmwh5_nmn.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/0-w6bd-n2t2n~.css\",\"style\"]\n:HL[\"/_next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.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/0-w6bd-n2t2n~.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0m48m-8no_mew.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/0uz5svjlo9dwl.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/0-w6bd-n2t2n~.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"s\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"TYz7xYDKn1qut8gR2RrXw\"}\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/0m48m-8no_mew.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/0m48m-8no_mew.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"ViewportBoundary"]
|
|
12
12
|
f:I[97367,["/_next/static/chunks/0m48m-8no_mew.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"MetadataBoundary"]
|
|
13
13
|
11:I[68027,["/_next/static/chunks/0m48m-8no_mew.js","/_next/static/chunks/0d3shmwh5_nmn.js"],"default",1]
|
|
14
|
-
:HL["/_next/static/chunks/
|
|
14
|
+
:HL["/_next/static/chunks/0-w6bd-n2t2n~.css","style"]
|
|
15
15
|
:HL["/_next/static/media/797e433ab948586e-s.p.0.q-h669a_dqa.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/0-w6bd-n2t2n~.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/_next/static/chunks/0m48m-8no_mew.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/0uz5svjlo9dwl.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/0-w6bd-n2t2n~.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"TYz7xYDKn1qut8gR2RrXw"}
|
|
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
package/server/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
projectAgentchattrConfigPath,
|
|
15
15
|
} = routes;
|
|
16
16
|
const { waitForAgentChattrReady, registerAgent, registerAgentWithRetry, deregisterAgent, startHeartbeat, stopHeartbeat } = require("./agentchattr-registry");
|
|
17
|
-
const { patchAgentchattrCss } = require("./install-agentchattr");
|
|
17
|
+
const { patchAgentchattrCss, patchCrashTimeout } = require("./install-agentchattr");
|
|
18
18
|
const { startQueueWatcher, stopQueueWatcher } = require("./queue-watcher");
|
|
19
19
|
|
|
20
20
|
const net = require("net");
|
|
@@ -154,6 +154,9 @@ const agentSessions = new Map();
|
|
|
154
154
|
// AgentChattr server processes — per-project (key = projectId)
|
|
155
155
|
const chattrProcesses = new Map();
|
|
156
156
|
|
|
157
|
+
// #631: Butler session — single global PTY (not per-project, no AC integration)
|
|
158
|
+
let butlerSession = { term: null, ws: null, state: "stopped", error: null, scrollback: Buffer.alloc(0) };
|
|
159
|
+
|
|
157
160
|
// --- MCP auth proxy for Codex (can't pass headers via -c flag) ---
|
|
158
161
|
// Maps "project/agent" → { server, port }
|
|
159
162
|
const mcpProxies = new Map();
|
|
@@ -1169,6 +1172,8 @@ async function handleAgentChattr(req, res) {
|
|
|
1169
1172
|
const pullResult = execFileSync("git", ["pull"], { cwd: acDir, encoding: "utf-8", timeout: 30000, stdio: "pipe" }).trim();
|
|
1170
1173
|
// #388: re-apply sender-overflow CSS patch after git pull
|
|
1171
1174
|
patchAgentchattrCss(acDir);
|
|
1175
|
+
// #629: re-apply crash timeout patch after git pull (pull may revert app.py)
|
|
1176
|
+
patchCrashTimeout(acDir);
|
|
1172
1177
|
const venvPython = path.join(acDir, ".venv", "bin", "python");
|
|
1173
1178
|
let pipResult = "";
|
|
1174
1179
|
const reqFile = path.join(acDir, "requirements.txt");
|
|
@@ -1384,6 +1389,127 @@ app.post("/api/agents/:project/:agent/write", (req, res) => {
|
|
|
1384
1389
|
}
|
|
1385
1390
|
});
|
|
1386
1391
|
|
|
1392
|
+
// --- Butler agent (#631) ---
|
|
1393
|
+
|
|
1394
|
+
function spawnButlerPty() {
|
|
1395
|
+
if (butlerSession.term) return { ok: true, pid: butlerSession.term.pid };
|
|
1396
|
+
|
|
1397
|
+
try {
|
|
1398
|
+
const cfg = readConfig();
|
|
1399
|
+
const butlerCfg = cfg.butler || {};
|
|
1400
|
+
const cwdRaw = butlerCfg.cwd || "~/docs/";
|
|
1401
|
+
const docsDir = cwdRaw.startsWith("~/") ? path.join(os.homedir(), cwdRaw.slice(2)) : cwdRaw;
|
|
1402
|
+
if (!fs.existsSync(docsDir)) {
|
|
1403
|
+
fs.mkdirSync(docsDir, { recursive: true, mode: 0o700 });
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
const command = butlerCfg.command || "claude";
|
|
1407
|
+
const args = [];
|
|
1408
|
+
if (butlerCfg.model) args.push("--model", butlerCfg.model);
|
|
1409
|
+
if (butlerCfg.auto_approve) args.push("--dangerously-skip-permissions");
|
|
1410
|
+
|
|
1411
|
+
const seedPath = path.join(__dirname, "..", "templates", "seeds", "butler.AGENTS.md");
|
|
1412
|
+
if (fs.existsSync(seedPath)) {
|
|
1413
|
+
const agentsPath = path.join(docsDir, "AGENTS.md");
|
|
1414
|
+
if (!fs.existsSync(agentsPath)) {
|
|
1415
|
+
fs.copyFileSync(seedPath, agentsPath);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// #635: seed README.md explaining ~/docs/ folder purpose
|
|
1420
|
+
const readmePath = path.join(docsDir, "README.md");
|
|
1421
|
+
if (!fs.existsSync(readmePath)) {
|
|
1422
|
+
fs.writeFileSync(readmePath, [
|
|
1423
|
+
"# ~/docs/",
|
|
1424
|
+
"",
|
|
1425
|
+
"Butler's working directory — cross-project operator notes and artifacts.",
|
|
1426
|
+
"Not git-tracked; operator-local.",
|
|
1427
|
+
"",
|
|
1428
|
+
"## File types",
|
|
1429
|
+
"",
|
|
1430
|
+
"| Prefix | Purpose |",
|
|
1431
|
+
"|--------|---------|",
|
|
1432
|
+
"| `PROPOSAL-<name>.md` | Feature proposals with phases and operator gates |",
|
|
1433
|
+
"| `REVIEW-<batch>.md` | PR review summaries |",
|
|
1434
|
+
"| `INFO-<topic>.md` | Research notes |",
|
|
1435
|
+
"| `PROGRESS-<project>.md` | Per-project progress (one file per project) |",
|
|
1436
|
+
"",
|
|
1437
|
+
].join("\n"));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const term = pty.spawn(command, args, {
|
|
1441
|
+
name: "xterm-256color",
|
|
1442
|
+
cols: 120,
|
|
1443
|
+
rows: 30,
|
|
1444
|
+
cwd: docsDir,
|
|
1445
|
+
env: { ...process.env },
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
butlerSession = {
|
|
1449
|
+
term,
|
|
1450
|
+
ws: null,
|
|
1451
|
+
state: "running",
|
|
1452
|
+
error: null,
|
|
1453
|
+
scrollback: Buffer.alloc(0),
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
const SCROLLBACK_SIZE = 64 * 1024;
|
|
1457
|
+
term.onData((data) => {
|
|
1458
|
+
const chunk = Buffer.from(data);
|
|
1459
|
+
butlerSession.scrollback = Buffer.concat([butlerSession.scrollback, chunk]);
|
|
1460
|
+
if (butlerSession.scrollback.length > SCROLLBACK_SIZE) {
|
|
1461
|
+
butlerSession.scrollback = butlerSession.scrollback.slice(-SCROLLBACK_SIZE);
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
term.onExit(({ exitCode }) => {
|
|
1466
|
+
if (butlerSession.term === term) {
|
|
1467
|
+
butlerSession.state = "stopped";
|
|
1468
|
+
butlerSession.error = exitCode ? `exit:${exitCode}` : null;
|
|
1469
|
+
butlerSession.term = null;
|
|
1470
|
+
if (butlerSession.ws && butlerSession.ws.readyState <= 1) {
|
|
1471
|
+
butlerSession.ws.close(1000, `exited:${exitCode}`);
|
|
1472
|
+
}
|
|
1473
|
+
butlerSession.ws = null;
|
|
1474
|
+
}
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
console.log(`[butler] spawned (PID: ${term.pid}, cwd: ${docsDir})`);
|
|
1478
|
+
return { ok: true, pid: term.pid };
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
butlerSession = { term: null, ws: null, state: "error", error: err.message, scrollback: Buffer.alloc(0) };
|
|
1481
|
+
return { ok: false, error: err.message };
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
function stopButlerPty() {
|
|
1486
|
+
if (butlerSession.term) {
|
|
1487
|
+
try { butlerSession.term.kill(); } catch {}
|
|
1488
|
+
butlerSession.term = null;
|
|
1489
|
+
}
|
|
1490
|
+
if (butlerSession.ws && butlerSession.ws.readyState <= 1) {
|
|
1491
|
+
butlerSession.ws.close(1000, "stopped");
|
|
1492
|
+
}
|
|
1493
|
+
butlerSession = { term: null, ws: null, state: "stopped", error: null, scrollback: Buffer.alloc(0) };
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
app.post("/api/butler/start", (_req, res) => {
|
|
1497
|
+
const result = spawnButlerPty();
|
|
1498
|
+
res.json(result);
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
app.post("/api/butler/stop", (_req, res) => {
|
|
1502
|
+
stopButlerPty();
|
|
1503
|
+
res.json({ ok: true });
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
app.get("/api/butler/status", (_req, res) => {
|
|
1507
|
+
res.json({
|
|
1508
|
+
running: butlerSession.state === "running" && !!butlerSession.term,
|
|
1509
|
+
pid: butlerSession.term ? butlerSession.term.pid : null,
|
|
1510
|
+
});
|
|
1511
|
+
});
|
|
1512
|
+
|
|
1387
1513
|
// --- Scheduled Triggers ---
|
|
1388
1514
|
|
|
1389
1515
|
const triggers = new Map();
|
|
@@ -1917,6 +2043,64 @@ wss.on("connection", async (ws, req) => {
|
|
|
1917
2043
|
});
|
|
1918
2044
|
});
|
|
1919
2045
|
|
|
2046
|
+
// --- Butler WebSocket (#631) ---
|
|
2047
|
+
const wssButler = new WebSocketServer({ server, path: "/ws/butler" });
|
|
2048
|
+
|
|
2049
|
+
wssButler.on("connection", async (ws) => {
|
|
2050
|
+
if (!butlerSession.term) {
|
|
2051
|
+
const result = spawnButlerPty();
|
|
2052
|
+
if (!result.ok) {
|
|
2053
|
+
ws.close(1011, "pty-spawn-failed");
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
if (butlerSession.ws && butlerSession.ws !== ws && butlerSession.ws.readyState <= 1) {
|
|
2059
|
+
butlerSession.ws.close(1000, "replaced");
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
butlerSession.ws = ws;
|
|
2063
|
+
|
|
2064
|
+
const dataHandler = butlerSession.term.onData((data) => {
|
|
2065
|
+
if (ws.readyState === ws.OPEN) {
|
|
2066
|
+
ws.send(scrubSecrets(data));
|
|
2067
|
+
}
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
ws.on("message", (msg) => {
|
|
2071
|
+
if (!butlerSession.term) return;
|
|
2072
|
+
const str = msg.toString();
|
|
2073
|
+
try {
|
|
2074
|
+
const parsed = JSON.parse(str);
|
|
2075
|
+
if (parsed.type === "resize") {
|
|
2076
|
+
if (typeof parsed.cols === "number" && typeof parsed.rows === "number" &&
|
|
2077
|
+
Number.isFinite(parsed.cols) && Number.isFinite(parsed.rows) &&
|
|
2078
|
+
parsed.cols >= 1 && parsed.cols <= 500 &&
|
|
2079
|
+
parsed.rows >= 1 && parsed.rows <= 500) {
|
|
2080
|
+
butlerSession.term.resize(parsed.cols, parsed.rows);
|
|
2081
|
+
}
|
|
2082
|
+
return;
|
|
2083
|
+
}
|
|
2084
|
+
if (parsed.type === "replay") {
|
|
2085
|
+
if (butlerSession.scrollback && butlerSession.scrollback.length > 0) {
|
|
2086
|
+
ws.send(scrubScrollback(butlerSession.scrollback));
|
|
2087
|
+
} else {
|
|
2088
|
+
ws.send(`\x1b[2m[butler online — waiting for input]\x1b[0m\r\n`);
|
|
2089
|
+
}
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
} catch {}
|
|
2093
|
+
butlerSession.term.write(str);
|
|
2094
|
+
});
|
|
2095
|
+
|
|
2096
|
+
ws.on("close", () => {
|
|
2097
|
+
dataHandler.dispose();
|
|
2098
|
+
if (butlerSession.ws === ws) {
|
|
2099
|
+
butlerSession.ws = null;
|
|
2100
|
+
}
|
|
2101
|
+
});
|
|
2102
|
+
});
|
|
2103
|
+
|
|
1920
2104
|
// --- Trigger auto-start from config ---
|
|
1921
2105
|
|
|
1922
2106
|
function syncTriggersFromConfig() {
|
|
@@ -2330,29 +2514,39 @@ server.listen(PORT, "127.0.0.1", async () => {
|
|
|
2330
2514
|
// #457: migrate bridge slugs in AC configs on startup.
|
|
2331
2515
|
// Renames [agents.discord-bridge] → [agents.dc] and
|
|
2332
2516
|
// [agents.telegram-bridge] → [agents.tg] so bridges register
|
|
2333
|
-
// under the short slug. Restarts AC for
|
|
2517
|
+
// under the short slug. Restarts AC ONLY for slug renames (not
|
|
2518
|
+
// fresh block appends) — #616: script-only patches should not
|
|
2519
|
+
// trigger AC restarts which kill bridge registration.
|
|
2334
2520
|
for (const p of (startupCfg.projects || [])) {
|
|
2335
2521
|
const acPath = projectAgentchattrConfigPath(p.id);
|
|
2336
2522
|
if (!fs.existsSync(acPath)) continue;
|
|
2337
2523
|
try {
|
|
2338
2524
|
const before = fs.readFileSync(acPath, "utf-8");
|
|
2525
|
+
// Track whether an actual slug RENAME happened (old → new).
|
|
2526
|
+
// Fresh block appends don't need an AC restart — AC picks them
|
|
2527
|
+
// up on its next natural start.
|
|
2528
|
+
const hadOldDc = /^\[agents\.discord-bridge\]\s*$/m.test(before);
|
|
2529
|
+
const hadOldTg = /^\[agents\.telegram-bridge\]\s*$/m.test(before);
|
|
2339
2530
|
const dc = patchAgentchattrConfigForDiscordBridge(before);
|
|
2340
2531
|
const tg = patchAgentchattrConfigForTelegramBridge(dc.text);
|
|
2341
2532
|
if (dc.changed || tg.changed) {
|
|
2342
2533
|
fs.writeFileSync(acPath, tg.text);
|
|
2343
2534
|
console.log(`[bridge-migrate] ${p.id}: migrated AC config slugs`);
|
|
2344
|
-
//
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2535
|
+
// Only restart AC when a slug was actually RENAMED — not when
|
|
2536
|
+
// a fresh block was appended (#616).
|
|
2537
|
+
if (hadOldDc || hadOldTg) {
|
|
2538
|
+
setTimeout(async () => {
|
|
2539
|
+
try {
|
|
2540
|
+
const r = await fetch(`http://127.0.0.1:${PORT}/api/agentchattr/${encodeURIComponent(p.id)}/restart`, {
|
|
2541
|
+
method: "POST",
|
|
2542
|
+
});
|
|
2543
|
+
if (r.ok) console.log(`[bridge-migrate] ${p.id}: restarted AC`);
|
|
2544
|
+
else console.warn(`[bridge-migrate] ${p.id}: AC restart returned ${r.status}`);
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
console.warn(`[bridge-migrate] ${p.id}: AC restart failed: ${err.message || err}`);
|
|
2547
|
+
}
|
|
2548
|
+
}, 3000);
|
|
2549
|
+
}
|
|
2356
2550
|
}
|
|
2357
2551
|
} catch {}
|
|
2358
2552
|
}
|
|
@@ -2496,22 +2690,19 @@ server.listen(PORT, "127.0.0.1", async () => {
|
|
|
2496
2690
|
console.warn(`[ghost-fix] ${p.id}: failed to patch app.py: ${err.message}`);
|
|
2497
2691
|
}
|
|
2498
2692
|
}
|
|
2499
|
-
// #502: increase crash timeout from 15s to 120s
|
|
2693
|
+
// #502 + #629: increase crash timeout from 15s to 120s.
|
|
2694
|
+
// Uses the shared patchCrashTimeout() from install-agentchattr.js.
|
|
2695
|
+
// For existing installs where AC is already running, the on-disk
|
|
2696
|
+
// patch alone is useless (Python caches module-level values at import).
|
|
2697
|
+
// Flag the project for AC restart so the running process picks it up.
|
|
2500
2698
|
if (fs.existsSync(appPath)) {
|
|
2501
2699
|
try {
|
|
2502
|
-
|
|
2700
|
+
const app = fs.readFileSync(appPath, "utf-8");
|
|
2503
2701
|
if (app.includes("_CRASH_TIMEOUT = 15")) {
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
);
|
|
2508
|
-
// Fix the misleading comment too
|
|
2509
|
-
app = app.replace(
|
|
2510
|
-
"# Crash timeout: if a wrapper hasn't heartbeated for 60s,\n",
|
|
2511
|
-
"# Crash timeout: if a wrapper hasn't heartbeated for 120s,\n",
|
|
2512
|
-
);
|
|
2513
|
-
fs.writeFileSync(appPath, app);
|
|
2514
|
-
console.log(`[idle-fix] ${p.id}: increased crash timeout to 120s (#502)`);
|
|
2702
|
+
patchCrashTimeout(acDir);
|
|
2703
|
+
console.log(`[idle-fix] ${p.id}: crash timeout patched on disk — AC restart required for running process to observe it (#629)`);
|
|
2704
|
+
if (!startupCfg._acRestartNeeded) startupCfg._acRestartNeeded = [];
|
|
2705
|
+
startupCfg._acRestartNeeded.push(p.id);
|
|
2515
2706
|
}
|
|
2516
2707
|
} catch (err) {
|
|
2517
2708
|
console.warn(`[idle-fix] ${p.id}: failed to patch app.py crash timeout: ${err.message}`);
|
|
@@ -2552,6 +2743,37 @@ server.listen(PORT, "127.0.0.1", async () => {
|
|
|
2552
2743
|
console.warn(`[#596] ${p.id}: config.toml migration failed: ${err.message}`);
|
|
2553
2744
|
}
|
|
2554
2745
|
}
|
|
2746
|
+
// #629: restart AC for projects where idle-fix patched the on-disk file
|
|
2747
|
+
// so the running Python process picks up _CRASH_TIMEOUT = 120.
|
|
2748
|
+
// Use port-alive check instead of chattrProcesses — AC may be running
|
|
2749
|
+
// from a previous QuadWork instance (tracked with process: null).
|
|
2750
|
+
if (startupCfg._acRestartNeeded) {
|
|
2751
|
+
for (const projectId of startupCfg._acRestartNeeded) {
|
|
2752
|
+
const { url } = resolveProjectChattr(projectId);
|
|
2753
|
+
const portMatch = url.match(/:(\d+)/);
|
|
2754
|
+
const port = portMatch ? parseInt(portMatch[1], 10) : 8300;
|
|
2755
|
+
isPortAlive(port).then((alive) => {
|
|
2756
|
+
if (!alive) return;
|
|
2757
|
+
console.log(`[idle-fix] ${projectId}: restarting AC (port ${port}) so running process observes _CRASH_TIMEOUT = 120 (#629)`);
|
|
2758
|
+
return fetch(`http://127.0.0.1:${PORT}/api/agentchattr/${encodeURIComponent(projectId)}/restart`, {
|
|
2759
|
+
method: "POST",
|
|
2760
|
+
headers: { "Content-Type": "application/json" },
|
|
2761
|
+
body: JSON.stringify({ action: "restart" }),
|
|
2762
|
+
});
|
|
2763
|
+
}).then((r) => {
|
|
2764
|
+
if (r && r.ok) console.log(`[idle-fix] ${projectId}: AC restarted successfully`);
|
|
2765
|
+
else if (r) console.warn(`[idle-fix] ${projectId}: AC restart returned ${r.status}`);
|
|
2766
|
+
}).catch((err) => {
|
|
2767
|
+
console.warn(`[idle-fix] ${projectId}: AC restart failed: ${err.message}`);
|
|
2768
|
+
});
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
// #631 + #632: auto-start Butler if enabled + auto_start
|
|
2772
|
+
if (startupCfg.butler && startupCfg.butler.enabled && startupCfg.butler.auto_start) {
|
|
2773
|
+
const result = spawnButlerPty();
|
|
2774
|
+
if (result.ok) console.log(`[butler] auto-started (PID: ${result.pid})`);
|
|
2775
|
+
else console.warn(`[butler] auto-start failed: ${result.error}`);
|
|
2776
|
+
}
|
|
2555
2777
|
// #416: start the AC health monitor
|
|
2556
2778
|
startAcHealthMonitor();
|
|
2557
2779
|
});
|
|
@@ -2573,6 +2795,8 @@ function shutdownChattrProcesses() {
|
|
|
2573
2795
|
}
|
|
2574
2796
|
}
|
|
2575
2797
|
chattrProcesses.clear();
|
|
2798
|
+
// #631: stop Butler PTY on shutdown
|
|
2799
|
+
stopButlerPty();
|
|
2576
2800
|
}
|
|
2577
2801
|
|
|
2578
2802
|
module.exports = { shutdownChattrProcesses };
|
|
@@ -195,6 +195,8 @@ function _installAgentChattrLocked(dir, setError) {
|
|
|
195
195
|
}
|
|
196
196
|
// #388: patch sender-column overflow CSS after clone/install
|
|
197
197
|
patchAgentchattrCss(dir);
|
|
198
|
+
// #629: patch crash timeout before AC's first import
|
|
199
|
+
patchCrashTimeout(dir);
|
|
198
200
|
return dir;
|
|
199
201
|
}
|
|
200
202
|
|
|
@@ -260,10 +262,36 @@ function patchAgentchattrCss(dir) {
|
|
|
260
262
|
}
|
|
261
263
|
}
|
|
262
264
|
|
|
265
|
+
/**
|
|
266
|
+
* #629: Patch AC's crash timeout from 15s to 120s.
|
|
267
|
+
* Must run at clone time (before any `python run.py`) so the first
|
|
268
|
+
* AC process imports the patched value. Idempotent.
|
|
269
|
+
*/
|
|
270
|
+
function patchCrashTimeout(dir) {
|
|
271
|
+
if (!dir) return;
|
|
272
|
+
const appPath = path.join(dir, "app.py");
|
|
273
|
+
if (!fs.existsSync(appPath)) return;
|
|
274
|
+
try {
|
|
275
|
+
let app = fs.readFileSync(appPath, "utf-8");
|
|
276
|
+
if (app.includes("_CRASH_TIMEOUT = 15")) {
|
|
277
|
+
app = app.replace("_CRASH_TIMEOUT = 15", "_CRASH_TIMEOUT = 120");
|
|
278
|
+
app = app.replace(
|
|
279
|
+
"# Crash timeout: if a wrapper hasn't heartbeated for 60s,\n",
|
|
280
|
+
"# Crash timeout: if a wrapper hasn't heartbeated for 120s,\n",
|
|
281
|
+
);
|
|
282
|
+
fs.writeFileSync(appPath, app);
|
|
283
|
+
console.log(`[idle-fix] patched crash timeout to 120s at clone time (#629): ${dir}`);
|
|
284
|
+
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.warn(`[idle-fix] failed to patch crash timeout in ${appPath}: ${err.message}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
263
290
|
module.exports = {
|
|
264
291
|
AGENTCHATTR_REPO,
|
|
265
292
|
findAgentChattr,
|
|
266
293
|
installAgentChattr,
|
|
267
294
|
chattrSpawnArgs,
|
|
268
295
|
patchAgentchattrCss,
|
|
296
|
+
patchCrashTimeout,
|
|
269
297
|
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// #629: patchCrashTimeout tests. Plain node:assert — run with
|
|
2
|
+
// `node server/install-agentchattr.patchCrashTimeout.test.js`.
|
|
3
|
+
|
|
4
|
+
const assert = require("node:assert/strict");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const { patchCrashTimeout } = require("./install-agentchattr");
|
|
9
|
+
|
|
10
|
+
const UNPATCHED_APP_PY = [
|
|
11
|
+
"import time",
|
|
12
|
+
"",
|
|
13
|
+
"# Crash timeout: if a wrapper hasn't heartbeated for 60s,",
|
|
14
|
+
"# consider it dead and deregister.",
|
|
15
|
+
"_CRASH_TIMEOUT = 15",
|
|
16
|
+
"",
|
|
17
|
+
"def check_heartbeats():",
|
|
18
|
+
" now = time.time()",
|
|
19
|
+
" for name, last_seen in list(_heartbeats.items()):",
|
|
20
|
+
" if last_seen > 0 and now - last_seen > _CRASH_TIMEOUT:",
|
|
21
|
+
' log.info(f"Crash timeout: deregistering {name} (no heartbeat for {_CRASH_TIMEOUT}s)")',
|
|
22
|
+
].join("\n");
|
|
23
|
+
|
|
24
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "qw-test-629-"));
|
|
25
|
+
|
|
26
|
+
function setup(content) {
|
|
27
|
+
const dir = fs.mkdtempSync(path.join(tmpDir, "ac-"));
|
|
28
|
+
if (content !== undefined) {
|
|
29
|
+
fs.writeFileSync(path.join(dir, "app.py"), content);
|
|
30
|
+
}
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 1) Patches _CRASH_TIMEOUT from 15 to 120
|
|
35
|
+
{
|
|
36
|
+
const dir = setup(UNPATCHED_APP_PY);
|
|
37
|
+
patchCrashTimeout(dir);
|
|
38
|
+
const result = fs.readFileSync(path.join(dir, "app.py"), "utf-8");
|
|
39
|
+
assert.ok(result.includes("_CRASH_TIMEOUT = 120"), "timeout patched to 120");
|
|
40
|
+
assert.ok(!result.includes("_CRASH_TIMEOUT = 15"), "old value removed");
|
|
41
|
+
assert.ok(result.includes("heartbeated for 120s"), "comment updated");
|
|
42
|
+
assert.ok(!result.includes("heartbeated for 60s"), "old comment removed");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 2) Idempotent — already-patched file is untouched
|
|
46
|
+
{
|
|
47
|
+
const dir = setup(UNPATCHED_APP_PY.replace("_CRASH_TIMEOUT = 15", "_CRASH_TIMEOUT = 120")
|
|
48
|
+
.replace("heartbeated for 60s", "heartbeated for 120s"));
|
|
49
|
+
const before = fs.readFileSync(path.join(dir, "app.py"), "utf-8");
|
|
50
|
+
patchCrashTimeout(dir);
|
|
51
|
+
const after = fs.readFileSync(path.join(dir, "app.py"), "utf-8");
|
|
52
|
+
assert.equal(before, after, "already-patched file unchanged");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3) No app.py — no crash
|
|
56
|
+
{
|
|
57
|
+
const dir = setup();
|
|
58
|
+
patchCrashTimeout(dir);
|
|
59
|
+
assert.ok(!fs.existsSync(path.join(dir, "app.py")), "no file created");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 4) Null/undefined dir — no crash
|
|
63
|
+
{
|
|
64
|
+
patchCrashTimeout(null);
|
|
65
|
+
patchCrashTimeout(undefined);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Cleanup
|
|
69
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
70
|
+
|
|
71
|
+
console.log("patchCrashTimeout: all tests passed");
|