{"_id":"@bpinternal/overwatch","_rev":"11-f3370a27d4792394c2ddef5a9e04d8ec","name":"@bpinternal/overwatch","dist-tags":{"latest":"0.3.7"},"versions":{"0.1.0":{"name":"@bpinternal/overwatch","version":"0.1.0","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.1.0","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/overwatch#readme","bugs":{"url":"https://github.com/botpress/overwatch/issues"},"dist":{"shasum":"2b75fc03d15b718d60098b4dd100d7838e27bc18","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.1.0.tgz","fileCount":81,"integrity":"sha512-knLWDlElAPUS0fMF0PzGA9mwdf5ohMsC6lyB+nuKI4M3kWBVOavH+Jngjk4hjkN0EC6GJTtkfayKp9fASohpxg==","signatures":[{"sig":"MEYCIQCs247DctUKlbXl6WRwt68m+9aBZgdog+B199EYWm2cWgIhAOFoX+V9WJm5+3+9jmYesUQUHjx0CtreD8aq6Ll9BopW","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":280228},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18","pnpm":">=9"},"gitHead":"33f46443c28e5769f1242106ed1fd6b415dd35f4","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},"repository":{"url":"git+https://github.com/botpress/overwatch.git","type":"git"},"_npmVersion":"10.9.3","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"22.19.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.1.0_1784222807615_0.5724859204856061","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bpinternal/overwatch","version":"0.2.0","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.2.0","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"d2159e31965e195d5365f9557e41024f973f2a41","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.2.0.tgz","fileCount":72,"integrity":"sha512-RWZkYk3qHbz8TnuIK+RFuR5Rp4IB9yN+K9qE4JHJDy1psD/Ehw0Ctu6D2ckOt/YBc9Qm+nDx3DVQV2cPaF7eoQ==","signatures":[{"sig":"MEQCIDrXYhExWL4WaaXpph0FFqjsSnDvl7IBUc9Uj/hz2vprAiBlKnSVmnBSD6Nyfy7b6rQzAT0BjMNKFVE9n3EgDtLbIw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":242384},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"cf7edee5c3de12bfcd8c6820d89bd714fde07db6","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.2.0_1784574547528_0.06526825850231632","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@bpinternal/overwatch","version":"0.3.0","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.0","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"bc844528ebfaa66b75e2ca168deed6e1a38d32c0","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.0.tgz","fileCount":75,"integrity":"sha512-Tj7lbrwbLE9+eRC0XOuJFQoK2LRNyEYyASDmmlds4DDy9CRbGqCk+1lSmXuQ0TWye1mcRcMtitM1sv4A29c6kA==","signatures":[{"sig":"MEQCIHpt9I/K7jvWFFEpInMb3X3b+JSEWN9QiQCstXxp1WrtAiAhXGkmfP0Jochi8vq/a5hVUiyONX+zhKaL2prFzn24Lw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":269244},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"9e5567aacedf5318b5e1809beaab6f5580012336","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.0_1784656945280_0.680588709168245","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@bpinternal/overwatch","version":"0.3.1","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.1","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"692384617abd21402bff51b2b56697bdbf83ad58","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.1.tgz","fileCount":75,"integrity":"sha512-u8uaPt7RJGT70JyYrX/cKtJZb33SmU0yxmcfURdLQ8xXlaMPY1zLQV2rsYYSKQJa2EGnrgs+IAqeYNaAhiU3Ng==","signatures":[{"sig":"MEUCIEqEdd791wFYrCb3ayudCMwi+sWbiKR/qCBHod2oyZPUAiEAw8B2sbYbKCN9g9tmHT2siZX9ntFeRWmnf7Il4NvCPx0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":269671},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"105fe223b508d2ecd2b4e6040a7fa7e5f154f974","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.1_1785428475371_0.5573855803018528","host":"s3://npm-registry-packages-npm-production"}},"0.3.2":{"name":"@bpinternal/overwatch","version":"0.3.2","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.2","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"bef86d04234a03de5c818ab27fd2b501a33aa988","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.2.tgz","fileCount":75,"integrity":"sha512-qVzIyTBuxKiDWv4ORbHDq3dy3faWs8pjBM3c6RLS/WiNJBJRQoHSoqmFTH25E9BjeH0Jvy+GuBZ8KSqdk5DVEQ==","signatures":[{"sig":"MEUCIQCPN0OJuqdue0banlrtKkJryENgVeYeGq49iSCgZjYzTAIgOLDwPZzAXxXTOQjJ2kau+Nx0dpbOFLA4+BcWBIavdic=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":270731},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"f03015a486fa96bfd2142ed1b907994c37aa17af","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.2_1785434178990_0.5491446018979762","host":"s3://npm-registry-packages-npm-production"}},"0.3.3":{"name":"@bpinternal/overwatch","version":"0.3.3","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.3","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"3402f8b0227e10e6919c2e95abb5bc333248a532","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.3.tgz","fileCount":84,"integrity":"sha512-3u+mcQWFHxdj7995cowzxusU0t99hnCI+Myv7l9F/r3dxwyK+PevEGXgAb3BFfUWY5nrcb6JH3i9EkE+xhj0/Q==","signatures":[{"sig":"MEUCIB7/eyBv2Qqumxhl2ng89+ppX8YJF8jmEcGqUYjqIbIhAiEApNGN4HUtgb+gDWUMess8FpeWOzRmQxSvl5N8Fo6fWUA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":300957},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"84883c89f223752da331c95421ee23c79c0a81b6","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@slack/web-api":"^8.0.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.3_1785509051437_0.9402761254990633","host":"s3://npm-registry-packages-npm-production"}},"0.3.4":{"name":"@bpinternal/overwatch","version":"0.3.4","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.4","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"dcbe73936f8b61c290adcee68aa54774432f66bf","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.4.tgz","fileCount":87,"integrity":"sha512-AyN6EvpPrVm6J6q66PHadr3Kv1aJrCLoVQfvhTh+sAVq+j0iHoL3TdeKVcOHKFkEbzfH8UfwaxEqpMmQJU9Zcw==","signatures":[{"sig":"MEQCIGBMhm2HLKL2srkJm31gwAvjd7/0ZzZih+mBFO2ft097AiAbu3WSSuoUCZw1YQqeVepkWn0R7WSLpYglZu0oCaxTMw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":324555},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"79d0f5067bb89b22ea5b343eaae92fc9f9d85de5","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@slack/web-api":"^8.0.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.4_1785768268458_0.4900486603003471","host":"s3://npm-registry-packages-npm-production"}},"0.3.5":{"name":"@bpinternal/overwatch","version":"0.3.5","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.5","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"e88da0fa716fd9725fbc1760e5c583779a85b731","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.5.tgz","fileCount":87,"integrity":"sha512-r5hmzIwML9d/R03gpAWZIw2Pbwko4vJWH8P2FvCSabNUBhNVDY0lkPQTXQUSjqiJ4Yqd28bZ40YjPa2kSHi5AA==","signatures":[{"sig":"MEUCIDBUFEjcTsQIzik8U3ey0e7G9bf4s9R9QPf+vtdOdp14AiEA50SSK8gnuHP/7ou103Fv7Ll710UJoyeIIie7Id8GxHU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":324711},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"91247e37930c834438e53c4111b77694dea040b5","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"octokit":"^5.0.5","commander":"^15.0.0","@daytona/sdk":"^0.195.0","@slack/web-api":"^8.0.0","@octokit/auth-app":"^8.2.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.5_1785783686645_0.5760479511960548","host":"s3://npm-registry-packages-npm-production"}},"0.3.6":{"name":"@bpinternal/overwatch","version":"0.3.6","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.6","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"669ee43e7682715a0a677af6faca1e3d8602f10b","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.6.tgz","fileCount":87,"integrity":"sha512-bYAQB6Fj0uOavGqXfwDatNvh/Yq329O55qFu8h65RBfyb7sSWy1K/ufC3HzE9h8XltGW8BzR37tiyYWQdV+S1Q==","signatures":[{"sig":"MEQCIHFB5eevDWv/9oFtYBJtBAY4GzMz9v0S3fF+4/AY/oRKAiADfULaBj2nZw2jmupXJ2bJH9mZpIe92FvT4LY0DB56uQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":325814},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"f69c01ee8ffb87ed96b87b8615fd98562573dee7","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"commander":"^15.0.0","@daytona/sdk":"^0.195.0","@octokit/core":"^7.0.6","@slack/web-api":"^8.0.0","@octokit/auth-app":"^8.2.0","@octokit/plugin-paginate-rest":"^14.0.0","@octokit/plugin-rest-endpoint-methods":"^17.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.6_1785870349667_0.8515469278540349","host":"s3://npm-registry-packages-npm-production"}},"0.3.7":{"name":"@bpinternal/overwatch","version":"0.3.7","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"license":"MIT","_id":"@bpinternal/overwatch@0.3.7","maintainers":[{"name":"allardy","email":"yann.allard1@gmail.com"},{"name":"michael.masson","email":"michael.masson@botpress.com"},{"name":"franklevasseur","email":"francois_levasseur@hotmail.com"},{"name":"slvnperron","email":"slvn.perron@gmail.com"},{"name":"xavierhamel","email":"xavier.hamel.protic@gmail.com"},{"name":"botpress-cloud-ops","email":"cloud-ops@botpress.com"}],"homepage":"https://github.com/botpress/packages#readme","bugs":{"url":"https://github.com/botpress/packages/issues"},"dist":{"shasum":"a3e52ce94ac88070e6e29b313a1e43812475064b","tarball":"https://registry.npmjs.org/@bpinternal/overwatch/-/overwatch-0.3.7.tgz","fileCount":87,"integrity":"sha512-8heuH7n2bwhACU2aZsRzRHcN2wRc8EVOmrrG6NpDmxRIyp4weD5UCL9j4uNvsWuPWUXXsrKR88xOCC6fkLkjXg==","signatures":[{"sig":"MEQCIDBHUtPKC9+eOU6YPc4etU6R8FHgShb5ZM8zY0Oxs/EvAiBMxQH+WqOEx7KJ2gTTgjv6fGeY3De5lcype7wrC9a1qw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bpinternal%2foverwatch@0.3.7","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":331189},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=20","pnpm":">=9"},"gitHead":"0d693a52a5c2d4e26ac429abbf2131ba70204a9c","scripts":{"test":"vitest --run","build":"pnpm run build:types && pnpm run bundle","bundle":"ts-node -T build.ts","check:type":"tsc --noEmit","build:types":"tsc -p ./tsconfig.build.json","prepublishOnly":"pnpm run check:type && pnpm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:144117d5-3e18-4bd7-8023-6660e9ec26bb"}},"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"_npmVersion":"11.16.0","description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"commander":"^15.0.0","@daytona/sdk":"^0.195.0","@octokit/core":"^7.0.6","@slack/web-api":"^8.0.0","@octokit/auth-app":"^8.2.0","@octokit/plugin-paginate-rest":"^14.0.0","@octokit/plugin-rest-endpoint-methods":"^17.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.29.3","devDependencies":{"glob":"^9.3.4","vitest":"^2.1.4","esbuild":"^0.25.10","ts-node":"^10.9.2","typescript":"^5.6.3","@types/node":"^22.9.0"},"peerDependencies":{"typescript":"^5"},"_npmOperationalInternal":{"tmp":"tmp/overwatch_0.3.7_1786125023187_0.33481739456431736","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-07-16T17:26:47.443Z","modified":"2026-09-02T19:11:32.073Z","0.1.0":"2026-07-16T17:26:47.773Z","0.2.0":"2026-07-20T19:09:07.673Z","0.3.0":"2026-07-21T18:02:25.431Z","0.3.1":"2026-07-30T16:21:15.518Z","0.3.2":"2026-07-30T17:56:19.129Z","0.3.3":"2026-07-31T14:44:11.559Z","0.3.4":"2026-08-03T14:44:28.630Z","0.3.5":"2026-08-03T19:01:26.773Z","0.3.6":"2026-08-04T19:05:49.798Z","0.3.7":"2026-08-07T17:50:23.315Z"},"bugs":{"url":"https://github.com/botpress/packages/issues"},"license":"MIT","homepage":"https://github.com/botpress/packages#readme","keywords":["control-loop","coding-agent","automation","codemod","github","pull-request","ci","daytona","claude","codex"],"repository":{"url":"git+https://github.com/botpress/packages.git","type":"git"},"description":"Headless library for building control loops: scheduled bots that scan a repo for a class of anomaly, fix a few instances with a coding agent, and open a PR — no server, UI, or persistent process.","maintainers":[{"email":"yann.allard1@gmail.com","name":"allardy"},{"email":"michael.masson@botpress.com","name":"michael.masson"},{"email":"slvn.perron@gmail.com","name":"slvnperron"},{"email":"xavier.hamel.protic@gmail.com","name":"xavierhamel"},{"email":"cloud-ops@botpress.com","name":"botpress-cloud-ops"}],"readme":"# Overwatch\n\nA headless library for building **control loops**: scheduled bots that scan a repo for one\nclass of anomaly, fix a few instances with a coding agent, and open a PR. There's no server or\nUI — you call `loop.run()` from whatever scheduler you already have (cron, CI, a serverless\nhandler), and each call does one full cycle:\n\n```\nsense → filter claimed → pick → actuator acts\n                                 └─ default: agent per signal → verify (re-sense) → commit → PR\n```\n\nEverything runs inside a fresh [Daytona](https://daytona.io) sandbox: the repo is cloned in,\nan agent CLI (Claude Code or Codex) is installed and driven non-interactively, and the sandbox\nis torn down at the end of the run.\n\n## Install\n\n```bash\npnpm add @bpinternal/overwatch     # or: npm install / yarn add\n```\n\nShips as a compiled build (CommonJS, with type declarations) that works from both `require` and\n`import` on Node 18+. To hack on the library itself, clone the repo and run `pnpm install`\n(pnpm is the supported package manager; the version is pinned via `packageManager`).\n\nYou'll need, at minimum:\n\n- A Daytona API key — passed as `config.sandbox.apiKey` per loop, or via the `DAYTONA_API_KEY`\n  env var (`DAYTONA_API_URL` / `DAYTONA_TARGET` also work as env vars if you need them)\n- An Anthropic or OpenAI API key — whichever agent you use\n- A GitHub token with push + PR permissions on the target repo, if you want the loop to open\n  PRs rather than just report\n\n## Quick start\n\n```ts\nimport { ControlLoop, Github, Codex, AgentPrActuator, sensors } from \"@bpinternal/overwatch\";\n\nconst loop = new ControlLoop({\n  // Slugified and applied as a label on every PR this loop opens; also what\n  // config.maxOpenPrCount counts against.\n  label: \"Naked Error Should be ServiceError\",\n\n  config: {\n    maxOpenPrCount: 1,\n    git: new Github({\n      repo: \"https://github.com/my-org/my-repo.git\",\n      branch: \"main\",\n      key: process.env.GITHUB_TOKEN,\n    }),\n    agent: new Codex({ apiKey: process.env.OPENAI_API_KEY!, model: \"gpt-5.5\" }),\n    hooks: {\n      setup: \"bun install\",\n      preCommit: \"bun run fix:format\",\n    },\n    sandbox: { apiKey: process.env.DAYTONA_API_KEY! },\n  },\n\n  // Detects anomalies. Scans src/ for raw `throw new Error(...)`.\n  sensor: sensors.astGrep({\n    pattern: \"throw new Error($$$)\",\n    language: \"ts\",\n    paths: [\"src/\"],\n  }),\n\n  // The actuator decides what to do with the picked signals. The default AgentPrActuator\n  // runs the agent per signal, verifies by re-sensing, then commits and opens a PR. Its\n  // `instructions` builder turns one signal into the agent's prompt — see \"Writing a\n  // custom actuator\" below for the built-in builders and for how to swap in an actuator\n  // that does something other than open a PR.\n  actuator: new AgentPrActuator({\n    instructions: (signal) => ({\n      instructions: [\n        `Fix this in ${signal.location?.file ?? \"the codebase\"}.`,\n        `Issue: ${signal.message}`,\n        \"Make the smallest change that resolves it — no unrelated refactors.\",\n      ].join(\"\\n\"),\n    }),\n  }),\n});\n\n// Expose the built-in CLI (see \"Running from CI\" below). Or call loop.run() directly.\nloop.cli();\n```\n\nRun it with your TypeScript runner — e.g. `pnpm dlx tsx my-loop.ts run` — on whatever cadence\nyou like (a cron trigger, a GitHub Action on a schedule, etc.); the loop itself doesn't stay\nresident between runs.\n\nSee `examples/` for fuller, realistic configurations (shared config in `examples/common.ts`,\nloops built on top of it).\n\n## Running from CI\n\nA loop is a plain script, so any TypeScript runner can run it. Rather than wiring up argument\nhandling yourself, end the file with **`loop.cli()`** — it turns the script into a small\ncommand-line tool with two subcommands:\n\n```ts\nconst loop = new ControlLoop({ /* ... */ });\n\nloop.cli(); // parses process.argv and runs the matching subcommand\n```\n\n```bash\ntsx my-loop.ts run                    # one full cycle: sense → pick → actuate (→ PR)\ntsx my-loop.ts apply-comments 1234    # apply new review comments on PR #1234, push in place\ntsx my-loop.ts --help                 # usage (works on each subcommand too)\n```\n\n- **`run`** exits **non-zero** if a fix couldn't be verified (`fix-failed`) or on any thrown\n  error, so a CI step fails loudly. Every other outcome — `clean`, `skipped`, `pr-opened` —\n  exits `0`. The result is also printed as a one-line summary.\n- **`apply-comments <pr>`** reads comments newer than the PR's head commit and applies them to\n  the PR's branch (see **PR review comments** above). Wire it to a `pull_request_review_comment`\n  trigger, passing the PR number. It first checks the PR carries this loop's label; if it\n  doesn't — a PR from another loop or a human — it does nothing and exits **non-zero**\n  (`wrong-loop`), so a mis-wired trigger fails loudly instead of pushing this loop's config\n  onto a foreign branch.\n\nHelp and argument parsing are handled by [commander](https://github.com/tj/commander.js), so\n`--help`, unknown-command errors, and per-command usage all come for free.\n\nA GitHub Actions workflow that runs the loop hourly and handles review comments as they land:\n\n```yaml\nname: control-loop\non:\n  schedule:\n    - cron: \"0 * * * *\"           # hourly\n  pull_request_review_comment:\n    types: [created]\n\njobs:\n  loop:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: pnpm\n      - run: pnpm install\n      - name: run\n        if: github.event_name == 'schedule'\n        run: pnpm dlx tsx my-loop.ts run\n        env:\n          DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}\n          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\n          GITHUB_TOKEN: ${{ secrets.GH_PUSH_TOKEN }}\n      - name: apply review comments\n        if: github.event_name == 'pull_request_review_comment'\n        run: pnpm dlx tsx my-loop.ts apply-comments ${{ github.event.pull_request.number }}\n        env:\n          DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}\n          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}\n          GITHUB_TOKEN: ${{ secrets.GH_PUSH_TOKEN }}\n```\n\nPass `config.maxOpenPrCount` to keep a scheduled `run` from piling up PRs faster than they merge.\n\n### Several loops, one entry point\n\nRunning many loops means many near-identical workflows — and a `pull_request_review_comment`\ntrigger can't know *which* loop opened the PR being commented on. **`LoopOrchestrator`** collapses\nthem into a single script: register the loops, end with `orchestrator.run()`, and one CLI fronts\nthem all.\n\n```ts\nimport { LoopOrchestrator } from \"@bpinternal/overwatch\";\nimport { nakedErrorLoop, reactDoctorLoop } from \"./loops\";\n\nnew LoopOrchestrator()\n  .register(nakedErrorLoop)\n  .register(reactDoctorLoop)\n  .run();\n```\n\n```bash\ntsx loops.ts list                     # the registered loops and their routing slugs\ntsx loops.ts run react-doctor-issues  # one cycle of a specific loop (by slug)\ntsx loops.ts apply-comments 1234      # route the event to whichever loop owns PR #1234\n```\n\n`apply-comments` matches the PR against each loop's label and applies the comments on the one that\nowns it — so a single comment webhook serves every loop. If no registered loop owns the PR, it\nexits **non-zero** (`no-matching-loop`). Loops are keyed by their label slug, so their labels must\nbe distinct (registering a collision throws), and they're expected to target the same repo (a\ncomment webhook is per-repo). In the workflow above, point the two comment/schedule steps at\n`loops.ts apply-comments …` / `loops.ts run <slug>` instead of a per-loop script.\n\n## Core concepts\n\nA control loop is five pieces of configuration wired into `ControlLoop`:\n\n| Piece | Type | Purpose |\n| --- | --- | --- |\n| `sensor` | `Sensor` | Scans the repo, returns `Signal[]` — the anomalies found |\n| `picker` | `Picker` (optional) | Chooses which signals to act on *this run* |\n| `actuator` | `Actuator` | Acts on the picked signals; the default runs the agent and opens a PR |\n| `config.agent` | `Agent` | Drives a coding agent CLI inside the sandbox |\n| `config.git` | `GitSource` | Where to clone from, where to push and open the PR |\n\nA **`Signal`** is the unit everything else operates on:\n\n```ts\ninterface Signal<TData = unknown> {\n  // Where the anomaly is. Optional — some anomalies are repo-wide. When present,\n  // `location.file` anchors the signal's identity (see below).\n  location?: { file: string; line?: number };\n  message: string;\n  priority?: \"low\" | \"medium\" | \"high\";\n  data?: TData; // anything sensor-specific; carried through to picker and actuator untouched\n}\n```\n\nSignals are matched across runs by **`location.file + message` only** — `line` is informational and\nnaturally shifts as fixes land, so never bake a line number or timestamp into `message`. This\nidentity is what lets the loop tell \"still broken, retry\" from \"fixed\" during verification, and\nwhat lets it skip signals an already-open PR has claimed.\n\n### What one `run()` does, in order\n\n1. Skip early if `config.maxOpenPrCount` open PRs already carry this loop's label.\n2. Spin up a sandbox, clone the repo, run `config.hooks.setup`.\n3. Run the sensor. No signals → done (`status: \"clean\"`).\n4. Drop signals already claimed by another open PR of this loop (see **Memory & claiming**\n   below).\n5. Run the picker (default: `pickers.count(1)`, i.e. the first signal in sensor order). No\n   signals picked → done.\n6. Hand the picked signals to `actuator.act(...)` and return whatever it reports. Steps 6a–6c\n   below are what the **default `AgentPrActuator`** does — a different actuator owns this step\n   entirely and may do none of them:\n   - **6a.** Check out a branch, run the agent once per picked signal using that signal's\n     instructions, then re-run the sensor to check which signals are gone. Anything still\n     present is retried (with a \"try a different approach\" nudge) up to `config.maxFixAttempts`\n     times (default 3).\n   - **6b.** If anything's still unresolved after all attempts → `status: \"fix-failed\"`,\n     nothing is pushed.\n   - **6c.** Otherwise: run `config.hooks.preCommit`, commit everything with `--no-verify`,\n     push, open the PR (labeled, with a claim marker in the body) → `status: \"pr-opened\"`.\n\n`run()` resolves to a `ControlLoopRunResult` — always check `.status` rather than assuming\nsuccess; a clean sensor pass and a \"we tried and failed\" pass are both normal outcomes, not\nexceptions.\n\n### PR review comments\n\n`loop.applyPrComments(prNumber)` is the other entry point — wire it to your GitHub webhook (or\npoll for it) for comment activity on PRs the loop opened. It fetches comments newer than the\nPR's head commit, has the agent apply them on the PR's existing branch, and pushes in place.\nComments older than the head are treated as already addressed, so re-triggering on the same\nthread is safe (`status: \"no-new-comments\"`).\n\nA comment starting with `/feedback` is applied like any other comment, but its text (prefix\nstripped) is also persisted into `.github/control-loop/memory-<label>.md` in the repo. That\nfile is read on every future run of the same loop and appended to every agent instruction as\nstanding guidance — this is how you correct a loop's behavior once and have it stick, since the\nmemory file ships with the PR that recorded it.\n\n### Notifications\n\n`config.notifications` is a list of destinations the loop reports its terminal outcomes to.\n`Slack` ships with the lib; subclass the abstract `Notification` (one `send(text, state?)`\nmethod) for anything else.\n\n```ts\nimport { Slack } from \"@bpinternal/overwatch\";\n\nnotifications: [\n  new Slack({\n    auth: { token: process.env.SLACK_BOT_TOKEN! }, // bot token with chat:write\n    channel: \"#overwatch\",\n    onSuccess: (context) => `A new PR was open: ${context.url}`,\n    onFailure: (reason) => reason,\n  }),\n],\n```\n\nThe wording is entirely yours — each handler returns the message to post, and returning\n`null`/`undefined` (or nothing) posts nothing, so a destination only speaks up about the events\nit has a handler for:\n\n| Handler | Fires on | Receives |\n| --- | --- | --- |\n| `onSuccess` | `run()` opened a PR | `{ label, url, signals }` |\n| `onFailure` | `run()` returned `fix-failed`, or either entry point threw | `reason: string` (already names the loop) |\n| `onCommentsApplied` | `applyPrComments()` pushed to the PR's branch | `{ label, branch, comments }` |\n\n`clean` and `skipped` runs — and comment events meant for another loop — notify nothing: those\nare what a scheduled loop returns on most ticks, and announcing them buries the runs worth\nlooking at. A destination that can't be reached (revoked token, bot not in the channel) is\nlogged as a warning and skipped; it never fails a run, and never replaces the run's own error.\n\nEvery message about the same PR after the first is grouped with it: `Slack` posts the\n\"comments applied\" update (and any later failure) as a **reply in the thread** of the message\nthat announced the PR, so a PR reads as one conversation.\n\nThat works across processes — the run that opens the PR and the comment webhook that updates it\nshare nothing but the PR — because each destination gets a `NotifyState`: whatever `send`\nreturns is stored in an invisible marker in the PR body (next to the claim marker) and handed\nback to that same destination on the next event. Slack's is just the first message's `ts`.\nIt never reaches your handlers; it's delivery mechanics, not wording. For a transport of your\nown: return state to record it, return nothing to keep what's already stored (that's how Slack\nkeeps replying to the thread's root rather than to its own last reply).\n\n### Claiming (safe concurrency)\n\nEvery opened PR embeds an invisible marker listing the signal keys it fixes. On the next run,\nsignals already claimed by an *open* PR of the same loop are excluded before picking — so\nrunning the loop again before a previous PR merges won't produce a duplicate fix for the same\nissue. A merged PR removes the signal at the source (the sensor stops reporting it); a\nclosed-without-merging PR releases its claim automatically since it's no longer open.\n\n## Writing a custom sensor\n\nA sensor is either a plain function or a script uploaded into the sandbox. Use `sensors.*` for\ncommon cases; write your own when they don't fit.\n\n**Host-side function** (`SensorFn`) — runs on your machine/CI runner, with a `RepoHandle` that\nproxies `exec`, `readFile`, `glob`, and `grep` into the sandbox:\n\n```ts\nimport type { RepoHandle } from \"@bpinternal/overwatch\";\nimport type { Signal } from \"@bpinternal/overwatch\";\n\nconst myTodoSensor = async (repo: RepoHandle): Promise<Signal[]> => {\n  const matches = await repo.grep(\"FIXME:\", [\"src/\"]);\n  return matches.map((match) => ({\n    location: { file: match.file, line: match.line },\n    message: `unresolved FIXME: ${match.content.trim()}`,\n    priority: \"low\",\n  }));\n};\n\nconst loop = new ControlLoop({ /* ... */ sensor: myTodoSensor, /* ... */ });\n```\n\nUse this shape whenever the check is cheap and expressible with `exec`/`grep`/`glob` — closures\nand host-side imports work normally, since it never leaves your process.\n\n**Sandbox script** (`sensors.script(localPath)`) — for heavier scanners (custom static\nanalysis, project-specific tooling) that should run *inside* the sandbox with the repo root as\ncwd, rather than round-tripping through the host:\n\n```ts\n// sensors/find-unhandled-promises.ts\nexport default async function (): Promise<import(\"@bpinternal/overwatch\").Signal[]> {\n  // this file is uploaded and run with `bun` inside the sandbox; cwd is the repo root\n  // ... your scanning logic ...\n  return [];\n}\n```\n\n```ts\nsensor: sensors.script(\"./sensors/find-unhandled-promises.ts\"),\n```\n\nThe default export must be `() => Promise<Signal[]>`. The loop wraps it in a small runner that\nwrites the result to a JSON file rather than stdout, so the script is free to `console.log` for\nits own debugging.\n\n**Built-in sensors** (`sensors.grep`, `sensors.astGrep`, `sensors.reactDoctor`) cover plain-text\nsearch, structural AST search, and [react-doctor](https://react.doctor) diagnostics\nrespectively — check `src/sensors/` for their full option shapes before writing a custom one;\na `grep`/`astGrep` call with a custom `message` function often replaces what would otherwise be\na bespoke sensor.\n\nWhatever shape you pick: keep `paths` (or the equivalent scope) intentional. An unscoped sensor\nturns \"fix this one thing\" into \"fix everything, everywhere, forever.\"\n\n## Writing a custom picker\n\nA picker narrows the sensed (and unclaimed) signals down to what one run should act on:\n\n```ts\ntype Picker = (signals: Signal[]) => Promise<Signal[]> | Signal[];\n```\n\n```ts\nimport type { Signal } from \"@bpinternal/overwatch\";\n\nconst highPriorityFirst = (max: number) => (signals: Signal[]): Signal[] =>\n  [...signals]\n    .sort((a, b) => rank(b.priority) - rank(a.priority))\n    .slice(0, max);\n\nfunction rank(p: Signal[\"priority\"]): number {\n  return { high: 2, medium: 1, low: 0 }[p];\n}\n```\n\nBuilt-ins: `pickers.count(n)` (first `n` in sensor order — the default) and\n`pickers.busiestFile(maxCount?)` (every signal in whichever single file has the most, so a run's\nfixes land together in one coherent PR). Sort inside your sensor, or write a picker like the one\nabove, to change what \"first\" means.\n\n## Actuators\n\nThe **actuator** owns everything after picking — it decides what actually *happens* to the\nsignals a run selected, and reports the outcome. It's an abstract class with one method:\n\n```ts\nabstract class Actuator<TData = unknown> {\n  abstract act(input: ActuatorInput<TData>): Promise<ControlLoopRunResult<TData>>;\n}\n```\n\n`act` receives the picked `signals`, the provisioned `sandbox`, an `AgentContext` (`ctx`) for\nrunning commands / writing files in it, a `sense()` callback to re-run the sensor, the loop's\n`config`, its `label`/`displayLabel`, and the run `log` — everything needed to do the work and\nreturn a `ControlLoopRunResult`.\n\n### The default: `AgentPrActuator`\n\nMost loops want the built-in behavior: run the agent on each signal, verify by re-sensing (with\nretries), then commit and open one PR. That's `AgentPrActuator`, and it takes an\n**instructions builder** — a function turning one signal into the agent's prompt:\n\n```ts\ntype InstructionBuilder<TData = unknown> =\n  (signal: Signal<TData>) => Promise<{ instructions: string }> | { instructions: string };\n```\n\n```ts\nimport { AgentPrActuator, actuators } from \"@bpinternal/overwatch\";\n\nnew AgentPrActuator({\n  instructions: (signal) => ({\n    instructions: [\n      `Fix this in ${signal.location?.file ?? \"the codebase\"}.`,\n      `Issue: ${signal.message}`,\n      \"Make the smallest change that resolves it — no unrelated refactors.\",\n    ].join(\"\\n\"),\n  }),\n});\n```\n\nReach into `signal.data` for anything sensor-specific — e.g. `sensors.reactDoctor` attaches the\nfull diagnostic (including its `help` text) there, and `sensors.astGrep`/`sensors.grep` attach\nthe raw match.\n\nTwo builders ship under `actuators` (pass one as `instructions`, or omit it — the default is\n`fromSignal()`):\n\n- `actuators.fromSignal()` — minimal instructions built from the signal alone; a good default.\n- `actuators.fromFile(localPath)` — reads a local markdown/text file once (cached) and uses it as\n  the instructions, with the signal's details appended. Use this when the fix follows a\n  house style that's easier to write as a doc than to reconstruct per-signal — see\n  `examples/blank-errors-instructions.md` for a real one (a full migration guide with a mapping\n  table and before/after snippets).\n\n`AgentPrActuator` appends two things to every instruction automatically: a retry nudge on\nrepeated attempts, and this loop's accumulated `/feedback` memory (see **PR review comments**\nabove). Don't duplicate either concern in your instructions builder.\n\n### Writing a custom actuator\n\nSubclass `Actuator` when you want a run to do something *other than* open a PR — post a report to\nSlack, write a summary file, call an API, open an issue instead of a PR. The actuator need not\ntouch a coding agent at all; it just has to return a `ControlLoopRunResult`:\n\n```ts\nimport { Actuator, type ActuatorInput, type ControlLoopRunResult } from \"@bpinternal/overwatch\";\n\nclass ReportOnly extends Actuator {\n  async act({ signals, log }: ActuatorInput): Promise<ControlLoopRunResult> {\n    log.info(`found ${signals.length} signal(s) — reporting without touching the repo`);\n    await postToSlack(signals.map((s) => `• ${s.location?.file ?? \"repo\"}: ${s.message}`).join(\"\\n\"));\n    return { status: \"clean\" }; // nothing was changed or opened\n  }\n}\n```\n\nThe loop still handles sensing, claim-filtering, picking, and the `maxOpenPrCount` gate around\nyour actuator — those key off PRs carrying the loop's label, so they simply no-op for an actuator\nthat never opens one.\n\n## Config reference (`ControlLoopConfig`)\n\nThe static, per-loop settings passed as `config`:\n\n- **`git`** (`GitSource`, required) — use `new Github({ repo, branch, key })`. `key` is\n  optional for cloning/counting PRs on a public repo, required to push or open a PR.\n- **`agent`** (`Agent`, required) — `new Claude({ apiKey, model?, timeoutSec? })` or\n  `new Codex({ apiKey, model?, timeoutSec? })`. Extend the abstract `Agent` class\n  (`prepare`, `executeAgent`) to plug in another CLI.\n- **`maxOpenPrCount`** — skip the run outright if this many PRs with the loop's label are\n  already open.\n- **`notifications`** (`Notification[]`) — destinations told about the run's outcome; see\n  **Notifications** above.\n- **`branchPrefix`** — default `\"control-loop\"`; pushed branches look like\n  `<prefix>/<label-slug>-<run-id>`.\n- **`hooks.setup`** — shell command run right after cloning (e.g. `\"bun install\"`). Non-zero\n  exit aborts the run.\n- **`hooks.preCommit`** — shell command run after the agent, before committing (e.g. your\n  repo's auto-formatter). Normalizes both agent edits and anything the loop wrote, so the PR\n  passes formatting CI. Non-zero exit aborts the run.\n- **`env`** — env vars set on the sandbox at creation (available to `hooks` and the agent).\n- **`maxFixAttempts`** — default 3; see step 6 above.\n- **`sandbox.apiKey`** — the Daytona API key sandboxes are created with, so loops hosted in\n  one process can use different Daytona accounts. Falls back to the `DAYTONA_API_KEY` env var\n  when omitted.\n- **`sandbox.snapshot`** — a Daytona snapshot to create the sandbox from instead of the default\n  image. Pre-bake your toolchain and the agent CLI here to skip per-run installs. Note:\n  resource sizing must be baked into the snapshot itself — it can't be requested at creation\n  time when using one.\n\n## Logging\n\nEvery run narrates its steps to stderr via `RunLog` (sandbox creation, cloning, sensing,\nper-signal agent runs, commit/push, PR open) — nothing to configure, just watch stderr when\nrunning a loop manually. Set `CONTROL_LOOP_SILENT=1` to mute it; colors otherwise follow\n`NO_COLOR` and TTY detection.\n\n## Extending\n\n- **New sensor flavor**: add a file to `src/sensors/` returning `SensorFn` and re-export it from\n  `src/sensors/index.ts` (or use `sensors.script`/a raw function inline — you don't need a\n  built-in for one-off scans).\n- **New picker**: any `(signals: Signal[]) => Signal[] | Promise<Signal[]>` works inline; add a\n  file to `src/pickers/` (re-exported from its `index.ts`) only if it's broadly reusable.\n- **New instructions style**: add an `InstructionBuilder` factory to `src/actuators/instructions.ts`\n  (like `fromFile`/`fromSignal`), or pass one inline to `AgentPrActuator`.\n- **New actuator kind** (doesn't open a PR / doesn't run an agent): add a file to `src/actuators/`\n  with a class that subclasses the abstract `Actuator`, re-exported from `src/actuators/index.ts`.\n- **New agent CLI**: extend the abstract `Agent` class in `src/agents.ts`.\n- **New notification destination**: add a file to `src/notifications/` with a class that\n  subclasses the abstract `Notification` and implements `send(text, state?)`, re-exported from\n  `src/notifications/index.ts`. The base handles which events fire and what they say; return a\n  `NotifyState` from `send` to have the loop hand it back on the next event about that PR.\n- **New forge** (GitLab, etc.): implement the `GitSource` interface from `src/github.ts`.\n\n## Disclaimer ⚠️\n\nThis package is published under the `@bpinternal` organization. All packages of this organization are meant to be used by the [Botpress](https://github.com/botpress/botpress) team internally and are not meant for our community. Since the packages are catered to our own use-cases, they might have less stable APIs, receive breaking changes without much warning, have minimal documentation and lack community-focused support. However, these packages were still left intentionally public for an important reason : We Love Open-Source. Therefore, if you wish to install or fork this package feel absolutly free to do it. We strongly recommend that you tag your versions properly.\n\nThe Botpress Engineering team.\n\n","readmeFilename":"README.md"}