sparkecoder 0.1.130 → 0.1.132
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 +3 -3
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1480 -638
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +2281 -808
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js.map +1 -1
- package/dist/{index-Bcz0aCAR.d.ts → index-BM99kjgq.d.ts} +177 -103
- package/dist/index.d.ts +5 -5
- package/dist/index.js +2215 -780
- package/dist/index.js.map +1 -1
- package/dist/{schema-BWbWmfDQ.d.ts → schema-Dz-wABVY.d.ts} +27 -4
- package/dist/{search-DOzC4ojH.d.ts → search-CVVfuBPZ.d.ts} +4 -4
- package/dist/server/index.js +2215 -780
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/build-context-and-solve-issue.md +74 -0
- package/dist/skills/default/doublecheck.md +95 -0
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js +11 -2
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/skills/default/build-context-and-solve-issue.md +74 -0
- package/src/skills/default/doublecheck.md +95 -0
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.html +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +20 -21
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.html +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.rsc +5 -5
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +5 -5
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.html +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +6 -6
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +3 -3
- package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__397fadd4._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__70cecda8._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +2 -2
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +9 -9
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/185f69f6478ba713.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/20ca4e35e9bb3e94.js +3 -0
- package/web/.next/standalone/web/.next/static/chunks/{a7d5d0791c8c6223.css → 34d933785a17edf3.css} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/7549a5b7c7f6786e.js +1 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/c5dd884b71007965.js → chunks/a839c83078c56476.js} +1 -1
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.3b336396.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/5ce348bf30bf5439-s.56c1f21e.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/6306c77e7c8268e4-s.e3369375.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.29207c2f.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7d817b4c03b0c5f1-s.a40b9a8b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.fe42ddf4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/chunks/185f69f6478ba713.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/20ca4e35e9bb3e94.js +3 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{a7d5d0791c8c6223.css → 34d933785a17edf3.css} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/7549a5b7c7f6786e.js +1 -0
- package/web/.next/{static/chunks/c5dd884b71007965.js → standalone/web/.next/static/static/chunks/a839c83078c56476.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/media/4fa387ec64143e14-s.3b336396.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/5ce348bf30bf5439-s.56c1f21e.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/6306c77e7c8268e4-s.e3369375.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/797e433ab948586e-s.p.29207c2f.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/7d817b4c03b0c5f1-s.a40b9a8b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/bbc41e54d2fcbd21-s.fe42ddf4.woff2 +0 -0
- package/web/.next/standalone/web/package-lock.json +21 -21
- package/web/.next/standalone/web/runtime-config.json +2 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +2 -2
- package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +111 -11
- package/web/.next/standalone/web/src/app/__sfapi/[...path]/route.ts +96 -0
- package/web/.next/standalone/web/src/app/api/config/route.ts +5 -12
- package/web/.next/standalone/web/src/app/docs/installation/page.mdx +2 -2
- package/web/.next/standalone/web/src/app/docs/page.mdx +1 -1
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +1 -1
- package/web/.next/standalone/web/src/lib/config.ts +26 -16
- package/web/.next/static/chunks/185f69f6478ba713.js +1 -0
- package/web/.next/static/chunks/20ca4e35e9bb3e94.js +3 -0
- package/web/.next/static/chunks/{a7d5d0791c8c6223.css → 34d933785a17edf3.css} +1 -1
- package/web/.next/static/chunks/7549a5b7c7f6786e.js +1 -0
- package/web/.next/{standalone/web/.next/static/chunks/c5dd884b71007965.js → static/chunks/a839c83078c56476.js} +1 -1
- package/web/.next/static/media/4fa387ec64143e14-s.3b336396.woff2 +0 -0
- package/web/.next/static/media/5ce348bf30bf5439-s.56c1f21e.woff2 +0 -0
- package/web/.next/static/media/6306c77e7c8268e4-s.e3369375.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.29207c2f.woff2 +0 -0
- package/web/.next/static/media/7d817b4c03b0c5f1-s.a40b9a8b.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.fe42ddf4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/chunks/9b88f148788e4504.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/b203b9aa975135d3.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/ea89ca7892d8c557.js +0 -1
- package/web/.next/standalone/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/chunks/9b88f148788e4504.js +0 -3
- package/web/.next/standalone/web/.next/static/static/chunks/b203b9aa975135d3.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/ea89ca7892d8c557.js +0 -1
- package/web/.next/standalone/web/.next/static/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/standalone/web/.next/static/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- package/web/.next/static/chunks/9b88f148788e4504.js +0 -3
- package/web/.next/static/chunks/b203b9aa975135d3.js +0 -1
- package/web/.next/static/chunks/ea89ca7892d8c557.js +0 -1
- package/web/.next/static/media/4fa387ec64143e14-s.c36e1862.woff2 +0 -0
- package/web/.next/static/media/5ce348bf30bf5439-s.ebceb24d.woff2 +0 -0
- package/web/.next/static/media/6306c77e7c8268e4-s.ff4a2084.woff2 +0 -0
- package/web/.next/static/media/797e433ab948586e-s.p.479bea2b.woff2 +0 -0
- package/web/.next/static/media/7d817b4c03b0c5f1-s.f377b9c4.woff2 +0 -0
- package/web/.next/static/media/bbc41e54d2fcbd21-s.d1207556.woff2 +0 -0
- /package/web/.next/standalone/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_ssgManifest.js +0 -0
- /package/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_buildManifest.js +0 -0
- /package/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{2mUQ8I-TCRE5uOBFhIWag → WaAcu3X3K00MDvfn1ik7H}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -305,8 +305,8 @@ var init_types = __esm({
|
|
|
305
305
|
authKey: z.string().optional()
|
|
306
306
|
}).optional();
|
|
307
307
|
SparkcoderConfigSchema = z.object({
|
|
308
|
-
// Default model to use (
|
|
309
|
-
defaultModel: z.string().default("
|
|
308
|
+
// Default model to use (LiteLLM model id)
|
|
309
|
+
defaultModel: z.string().default("gpt-5.5"),
|
|
310
310
|
// Working directory for file operations
|
|
311
311
|
workingDirectory: z.string().optional(),
|
|
312
312
|
// Tool approval settings
|
|
@@ -345,6 +345,14 @@ var init_types = __esm({
|
|
|
345
345
|
webhooks: z.object({
|
|
346
346
|
token: z.string().optional()
|
|
347
347
|
}).optional(),
|
|
348
|
+
// Self-update: when running as the managed service, periodically check
|
|
349
|
+
// npm for a newer published version and, if found, re-run the hosted
|
|
350
|
+
// installer (full upgrade + restart). Disabled automatically when not
|
|
351
|
+
// running from a global install (e.g. dev/source checkouts).
|
|
352
|
+
autoUpdate: z.object({
|
|
353
|
+
enabled: z.boolean().optional().default(true),
|
|
354
|
+
intervalHours: z.number().positive().optional().default(6)
|
|
355
|
+
}).optional().default({}),
|
|
348
356
|
// Database path (used for local SQLite - ignored if remoteServer is configured)
|
|
349
357
|
databasePath: z.string().optional().default("./sparkecoder.db"),
|
|
350
358
|
// Remote server configuration (for centralized storage)
|
|
@@ -452,6 +460,7 @@ __export(config_exports, {
|
|
|
452
460
|
requiresApproval: () => requiresApproval,
|
|
453
461
|
saveAuthKey: () => saveAuthKey,
|
|
454
462
|
setApiKey: () => setApiKey,
|
|
463
|
+
setCfAccessConfig: () => setCfAccessConfig,
|
|
455
464
|
setMcpServers: () => setMcpServers,
|
|
456
465
|
setPublicUrl: () => setPublicUrl,
|
|
457
466
|
setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
|
|
@@ -640,12 +649,12 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
640
649
|
]
|
|
641
650
|
};
|
|
642
651
|
const DEFAULT_REMOTE_URL = "https://agent-remote-server.sparkecode.com";
|
|
643
|
-
const
|
|
652
|
+
const remoteUrl2 = process.env.SPARKECODER_REMOTE_URL || config.remoteServer?.url || DEFAULT_REMOTE_URL;
|
|
644
653
|
const remoteAuthKey = process.env.SPARKECODER_AUTH_KEY || config.remoteServer?.authKey || loadStoredAuthKey();
|
|
645
654
|
const resolvedRemoteServer = {
|
|
646
|
-
url:
|
|
655
|
+
url: remoteUrl2,
|
|
647
656
|
authKey: remoteAuthKey,
|
|
648
|
-
isConfigured: !!
|
|
657
|
+
isConfigured: !!remoteUrl2 && !!remoteAuthKey
|
|
649
658
|
};
|
|
650
659
|
const resolved = {
|
|
651
660
|
...config,
|
|
@@ -801,6 +810,40 @@ function setPublicUrl(publicUrl) {
|
|
|
801
810
|
console.warn("[config] failed to persist publicUrl:", err?.message || err);
|
|
802
811
|
}
|
|
803
812
|
}
|
|
813
|
+
function setCfAccessConfig(input) {
|
|
814
|
+
const applyToAuth = (auth) => {
|
|
815
|
+
const curAuth = auth || {};
|
|
816
|
+
const curCf = curAuth.cfAccess || {};
|
|
817
|
+
const nextCf = { ...curCf };
|
|
818
|
+
if (input.enabled !== void 0) nextCf.enabled = input.enabled;
|
|
819
|
+
if (input.teamDomain !== void 0) nextCf.teamDomain = input.teamDomain;
|
|
820
|
+
if (input.audTag !== void 0) nextCf.audTag = input.audTag;
|
|
821
|
+
const nextAuth = { ...curAuth, cfAccess: nextCf };
|
|
822
|
+
if (input.allowedEmails !== void 0) nextAuth.allowedEmails = input.allowedEmails;
|
|
823
|
+
return nextAuth;
|
|
824
|
+
};
|
|
825
|
+
if (cachedConfig) {
|
|
826
|
+
cachedConfig.auth = applyToAuth(cachedConfig.auth);
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
830
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
831
|
+
let raw = {};
|
|
832
|
+
if (existsSync(target)) {
|
|
833
|
+
try {
|
|
834
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
835
|
+
} catch {
|
|
836
|
+
raw = {};
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
raw = createDefaultConfig();
|
|
840
|
+
}
|
|
841
|
+
raw.auth = applyToAuth(raw.auth);
|
|
842
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
843
|
+
} catch (err) {
|
|
844
|
+
console.warn("[config] failed to persist cf-access config:", err?.message || err);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
804
847
|
function clearSlackConfig() {
|
|
805
848
|
if (cachedConfig) cachedConfig.slack = {};
|
|
806
849
|
try {
|
|
@@ -842,7 +885,7 @@ function autoApproveAllTools(sessionConfig) {
|
|
|
842
885
|
}
|
|
843
886
|
function createDefaultConfig() {
|
|
844
887
|
return {
|
|
845
|
-
defaultModel: "
|
|
888
|
+
defaultModel: "gpt-5.5",
|
|
846
889
|
// workingDirectory is intentionally not set - defaults to where CLI is run
|
|
847
890
|
toolApprovals: {
|
|
848
891
|
bash: true,
|
|
@@ -864,6 +907,10 @@ function createDefaultConfig() {
|
|
|
864
907
|
port: 3141,
|
|
865
908
|
host: "0.0.0.0"
|
|
866
909
|
},
|
|
910
|
+
autoUpdate: {
|
|
911
|
+
enabled: true,
|
|
912
|
+
intervalHours: 6
|
|
913
|
+
},
|
|
867
914
|
databasePath: "./sparkecoder.db"
|
|
868
915
|
};
|
|
869
916
|
}
|
|
@@ -1090,6 +1137,7 @@ var init_config = __esm({
|
|
|
1090
1137
|
openai: "OPENAI_API_KEY",
|
|
1091
1138
|
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
1092
1139
|
xai: "XAI_API_KEY",
|
|
1140
|
+
litellm: "LITELLM_API_KEY",
|
|
1093
1141
|
"ai-gateway": "AI_GATEWAY_API_KEY"
|
|
1094
1142
|
};
|
|
1095
1143
|
SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
|
|
@@ -4862,11 +4910,11 @@ async function getRepoNamespace(workingDirectory, configuredNamespace) {
|
|
|
4862
4910
|
if (configuredNamespace) {
|
|
4863
4911
|
return configuredNamespace;
|
|
4864
4912
|
}
|
|
4865
|
-
const
|
|
4866
|
-
if (!
|
|
4913
|
+
const remoteUrl2 = getGitRemoteUrl(workingDirectory);
|
|
4914
|
+
if (!remoteUrl2) {
|
|
4867
4915
|
return null;
|
|
4868
4916
|
}
|
|
4869
|
-
const parsed = parseGitRemoteUrl(
|
|
4917
|
+
const parsed = parseGitRemoteUrl(remoteUrl2);
|
|
4870
4918
|
if (!parsed) {
|
|
4871
4919
|
return null;
|
|
4872
4920
|
}
|
|
@@ -6563,7 +6611,8 @@ async function buildSystemPrompt(options) {
|
|
|
6563
6611
|
sessionId,
|
|
6564
6612
|
discoveredSkills,
|
|
6565
6613
|
activeFiles = [],
|
|
6566
|
-
customInstructions
|
|
6614
|
+
customInstructions,
|
|
6615
|
+
taskScopedSkills
|
|
6567
6616
|
} = options;
|
|
6568
6617
|
let alwaysLoadedContent = "";
|
|
6569
6618
|
let globMatchedContent = "";
|
|
@@ -6584,6 +6633,22 @@ async function buildSystemPrompt(options) {
|
|
|
6584
6633
|
const skills2 = await loadAllSkills2(skillsDirectories);
|
|
6585
6634
|
onDemandSkillsContext = formatSkillsForContext(skills2);
|
|
6586
6635
|
}
|
|
6636
|
+
let taskScopedSkillsBlock = "";
|
|
6637
|
+
if (taskScopedSkills && (taskScopedSkills.always.length > 0 || taskScopedSkills.onDemand.length > 0)) {
|
|
6638
|
+
const parts = ["<task_provided_skills>"];
|
|
6639
|
+
parts.push("These skills were supplied with this task and are available for this run only.");
|
|
6640
|
+
if (taskScopedSkills.always.length > 0) {
|
|
6641
|
+
parts.push(formatAlwaysLoadedSkills(taskScopedSkills.always));
|
|
6642
|
+
}
|
|
6643
|
+
if (taskScopedSkills.onDemand.length > 0) {
|
|
6644
|
+
parts.push("Load any of these on demand with the load_skill tool:");
|
|
6645
|
+
for (const s of taskScopedSkills.onDemand) {
|
|
6646
|
+
parts.push(`- ${s.name}: ${s.description}`);
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
parts.push("</task_provided_skills>");
|
|
6650
|
+
taskScopedSkillsBlock = parts.join("\n");
|
|
6651
|
+
}
|
|
6587
6652
|
const todos = await todoQueries.getBySession(sessionId);
|
|
6588
6653
|
const todosContext = formatTodosForContext(todos);
|
|
6589
6654
|
const plans = await readSessionPlans(workingDirectory, sessionId);
|
|
@@ -6876,6 +6941,8 @@ ${globMatchedContent}
|
|
|
6876
6941
|
${onDemandSkillsContext}
|
|
6877
6942
|
</on_demand_skills>
|
|
6878
6943
|
|
|
6944
|
+
${taskScopedSkillsBlock}
|
|
6945
|
+
|
|
6879
6946
|
<current_task_list>
|
|
6880
6947
|
${todosContext}
|
|
6881
6948
|
</current_task_list>
|
|
@@ -7497,6 +7564,111 @@ var init_sanitize_messages = __esm({
|
|
|
7497
7564
|
}
|
|
7498
7565
|
});
|
|
7499
7566
|
|
|
7567
|
+
// src/utils/cap-image-count.ts
|
|
7568
|
+
function isImagePart(part) {
|
|
7569
|
+
if (!part || typeof part !== "object") return false;
|
|
7570
|
+
const t = part.type;
|
|
7571
|
+
if (t === "image") return true;
|
|
7572
|
+
if (t === "image-data") return true;
|
|
7573
|
+
if (t === "media") {
|
|
7574
|
+
const data = part.data;
|
|
7575
|
+
const mt = part.mediaType;
|
|
7576
|
+
if (typeof data === "string" && typeof mt === "string" && mt.startsWith("image/")) {
|
|
7577
|
+
return true;
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7580
|
+
return false;
|
|
7581
|
+
}
|
|
7582
|
+
function makePlaceholder() {
|
|
7583
|
+
return { type: "text", text: IMAGE_TRUNCATED_PLACEHOLDER };
|
|
7584
|
+
}
|
|
7585
|
+
function countImages(messages) {
|
|
7586
|
+
let n = 0;
|
|
7587
|
+
for (const msg of messages) {
|
|
7588
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7589
|
+
for (const part of msg.content) {
|
|
7590
|
+
if (isImagePart(part)) {
|
|
7591
|
+
n++;
|
|
7592
|
+
continue;
|
|
7593
|
+
}
|
|
7594
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7595
|
+
for (const sub of part.output.value) {
|
|
7596
|
+
if (isImagePart(sub)) n++;
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7599
|
+
}
|
|
7600
|
+
}
|
|
7601
|
+
return n;
|
|
7602
|
+
}
|
|
7603
|
+
function capImageCount(messages, max = MAX_IMAGES_IN_CONTEXT) {
|
|
7604
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7605
|
+
if (max < 0) throw new Error("capImageCount: max must be >= 0");
|
|
7606
|
+
const total = countImages(messages);
|
|
7607
|
+
if (total <= max) return messages;
|
|
7608
|
+
let toDrop = total - max;
|
|
7609
|
+
let mutated = false;
|
|
7610
|
+
const out = messages.slice();
|
|
7611
|
+
for (let i = 0; i < out.length && toDrop > 0; i++) {
|
|
7612
|
+
const msg = out[i];
|
|
7613
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7614
|
+
let contentCloned = false;
|
|
7615
|
+
const ensureContentCloned = () => {
|
|
7616
|
+
if (contentCloned) return;
|
|
7617
|
+
out[i] = { ...msg, content: [...msg.content] };
|
|
7618
|
+
contentCloned = true;
|
|
7619
|
+
};
|
|
7620
|
+
const content = () => out[i].content;
|
|
7621
|
+
for (let j = 0; j < content().length && toDrop > 0; j++) {
|
|
7622
|
+
const part = content()[j];
|
|
7623
|
+
if (isImagePart(part)) {
|
|
7624
|
+
ensureContentCloned();
|
|
7625
|
+
out[i].content[j] = makePlaceholder();
|
|
7626
|
+
toDrop--;
|
|
7627
|
+
mutated = true;
|
|
7628
|
+
continue;
|
|
7629
|
+
}
|
|
7630
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7631
|
+
const innerImages = [];
|
|
7632
|
+
const innerValue = part.output.value;
|
|
7633
|
+
for (let k = 0; k < innerValue.length; k++) {
|
|
7634
|
+
if (isImagePart(innerValue[k])) innerImages.push(k);
|
|
7635
|
+
}
|
|
7636
|
+
if (innerImages.length === 0) continue;
|
|
7637
|
+
const dropHere = Math.min(innerImages.length, toDrop);
|
|
7638
|
+
ensureContentCloned();
|
|
7639
|
+
const newOutputValue = [...innerValue];
|
|
7640
|
+
for (let d = 0; d < dropHere; d++) {
|
|
7641
|
+
newOutputValue[innerImages[d]] = makePlaceholder();
|
|
7642
|
+
}
|
|
7643
|
+
const newPart = {
|
|
7644
|
+
...part,
|
|
7645
|
+
output: {
|
|
7646
|
+
...part.output,
|
|
7647
|
+
value: newOutputValue
|
|
7648
|
+
}
|
|
7649
|
+
};
|
|
7650
|
+
out[i].content[j] = newPart;
|
|
7651
|
+
toDrop -= dropHere;
|
|
7652
|
+
mutated = true;
|
|
7653
|
+
}
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
if (mutated) {
|
|
7657
|
+
console.warn(
|
|
7658
|
+
`[cap-image-count] Replaced ${total - max} oldest image(s) with text placeholder (total=${total}, kept=${max}). This prevents request-too-large errors at the model / tunnel layer.`
|
|
7659
|
+
);
|
|
7660
|
+
}
|
|
7661
|
+
return mutated ? out : messages;
|
|
7662
|
+
}
|
|
7663
|
+
var MAX_IMAGES_IN_CONTEXT, IMAGE_TRUNCATED_PLACEHOLDER;
|
|
7664
|
+
var init_cap_image_count = __esm({
|
|
7665
|
+
"src/utils/cap-image-count.ts"() {
|
|
7666
|
+
"use strict";
|
|
7667
|
+
MAX_IMAGES_IN_CONTEXT = 11;
|
|
7668
|
+
IMAGE_TRUNCATED_PLACEHOLDER = "[image truncated due to length of conversation]";
|
|
7669
|
+
}
|
|
7670
|
+
});
|
|
7671
|
+
|
|
7500
7672
|
// src/agent/model-limits.ts
|
|
7501
7673
|
function getModelLimits(modelId) {
|
|
7502
7674
|
const normalized = modelId.trim().toLowerCase();
|
|
@@ -7512,18 +7684,9 @@ var init_model_limits = __esm({
|
|
|
7512
7684
|
"src/agent/model-limits.ts"() {
|
|
7513
7685
|
"use strict";
|
|
7514
7686
|
MODEL_LIMITS = {
|
|
7515
|
-
"
|
|
7516
|
-
"
|
|
7517
|
-
"
|
|
7518
|
-
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7519
|
-
"anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7520
|
-
"google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7521
|
-
"google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7522
|
-
"google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7523
|
-
"openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
7524
|
-
"openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7525
|
-
"openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7526
|
-
"xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
7687
|
+
"claude-opus-4-8": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7688
|
+
"gpt-5.5": { contextWindow: 35e4, rollingTarget: 15e4 },
|
|
7689
|
+
"claude-fable-5": { contextWindow: 2e5, rollingTarget: 15e4 }
|
|
7527
7690
|
};
|
|
7528
7691
|
DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
7529
7692
|
PREFIX_DEFAULTS = {
|
|
@@ -7597,6 +7760,32 @@ var init_conversation_archive = __esm({
|
|
|
7597
7760
|
|
|
7598
7761
|
// src/agent/context.ts
|
|
7599
7762
|
import { generateText as generateText2 } from "ai";
|
|
7763
|
+
function stripBinaryContentForSummary(value) {
|
|
7764
|
+
if (Array.isArray(value)) return value.map(stripBinaryContentForSummary);
|
|
7765
|
+
if (!value || typeof value !== "object") return value;
|
|
7766
|
+
const record = value;
|
|
7767
|
+
const type = record.type;
|
|
7768
|
+
if ((type === "image-data" || type === "file-data" || type === "media") && typeof record.data === "string") {
|
|
7769
|
+
const mediaType = typeof record.mediaType === "string" ? record.mediaType : "unknown media type";
|
|
7770
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
7771
|
+
return {
|
|
7772
|
+
...record,
|
|
7773
|
+
data: `[${type}${filename}; ${mediaType}; ${record.data.length} base64 chars omitted for summary]`
|
|
7774
|
+
};
|
|
7775
|
+
}
|
|
7776
|
+
if (type === "image" && typeof record.image === "string") {
|
|
7777
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
7778
|
+
return {
|
|
7779
|
+
...record,
|
|
7780
|
+
image: `[image${filename}; ${record.image.length} base64 chars omitted for summary]`
|
|
7781
|
+
};
|
|
7782
|
+
}
|
|
7783
|
+
const out = {};
|
|
7784
|
+
for (const [key2, nested] of Object.entries(record)) {
|
|
7785
|
+
out[key2] = stripBinaryContentForSummary(nested);
|
|
7786
|
+
}
|
|
7787
|
+
return out;
|
|
7788
|
+
}
|
|
7600
7789
|
function stripOrphanedToolResults(msg, removedIds) {
|
|
7601
7790
|
if (!Array.isArray(msg.content)) return msg;
|
|
7602
7791
|
const parts = msg.content.filter((part) => {
|
|
@@ -7757,6 +7946,7 @@ var init_context = __esm({
|
|
|
7757
7946
|
init_tokens();
|
|
7758
7947
|
init_prompts();
|
|
7759
7948
|
init_sanitize_messages();
|
|
7949
|
+
init_cap_image_count();
|
|
7760
7950
|
init_model_limits();
|
|
7761
7951
|
TOOL_OUTPUT_TRIM_CHARS = 400;
|
|
7762
7952
|
COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -7806,6 +7996,7 @@ ${summaryContent}`
|
|
|
7806
7996
|
messages = repairToolPairing(messages);
|
|
7807
7997
|
messages = ensureToolResultsFollowCalls(messages);
|
|
7808
7998
|
messages = ensureEndsWithUserOrTool(messages);
|
|
7999
|
+
messages = capImageCount(messages);
|
|
7809
8000
|
return messages;
|
|
7810
8001
|
}
|
|
7811
8002
|
// ---------------------------------------------------------------------------
|
|
@@ -7923,7 +8114,7 @@ ${summaryContent}`
|
|
|
7923
8114
|
}
|
|
7924
8115
|
async summarizeChunk(chunk) {
|
|
7925
8116
|
const historyText = chunk.map((msg) => {
|
|
7926
|
-
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
8117
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(stripBinaryContentForSummary(msg.content));
|
|
7927
8118
|
return `[${msg.role}]: ${content}`;
|
|
7928
8119
|
}).join("\n\n");
|
|
7929
8120
|
try {
|
|
@@ -8171,6 +8362,127 @@ var init_persistence = __esm({
|
|
|
8171
8362
|
});
|
|
8172
8363
|
|
|
8173
8364
|
// src/integrations/slack/client.ts
|
|
8365
|
+
var client_exports = {};
|
|
8366
|
+
__export(client_exports, {
|
|
8367
|
+
LOADING_REACTION: () => LOADING_REACTION,
|
|
8368
|
+
RESULT_REACTIONS: () => RESULT_REACTIONS,
|
|
8369
|
+
addLoadingReaction: () => addLoadingReaction,
|
|
8370
|
+
addResultReaction: () => addResultReaction,
|
|
8371
|
+
botParticipatedInThread: () => botParticipatedInThread,
|
|
8372
|
+
ensureSlackSelfIdentity: () => ensureSlackSelfIdentity,
|
|
8373
|
+
getCachedSlackSelfIdentity: () => getCachedSlackSelfIdentity,
|
|
8374
|
+
getDefaultOrchestratorName: () => getDefaultOrchestratorName,
|
|
8375
|
+
getSlackAdapter: () => getSlackAdapter,
|
|
8376
|
+
getSlackAllowlistPolicy: () => getSlackAllowlistPolicy,
|
|
8377
|
+
getSlackBotToken: () => getSlackBotToken,
|
|
8378
|
+
getSlackDeniedReplyPolicy: () => getSlackDeniedReplyPolicy,
|
|
8379
|
+
getSlackSigningSecret: () => getSlackSigningSecret,
|
|
8380
|
+
isSlackConfigured: () => isSlackConfigured,
|
|
8381
|
+
normalizeSlackMentions: () => normalizeSlackMentions,
|
|
8382
|
+
noteBotPostedInThread: () => noteBotPostedInThread,
|
|
8383
|
+
postThreadMessage: () => postThreadMessage,
|
|
8384
|
+
removeLoadingReaction: () => removeLoadingReaction,
|
|
8385
|
+
resolveSlackUserInfo: () => resolveSlackUserInfo,
|
|
8386
|
+
resolveSlackUserName: () => resolveSlackUserName
|
|
8387
|
+
});
|
|
8388
|
+
function slackBackoffMs(attempt) {
|
|
8389
|
+
const expo = SLACK_BACKOFF_BASE_MS * 2 ** attempt;
|
|
8390
|
+
const jitter = Math.floor(Math.random() * SLACK_BACKOFF_BASE_MS);
|
|
8391
|
+
return Math.min(expo + jitter, SLACK_BACKOFF_CAP_MS);
|
|
8392
|
+
}
|
|
8393
|
+
async function slackFetchWithRetry(url, init, attempts = SLACK_FETCH_ATTEMPTS) {
|
|
8394
|
+
let lastErr;
|
|
8395
|
+
for (let i = 0; i < attempts; i++) {
|
|
8396
|
+
const isLast = i === attempts - 1;
|
|
8397
|
+
try {
|
|
8398
|
+
const res = await fetch(url, init);
|
|
8399
|
+
if ((res.status === 429 || res.status >= 500) && !isLast) {
|
|
8400
|
+
const ra = Number(res.headers.get("retry-after"));
|
|
8401
|
+
const waitMs = Number.isFinite(ra) && ra > 0 ? Math.min(ra * 1e3, SLACK_BACKOFF_CAP_MS) : slackBackoffMs(i);
|
|
8402
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
8403
|
+
continue;
|
|
8404
|
+
}
|
|
8405
|
+
return res;
|
|
8406
|
+
} catch (err) {
|
|
8407
|
+
lastErr = err;
|
|
8408
|
+
if (isLast) throw err;
|
|
8409
|
+
await new Promise((r) => setTimeout(r, slackBackoffMs(i)));
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
throw lastErr ?? new Error("slack fetch failed");
|
|
8413
|
+
}
|
|
8414
|
+
function reactionKey(channel, ts) {
|
|
8415
|
+
return `${channel}\u241F${ts}`;
|
|
8416
|
+
}
|
|
8417
|
+
async function addLoadingReaction(channel, timestamp) {
|
|
8418
|
+
const adapter = getSlackAdapter();
|
|
8419
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8420
|
+
const key2 = reactionKey(channel, timestamp);
|
|
8421
|
+
const inFlight = (async () => {
|
|
8422
|
+
try {
|
|
8423
|
+
const res = await adapter.addReaction({ channel, timestamp, name: LOADING_REACTION });
|
|
8424
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
8425
|
+
console.warn(`[slack] addReaction ${LOADING_REACTION} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
8426
|
+
}
|
|
8427
|
+
return res;
|
|
8428
|
+
} catch (err) {
|
|
8429
|
+
console.warn(`[slack] addReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
8430
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8431
|
+
}
|
|
8432
|
+
})();
|
|
8433
|
+
pendingAdds.set(key2, inFlight);
|
|
8434
|
+
void inFlight.finally(() => {
|
|
8435
|
+
if (pendingAdds.get(key2) === inFlight) pendingAdds.delete(key2);
|
|
8436
|
+
});
|
|
8437
|
+
return inFlight;
|
|
8438
|
+
}
|
|
8439
|
+
async function removeLoadingReaction(channel, timestamp) {
|
|
8440
|
+
const adapter = getSlackAdapter();
|
|
8441
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8442
|
+
const pending = pendingAdds.get(reactionKey(channel, timestamp));
|
|
8443
|
+
if (pending) {
|
|
8444
|
+
try {
|
|
8445
|
+
await pending;
|
|
8446
|
+
} catch {
|
|
8447
|
+
}
|
|
8448
|
+
}
|
|
8449
|
+
try {
|
|
8450
|
+
const res = await adapter.removeReaction({ channel, timestamp, name: LOADING_REACTION });
|
|
8451
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
8452
|
+
console.warn(`[slack] removeReaction ${LOADING_REACTION} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
8453
|
+
}
|
|
8454
|
+
return res;
|
|
8455
|
+
} catch (err) {
|
|
8456
|
+
console.warn(`[slack] removeReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
8457
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8460
|
+
async function addResultReaction(channel, timestamp, state2) {
|
|
8461
|
+
const name = RESULT_REACTIONS[state2];
|
|
8462
|
+
if (!name) return { ok: false, error: `no_reaction_for_state:${state2}` };
|
|
8463
|
+
const adapter = getSlackAdapter();
|
|
8464
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8465
|
+
try {
|
|
8466
|
+
const res = await adapter.addReaction({ channel, timestamp, name });
|
|
8467
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
8468
|
+
console.warn(`[slack] addReaction ${name} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
8469
|
+
}
|
|
8470
|
+
return res;
|
|
8471
|
+
} catch (err) {
|
|
8472
|
+
console.warn(`[slack] addResultReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
8473
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8474
|
+
}
|
|
8475
|
+
}
|
|
8476
|
+
async function postThreadMessage(channel, threadTs, text) {
|
|
8477
|
+
const adapter = getSlackAdapter();
|
|
8478
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8479
|
+
try {
|
|
8480
|
+
return await adapter.postMessage({ channel, text, threadTs });
|
|
8481
|
+
} catch (err) {
|
|
8482
|
+
console.warn(`[slack] postThreadMessage threw on ${channel}/${threadTs}:`, err?.message || err);
|
|
8483
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8484
|
+
}
|
|
8485
|
+
}
|
|
8174
8486
|
function readSlackConfig() {
|
|
8175
8487
|
try {
|
|
8176
8488
|
const cfg = getConfig();
|
|
@@ -8188,9 +8500,25 @@ function readSlackConfig() {
|
|
|
8188
8500
|
function getSlackAdapter() {
|
|
8189
8501
|
const cfg = readSlackConfig();
|
|
8190
8502
|
if (!cfg) return void 0;
|
|
8503
|
+
const slackForm = async (endpoint, params) => {
|
|
8504
|
+
const body = new URLSearchParams(params).toString();
|
|
8505
|
+
const res = await fetch(`https://slack.com/api/${endpoint}`, {
|
|
8506
|
+
method: "POST",
|
|
8507
|
+
headers: {
|
|
8508
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
8509
|
+
Authorization: `Bearer ${cfg.botToken}`
|
|
8510
|
+
},
|
|
8511
|
+
body
|
|
8512
|
+
});
|
|
8513
|
+
const data = await res.json().catch(() => ({}));
|
|
8514
|
+
if (!res.ok || data?.ok === false) {
|
|
8515
|
+
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
8516
|
+
}
|
|
8517
|
+
return { ok: true };
|
|
8518
|
+
};
|
|
8191
8519
|
return {
|
|
8192
8520
|
async postMessage({ channel, text, threadTs }) {
|
|
8193
|
-
const res = await
|
|
8521
|
+
const res = await slackFetchWithRetry("https://slack.com/api/chat.postMessage", {
|
|
8194
8522
|
method: "POST",
|
|
8195
8523
|
headers: {
|
|
8196
8524
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -8203,6 +8531,12 @@ function getSlackAdapter() {
|
|
|
8203
8531
|
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
8204
8532
|
}
|
|
8205
8533
|
return { ok: true, ts: data?.ts };
|
|
8534
|
+
},
|
|
8535
|
+
addReaction({ channel, timestamp, name }) {
|
|
8536
|
+
return slackForm("reactions.add", { channel, timestamp, name });
|
|
8537
|
+
},
|
|
8538
|
+
removeReaction({ channel, timestamp, name }) {
|
|
8539
|
+
return slackForm("reactions.remove", { channel, timestamp, name });
|
|
8206
8540
|
}
|
|
8207
8541
|
};
|
|
8208
8542
|
}
|
|
@@ -8403,12 +8737,31 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8403
8737
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8404
8738
|
}
|
|
8405
8739
|
}
|
|
8406
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8740
|
+
var LOADING_REACTION, RESULT_REACTIONS, SLACK_FETCH_ATTEMPTS, SLACK_BACKOFF_BASE_MS, SLACK_BACKOFF_CAP_MS, REACTION_SOFT_ERRORS, pendingAdds, cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8407
8741
|
var init_client3 = __esm({
|
|
8408
8742
|
"src/integrations/slack/client.ts"() {
|
|
8409
8743
|
"use strict";
|
|
8410
8744
|
init_config();
|
|
8411
8745
|
init_persistence();
|
|
8746
|
+
LOADING_REACTION = "hourglass_flowing_sand";
|
|
8747
|
+
RESULT_REACTIONS = {
|
|
8748
|
+
responded: "white_check_mark",
|
|
8749
|
+
skipped: "zzz",
|
|
8750
|
+
handed_off: "eyes",
|
|
8751
|
+
failed: "warning"
|
|
8752
|
+
};
|
|
8753
|
+
SLACK_FETCH_ATTEMPTS = 3;
|
|
8754
|
+
SLACK_BACKOFF_BASE_MS = 400;
|
|
8755
|
+
SLACK_BACKOFF_CAP_MS = 3e4;
|
|
8756
|
+
REACTION_SOFT_ERRORS = /* @__PURE__ */ new Set([
|
|
8757
|
+
"already_reacted",
|
|
8758
|
+
// add: someone (or we) already added this emoji
|
|
8759
|
+
"no_reaction",
|
|
8760
|
+
// remove: the emoji isn't on the message
|
|
8761
|
+
"message_not_found"
|
|
8762
|
+
// remove/add: original message deleted
|
|
8763
|
+
]);
|
|
8764
|
+
pendingAdds = /* @__PURE__ */ new Map();
|
|
8412
8765
|
cachedSelf = null;
|
|
8413
8766
|
selfInflight = null;
|
|
8414
8767
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -8421,83 +8774,541 @@ var init_client3 = __esm({
|
|
|
8421
8774
|
}
|
|
8422
8775
|
});
|
|
8423
8776
|
|
|
8424
|
-
// src/
|
|
8425
|
-
function
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
}
|
|
8431
|
-
|
|
8432
|
-
|
|
8777
|
+
// src/agent/session-lock.ts
|
|
8778
|
+
async function withSessionLock(sessionId, fn) {
|
|
8779
|
+
let state2 = locks.get(sessionId);
|
|
8780
|
+
if (!state2) {
|
|
8781
|
+
state2 = { tail: Promise.resolve(), pending: 0 };
|
|
8782
|
+
locks.set(sessionId, state2);
|
|
8783
|
+
}
|
|
8784
|
+
state2.pending++;
|
|
8785
|
+
const prev = state2.tail;
|
|
8786
|
+
let release;
|
|
8787
|
+
const next = new Promise((resolve13) => {
|
|
8788
|
+
release = resolve13;
|
|
8789
|
+
});
|
|
8790
|
+
state2.tail = prev.then(() => next);
|
|
8791
|
+
await prev;
|
|
8792
|
+
try {
|
|
8793
|
+
return await fn();
|
|
8794
|
+
} finally {
|
|
8795
|
+
release();
|
|
8796
|
+
state2.pending--;
|
|
8797
|
+
if (state2.pending === 0 && locks.get(sessionId) === state2) {
|
|
8798
|
+
locks.delete(sessionId);
|
|
8799
|
+
}
|
|
8800
|
+
}
|
|
8433
8801
|
}
|
|
8434
|
-
function
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
8438
|
-
return false;
|
|
8802
|
+
function isSessionLocked(sessionId) {
|
|
8803
|
+
const s = locks.get(sessionId);
|
|
8804
|
+
return !!s && s.pending > 0;
|
|
8439
8805
|
}
|
|
8440
|
-
|
|
8441
|
-
|
|
8442
|
-
|
|
8443
|
-
|
|
8444
|
-
|
|
8445
|
-
return { event: null, dropReason: "bot_message" };
|
|
8446
|
-
}
|
|
8447
|
-
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
8448
|
-
return { event: null, dropReason: "ignored_subtype" };
|
|
8806
|
+
var locks;
|
|
8807
|
+
var init_session_lock = __esm({
|
|
8808
|
+
"src/agent/session-lock.ts"() {
|
|
8809
|
+
"use strict";
|
|
8810
|
+
locks = /* @__PURE__ */ new Map();
|
|
8449
8811
|
}
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8812
|
+
});
|
|
8813
|
+
|
|
8814
|
+
// src/orchestrator/webhook-events.ts
|
|
8815
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
8816
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
8817
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
8818
|
+
function logFilePath() {
|
|
8819
|
+
return join10(getAppDataDirectory(), "webhook-events.jsonl");
|
|
8820
|
+
}
|
|
8821
|
+
function ensureLoaded() {
|
|
8822
|
+
if (cache !== null) return cache;
|
|
8823
|
+
cache = [];
|
|
8824
|
+
try {
|
|
8825
|
+
const p = logFilePath();
|
|
8826
|
+
if (!existsSync17(p)) return cache;
|
|
8827
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
8828
|
+
for (const line of lines) {
|
|
8829
|
+
try {
|
|
8830
|
+
cache.push(JSON.parse(line));
|
|
8831
|
+
} catch {
|
|
8832
|
+
}
|
|
8457
8833
|
}
|
|
8458
|
-
if (
|
|
8459
|
-
|
|
8834
|
+
if (cache.length > MAX_EVENTS) {
|
|
8835
|
+
cache = cache.slice(-MAX_EVENTS);
|
|
8836
|
+
try {
|
|
8837
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
8838
|
+
} catch {
|
|
8839
|
+
}
|
|
8460
8840
|
}
|
|
8461
|
-
|
|
8841
|
+
} catch {
|
|
8462
8842
|
}
|
|
8463
|
-
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
const
|
|
8467
|
-
|
|
8468
|
-
if (
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
}
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
if (channelAllowlistActive && !policy.allowedChannels.includes(event.channel)) {
|
|
8475
|
-
return { event: null, dropReason: "channel_not_allowed" };
|
|
8476
|
-
}
|
|
8477
|
-
if (!userOk) {
|
|
8478
|
-
return { event: null, dropReason: "user_not_allowed" };
|
|
8479
|
-
}
|
|
8843
|
+
return cache;
|
|
8844
|
+
}
|
|
8845
|
+
function appendEvent(ev) {
|
|
8846
|
+
const list = ensureLoaded();
|
|
8847
|
+
list.push(ev);
|
|
8848
|
+
if (list.length > MAX_EVENTS) list.shift();
|
|
8849
|
+
try {
|
|
8850
|
+
const p = logFilePath();
|
|
8851
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
8852
|
+
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
8853
|
+
} catch {
|
|
8480
8854
|
}
|
|
8481
|
-
const ref = {
|
|
8482
|
-
channel: "slack",
|
|
8483
|
-
slackChannel: event.channel,
|
|
8484
|
-
threadTs: event.thread_ts || event.ts,
|
|
8485
|
-
teamId: event.team,
|
|
8486
|
-
user: event.user
|
|
8487
|
-
};
|
|
8488
|
-
const label = slackChannel.displayLabel(ref);
|
|
8489
|
-
return {
|
|
8490
|
-
event: {
|
|
8491
|
-
ref,
|
|
8492
|
-
content: `[${label}] ${text}`,
|
|
8493
|
-
wake: "now",
|
|
8494
|
-
enqueuedAt: /* @__PURE__ */ new Date()
|
|
8495
|
-
}
|
|
8496
|
-
};
|
|
8497
8855
|
}
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8856
|
+
function recordEvent(ev) {
|
|
8857
|
+
const full = {
|
|
8858
|
+
id: ev.id ?? nanoid4(),
|
|
8859
|
+
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8860
|
+
source: ev.source,
|
|
8861
|
+
status: ev.status,
|
|
8862
|
+
subtype: ev.subtype,
|
|
8863
|
+
channel: ev.channel,
|
|
8864
|
+
user: ev.user,
|
|
8865
|
+
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
8866
|
+
dropReason: ev.dropReason,
|
|
8867
|
+
error: ev.error,
|
|
8868
|
+
sessionId: ev.sessionId,
|
|
8869
|
+
durationMs: ev.durationMs,
|
|
8870
|
+
meta: ev.meta
|
|
8871
|
+
};
|
|
8872
|
+
appendEvent(full);
|
|
8873
|
+
return full.id;
|
|
8874
|
+
}
|
|
8875
|
+
function updateEvent(id, patch) {
|
|
8876
|
+
const list = ensureLoaded();
|
|
8877
|
+
const i = list.findIndex((e) => e.id === id);
|
|
8878
|
+
if (i < 0) return;
|
|
8879
|
+
list[i] = { ...list[i], ...patch };
|
|
8880
|
+
try {
|
|
8881
|
+
const p = logFilePath();
|
|
8882
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
8883
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
8884
|
+
} catch {
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8887
|
+
function listEvents(filter = {}) {
|
|
8888
|
+
const list = ensureLoaded();
|
|
8889
|
+
const q = filter.q?.toLowerCase();
|
|
8890
|
+
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
8891
|
+
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
8892
|
+
const matched = list.filter((e) => {
|
|
8893
|
+
if (filter.source && e.source !== filter.source) return false;
|
|
8894
|
+
if (filter.status && e.status !== filter.status) return false;
|
|
8895
|
+
const t = Date.parse(e.ts);
|
|
8896
|
+
if (t < sinceTs) return false;
|
|
8897
|
+
if (t >= beforeTs) return false;
|
|
8898
|
+
if (q) {
|
|
8899
|
+
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
8900
|
+
if (!hay.includes(q)) return false;
|
|
8901
|
+
}
|
|
8902
|
+
return true;
|
|
8903
|
+
});
|
|
8904
|
+
matched.reverse();
|
|
8905
|
+
const offset = Math.max(0, filter.offset ?? 0);
|
|
8906
|
+
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
8907
|
+
return {
|
|
8908
|
+
events: matched.slice(offset, offset + limit),
|
|
8909
|
+
total: matched.length
|
|
8910
|
+
};
|
|
8911
|
+
}
|
|
8912
|
+
function clearAllEvents() {
|
|
8913
|
+
cache = [];
|
|
8914
|
+
try {
|
|
8915
|
+
writeFileSync4(logFilePath(), "");
|
|
8916
|
+
} catch {
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
var MAX_EVENTS, cache;
|
|
8920
|
+
var init_webhook_events = __esm({
|
|
8921
|
+
"src/orchestrator/webhook-events.ts"() {
|
|
8922
|
+
"use strict";
|
|
8923
|
+
init_config();
|
|
8924
|
+
MAX_EVENTS = 1e3;
|
|
8925
|
+
cache = null;
|
|
8926
|
+
}
|
|
8927
|
+
});
|
|
8928
|
+
|
|
8929
|
+
// src/orchestrator/inbox.ts
|
|
8930
|
+
var inbox_exports = {};
|
|
8931
|
+
__export(inbox_exports, {
|
|
8932
|
+
clearInbox: () => clearInbox,
|
|
8933
|
+
flush: () => flush,
|
|
8934
|
+
peekInbox: () => peekInbox,
|
|
8935
|
+
pushToInbox: () => pushToInbox,
|
|
8936
|
+
setFlushHandler: () => setFlushHandler
|
|
8937
|
+
});
|
|
8938
|
+
function setFlushHandler(fn) {
|
|
8939
|
+
flushHandler = fn;
|
|
8940
|
+
}
|
|
8941
|
+
function entryFor(sessionId) {
|
|
8942
|
+
let e = inboxes.get(sessionId);
|
|
8943
|
+
if (!e) {
|
|
8944
|
+
e = { pending: [] };
|
|
8945
|
+
inboxes.set(sessionId, e);
|
|
8946
|
+
}
|
|
8947
|
+
return e;
|
|
8948
|
+
}
|
|
8949
|
+
function pushToInbox(orchestratorSessionId, event) {
|
|
8950
|
+
const e = entryFor(orchestratorSessionId);
|
|
8951
|
+
e.pending.push(event);
|
|
8952
|
+
try {
|
|
8953
|
+
trackInbound(orchestratorSessionId, event);
|
|
8954
|
+
} catch {
|
|
8955
|
+
}
|
|
8956
|
+
if (event.wake === "now") {
|
|
8957
|
+
scheduleFlush(orchestratorSessionId);
|
|
8958
|
+
}
|
|
8959
|
+
}
|
|
8960
|
+
function scheduleFlush(sessionId) {
|
|
8961
|
+
const e = inboxes.get(sessionId);
|
|
8962
|
+
if (!e) return;
|
|
8963
|
+
if (e.timer) clearTimeout(e.timer);
|
|
8964
|
+
e.timer = setTimeout(() => {
|
|
8965
|
+
void flush(sessionId);
|
|
8966
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
8967
|
+
}
|
|
8968
|
+
async function flush(sessionId) {
|
|
8969
|
+
const e = inboxes.get(sessionId);
|
|
8970
|
+
if (!e) return;
|
|
8971
|
+
if (e.timer) {
|
|
8972
|
+
clearTimeout(e.timer);
|
|
8973
|
+
e.timer = void 0;
|
|
8974
|
+
}
|
|
8975
|
+
const events = e.pending.splice(0);
|
|
8976
|
+
if (events.length === 0) return;
|
|
8977
|
+
if (!flushHandler) {
|
|
8978
|
+
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
8979
|
+
return;
|
|
8980
|
+
}
|
|
8981
|
+
try {
|
|
8982
|
+
await flushHandler(sessionId, events);
|
|
8983
|
+
} catch (err) {
|
|
8984
|
+
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
8985
|
+
}
|
|
8986
|
+
}
|
|
8987
|
+
function peekInbox(sessionId) {
|
|
8988
|
+
return inboxes.get(sessionId)?.pending.slice() ?? [];
|
|
8989
|
+
}
|
|
8990
|
+
function clearInbox(sessionId) {
|
|
8991
|
+
const e = inboxes.get(sessionId);
|
|
8992
|
+
if (!e) return;
|
|
8993
|
+
if (e.timer) {
|
|
8994
|
+
clearTimeout(e.timer);
|
|
8995
|
+
e.timer = void 0;
|
|
8996
|
+
}
|
|
8997
|
+
e.pending.length = 0;
|
|
8998
|
+
}
|
|
8999
|
+
var inboxes, FLUSH_DEBOUNCE_MS, flushHandler;
|
|
9000
|
+
var init_inbox = __esm({
|
|
9001
|
+
"src/orchestrator/inbox.ts"() {
|
|
9002
|
+
"use strict";
|
|
9003
|
+
init_inbox_acks();
|
|
9004
|
+
inboxes = /* @__PURE__ */ new Map();
|
|
9005
|
+
FLUSH_DEBOUNCE_MS = 200;
|
|
9006
|
+
flushHandler = null;
|
|
9007
|
+
}
|
|
9008
|
+
});
|
|
9009
|
+
|
|
9010
|
+
// src/orchestrator/inbox-acks.ts
|
|
9011
|
+
var inbox_acks_exports = {};
|
|
9012
|
+
__export(inbox_acks_exports, {
|
|
9013
|
+
MAX_ATTEMPTS: () => MAX_ATTEMPTS,
|
|
9014
|
+
RECONCILE_EVERY_MS: () => RECONCILE_EVERY_MS,
|
|
9015
|
+
REPLAY_AFTER_MS: () => REPLAY_AFTER_MS,
|
|
9016
|
+
__getAck: () => __getAck,
|
|
9017
|
+
__listAcks: () => __listAcks,
|
|
9018
|
+
__resetAcks: () => __resetAcks,
|
|
9019
|
+
eventKey: () => eventKey,
|
|
9020
|
+
markRespondedForThread: () => markRespondedForThread,
|
|
9021
|
+
markState: () => markState,
|
|
9022
|
+
reconcileOnce: () => reconcileOnce,
|
|
9023
|
+
resolveBatchOnTurnEnd: () => resolveBatchOnTurnEnd,
|
|
9024
|
+
startReconciler: () => startReconciler,
|
|
9025
|
+
stopReconciler: () => stopReconciler,
|
|
9026
|
+
trackInbound: () => trackInbound
|
|
9027
|
+
});
|
|
9028
|
+
function eventKey(event) {
|
|
9029
|
+
const ref = event.ref;
|
|
9030
|
+
const ch = ref?.channel ?? "unknown";
|
|
9031
|
+
switch (ch) {
|
|
9032
|
+
case "slack":
|
|
9033
|
+
return `slack${SEP}${ref.slackChannel}${SEP}${ref.messageTs ?? ref.threadTs ?? ""}`;
|
|
9034
|
+
case "system":
|
|
9035
|
+
return `system${SEP}${ref.workerId}${SEP}${ref.kind}`;
|
|
9036
|
+
case "webhook":
|
|
9037
|
+
return `webhook${SEP}${ref.webhookId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
9038
|
+
case "schedule":
|
|
9039
|
+
return `schedule${SEP}${ref.scheduleId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
9040
|
+
default:
|
|
9041
|
+
return `${ch}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}${SEP}${event.content.slice(0, 40)}`;
|
|
9042
|
+
}
|
|
9043
|
+
}
|
|
9044
|
+
function trackInbound(sessionId, event) {
|
|
9045
|
+
if (event.wake === "never") return null;
|
|
9046
|
+
const key2 = eventKey(event);
|
|
9047
|
+
const existing = ledger.get(key2);
|
|
9048
|
+
if (existing) return key2;
|
|
9049
|
+
const ref = event.ref;
|
|
9050
|
+
const now = Date.now();
|
|
9051
|
+
const entry2 = {
|
|
9052
|
+
key: key2,
|
|
9053
|
+
sessionId,
|
|
9054
|
+
event,
|
|
9055
|
+
channel: ref?.channel ?? "unknown",
|
|
9056
|
+
state: "working",
|
|
9057
|
+
attempts: 0,
|
|
9058
|
+
trackedAt: now,
|
|
9059
|
+
updatedAt: now
|
|
9060
|
+
};
|
|
9061
|
+
if (ref?.channel === "slack") {
|
|
9062
|
+
entry2.slackChannel = ref.slackChannel;
|
|
9063
|
+
entry2.threadTs = ref.threadTs;
|
|
9064
|
+
entry2.messageTs = ref.messageTs;
|
|
9065
|
+
}
|
|
9066
|
+
ledger.set(key2, entry2);
|
|
9067
|
+
if (ledger.size > MAX_ENTRIES) pruneOldest();
|
|
9068
|
+
return key2;
|
|
9069
|
+
}
|
|
9070
|
+
function markState(key2, state2) {
|
|
9071
|
+
const entry2 = ledger.get(key2);
|
|
9072
|
+
if (!entry2) return;
|
|
9073
|
+
if (TERMINAL.has(entry2.state)) return;
|
|
9074
|
+
entry2.state = state2;
|
|
9075
|
+
entry2.updatedAt = Date.now();
|
|
9076
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
9077
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, state2);
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
function markRespondedForThread(slackChannel2, threadTs) {
|
|
9081
|
+
if (!slackChannel2 || !threadTs) return;
|
|
9082
|
+
for (const entry2 of ledger.values()) {
|
|
9083
|
+
if (entry2.channel === "slack" && entry2.state === "working" && entry2.slackChannel === slackChannel2 && entry2.threadTs === threadTs) {
|
|
9084
|
+
markState(entry2.key, "responded");
|
|
9085
|
+
}
|
|
9086
|
+
}
|
|
9087
|
+
}
|
|
9088
|
+
function resolveBatchOnTurnEnd(events, ok) {
|
|
9089
|
+
if (!ok) return;
|
|
9090
|
+
for (const ev of events) {
|
|
9091
|
+
const key2 = eventKey(ev);
|
|
9092
|
+
const entry2 = ledger.get(key2);
|
|
9093
|
+
if (!entry2 || entry2.state !== "working") continue;
|
|
9094
|
+
if (entry2.channel === "slack") continue;
|
|
9095
|
+
markState(key2, "responded");
|
|
9096
|
+
}
|
|
9097
|
+
}
|
|
9098
|
+
async function reconcileOnce(now = Date.now()) {
|
|
9099
|
+
let pushToInbox2 = null;
|
|
9100
|
+
const toReplay = [];
|
|
9101
|
+
for (const entry2 of ledger.values()) {
|
|
9102
|
+
if (TERMINAL.has(entry2.state)) {
|
|
9103
|
+
if (now - entry2.updatedAt > PRUNE_AFTER_MS) ledger.delete(entry2.key);
|
|
9104
|
+
continue;
|
|
9105
|
+
}
|
|
9106
|
+
if (isSessionLocked(entry2.sessionId)) continue;
|
|
9107
|
+
if (now - entry2.updatedAt < REPLAY_AFTER_MS) continue;
|
|
9108
|
+
if (entry2.attempts >= MAX_ATTEMPTS) {
|
|
9109
|
+
failEntry(entry2);
|
|
9110
|
+
continue;
|
|
9111
|
+
}
|
|
9112
|
+
toReplay.push(entry2);
|
|
9113
|
+
}
|
|
9114
|
+
if (toReplay.length === 0) return;
|
|
9115
|
+
try {
|
|
9116
|
+
({ pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports)));
|
|
9117
|
+
} catch {
|
|
9118
|
+
return;
|
|
9119
|
+
}
|
|
9120
|
+
for (const entry2 of toReplay) {
|
|
9121
|
+
entry2.attempts += 1;
|
|
9122
|
+
entry2.updatedAt = Date.now();
|
|
9123
|
+
const nudged = {
|
|
9124
|
+
...entry2.event,
|
|
9125
|
+
content: `[REPLAY attempt ${entry2.attempts}/${MAX_ATTEMPTS} \u2014 you received this but have not yet replied to it or marked it handled. Respond now on the originating channel; if it genuinely needs no reply, you can ignore it.]
|
|
9126
|
+
${entry2.event.content}`,
|
|
9127
|
+
wake: "now"
|
|
9128
|
+
};
|
|
9129
|
+
try {
|
|
9130
|
+
pushToInbox2(entry2.sessionId, nudged);
|
|
9131
|
+
} catch {
|
|
9132
|
+
}
|
|
9133
|
+
}
|
|
9134
|
+
}
|
|
9135
|
+
function failEntry(entry2) {
|
|
9136
|
+
entry2.state = "failed";
|
|
9137
|
+
entry2.updatedAt = Date.now();
|
|
9138
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
9139
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, "failed");
|
|
9140
|
+
if (entry2.threadTs) {
|
|
9141
|
+
fireFallback(
|
|
9142
|
+
entry2.slackChannel,
|
|
9143
|
+
entry2.threadTs,
|
|
9144
|
+
`:warning: I wasn't able to handle this after ${entry2.attempts} attempt(s). It may need a human \u2014 flagging it here so it isn't lost.`
|
|
9145
|
+
);
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
recordEvent({
|
|
9149
|
+
source: "daemon",
|
|
9150
|
+
status: "failed",
|
|
9151
|
+
channel: entry2.channel,
|
|
9152
|
+
sessionId: entry2.sessionId,
|
|
9153
|
+
error: `unacknowledged after ${entry2.attempts} replay attempt(s)`,
|
|
9154
|
+
textSnippet: entry2.event.content.slice(0, 200),
|
|
9155
|
+
meta: { ackKey: entry2.key, ackState: "failed" }
|
|
9156
|
+
});
|
|
9157
|
+
}
|
|
9158
|
+
function pruneOldest() {
|
|
9159
|
+
const terminal = [];
|
|
9160
|
+
for (const e of ledger.values()) if (TERMINAL.has(e.state)) terminal.push(e);
|
|
9161
|
+
terminal.sort((a, b) => a.updatedAt - b.updatedAt);
|
|
9162
|
+
for (const e of terminal) {
|
|
9163
|
+
if (ledger.size <= MAX_ENTRIES) break;
|
|
9164
|
+
ledger.delete(e.key);
|
|
9165
|
+
}
|
|
9166
|
+
while (ledger.size > MAX_ENTRIES) {
|
|
9167
|
+
const oldest = ledger.keys().next().value;
|
|
9168
|
+
if (!oldest) break;
|
|
9169
|
+
ledger.delete(oldest);
|
|
9170
|
+
}
|
|
9171
|
+
}
|
|
9172
|
+
function fireResultReaction(channel, ts, state2) {
|
|
9173
|
+
if (typeof addResultReaction !== "function") return;
|
|
9174
|
+
try {
|
|
9175
|
+
void Promise.resolve(addResultReaction(channel, ts, state2)).catch(() => {
|
|
9176
|
+
});
|
|
9177
|
+
} catch {
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
function fireFallback(channel, threadTs, text) {
|
|
9181
|
+
if (typeof postThreadMessage !== "function") return;
|
|
9182
|
+
try {
|
|
9183
|
+
void Promise.resolve(postThreadMessage(channel, threadTs, text)).catch(() => {
|
|
9184
|
+
});
|
|
9185
|
+
} catch {
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
function startReconciler() {
|
|
9189
|
+
if (reconcileTimer) return;
|
|
9190
|
+
reconcileTimer = setInterval(() => {
|
|
9191
|
+
void reconcileOnce();
|
|
9192
|
+
}, RECONCILE_EVERY_MS);
|
|
9193
|
+
if (typeof reconcileTimer.unref === "function") reconcileTimer.unref();
|
|
9194
|
+
}
|
|
9195
|
+
function stopReconciler() {
|
|
9196
|
+
if (reconcileTimer) {
|
|
9197
|
+
clearInterval(reconcileTimer);
|
|
9198
|
+
reconcileTimer = null;
|
|
9199
|
+
}
|
|
9200
|
+
}
|
|
9201
|
+
function __getAck(key2) {
|
|
9202
|
+
return ledger.get(key2);
|
|
9203
|
+
}
|
|
9204
|
+
function __listAcks() {
|
|
9205
|
+
return [...ledger.values()];
|
|
9206
|
+
}
|
|
9207
|
+
function __resetAcks() {
|
|
9208
|
+
ledger.clear();
|
|
9209
|
+
}
|
|
9210
|
+
var REPLAY_AFTER_MS, RECONCILE_EVERY_MS, MAX_ATTEMPTS, PRUNE_AFTER_MS, MAX_ENTRIES, TERMINAL, SEP, ledger, reconcileTimer;
|
|
9211
|
+
var init_inbox_acks = __esm({
|
|
9212
|
+
"src/orchestrator/inbox-acks.ts"() {
|
|
9213
|
+
"use strict";
|
|
9214
|
+
init_session_lock();
|
|
9215
|
+
init_webhook_events();
|
|
9216
|
+
init_client3();
|
|
9217
|
+
REPLAY_AFTER_MS = 3 * 6e4;
|
|
9218
|
+
RECONCILE_EVERY_MS = 6e4;
|
|
9219
|
+
MAX_ATTEMPTS = 2;
|
|
9220
|
+
PRUNE_AFTER_MS = 60 * 6e4;
|
|
9221
|
+
MAX_ENTRIES = 5e3;
|
|
9222
|
+
TERMINAL = /* @__PURE__ */ new Set(["responded", "skipped", "handed_off", "failed"]);
|
|
9223
|
+
SEP = "\u241F";
|
|
9224
|
+
ledger = /* @__PURE__ */ new Map();
|
|
9225
|
+
reconcileTimer = null;
|
|
9226
|
+
}
|
|
9227
|
+
});
|
|
9228
|
+
|
|
9229
|
+
// src/integrations/channels/slack.ts
|
|
9230
|
+
function threadKey(channel, threadTs) {
|
|
9231
|
+
return `${channel}\u241F${threadTs}`;
|
|
9232
|
+
}
|
|
9233
|
+
function markThreadOwned(channel, threadTs) {
|
|
9234
|
+
ownedThreads.add(threadKey(channel, threadTs));
|
|
9235
|
+
}
|
|
9236
|
+
function isThreadOwned(channel, threadTs) {
|
|
9237
|
+
return ownedThreads.has(threadKey(channel, threadTs));
|
|
9238
|
+
}
|
|
9239
|
+
function isSelfAuthored(event, self) {
|
|
9240
|
+
if (!self) return true;
|
|
9241
|
+
if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
|
|
9242
|
+
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
9243
|
+
return false;
|
|
9244
|
+
}
|
|
9245
|
+
function slackEventToInboundResult(event, opts = {}) {
|
|
9246
|
+
if (!event) return { event: null, dropReason: "empty_text" };
|
|
9247
|
+
const self = opts.self ?? getCachedSlackSelfIdentity();
|
|
9248
|
+
const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
|
|
9249
|
+
if (isBotAuthored && isSelfAuthored(event, self)) {
|
|
9250
|
+
return { event: null, dropReason: "bot_message" };
|
|
9251
|
+
}
|
|
9252
|
+
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
9253
|
+
return { event: null, dropReason: "ignored_subtype" };
|
|
9254
|
+
}
|
|
9255
|
+
const isDm = event.type === "message" && event.channel_type === "im";
|
|
9256
|
+
const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
|
|
9257
|
+
const isNonThreadChannelMsg = event.type === "message" && !isDm && !isThreadReply && (event.channel_type === "channel" || event.channel_type === "group" || event.channel_type === "mpim" || // Some payload shapes omit channel_type for channel messages.
|
|
9258
|
+
typeof event.channel === "string");
|
|
9259
|
+
if (event.type !== "app_mention" && !isDm && !isThreadReply) {
|
|
9260
|
+
if (isNonThreadChannelMsg) {
|
|
9261
|
+
return { event: null, dropReason: "non_thread_channel_msg" };
|
|
9262
|
+
}
|
|
9263
|
+
if (event.type !== "message") {
|
|
9264
|
+
return { event: null, dropReason: "non_message_event" };
|
|
9265
|
+
}
|
|
9266
|
+
return { event: null, dropReason: "unsupported_type" };
|
|
9267
|
+
}
|
|
9268
|
+
const text = (event.text ?? "").trim();
|
|
9269
|
+
const hasFiles = Array.isArray(event.files) && event.files.length > 0;
|
|
9270
|
+
if (!text && !hasFiles) return { event: null, dropReason: "empty_text" };
|
|
9271
|
+
const policy = getSlackAllowlistPolicy();
|
|
9272
|
+
const userAllowlistActive = policy.allowedUsers.length > 0;
|
|
9273
|
+
const userOk = !userAllowlistActive || event.user && policy.allowedUsers.includes(event.user);
|
|
9274
|
+
if (isDm) {
|
|
9275
|
+
if (!policy.allowDmsFromAnyone && !(event.user && policy.allowedUsers.includes(event.user))) {
|
|
9276
|
+
return { event: null, dropReason: "dm_blocked" };
|
|
9277
|
+
}
|
|
9278
|
+
} else {
|
|
9279
|
+
const channelAllowlistActive = policy.allowedChannels.length > 0;
|
|
9280
|
+
if (channelAllowlistActive && !policy.allowedChannels.includes(event.channel)) {
|
|
9281
|
+
return { event: null, dropReason: "channel_not_allowed" };
|
|
9282
|
+
}
|
|
9283
|
+
if (!userOk) {
|
|
9284
|
+
return { event: null, dropReason: "user_not_allowed" };
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9287
|
+
const ref = {
|
|
9288
|
+
channel: "slack",
|
|
9289
|
+
slackChannel: event.channel,
|
|
9290
|
+
// For thread replies, threadTs points at the parent (so our reply
|
|
9291
|
+
// continues the thread). messageTs is the inbound message's own ts —
|
|
9292
|
+
// used by reaction add/remove (which target the message itself, not
|
|
9293
|
+
// its parent) and any future "edit this message" operations.
|
|
9294
|
+
threadTs: event.thread_ts || event.ts,
|
|
9295
|
+
messageTs: event.ts,
|
|
9296
|
+
teamId: event.team,
|
|
9297
|
+
user: event.user
|
|
9298
|
+
};
|
|
9299
|
+
const label = slackChannel.displayLabel(ref);
|
|
9300
|
+
return {
|
|
9301
|
+
event: {
|
|
9302
|
+
ref,
|
|
9303
|
+
content: `[${label}] ${text}`,
|
|
9304
|
+
wake: "now",
|
|
9305
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
9306
|
+
}
|
|
9307
|
+
};
|
|
9308
|
+
}
|
|
9309
|
+
var ownedThreads, slackChannel, IGNORED_MESSAGE_SUBTYPES;
|
|
9310
|
+
var init_slack = __esm({
|
|
9311
|
+
"src/integrations/channels/slack.ts"() {
|
|
8501
9312
|
"use strict";
|
|
8502
9313
|
init_client3();
|
|
8503
9314
|
ownedThreads = /* @__PURE__ */ new Set();
|
|
@@ -8517,6 +9328,8 @@ var init_slack = __esm({
|
|
|
8517
9328
|
if (r.slackChannel && r.threadTs) {
|
|
8518
9329
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
8519
9330
|
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
9331
|
+
void Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports)).then((m) => m.markRespondedForThread(r.slackChannel, r.threadTs)).catch(() => {
|
|
9332
|
+
});
|
|
8520
9333
|
}
|
|
8521
9334
|
},
|
|
8522
9335
|
displayLabel(ref) {
|
|
@@ -8549,8 +9362,14 @@ var init_slack = __esm({
|
|
|
8549
9362
|
// also-broadcast-to-channel replies; the regular thread reply already fires
|
|
8550
9363
|
"message_replied",
|
|
8551
9364
|
// legacy parent-thread bump
|
|
8552
|
-
|
|
8553
|
-
//
|
|
9365
|
+
// NOTE: `file_share` is intentionally NOT ignored. It's the subtype Slack
|
|
9366
|
+
// attaches to a normal user message that includes file uploads — the
|
|
9367
|
+
// event still has `text`, `user`, `channel`, and `files: [...]`. The
|
|
9368
|
+
// route handler (src/server/routes/slack.ts) hands the `files[]` array
|
|
9369
|
+
// to `ingestSlackFiles` (src/integrations/slack/files.ts), which
|
|
9370
|
+
// downloads each file using the bot token, re-uploads it to GCS via
|
|
9371
|
+
// the remote-server's /storage/upload-url endpoint, and appends the
|
|
9372
|
+
// resulting short public URLs to the inbound message content.
|
|
8554
9373
|
"reply_broadcast",
|
|
8555
9374
|
"tombstone",
|
|
8556
9375
|
"huddle_thread"
|
|
@@ -8759,7 +9578,7 @@ var init_messenger = __esm({
|
|
|
8759
9578
|
});
|
|
8760
9579
|
|
|
8761
9580
|
// src/orchestrator/schedules-store.ts
|
|
8762
|
-
import { nanoid as
|
|
9581
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
8763
9582
|
async function readOrch(orchestratorSessionId) {
|
|
8764
9583
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
8765
9584
|
if (!s) return null;
|
|
@@ -8774,7 +9593,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
8774
9593
|
const data = await readOrch(orchestratorSessionId);
|
|
8775
9594
|
if (!data) throw new Error("orchestrator session not found");
|
|
8776
9595
|
const row = {
|
|
8777
|
-
id: `sch_${
|
|
9596
|
+
id: `sch_${nanoid5(10)}`,
|
|
8778
9597
|
name: input.name,
|
|
8779
9598
|
cron: input.cron,
|
|
8780
9599
|
prompt: input.prompt,
|
|
@@ -8811,7 +9630,7 @@ var init_schedules_store = __esm({
|
|
|
8811
9630
|
|
|
8812
9631
|
// src/orchestrator/webhooks-store.ts
|
|
8813
9632
|
import { randomBytes } from "crypto";
|
|
8814
|
-
import { nanoid as
|
|
9633
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8815
9634
|
function newToken() {
|
|
8816
9635
|
return randomBytes(24).toString("base64url");
|
|
8817
9636
|
}
|
|
@@ -8828,7 +9647,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
8828
9647
|
const data = await readOrch2(orchestratorSessionId);
|
|
8829
9648
|
if (!data) throw new Error("orchestrator session not found");
|
|
8830
9649
|
const row = {
|
|
8831
|
-
id: `whk_${
|
|
9650
|
+
id: `whk_${nanoid6(10)}`,
|
|
8832
9651
|
name: input.name,
|
|
8833
9652
|
token: newToken(),
|
|
8834
9653
|
wake: input.wake ?? "now",
|
|
@@ -8973,7 +9792,9 @@ function buildAgentTool(opts) {
|
|
|
8973
9792
|
workingDirectory: input.workingDirectory ?? opts.defaultWorkingDirectory,
|
|
8974
9793
|
name: input.name,
|
|
8975
9794
|
maxIterations: input.maxIterations ?? 100,
|
|
8976
|
-
orchestratorSessionId: opts.orchestratorSessionId
|
|
9795
|
+
orchestratorSessionId: opts.orchestratorSessionId,
|
|
9796
|
+
...input.mcpServers ? { mcpServers: input.mcpServers } : {},
|
|
9797
|
+
...input.skills ? { skills: input.skills } : {}
|
|
8977
9798
|
}
|
|
8978
9799
|
});
|
|
8979
9800
|
return {
|
|
@@ -9146,6 +9967,26 @@ var init_orchestrator_actions = __esm({
|
|
|
9146
9967
|
model: z14.string().optional().describe("spawn only: model override."),
|
|
9147
9968
|
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
9148
9969
|
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
9970
|
+
mcpServers: z14.array(
|
|
9971
|
+
z14.object({
|
|
9972
|
+
name: z14.string(),
|
|
9973
|
+
transport: z14.enum(["http", "sse", "stdio"]),
|
|
9974
|
+
url: z14.string().optional(),
|
|
9975
|
+
headers: z14.record(z14.string(), z14.string()).optional(),
|
|
9976
|
+
command: z14.string().optional(),
|
|
9977
|
+
args: z14.array(z14.string()).optional(),
|
|
9978
|
+
env: z14.record(z14.string(), z14.string()).optional()
|
|
9979
|
+
})
|
|
9980
|
+
).optional().describe("spawn only: task-scoped MCP servers (with auth headers) connected for this worker only, tools exposed as mcp_<name>_<tool>."),
|
|
9981
|
+
skills: z14.array(
|
|
9982
|
+
z14.object({
|
|
9983
|
+
name: z14.string(),
|
|
9984
|
+
description: z14.string().optional(),
|
|
9985
|
+
content: z14.string(),
|
|
9986
|
+
alwaysApply: z14.boolean().optional(),
|
|
9987
|
+
globs: z14.array(z14.string()).optional()
|
|
9988
|
+
})
|
|
9989
|
+
).optional().describe("spawn only: task-scoped skills (inline markdown) available to this worker only."),
|
|
9149
9990
|
// message
|
|
9150
9991
|
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
9151
9992
|
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
@@ -9185,9 +10026,9 @@ var init_orchestrator_actions = __esm({
|
|
|
9185
10026
|
});
|
|
9186
10027
|
|
|
9187
10028
|
// src/integrations/mcp/store.ts
|
|
9188
|
-
import { nanoid as
|
|
9189
|
-
import { existsSync as
|
|
9190
|
-
import { resolve as resolve10, join as
|
|
10029
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
10030
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
|
|
10031
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
9191
10032
|
function readServers() {
|
|
9192
10033
|
try {
|
|
9193
10034
|
const cfg = getConfig();
|
|
@@ -9199,12 +10040,12 @@ function readServers() {
|
|
|
9199
10040
|
function refreshMcpServersFromDisk() {
|
|
9200
10041
|
const candidates = [
|
|
9201
10042
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9202
|
-
|
|
10043
|
+
join11(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9203
10044
|
];
|
|
9204
10045
|
for (const path of candidates) {
|
|
9205
|
-
if (!
|
|
10046
|
+
if (!existsSync18(path)) continue;
|
|
9206
10047
|
try {
|
|
9207
|
-
const raw = JSON.parse(
|
|
10048
|
+
const raw = JSON.parse(readFileSync9(path, "utf-8"));
|
|
9208
10049
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9209
10050
|
setMcpServers(servers2);
|
|
9210
10051
|
return servers2;
|
|
@@ -9223,7 +10064,7 @@ function createMcpServer(input) {
|
|
|
9223
10064
|
const all = readServers();
|
|
9224
10065
|
validateInput(input);
|
|
9225
10066
|
const row = {
|
|
9226
|
-
id: `mcp_${
|
|
10067
|
+
id: `mcp_${nanoid7(10)}`,
|
|
9227
10068
|
name: sanitizeName(input.name),
|
|
9228
10069
|
transport: input.transport,
|
|
9229
10070
|
url: input.url,
|
|
@@ -9394,6 +10235,159 @@ var init_pool = __esm({
|
|
|
9394
10235
|
}
|
|
9395
10236
|
});
|
|
9396
10237
|
|
|
10238
|
+
// src/integrations/mcp/task-scoped.ts
|
|
10239
|
+
import { createMCPClient as createMCPClient2 } from "@ai-sdk/mcp";
|
|
10240
|
+
function sanitizeName2(raw) {
|
|
10241
|
+
return raw.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/_+/g, "_");
|
|
10242
|
+
}
|
|
10243
|
+
function buildHttpLikeTransport(server) {
|
|
10244
|
+
if (!server.url) {
|
|
10245
|
+
throw new Error(`${server.transport} transport requires a url`);
|
|
10246
|
+
}
|
|
10247
|
+
return {
|
|
10248
|
+
type: server.transport,
|
|
10249
|
+
url: server.url,
|
|
10250
|
+
headers: server.headers
|
|
10251
|
+
};
|
|
10252
|
+
}
|
|
10253
|
+
async function buildStdioTransport2(server) {
|
|
10254
|
+
if (!server.command) {
|
|
10255
|
+
throw new Error("stdio transport requires a command");
|
|
10256
|
+
}
|
|
10257
|
+
const mod = await import("@ai-sdk/mcp/mcp-stdio");
|
|
10258
|
+
const Cls = mod.Experimental_StdioMCPTransport || mod.StdioClientTransport;
|
|
10259
|
+
if (!Cls) throw new Error("@ai-sdk/mcp/mcp-stdio is missing the stdio transport class");
|
|
10260
|
+
return new Cls({
|
|
10261
|
+
command: server.command,
|
|
10262
|
+
args: server.args ?? [],
|
|
10263
|
+
env: server.env
|
|
10264
|
+
});
|
|
10265
|
+
}
|
|
10266
|
+
async function buildTransport(server) {
|
|
10267
|
+
return server.transport === "stdio" ? await buildStdioTransport2(server) : buildHttpLikeTransport(server);
|
|
10268
|
+
}
|
|
10269
|
+
async function connectTaskMcpServers(servers2, opts = {}) {
|
|
10270
|
+
const tools = {};
|
|
10271
|
+
const connected = [];
|
|
10272
|
+
const errors = [];
|
|
10273
|
+
const clients2 = [];
|
|
10274
|
+
for (const raw of servers2 ?? []) {
|
|
10275
|
+
const name = sanitizeName2(raw.name || "");
|
|
10276
|
+
if (!name) {
|
|
10277
|
+
errors.push({ name: raw.name || "(unnamed)", error: "server name is required" });
|
|
10278
|
+
continue;
|
|
10279
|
+
}
|
|
10280
|
+
let client = null;
|
|
10281
|
+
try {
|
|
10282
|
+
const transport = await buildTransport(raw);
|
|
10283
|
+
client = await createMCPClient2({ transport });
|
|
10284
|
+
clients2.push(client);
|
|
10285
|
+
const serverTools = await client.tools();
|
|
10286
|
+
for (const [toolName, t] of Object.entries(serverTools)) {
|
|
10287
|
+
tools[`mcp_${name}_${toolName}`] = t;
|
|
10288
|
+
}
|
|
10289
|
+
connected.push(name);
|
|
10290
|
+
} catch (err) {
|
|
10291
|
+
const message = err?.message || String(err);
|
|
10292
|
+
errors.push({ name, error: message });
|
|
10293
|
+
if (!opts.quiet) {
|
|
10294
|
+
console.warn(`[mcp:task] connecting "${name}" failed: ${message}`);
|
|
10295
|
+
}
|
|
10296
|
+
if (client) {
|
|
10297
|
+
try {
|
|
10298
|
+
await client.close();
|
|
10299
|
+
} catch {
|
|
10300
|
+
}
|
|
10301
|
+
const idx = clients2.indexOf(client);
|
|
10302
|
+
if (idx >= 0) clients2.splice(idx, 1);
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
10305
|
+
}
|
|
10306
|
+
let closed = false;
|
|
10307
|
+
const close = async () => {
|
|
10308
|
+
if (closed) return;
|
|
10309
|
+
closed = true;
|
|
10310
|
+
await Promise.all(
|
|
10311
|
+
clients2.map(async (c) => {
|
|
10312
|
+
try {
|
|
10313
|
+
await c.close();
|
|
10314
|
+
} catch {
|
|
10315
|
+
}
|
|
10316
|
+
})
|
|
10317
|
+
);
|
|
10318
|
+
};
|
|
10319
|
+
return { tools, connected, errors, close };
|
|
10320
|
+
}
|
|
10321
|
+
var init_task_scoped = __esm({
|
|
10322
|
+
"src/integrations/mcp/task-scoped.ts"() {
|
|
10323
|
+
"use strict";
|
|
10324
|
+
}
|
|
10325
|
+
});
|
|
10326
|
+
|
|
10327
|
+
// src/skills/task-scoped.ts
|
|
10328
|
+
import { mkdtemp, writeFile as writeFile5, rm } from "fs/promises";
|
|
10329
|
+
import { tmpdir } from "os";
|
|
10330
|
+
import { join as join12 } from "path";
|
|
10331
|
+
function safeFileName(name, index) {
|
|
10332
|
+
const base = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
10333
|
+
return `${base || `skill-${index + 1}`}.md`;
|
|
10334
|
+
}
|
|
10335
|
+
function escapeFrontmatterValue(value) {
|
|
10336
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
10337
|
+
}
|
|
10338
|
+
function buildSkillFile(skill) {
|
|
10339
|
+
const lines = ["---"];
|
|
10340
|
+
lines.push(`name: ${escapeFrontmatterValue(skill.name)}`);
|
|
10341
|
+
lines.push(`description: ${escapeFrontmatterValue(skill.description || skill.name)}`);
|
|
10342
|
+
if (skill.alwaysApply) lines.push("alwaysApply: true");
|
|
10343
|
+
if (skill.globs && skill.globs.length > 0) {
|
|
10344
|
+
lines.push(`globs: [${skill.globs.map((g) => escapeFrontmatterValue(g)).join(", ")}]`);
|
|
10345
|
+
}
|
|
10346
|
+
lines.push("---");
|
|
10347
|
+
lines.push("");
|
|
10348
|
+
lines.push(skill.content);
|
|
10349
|
+
return lines.join("\n");
|
|
10350
|
+
}
|
|
10351
|
+
async function materializeTaskSkills(skills2, taskId) {
|
|
10352
|
+
if (!skills2 || skills2.length === 0) return null;
|
|
10353
|
+
const safeTaskId = taskId.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
10354
|
+
const dir = await mkdtemp(join12(tmpdir(), `sparkecoder-task-skills-${safeTaskId}-`));
|
|
10355
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10356
|
+
await Promise.all(
|
|
10357
|
+
skills2.map(async (skill, i) => {
|
|
10358
|
+
let fileName = safeFileName(skill.name, i);
|
|
10359
|
+
while (seen.has(fileName)) fileName = `dup-${i}-${fileName}`;
|
|
10360
|
+
seen.add(fileName);
|
|
10361
|
+
await writeFile5(join12(dir, fileName), buildSkillFile(skill), "utf-8");
|
|
10362
|
+
})
|
|
10363
|
+
);
|
|
10364
|
+
const loaded2 = await loadSkillsFromDirectory(dir, { priority: 1, defaultLoadType: "on_demand" });
|
|
10365
|
+
const alwaysSkills = loaded2.filter((s) => s.alwaysApply || s.loadType === "always");
|
|
10366
|
+
const onDemand = loaded2.filter((s) => !(s.alwaysApply || s.loadType === "always"));
|
|
10367
|
+
const always = (await Promise.all(
|
|
10368
|
+
alwaysSkills.map(async (s) => {
|
|
10369
|
+
const withContent = await loadSkillContent(s.name, [dir]);
|
|
10370
|
+
return withContent ? { ...s, content: withContent.content } : null;
|
|
10371
|
+
})
|
|
10372
|
+
)).filter((s) => s !== null);
|
|
10373
|
+
let cleaned = false;
|
|
10374
|
+
const cleanup2 = async () => {
|
|
10375
|
+
if (cleaned) return;
|
|
10376
|
+
cleaned = true;
|
|
10377
|
+
try {
|
|
10378
|
+
await rm(dir, { recursive: true, force: true });
|
|
10379
|
+
} catch {
|
|
10380
|
+
}
|
|
10381
|
+
};
|
|
10382
|
+
return { dir, always, onDemand, cleanup: cleanup2 };
|
|
10383
|
+
}
|
|
10384
|
+
var init_task_scoped2 = __esm({
|
|
10385
|
+
"src/skills/task-scoped.ts"() {
|
|
10386
|
+
"use strict";
|
|
10387
|
+
init_skills();
|
|
10388
|
+
}
|
|
10389
|
+
});
|
|
10390
|
+
|
|
9397
10391
|
// src/utils/webhook.ts
|
|
9398
10392
|
var webhook_exports = {};
|
|
9399
10393
|
__export(webhook_exports, {
|
|
@@ -9552,79 +10546,57 @@ var init_pending_input = __esm({
|
|
|
9552
10546
|
}
|
|
9553
10547
|
});
|
|
9554
10548
|
|
|
9555
|
-
// src/
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
if (!e) {
|
|
9570
|
-
e = { pending: [] };
|
|
9571
|
-
inboxes.set(sessionId, e);
|
|
9572
|
-
}
|
|
9573
|
-
return e;
|
|
10549
|
+
// src/utils/local-device-time.ts
|
|
10550
|
+
function formatLocalDeviceTimeLine(now = /* @__PURE__ */ new Date()) {
|
|
10551
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
10552
|
+
const formatted = now.toLocaleString("en-US", {
|
|
10553
|
+
weekday: "long",
|
|
10554
|
+
year: "numeric",
|
|
10555
|
+
month: "long",
|
|
10556
|
+
day: "numeric",
|
|
10557
|
+
hour: "numeric",
|
|
10558
|
+
minute: "2-digit",
|
|
10559
|
+
second: "2-digit",
|
|
10560
|
+
timeZoneName: "short"
|
|
10561
|
+
});
|
|
10562
|
+
return `${LOCAL_TIME_MARKER} ${formatted} (${timeZone})]`;
|
|
9574
10563
|
}
|
|
9575
|
-
function
|
|
9576
|
-
|
|
9577
|
-
e.pending.push(event);
|
|
9578
|
-
if (event.wake === "now") {
|
|
9579
|
-
scheduleFlush(orchestratorSessionId);
|
|
9580
|
-
}
|
|
10564
|
+
function hasLocalDeviceTimeLine(text) {
|
|
10565
|
+
return text.includes(LOCAL_TIME_MARKER);
|
|
9581
10566
|
}
|
|
9582
|
-
function
|
|
9583
|
-
const
|
|
9584
|
-
if (!
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
void flush(sessionId);
|
|
9588
|
-
}, FLUSH_DEBOUNCE_MS);
|
|
10567
|
+
function prependLocalDeviceTimeToUserMessage(text, now) {
|
|
10568
|
+
const trimmed = text.trim();
|
|
10569
|
+
if (!trimmed || hasLocalDeviceTimeLine(text)) return text;
|
|
10570
|
+
return `${formatLocalDeviceTimeLine(now)}
|
|
10571
|
+
${text}`;
|
|
9589
10572
|
}
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
if (e.timer) {
|
|
9594
|
-
clearTimeout(e.timer);
|
|
9595
|
-
e.timer = void 0;
|
|
9596
|
-
}
|
|
9597
|
-
const events = e.pending.splice(0);
|
|
9598
|
-
if (events.length === 0) return;
|
|
9599
|
-
if (!flushHandler) {
|
|
9600
|
-
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
9601
|
-
return;
|
|
10573
|
+
function prependLocalDeviceTimeToUserContent(content, now) {
|
|
10574
|
+
if (typeof content === "string") {
|
|
10575
|
+
return prependLocalDeviceTimeToUserMessage(content, now);
|
|
9602
10576
|
}
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
10577
|
+
const line = formatLocalDeviceTimeLine(now);
|
|
10578
|
+
if (content.some((p) => p.type === "text" && p.text && hasLocalDeviceTimeLine(p.text))) {
|
|
10579
|
+
return content;
|
|
9607
10580
|
}
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
10581
|
+
const userIdx = content.findIndex(
|
|
10582
|
+
(p) => p.type === "text" && p.text?.includes("[USER MESSAGE]")
|
|
10583
|
+
);
|
|
10584
|
+
if (userIdx >= 0 && content[userIdx].text) {
|
|
10585
|
+
const copy = content.map((p) => ({ ...p }));
|
|
10586
|
+
copy[userIdx] = {
|
|
10587
|
+
...copy[userIdx],
|
|
10588
|
+
text: `${line}
|
|
10589
|
+
${copy[userIdx].text}`
|
|
10590
|
+
};
|
|
10591
|
+
return copy;
|
|
9618
10592
|
}
|
|
9619
|
-
|
|
10593
|
+
return [{ type: "text", text: line }, ...content];
|
|
9620
10594
|
}
|
|
9621
|
-
var
|
|
9622
|
-
var
|
|
9623
|
-
"src/
|
|
10595
|
+
var LOCAL_TIME_MARKER;
|
|
10596
|
+
var init_local_device_time = __esm({
|
|
10597
|
+
"src/utils/local-device-time.ts"() {
|
|
9624
10598
|
"use strict";
|
|
9625
|
-
|
|
9626
|
-
FLUSH_DEBOUNCE_MS = 200;
|
|
9627
|
-
flushHandler = null;
|
|
10599
|
+
LOCAL_TIME_MARKER = "[Local device time:";
|
|
9628
10600
|
}
|
|
9629
10601
|
});
|
|
9630
10602
|
|
|
@@ -9836,10 +10808,10 @@ __export(recorder_exports, {
|
|
|
9836
10808
|
});
|
|
9837
10809
|
import { exec as exec5 } from "child_process";
|
|
9838
10810
|
import { promisify as promisify5 } from "util";
|
|
9839
|
-
import { writeFile as
|
|
9840
|
-
import { join as
|
|
9841
|
-
import { tmpdir } from "os";
|
|
9842
|
-
import { nanoid as
|
|
10811
|
+
import { writeFile as writeFile6, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm as rm2 } from "fs/promises";
|
|
10812
|
+
import { join as join13 } from "path";
|
|
10813
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10814
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
9843
10815
|
async function checkFfmpeg() {
|
|
9844
10816
|
try {
|
|
9845
10817
|
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
@@ -9850,7 +10822,7 @@ async function checkFfmpeg() {
|
|
|
9850
10822
|
}
|
|
9851
10823
|
async function cleanup(dir) {
|
|
9852
10824
|
try {
|
|
9853
|
-
await
|
|
10825
|
+
await rm2(dir, { recursive: true, force: true });
|
|
9854
10826
|
} catch {
|
|
9855
10827
|
}
|
|
9856
10828
|
}
|
|
@@ -9894,21 +10866,21 @@ var init_recorder = __esm({
|
|
|
9894
10866
|
*/
|
|
9895
10867
|
async encode() {
|
|
9896
10868
|
if (this.frames.length === 0) return null;
|
|
9897
|
-
const workDir =
|
|
10869
|
+
const workDir = join13(tmpdir2(), `sparkecoder-recording-${nanoid8(8)}`);
|
|
9898
10870
|
await mkdir4(workDir, { recursive: true });
|
|
9899
10871
|
try {
|
|
9900
10872
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9901
|
-
const framePath =
|
|
9902
|
-
await
|
|
10873
|
+
const framePath = join13(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10874
|
+
await writeFile6(framePath, this.frames[i].data);
|
|
9903
10875
|
}
|
|
9904
10876
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9905
10877
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9906
10878
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9907
|
-
const outputPath =
|
|
10879
|
+
const outputPath = join13(workDir, `recording_${this.sessionId}.mp4`);
|
|
9908
10880
|
const hasFfmpeg = await checkFfmpeg();
|
|
9909
10881
|
if (hasFfmpeg) {
|
|
9910
10882
|
await execAsync5(
|
|
9911
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
10883
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join13(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9912
10884
|
{ timeout: 12e4 }
|
|
9913
10885
|
);
|
|
9914
10886
|
} else {
|
|
@@ -9920,7 +10892,7 @@ var init_recorder = __esm({
|
|
|
9920
10892
|
const files = await readdir5(workDir);
|
|
9921
10893
|
for (const f of files) {
|
|
9922
10894
|
if (f.startsWith("frame_")) {
|
|
9923
|
-
await unlink2(
|
|
10895
|
+
await unlink2(join13(workDir, f)).catch(() => {
|
|
9924
10896
|
});
|
|
9925
10897
|
}
|
|
9926
10898
|
}
|
|
@@ -9949,7 +10921,7 @@ import {
|
|
|
9949
10921
|
stepCountIs as stepCountIs2
|
|
9950
10922
|
} from "ai";
|
|
9951
10923
|
import { z as z15 } from "zod";
|
|
9952
|
-
import { nanoid as
|
|
10924
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
9953
10925
|
function anySignal(signals) {
|
|
9954
10926
|
const ctrl = new AbortController();
|
|
9955
10927
|
for (const s of signals) {
|
|
@@ -9993,10 +10965,13 @@ var init_agent = __esm({
|
|
|
9993
10965
|
init_prompts();
|
|
9994
10966
|
init_orchestrator_actions();
|
|
9995
10967
|
init_pool();
|
|
10968
|
+
init_task_scoped();
|
|
10969
|
+
init_task_scoped2();
|
|
9996
10970
|
init_webhook2();
|
|
9997
10971
|
init_questions();
|
|
9998
10972
|
init_pending_input();
|
|
9999
10973
|
init_inbox();
|
|
10974
|
+
init_local_device_time();
|
|
10000
10975
|
init_system();
|
|
10001
10976
|
init_context();
|
|
10002
10977
|
init_prompts();
|
|
@@ -10159,9 +11134,11 @@ ${prompt}` });
|
|
|
10159
11134
|
*/
|
|
10160
11135
|
async stream(options) {
|
|
10161
11136
|
const config = getConfig();
|
|
10162
|
-
const
|
|
11137
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
11138
|
+
const userContent = this.buildUserMessageContent(prompt, options.attachments);
|
|
11139
|
+
const persistedUserContent = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserContent(userContent) : userContent;
|
|
10163
11140
|
if (!options.skipSaveUserMessage) {
|
|
10164
|
-
await this.context.addUserMessage(
|
|
11141
|
+
await this.context.addUserMessage(persistedUserContent);
|
|
10165
11142
|
}
|
|
10166
11143
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10167
11144
|
let systemPrompt = await buildSystemPrompt({
|
|
@@ -10239,7 +11216,8 @@ ${personality.trim()}
|
|
|
10239
11216
|
*/
|
|
10240
11217
|
async run(options) {
|
|
10241
11218
|
const config = getConfig();
|
|
10242
|
-
|
|
11219
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
11220
|
+
await this.context.addUserMessage(prompt);
|
|
10243
11221
|
const systemPrompt = await buildSystemPrompt({
|
|
10244
11222
|
workingDirectory: this.session.workingDirectory,
|
|
10245
11223
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -10283,355 +11261,387 @@ ${personality.trim()}
|
|
|
10283
11261
|
*/
|
|
10284
11262
|
async runTask(options) {
|
|
10285
11263
|
const config = getConfig();
|
|
10286
|
-
const
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
const completion = { signal: null };
|
|
10301
|
-
const onComplete = (signal) => {
|
|
10302
|
-
completion.signal = signal;
|
|
10303
|
-
};
|
|
10304
|
-
let taskRecorder = null;
|
|
10305
|
-
const sessionId = this.session.id;
|
|
10306
|
-
const emit = options.writeSSE;
|
|
10307
|
-
const bashProgressHandler = (progress) => {
|
|
10308
|
-
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
10309
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
10310
|
-
});
|
|
10311
|
-
const port = progress.browserStreamPort;
|
|
10312
|
-
if (port && progress.status === "started") {
|
|
10313
|
-
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
10314
|
-
const proxy = getOrCreateProxy2(sessionId, port);
|
|
10315
|
-
if (!taskRecorder) {
|
|
10316
|
-
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
10317
|
-
taskRecorder = new FrameRecorder2(sessionId);
|
|
10318
|
-
taskRecorder.start();
|
|
10319
|
-
});
|
|
10320
|
-
}
|
|
10321
|
-
if (proxy.listenerCount("frame") === 0) {
|
|
10322
|
-
proxy.on("frame", (frame) => {
|
|
10323
|
-
taskRecorder?.addFrame(frame);
|
|
10324
|
-
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
10325
|
-
});
|
|
10326
|
-
});
|
|
10327
|
-
proxy.on("status", (s) => {
|
|
10328
|
-
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
10329
|
-
});
|
|
10330
|
-
});
|
|
10331
|
-
}
|
|
11264
|
+
const taskScopedCleanups = [];
|
|
11265
|
+
try {
|
|
11266
|
+
const maxIterations = options.taskConfig.maxIterations ?? 50;
|
|
11267
|
+
const webhookUrl = options.taskConfig.webhookUrl;
|
|
11268
|
+
const parentTaskId = options.taskConfig.parentTaskId;
|
|
11269
|
+
const fireWebhook = (type, data) => {
|
|
11270
|
+
if (!webhookUrl) return;
|
|
11271
|
+
sendWebhook(webhookUrl, {
|
|
11272
|
+
type,
|
|
11273
|
+
taskId: this.session.id,
|
|
11274
|
+
sessionId: this.session.id,
|
|
11275
|
+
...parentTaskId ? { parentTaskId } : {},
|
|
11276
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11277
|
+
data
|
|
10332
11278
|
});
|
|
11279
|
+
};
|
|
11280
|
+
const completion = { signal: null };
|
|
11281
|
+
const onComplete = (signal) => {
|
|
11282
|
+
completion.signal = signal;
|
|
11283
|
+
};
|
|
11284
|
+
let taskMcpTools = {};
|
|
11285
|
+
if (options.mcpServers && options.mcpServers.length > 0) {
|
|
11286
|
+
const mcpConnection = await connectTaskMcpServers(options.mcpServers, { quiet: true });
|
|
11287
|
+
taskScopedCleanups.push(mcpConnection.close);
|
|
11288
|
+
taskMcpTools = mcpConnection.tools;
|
|
11289
|
+
if (mcpConnection.connected.length > 0) {
|
|
11290
|
+
console.log(`[TASK] connected ${mcpConnection.connected.length} task-scoped MCP server(s): ${mcpConnection.connected.join(", ")}`);
|
|
11291
|
+
}
|
|
11292
|
+
for (const e of mcpConnection.errors) {
|
|
11293
|
+
console.warn(`[TASK] task-scoped MCP server "${e.name}" failed to connect: ${e.error}`);
|
|
11294
|
+
if (options.writeSSE) await options.writeSSE(JSON.stringify({ type: "task-mcp-error", data: { name: e.name, error: e.error } }));
|
|
11295
|
+
}
|
|
10333
11296
|
}
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
options.onToolProgress?.({ toolName: "
|
|
10342
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "
|
|
10343
|
-
});
|
|
10344
|
-
},
|
|
10345
|
-
onSearchProgress: (progress) => {
|
|
10346
|
-
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
10347
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
11297
|
+
const materializedSkills = await materializeTaskSkills(options.skills, this.session.id);
|
|
11298
|
+
if (materializedSkills) taskScopedCleanups.push(materializedSkills.cleanup);
|
|
11299
|
+
const taskSkillsDir = materializedSkills?.dir;
|
|
11300
|
+
let taskRecorder = null;
|
|
11301
|
+
const sessionId = this.session.id;
|
|
11302
|
+
const emit = options.writeSSE;
|
|
11303
|
+
const bashProgressHandler = (progress) => {
|
|
11304
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
11305
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
10348
11306
|
});
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
11307
|
+
const port = progress.browserStreamPort;
|
|
11308
|
+
if (port && progress.status === "started") {
|
|
11309
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
11310
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
11311
|
+
if (!taskRecorder) {
|
|
11312
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
11313
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
11314
|
+
taskRecorder.start();
|
|
11315
|
+
});
|
|
11316
|
+
}
|
|
11317
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
11318
|
+
proxy.on("frame", (frame) => {
|
|
11319
|
+
taskRecorder?.addFrame(frame);
|
|
11320
|
+
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
11321
|
+
});
|
|
11322
|
+
});
|
|
11323
|
+
proxy.on("status", (s) => {
|
|
11324
|
+
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
11325
|
+
});
|
|
11326
|
+
});
|
|
11327
|
+
}
|
|
10367
11328
|
});
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
}
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
11329
|
+
}
|
|
11330
|
+
};
|
|
11331
|
+
const taskTools = await createTools({
|
|
11332
|
+
sessionId: this.session.id,
|
|
11333
|
+
workingDirectory: this.session.workingDirectory,
|
|
11334
|
+
onBashProgress: bashProgressHandler,
|
|
11335
|
+
onWriteFileProgress: (progress) => {
|
|
11336
|
+
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
11337
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "write_file", data: progress })).catch(() => {
|
|
11338
|
+
});
|
|
11339
|
+
},
|
|
11340
|
+
onSearchProgress: (progress) => {
|
|
11341
|
+
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
11342
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
11343
|
+
});
|
|
11344
|
+
},
|
|
11345
|
+
// Add the task-scoped skills temp dir (if any) so load_skill can list
|
|
11346
|
+
// and load the inline skills supplied with this task.
|
|
11347
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
11348
|
+
taskTools: {
|
|
11349
|
+
outputSchema: options.taskConfig.outputSchema,
|
|
11350
|
+
onComplete,
|
|
11351
|
+
onQuestion: async (question) => {
|
|
11352
|
+
const payload = {
|
|
11353
|
+
questionId: question.questionId,
|
|
11354
|
+
question: question.question,
|
|
11355
|
+
context: question.context,
|
|
11356
|
+
choices: question.choices,
|
|
11357
|
+
status: "pending"
|
|
11358
|
+
};
|
|
11359
|
+
const answerPromise = waitForTaskQuestionAnswer({
|
|
11360
|
+
taskId: this.session.id,
|
|
11361
|
+
questionId: question.questionId,
|
|
11362
|
+
question: question.question,
|
|
11363
|
+
context: question.context,
|
|
11364
|
+
choices: question.choices
|
|
11365
|
+
});
|
|
11366
|
+
fireWebhook("task.question", payload);
|
|
11367
|
+
if (emit) {
|
|
11368
|
+
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
11369
|
+
}
|
|
11370
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
11371
|
+
if (orchId) {
|
|
11372
|
+
pushToInbox(orchId, workerQuestionEvent(
|
|
11373
|
+
this.session.id,
|
|
11374
|
+
this.session.name || "worker",
|
|
11375
|
+
question.question,
|
|
11376
|
+
question.questionId
|
|
11377
|
+
));
|
|
11378
|
+
}
|
|
11379
|
+
const answer = await answerPromise;
|
|
11380
|
+
const answeredPayload = {
|
|
11381
|
+
questionId: question.questionId,
|
|
11382
|
+
answer: answer.answer,
|
|
11383
|
+
answeredBy: answer.answeredBy
|
|
11384
|
+
};
|
|
11385
|
+
fireWebhook("task.question_answered", answeredPayload);
|
|
11386
|
+
if (emit) {
|
|
11387
|
+
await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
|
|
11388
|
+
}
|
|
11389
|
+
return answer;
|
|
10390
11390
|
}
|
|
10391
|
-
return answer;
|
|
10392
11391
|
}
|
|
11392
|
+
});
|
|
11393
|
+
for (const [name, t] of Object.entries(taskMcpTools)) {
|
|
11394
|
+
taskTools[name] = t;
|
|
10393
11395
|
}
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
11396
|
+
const baseSystemPrompt = await buildSystemPrompt({
|
|
11397
|
+
workingDirectory: this.session.workingDirectory,
|
|
11398
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
11399
|
+
sessionId: this.session.id,
|
|
11400
|
+
discoveredSkills: config.discoveredSkills,
|
|
11401
|
+
activeFiles: [],
|
|
11402
|
+
taskScopedSkills: materializedSkills ? { always: materializedSkills.always, onDemand: materializedSkills.onDemand } : void 0
|
|
11403
|
+
});
|
|
11404
|
+
const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
|
|
11405
|
+
const systemPrompt = `${baseSystemPrompt}
|
|
10404
11406
|
|
|
10405
11407
|
${taskAddendum}`;
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
}
|
|
10410
|
-
await this.context.addUserMessage(options.prompt);
|
|
10411
|
-
let iteration = 0;
|
|
10412
|
-
while (iteration < maxIterations) {
|
|
10413
|
-
iteration++;
|
|
10414
|
-
if (options.abortSignal?.aborted) {
|
|
10415
|
-
const cancelError = "Task was cancelled";
|
|
10416
|
-
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
10417
|
-
clearInterruptController(this.session.id);
|
|
10418
|
-
return { status: "failed", error: cancelError, iterations: iteration };
|
|
11408
|
+
fireWebhook("task.started", { prompt: options.prompt });
|
|
11409
|
+
if (emit) {
|
|
11410
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: options.prompt } }));
|
|
10419
11411
|
}
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
11412
|
+
await this.context.addUserMessage(options.prompt);
|
|
11413
|
+
let iteration = 0;
|
|
11414
|
+
while (iteration < maxIterations) {
|
|
11415
|
+
iteration++;
|
|
11416
|
+
if (options.abortSignal?.aborted) {
|
|
11417
|
+
const cancelError = "Task was cancelled";
|
|
11418
|
+
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
11419
|
+
clearInterruptController(this.session.id);
|
|
11420
|
+
return { status: "failed", error: cancelError, iterations: iteration };
|
|
11421
|
+
}
|
|
11422
|
+
const pending = drainInputs(this.session.id);
|
|
11423
|
+
for (const p of pending) {
|
|
11424
|
+
const labelled = p.source === "orchestrator" ? `[message from orchestrator]
|
|
10423
11425
|
${p.text}` : p.source === "system" ? `[system note]
|
|
10424
11426
|
${p.text}` : p.text;
|
|
10425
|
-
if (emit) {
|
|
10426
|
-
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
10427
|
-
}
|
|
10428
|
-
await this.context.addUserMessage(labelled);
|
|
10429
|
-
}
|
|
10430
|
-
const interruptController = new AbortController();
|
|
10431
|
-
registerInterruptController(this.session.id, interruptController);
|
|
10432
|
-
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
10433
|
-
const messages = await this.context.getMessages();
|
|
10434
|
-
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10435
|
-
if (emit) {
|
|
10436
|
-
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
10437
|
-
}
|
|
10438
|
-
let textStarted = false;
|
|
10439
|
-
let textId = `text_${Date.now()}`;
|
|
10440
|
-
let reasoningId = `reasoning_${Date.now()}`;
|
|
10441
|
-
let reasoningStarted = false;
|
|
10442
|
-
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
10443
|
-
const iterStream = streamText2({
|
|
10444
|
-
model: resolveModel(this.session.model),
|
|
10445
|
-
system: systemPrompt,
|
|
10446
|
-
messages,
|
|
10447
|
-
tools: wrapToolsNeverThrow(taskTools),
|
|
10448
|
-
stopWhen: stepCountIs2(500),
|
|
10449
|
-
abortSignal: combinedAbort,
|
|
10450
|
-
providerOptions: useAnthropic ? {
|
|
10451
|
-
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10452
|
-
} : void 0,
|
|
10453
|
-
// See the matching note in `stream()` — repair tool pairing before
|
|
10454
|
-
// every step so we never feed the model an orphan tool-call.
|
|
10455
|
-
prepareStep: async ({ messages: stepMessages }) => {
|
|
10456
|
-
const paired = repairToolPairing(stepMessages);
|
|
10457
|
-
const ordered = ensureToolResultsFollowCalls(paired);
|
|
10458
|
-
if (ordered === stepMessages) return {};
|
|
10459
|
-
return { messages: ordered };
|
|
10460
|
-
},
|
|
10461
|
-
onStepFinish: async (step) => {
|
|
10462
|
-
options.onStepFinish?.(step);
|
|
10463
|
-
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
10464
11427
|
if (emit) {
|
|
10465
|
-
|
|
10466
|
-
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
10467
|
-
textStarted = false;
|
|
10468
|
-
textId = `text_${Date.now()}`;
|
|
10469
|
-
}
|
|
10470
|
-
await emit(JSON.stringify({ type: "finish-step" }));
|
|
11428
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
10471
11429
|
}
|
|
11430
|
+
await this.context.addUserMessage(labelled);
|
|
10472
11431
|
}
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
11432
|
+
const interruptController = new AbortController();
|
|
11433
|
+
registerInterruptController(this.session.id, interruptController);
|
|
11434
|
+
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
11435
|
+
const messages = await this.context.getMessages();
|
|
11436
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
11437
|
+
if (emit) {
|
|
11438
|
+
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
11439
|
+
}
|
|
11440
|
+
let textStarted = false;
|
|
11441
|
+
let textId = `text_${Date.now()}`;
|
|
11442
|
+
let reasoningId = `reasoning_${Date.now()}`;
|
|
11443
|
+
let reasoningStarted = false;
|
|
11444
|
+
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
11445
|
+
const iterStream = streamText2({
|
|
11446
|
+
model: resolveModel(this.session.model),
|
|
11447
|
+
system: systemPrompt,
|
|
11448
|
+
messages,
|
|
11449
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
11450
|
+
stopWhen: stepCountIs2(500),
|
|
11451
|
+
abortSignal: combinedAbort,
|
|
11452
|
+
providerOptions: useAnthropic ? {
|
|
11453
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
11454
|
+
} : void 0,
|
|
11455
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
11456
|
+
// every step so we never feed the model an orphan tool-call.
|
|
11457
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
11458
|
+
const paired = repairToolPairing(stepMessages);
|
|
11459
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
11460
|
+
if (ordered === stepMessages) return {};
|
|
11461
|
+
return { messages: ordered };
|
|
11462
|
+
},
|
|
11463
|
+
onStepFinish: async (step) => {
|
|
11464
|
+
options.onStepFinish?.(step);
|
|
11465
|
+
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
11466
|
+
if (emit) {
|
|
11467
|
+
if (textStarted) {
|
|
11468
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
11469
|
+
textStarted = false;
|
|
11470
|
+
textId = `text_${Date.now()}`;
|
|
11471
|
+
}
|
|
11472
|
+
await emit(JSON.stringify({ type: "finish-step" }));
|
|
10480
11473
|
}
|
|
10481
|
-
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
10482
|
-
}
|
|
10483
|
-
} else if (part.type === "reasoning-start") {
|
|
10484
|
-
if (emit) {
|
|
10485
|
-
await emit(JSON.stringify({ type: "reasoning-start", id: reasoningId }));
|
|
10486
|
-
reasoningStarted = true;
|
|
10487
|
-
}
|
|
10488
|
-
} else if (part.type === "reasoning-delta") {
|
|
10489
|
-
if (emit) {
|
|
10490
|
-
await emit(JSON.stringify({ type: "reasoning-delta", id: reasoningId, delta: part.text }));
|
|
10491
|
-
}
|
|
10492
|
-
} else if (part.type === "reasoning-end") {
|
|
10493
|
-
if (emit && reasoningStarted) {
|
|
10494
|
-
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
10495
|
-
reasoningStarted = false;
|
|
10496
|
-
reasoningId = `reasoning_${Date.now()}`;
|
|
10497
|
-
}
|
|
10498
|
-
} else if (part.type === "tool-call-streaming-start") {
|
|
10499
|
-
if (emit) {
|
|
10500
|
-
const p = part;
|
|
10501
|
-
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: p.toolCallId, toolName: p.toolName }));
|
|
10502
|
-
toolCallStarts.add(p.toolCallId);
|
|
10503
|
-
}
|
|
10504
|
-
} else if (part.type === "tool-call-delta") {
|
|
10505
|
-
if (emit) {
|
|
10506
|
-
const p = part;
|
|
10507
|
-
await emit(JSON.stringify({ type: "tool-input-delta", toolCallId: p.toolCallId, argsTextDelta: p.argsTextDelta }));
|
|
10508
11474
|
}
|
|
10509
|
-
}
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
11475
|
+
});
|
|
11476
|
+
for await (const part of iterStream.fullStream) {
|
|
11477
|
+
if (part.type === "text-delta") {
|
|
11478
|
+
if (emit) {
|
|
11479
|
+
if (!textStarted) {
|
|
11480
|
+
await emit(JSON.stringify({ type: "text-start", id: textId }));
|
|
11481
|
+
textStarted = true;
|
|
11482
|
+
}
|
|
11483
|
+
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
11484
|
+
}
|
|
11485
|
+
} else if (part.type === "reasoning-start") {
|
|
11486
|
+
if (emit) {
|
|
11487
|
+
await emit(JSON.stringify({ type: "reasoning-start", id: reasoningId }));
|
|
11488
|
+
reasoningStarted = true;
|
|
11489
|
+
}
|
|
11490
|
+
} else if (part.type === "reasoning-delta") {
|
|
11491
|
+
if (emit) {
|
|
11492
|
+
await emit(JSON.stringify({ type: "reasoning-delta", id: reasoningId, delta: part.text }));
|
|
11493
|
+
}
|
|
11494
|
+
} else if (part.type === "reasoning-end") {
|
|
11495
|
+
if (emit && reasoningStarted) {
|
|
11496
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
11497
|
+
reasoningStarted = false;
|
|
11498
|
+
reasoningId = `reasoning_${Date.now()}`;
|
|
11499
|
+
}
|
|
11500
|
+
} else if (part.type === "tool-call-streaming-start") {
|
|
11501
|
+
if (emit) {
|
|
11502
|
+
const p = part;
|
|
11503
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: p.toolCallId, toolName: p.toolName }));
|
|
11504
|
+
toolCallStarts.add(p.toolCallId);
|
|
11505
|
+
}
|
|
11506
|
+
} else if (part.type === "tool-call-delta") {
|
|
11507
|
+
if (emit) {
|
|
11508
|
+
const p = part;
|
|
11509
|
+
await emit(JSON.stringify({ type: "tool-input-delta", toolCallId: p.toolCallId, argsTextDelta: p.argsTextDelta }));
|
|
11510
|
+
}
|
|
11511
|
+
} else if (part.type === "tool-call") {
|
|
11512
|
+
if (emit) {
|
|
11513
|
+
if (!toolCallStarts.has(part.toolCallId)) {
|
|
11514
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: part.toolCallId, toolName: part.toolName }));
|
|
11515
|
+
toolCallStarts.add(part.toolCallId);
|
|
11516
|
+
}
|
|
11517
|
+
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
11518
|
+
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
11519
|
+
}
|
|
11520
|
+
} else if (part.type === "tool-result") {
|
|
11521
|
+
if (emit) {
|
|
11522
|
+
await emit(JSON.stringify({ type: "tool-output-available", toolCallId: part.toolCallId, output: part.output }));
|
|
11523
|
+
}
|
|
11524
|
+
} else if (part.type === "error") {
|
|
11525
|
+
console.error("Task stream error:", part.error);
|
|
11526
|
+
if (emit) {
|
|
11527
|
+
await emit(JSON.stringify({ type: "error", errorText: String(part.error) }));
|
|
10514
11528
|
}
|
|
10515
|
-
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
10516
|
-
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
10517
11529
|
}
|
|
10518
|
-
}
|
|
10519
|
-
|
|
10520
|
-
|
|
11530
|
+
}
|
|
11531
|
+
if (emit && textStarted) {
|
|
11532
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
11533
|
+
}
|
|
11534
|
+
if (emit && reasoningStarted) {
|
|
11535
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
11536
|
+
}
|
|
11537
|
+
const interrupted = interruptController.signal.aborted;
|
|
11538
|
+
clearInterruptController(this.session.id);
|
|
11539
|
+
const iterResponse = await iterStream.response;
|
|
11540
|
+
const responseMessages = iterResponse.messages;
|
|
11541
|
+
await this.context.addResponseMessages(responseMessages);
|
|
11542
|
+
const resultText = await iterStream.text;
|
|
11543
|
+
const resultSteps = await iterStream.steps;
|
|
11544
|
+
if (resultText) {
|
|
11545
|
+
options.onText?.(resultText);
|
|
11546
|
+
fireWebhook("task.message", { iteration, text: resultText });
|
|
11547
|
+
}
|
|
11548
|
+
for (const step of resultSteps) {
|
|
11549
|
+
if (step.toolCalls) {
|
|
11550
|
+
for (const tc of step.toolCalls) {
|
|
11551
|
+
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
11552
|
+
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
11553
|
+
}
|
|
10521
11554
|
}
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
11555
|
+
if (step.toolResults) {
|
|
11556
|
+
for (const tr of step.toolResults) {
|
|
11557
|
+
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.output });
|
|
11558
|
+
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.output });
|
|
11559
|
+
}
|
|
10526
11560
|
}
|
|
10527
11561
|
}
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
const responseMessages = iterResponse.messages;
|
|
10539
|
-
await this.context.addResponseMessages(responseMessages);
|
|
10540
|
-
const resultText = await iterStream.text;
|
|
10541
|
-
const resultSteps = await iterStream.steps;
|
|
10542
|
-
if (resultText) {
|
|
10543
|
-
options.onText?.(resultText);
|
|
10544
|
-
fireWebhook("task.message", { iteration, text: resultText });
|
|
10545
|
-
}
|
|
10546
|
-
for (const step of resultSteps) {
|
|
10547
|
-
if (step.toolCalls) {
|
|
10548
|
-
for (const tc of step.toolCalls) {
|
|
10549
|
-
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
10550
|
-
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
11562
|
+
if (completion.signal) {
|
|
11563
|
+
const sig = completion.signal;
|
|
11564
|
+
const finalStatus = sig.status;
|
|
11565
|
+
let fileUrls;
|
|
11566
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
11567
|
+
const resultObj = sig.result;
|
|
11568
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
11569
|
+
if (filePaths.length > 0) {
|
|
11570
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
11571
|
+
}
|
|
10551
11572
|
}
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
11573
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
11574
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
11575
|
+
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
11576
|
+
fireWebhook(eventType, {
|
|
11577
|
+
status: finalStatus,
|
|
11578
|
+
result: sig.result,
|
|
11579
|
+
error: sig.error,
|
|
11580
|
+
iterations: iteration,
|
|
11581
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
11582
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
11583
|
+
});
|
|
11584
|
+
const updatedTask2 = {
|
|
11585
|
+
...options.taskConfig,
|
|
11586
|
+
status: finalStatus,
|
|
11587
|
+
result: sig.result,
|
|
11588
|
+
error: sig.error,
|
|
11589
|
+
iterations: iteration
|
|
11590
|
+
};
|
|
11591
|
+
await sessionQueries.update(this.session.id, {
|
|
11592
|
+
config: { ...this.session.config, task: updatedTask2 }
|
|
11593
|
+
});
|
|
11594
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
11595
|
+
if (orchId) {
|
|
11596
|
+
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
11597
|
+
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
10557
11598
|
}
|
|
11599
|
+
return {
|
|
11600
|
+
status: finalStatus,
|
|
11601
|
+
result: sig.result,
|
|
11602
|
+
error: sig.error,
|
|
11603
|
+
iterations: iteration
|
|
11604
|
+
};
|
|
10558
11605
|
}
|
|
10559
|
-
|
|
10560
|
-
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
let fileUrls;
|
|
10564
|
-
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
10565
|
-
const resultObj = sig.result;
|
|
10566
|
-
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
10567
|
-
if (filePaths.length > 0) {
|
|
10568
|
-
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
11606
|
+
if (!interrupted) {
|
|
11607
|
+
const continuationPrompt = "Continue working on the task. Before calling `complete_task`, VERIFY your work is correct \u2014 re-read edited files, run the linter, run tests if applicable, and check the browser/server if you made UI or API changes. Make sure you searched the right directories and found everything relevant. When fully verified, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason.";
|
|
11608
|
+
if (emit) {
|
|
11609
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: continuationPrompt } }));
|
|
10569
11610
|
}
|
|
11611
|
+
await this.context.addUserMessage(continuationPrompt);
|
|
10570
11612
|
}
|
|
10571
|
-
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
10572
|
-
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
10573
|
-
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
10574
|
-
fireWebhook(eventType, {
|
|
10575
|
-
status: finalStatus,
|
|
10576
|
-
result: sig.result,
|
|
10577
|
-
error: sig.error,
|
|
10578
|
-
iterations: iteration,
|
|
10579
|
-
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
10580
|
-
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
10581
|
-
});
|
|
10582
|
-
const updatedTask2 = {
|
|
10583
|
-
...options.taskConfig,
|
|
10584
|
-
status: finalStatus,
|
|
10585
|
-
result: sig.result,
|
|
10586
|
-
error: sig.error,
|
|
10587
|
-
iterations: iteration
|
|
10588
|
-
};
|
|
10589
|
-
await sessionQueries.update(this.session.id, {
|
|
10590
|
-
config: { ...this.session.config, task: updatedTask2 }
|
|
10591
|
-
});
|
|
10592
|
-
const orchId = this.session.config?.orchestratorSessionId;
|
|
10593
|
-
if (orchId) {
|
|
10594
|
-
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
10595
|
-
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
10596
|
-
}
|
|
10597
|
-
return {
|
|
10598
|
-
status: finalStatus,
|
|
10599
|
-
result: sig.result,
|
|
10600
|
-
error: sig.error,
|
|
10601
|
-
iterations: iteration
|
|
10602
|
-
};
|
|
10603
11613
|
}
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
11614
|
+
clearInterruptController(this.session.id);
|
|
11615
|
+
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
11616
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
11617
|
+
fireWebhook("task.failed", {
|
|
11618
|
+
status: "failed",
|
|
11619
|
+
error: timeoutError,
|
|
11620
|
+
iterations: iteration,
|
|
11621
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
11622
|
+
});
|
|
11623
|
+
const updatedTask = {
|
|
11624
|
+
...options.taskConfig,
|
|
11625
|
+
status: "failed",
|
|
11626
|
+
error: timeoutError,
|
|
11627
|
+
iterations: iteration
|
|
11628
|
+
};
|
|
11629
|
+
await sessionQueries.update(this.session.id, {
|
|
11630
|
+
config: { ...this.session.config, task: updatedTask }
|
|
11631
|
+
});
|
|
11632
|
+
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
11633
|
+
if (orchIdTimeout) {
|
|
11634
|
+
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
11635
|
+
}
|
|
11636
|
+
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
11637
|
+
} finally {
|
|
11638
|
+
for (const cleanup2 of taskScopedCleanups) {
|
|
11639
|
+
try {
|
|
11640
|
+
await cleanup2();
|
|
11641
|
+
} catch {
|
|
10608
11642
|
}
|
|
10609
|
-
await this.context.addUserMessage(continuationPrompt);
|
|
10610
11643
|
}
|
|
10611
11644
|
}
|
|
10612
|
-
clearInterruptController(this.session.id);
|
|
10613
|
-
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
10614
|
-
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
10615
|
-
fireWebhook("task.failed", {
|
|
10616
|
-
status: "failed",
|
|
10617
|
-
error: timeoutError,
|
|
10618
|
-
iterations: iteration,
|
|
10619
|
-
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
10620
|
-
});
|
|
10621
|
-
const updatedTask = {
|
|
10622
|
-
...options.taskConfig,
|
|
10623
|
-
status: "failed",
|
|
10624
|
-
error: timeoutError,
|
|
10625
|
-
iterations: iteration
|
|
10626
|
-
};
|
|
10627
|
-
await sessionQueries.update(this.session.id, {
|
|
10628
|
-
config: { ...this.session.config, task: updatedTask }
|
|
10629
|
-
});
|
|
10630
|
-
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
10631
|
-
if (orchIdTimeout) {
|
|
10632
|
-
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
10633
|
-
}
|
|
10634
|
-
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
10635
11645
|
}
|
|
10636
11646
|
/**
|
|
10637
11647
|
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
@@ -10691,11 +11701,11 @@ ${p.text}` : p.text;
|
|
|
10691
11701
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10692
11702
|
if (!isRemoteConfigured2()) return [];
|
|
10693
11703
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
10694
|
-
const { join:
|
|
11704
|
+
const { join: join20, basename: basename7 } = await import("path");
|
|
10695
11705
|
const urls = [];
|
|
10696
11706
|
for (const filePath of filePaths) {
|
|
10697
11707
|
try {
|
|
10698
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
11708
|
+
const fullPath = filePath.startsWith("/") ? filePath : join20(this.session.workingDirectory, filePath);
|
|
10699
11709
|
const fileName = basename7(fullPath);
|
|
10700
11710
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10701
11711
|
const mimeMap = {
|
|
@@ -10757,7 +11767,7 @@ ${p.text}` : p.text;
|
|
|
10757
11767
|
description: originalTool.description || "",
|
|
10758
11768
|
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
10759
11769
|
execute: async (input, toolOptions) => {
|
|
10760
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
11770
|
+
const toolCallId = toolOptions.toolCallId || nanoid9();
|
|
10761
11771
|
const execution = toolExecutionQueries.create({
|
|
10762
11772
|
sessionId: this.session.id,
|
|
10763
11773
|
toolName: name,
|
|
@@ -10798,219 +11808,71 @@ ${p.text}` : p.text;
|
|
|
10798
11808
|
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
10799
11809
|
throw error;
|
|
10800
11810
|
}
|
|
10801
|
-
}
|
|
10802
|
-
});
|
|
10803
|
-
}
|
|
10804
|
-
return wrappedTools;
|
|
10805
|
-
}
|
|
10806
|
-
/**
|
|
10807
|
-
* Wait for all pending approvals
|
|
10808
|
-
*/
|
|
10809
|
-
async waitForApprovals() {
|
|
10810
|
-
return Array.from(this.pendingApprovals.values());
|
|
10811
|
-
}
|
|
10812
|
-
/**
|
|
10813
|
-
* Approve a pending tool execution
|
|
10814
|
-
*/
|
|
10815
|
-
async approve(toolCallId) {
|
|
10816
|
-
const resolver = approvalResolvers.get(toolCallId);
|
|
10817
|
-
if (resolver) {
|
|
10818
|
-
resolver.resolve(true);
|
|
10819
|
-
return { approved: true };
|
|
10820
|
-
}
|
|
10821
|
-
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
10822
|
-
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
10823
|
-
if (!execution) {
|
|
10824
|
-
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
10825
|
-
}
|
|
10826
|
-
await toolExecutionQueries.approve(execution.id);
|
|
10827
|
-
return { approved: true };
|
|
10828
|
-
}
|
|
10829
|
-
/**
|
|
10830
|
-
* Reject a pending tool execution
|
|
10831
|
-
*/
|
|
10832
|
-
async reject(toolCallId, reason) {
|
|
10833
|
-
const resolver = approvalResolvers.get(toolCallId);
|
|
10834
|
-
if (resolver) {
|
|
10835
|
-
resolver.reason = reason;
|
|
10836
|
-
resolver.resolve(false);
|
|
10837
|
-
return { rejected: true };
|
|
10838
|
-
}
|
|
10839
|
-
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
10840
|
-
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
10841
|
-
if (!execution) {
|
|
10842
|
-
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
10843
|
-
}
|
|
10844
|
-
await toolExecutionQueries.reject(execution.id);
|
|
10845
|
-
return { rejected: true };
|
|
10846
|
-
}
|
|
10847
|
-
/**
|
|
10848
|
-
* Get pending approvals
|
|
10849
|
-
*/
|
|
10850
|
-
async getPendingApprovals() {
|
|
10851
|
-
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
10852
|
-
}
|
|
10853
|
-
/**
|
|
10854
|
-
* Get context statistics
|
|
10855
|
-
*/
|
|
10856
|
-
getContextStats() {
|
|
10857
|
-
return this.context.getStats();
|
|
10858
|
-
}
|
|
10859
|
-
/**
|
|
10860
|
-
* Clear conversation context (start fresh)
|
|
10861
|
-
*/
|
|
10862
|
-
clearContext() {
|
|
10863
|
-
this.context.clear();
|
|
10864
|
-
}
|
|
10865
|
-
};
|
|
10866
|
-
}
|
|
10867
|
-
});
|
|
10868
|
-
|
|
10869
|
-
// src/agent/session-lock.ts
|
|
10870
|
-
async function withSessionLock(sessionId, fn) {
|
|
10871
|
-
let state2 = locks.get(sessionId);
|
|
10872
|
-
if (!state2) {
|
|
10873
|
-
state2 = { tail: Promise.resolve(), pending: 0 };
|
|
10874
|
-
locks.set(sessionId, state2);
|
|
10875
|
-
}
|
|
10876
|
-
state2.pending++;
|
|
10877
|
-
const prev = state2.tail;
|
|
10878
|
-
let release;
|
|
10879
|
-
const next = new Promise((resolve13) => {
|
|
10880
|
-
release = resolve13;
|
|
10881
|
-
});
|
|
10882
|
-
state2.tail = prev.then(() => next);
|
|
10883
|
-
await prev;
|
|
10884
|
-
try {
|
|
10885
|
-
return await fn();
|
|
10886
|
-
} finally {
|
|
10887
|
-
release();
|
|
10888
|
-
state2.pending--;
|
|
10889
|
-
if (state2.pending === 0 && locks.get(sessionId) === state2) {
|
|
10890
|
-
locks.delete(sessionId);
|
|
10891
|
-
}
|
|
10892
|
-
}
|
|
10893
|
-
}
|
|
10894
|
-
var locks;
|
|
10895
|
-
var init_session_lock = __esm({
|
|
10896
|
-
"src/agent/session-lock.ts"() {
|
|
10897
|
-
"use strict";
|
|
10898
|
-
locks = /* @__PURE__ */ new Map();
|
|
10899
|
-
}
|
|
10900
|
-
});
|
|
10901
|
-
|
|
10902
|
-
// src/orchestrator/webhook-events.ts
|
|
10903
|
-
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
10904
|
-
import { dirname as dirname7, join as join12 } from "path";
|
|
10905
|
-
import { nanoid as nanoid9 } from "nanoid";
|
|
10906
|
-
function logFilePath() {
|
|
10907
|
-
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10908
|
-
}
|
|
10909
|
-
function ensureLoaded() {
|
|
10910
|
-
if (cache !== null) return cache;
|
|
10911
|
-
cache = [];
|
|
10912
|
-
try {
|
|
10913
|
-
const p = logFilePath();
|
|
10914
|
-
if (!existsSync18(p)) return cache;
|
|
10915
|
-
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
10916
|
-
for (const line of lines) {
|
|
10917
|
-
try {
|
|
10918
|
-
cache.push(JSON.parse(line));
|
|
10919
|
-
} catch {
|
|
10920
|
-
}
|
|
10921
|
-
}
|
|
10922
|
-
if (cache.length > MAX_EVENTS) {
|
|
10923
|
-
cache = cache.slice(-MAX_EVENTS);
|
|
10924
|
-
try {
|
|
10925
|
-
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10926
|
-
} catch {
|
|
10927
|
-
}
|
|
10928
|
-
}
|
|
10929
|
-
} catch {
|
|
10930
|
-
}
|
|
10931
|
-
return cache;
|
|
10932
|
-
}
|
|
10933
|
-
function appendEvent(ev) {
|
|
10934
|
-
const list = ensureLoaded();
|
|
10935
|
-
list.push(ev);
|
|
10936
|
-
if (list.length > MAX_EVENTS) list.shift();
|
|
10937
|
-
try {
|
|
10938
|
-
const p = logFilePath();
|
|
10939
|
-
mkdirSync7(dirname7(p), { recursive: true });
|
|
10940
|
-
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10941
|
-
} catch {
|
|
10942
|
-
}
|
|
10943
|
-
}
|
|
10944
|
-
function recordEvent(ev) {
|
|
10945
|
-
const full = {
|
|
10946
|
-
id: ev.id ?? nanoid9(),
|
|
10947
|
-
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10948
|
-
source: ev.source,
|
|
10949
|
-
status: ev.status,
|
|
10950
|
-
subtype: ev.subtype,
|
|
10951
|
-
channel: ev.channel,
|
|
10952
|
-
user: ev.user,
|
|
10953
|
-
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
10954
|
-
dropReason: ev.dropReason,
|
|
10955
|
-
error: ev.error,
|
|
10956
|
-
sessionId: ev.sessionId,
|
|
10957
|
-
durationMs: ev.durationMs,
|
|
10958
|
-
meta: ev.meta
|
|
10959
|
-
};
|
|
10960
|
-
appendEvent(full);
|
|
10961
|
-
return full.id;
|
|
10962
|
-
}
|
|
10963
|
-
function updateEvent(id, patch) {
|
|
10964
|
-
const list = ensureLoaded();
|
|
10965
|
-
const i = list.findIndex((e) => e.id === id);
|
|
10966
|
-
if (i < 0) return;
|
|
10967
|
-
list[i] = { ...list[i], ...patch };
|
|
10968
|
-
try {
|
|
10969
|
-
const p = logFilePath();
|
|
10970
|
-
mkdirSync7(dirname7(p), { recursive: true });
|
|
10971
|
-
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10972
|
-
} catch {
|
|
10973
|
-
}
|
|
10974
|
-
}
|
|
10975
|
-
function listEvents(filter = {}) {
|
|
10976
|
-
const list = ensureLoaded();
|
|
10977
|
-
const q = filter.q?.toLowerCase();
|
|
10978
|
-
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
10979
|
-
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
10980
|
-
const matched = list.filter((e) => {
|
|
10981
|
-
if (filter.source && e.source !== filter.source) return false;
|
|
10982
|
-
if (filter.status && e.status !== filter.status) return false;
|
|
10983
|
-
const t = Date.parse(e.ts);
|
|
10984
|
-
if (t < sinceTs) return false;
|
|
10985
|
-
if (t >= beforeTs) return false;
|
|
10986
|
-
if (q) {
|
|
10987
|
-
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
10988
|
-
if (!hay.includes(q)) return false;
|
|
10989
|
-
}
|
|
10990
|
-
return true;
|
|
10991
|
-
});
|
|
10992
|
-
matched.reverse();
|
|
10993
|
-
const offset = Math.max(0, filter.offset ?? 0);
|
|
10994
|
-
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
10995
|
-
return {
|
|
10996
|
-
events: matched.slice(offset, offset + limit),
|
|
10997
|
-
total: matched.length
|
|
10998
|
-
};
|
|
10999
|
-
}
|
|
11000
|
-
function clearAllEvents() {
|
|
11001
|
-
cache = [];
|
|
11002
|
-
try {
|
|
11003
|
-
writeFileSync4(logFilePath(), "");
|
|
11004
|
-
} catch {
|
|
11005
|
-
}
|
|
11006
|
-
}
|
|
11007
|
-
var MAX_EVENTS, cache;
|
|
11008
|
-
var init_webhook_events = __esm({
|
|
11009
|
-
"src/orchestrator/webhook-events.ts"() {
|
|
11010
|
-
"use strict";
|
|
11011
|
-
init_config();
|
|
11012
|
-
MAX_EVENTS = 1e3;
|
|
11013
|
-
cache = null;
|
|
11811
|
+
}
|
|
11812
|
+
});
|
|
11813
|
+
}
|
|
11814
|
+
return wrappedTools;
|
|
11815
|
+
}
|
|
11816
|
+
/**
|
|
11817
|
+
* Wait for all pending approvals
|
|
11818
|
+
*/
|
|
11819
|
+
async waitForApprovals() {
|
|
11820
|
+
return Array.from(this.pendingApprovals.values());
|
|
11821
|
+
}
|
|
11822
|
+
/**
|
|
11823
|
+
* Approve a pending tool execution
|
|
11824
|
+
*/
|
|
11825
|
+
async approve(toolCallId) {
|
|
11826
|
+
const resolver = approvalResolvers.get(toolCallId);
|
|
11827
|
+
if (resolver) {
|
|
11828
|
+
resolver.resolve(true);
|
|
11829
|
+
return { approved: true };
|
|
11830
|
+
}
|
|
11831
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11832
|
+
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
11833
|
+
if (!execution) {
|
|
11834
|
+
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
11835
|
+
}
|
|
11836
|
+
await toolExecutionQueries.approve(execution.id);
|
|
11837
|
+
return { approved: true };
|
|
11838
|
+
}
|
|
11839
|
+
/**
|
|
11840
|
+
* Reject a pending tool execution
|
|
11841
|
+
*/
|
|
11842
|
+
async reject(toolCallId, reason) {
|
|
11843
|
+
const resolver = approvalResolvers.get(toolCallId);
|
|
11844
|
+
if (resolver) {
|
|
11845
|
+
resolver.reason = reason;
|
|
11846
|
+
resolver.resolve(false);
|
|
11847
|
+
return { rejected: true };
|
|
11848
|
+
}
|
|
11849
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11850
|
+
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
11851
|
+
if (!execution) {
|
|
11852
|
+
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
11853
|
+
}
|
|
11854
|
+
await toolExecutionQueries.reject(execution.id);
|
|
11855
|
+
return { rejected: true };
|
|
11856
|
+
}
|
|
11857
|
+
/**
|
|
11858
|
+
* Get pending approvals
|
|
11859
|
+
*/
|
|
11860
|
+
async getPendingApprovals() {
|
|
11861
|
+
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11862
|
+
}
|
|
11863
|
+
/**
|
|
11864
|
+
* Get context statistics
|
|
11865
|
+
*/
|
|
11866
|
+
getContextStats() {
|
|
11867
|
+
return this.context.getStats();
|
|
11868
|
+
}
|
|
11869
|
+
/**
|
|
11870
|
+
* Clear conversation context (start fresh)
|
|
11871
|
+
*/
|
|
11872
|
+
clearContext() {
|
|
11873
|
+
this.context.clear();
|
|
11874
|
+
}
|
|
11875
|
+
};
|
|
11014
11876
|
}
|
|
11015
11877
|
});
|
|
11016
11878
|
|
|
@@ -11086,7 +11948,24 @@ async function runDaemonTurn(sessionId, events) {
|
|
|
11086
11948
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11087
11949
|
meta: { triggeredBy: events.map((e) => e.content?.slice(0, 80)) }
|
|
11088
11950
|
});
|
|
11951
|
+
try {
|
|
11952
|
+
resolveBatchOnTurnEnd(events, !error);
|
|
11953
|
+
} catch (err) {
|
|
11954
|
+
console.error("[daemon] ack bookkeeping threw:", err?.message || err);
|
|
11955
|
+
}
|
|
11089
11956
|
broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
|
|
11957
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11958
|
+
for (const ev of events) {
|
|
11959
|
+
if (ev.ref?.channel !== "slack") continue;
|
|
11960
|
+
const ref = ev.ref;
|
|
11961
|
+
const channel = ref.slackChannel;
|
|
11962
|
+
const ts = ref.messageTs;
|
|
11963
|
+
if (!channel || !ts) continue;
|
|
11964
|
+
const key2 = `${channel}\u241F${ts}`;
|
|
11965
|
+
if (seen.has(key2)) continue;
|
|
11966
|
+
seen.add(key2);
|
|
11967
|
+
void removeLoadingReaction(channel, ts);
|
|
11968
|
+
}
|
|
11090
11969
|
}
|
|
11091
11970
|
var listeners;
|
|
11092
11971
|
var init_daemon = __esm({
|
|
@@ -11097,6 +11976,8 @@ var init_daemon = __esm({
|
|
|
11097
11976
|
init_db();
|
|
11098
11977
|
init_inbox();
|
|
11099
11978
|
init_webhook_events();
|
|
11979
|
+
init_inbox_acks();
|
|
11980
|
+
init_client3();
|
|
11100
11981
|
listeners = /* @__PURE__ */ new Map();
|
|
11101
11982
|
}
|
|
11102
11983
|
});
|
|
@@ -11195,6 +12076,233 @@ var init_ensure_orchestrator = __esm({
|
|
|
11195
12076
|
}
|
|
11196
12077
|
});
|
|
11197
12078
|
|
|
12079
|
+
// src/orchestrator/self-update.ts
|
|
12080
|
+
var self_update_exports = {};
|
|
12081
|
+
__export(self_update_exports, {
|
|
12082
|
+
__test: () => __test,
|
|
12083
|
+
startSelfUpdater: () => startSelfUpdater,
|
|
12084
|
+
stopSelfUpdater: () => stopSelfUpdater
|
|
12085
|
+
});
|
|
12086
|
+
import { spawn as spawn2, execFile } from "child_process";
|
|
12087
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10 } from "fs";
|
|
12088
|
+
import { dirname as dirname10, join as join18 } from "path";
|
|
12089
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12090
|
+
function currentVersion2() {
|
|
12091
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
12092
|
+
const candidates = [
|
|
12093
|
+
join18(here, "..", "..", "package.json"),
|
|
12094
|
+
join18(here, "..", "package.json"),
|
|
12095
|
+
join18(process.cwd(), "package.json")
|
|
12096
|
+
];
|
|
12097
|
+
for (const p of candidates) {
|
|
12098
|
+
try {
|
|
12099
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
12100
|
+
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
12101
|
+
} catch {
|
|
12102
|
+
}
|
|
12103
|
+
}
|
|
12104
|
+
return "0.0.0";
|
|
12105
|
+
}
|
|
12106
|
+
function isLikelyGlobalInstall() {
|
|
12107
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
12108
|
+
return here.includes("/node_modules/sparkecoder/") || here.includes("\\node_modules\\sparkecoder\\");
|
|
12109
|
+
}
|
|
12110
|
+
function isEnabled() {
|
|
12111
|
+
if (process.env.SPARKECODER_AUTO_UPDATE === "false" || process.env.SPARKECODER_AUTO_UPDATE === "0") return false;
|
|
12112
|
+
try {
|
|
12113
|
+
const cfg = getConfig();
|
|
12114
|
+
if (cfg?.autoUpdate?.enabled === false) return false;
|
|
12115
|
+
} catch {
|
|
12116
|
+
}
|
|
12117
|
+
return true;
|
|
12118
|
+
}
|
|
12119
|
+
function remoteUrl() {
|
|
12120
|
+
try {
|
|
12121
|
+
const cfg = getConfig();
|
|
12122
|
+
const url = cfg?.remoteServer?.url;
|
|
12123
|
+
return typeof url === "string" && url.length > 0 ? url.replace(/\/+$/, "") : null;
|
|
12124
|
+
} catch {
|
|
12125
|
+
return null;
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
12128
|
+
function intervalMs() {
|
|
12129
|
+
try {
|
|
12130
|
+
const h = getConfig()?.autoUpdate?.intervalHours;
|
|
12131
|
+
if (typeof h === "number" && h > 0) return h * 60 * 6e4;
|
|
12132
|
+
} catch {
|
|
12133
|
+
}
|
|
12134
|
+
return DEFAULT_INTERVAL_HOURS * 60 * 6e4;
|
|
12135
|
+
}
|
|
12136
|
+
function semverGt(a, b) {
|
|
12137
|
+
const parse = (v) => v.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
12138
|
+
const pa = parse(a);
|
|
12139
|
+
const pb = parse(b);
|
|
12140
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
12141
|
+
const x = pa[i] ?? 0;
|
|
12142
|
+
const y = pb[i] ?? 0;
|
|
12143
|
+
if (x > y) return true;
|
|
12144
|
+
if (x < y) return false;
|
|
12145
|
+
}
|
|
12146
|
+
return false;
|
|
12147
|
+
}
|
|
12148
|
+
function statePath() {
|
|
12149
|
+
try {
|
|
12150
|
+
return join18(getAppDataDirectory(), "self-update-state.json");
|
|
12151
|
+
} catch {
|
|
12152
|
+
return null;
|
|
12153
|
+
}
|
|
12154
|
+
}
|
|
12155
|
+
function readState() {
|
|
12156
|
+
const p = statePath();
|
|
12157
|
+
if (!p) return {};
|
|
12158
|
+
try {
|
|
12159
|
+
return JSON.parse(readFileSync11(p, "utf8"));
|
|
12160
|
+
} catch {
|
|
12161
|
+
return {};
|
|
12162
|
+
}
|
|
12163
|
+
}
|
|
12164
|
+
function writeState(s) {
|
|
12165
|
+
const p = statePath();
|
|
12166
|
+
if (!p) return;
|
|
12167
|
+
try {
|
|
12168
|
+
mkdirSync10(dirname10(p), { recursive: true });
|
|
12169
|
+
writeFileSync7(p, JSON.stringify(s));
|
|
12170
|
+
} catch {
|
|
12171
|
+
}
|
|
12172
|
+
}
|
|
12173
|
+
function attemptedRecently(target, now) {
|
|
12174
|
+
const s = readState();
|
|
12175
|
+
return s.lastTarget === target && typeof s.lastAttemptAt === "number" && now - s.lastAttemptAt < RETRY_COOLDOWN_MS;
|
|
12176
|
+
}
|
|
12177
|
+
function latestPublishedVersion() {
|
|
12178
|
+
return new Promise((resolve13) => {
|
|
12179
|
+
execFile("npm", ["view", "sparkecoder", "version"], { timeout: 3e4 }, (err, stdout) => {
|
|
12180
|
+
if (err) {
|
|
12181
|
+
resolve13(null);
|
|
12182
|
+
return;
|
|
12183
|
+
}
|
|
12184
|
+
const v = String(stdout).trim();
|
|
12185
|
+
resolve13(/^\d+\.\d+\.\d+/.test(v) ? v : null);
|
|
12186
|
+
});
|
|
12187
|
+
});
|
|
12188
|
+
}
|
|
12189
|
+
function runInstaller(url) {
|
|
12190
|
+
const secret = process.env.SPARKECODER_SETUP_SECRET || process.env.SPARKECODER_TUNNEL_SECRET || "";
|
|
12191
|
+
const query = secret ? `?secret=${encodeURIComponent(secret)}` : "";
|
|
12192
|
+
const oneLiner = `bash -c "$(curl -fsSL '${url}/install.sh${query}')" >/tmp/sparkecoder-selfupdate.log 2>&1`;
|
|
12193
|
+
const child = spawn2("bash", ["-lc", oneLiner], {
|
|
12194
|
+
detached: true,
|
|
12195
|
+
stdio: "ignore"
|
|
12196
|
+
});
|
|
12197
|
+
child.unref();
|
|
12198
|
+
}
|
|
12199
|
+
async function checkAndUpdate() {
|
|
12200
|
+
if (upgrading || !isEnabled()) return;
|
|
12201
|
+
const url = remoteUrl();
|
|
12202
|
+
if (!url) return;
|
|
12203
|
+
const latest = await latestPublishedVersion();
|
|
12204
|
+
if (!latest) return;
|
|
12205
|
+
const current = currentVersion2();
|
|
12206
|
+
if (!semverGt(latest, current)) return;
|
|
12207
|
+
const now = Date.now();
|
|
12208
|
+
if (attemptedRecently(latest, now)) {
|
|
12209
|
+
console.log(`[self-update] v${latest} already attempted recently; skipping until cooldown elapses`);
|
|
12210
|
+
return;
|
|
12211
|
+
}
|
|
12212
|
+
upgrading = true;
|
|
12213
|
+
const announced = await announceUpdate(latest);
|
|
12214
|
+
const delay = announced ? ANNOUNCE_GRACE_MS : 0;
|
|
12215
|
+
if (announced) {
|
|
12216
|
+
console.log(`[self-update] announced v${latest} in Slack; updating in ${Math.round(delay / 6e4)}m`);
|
|
12217
|
+
}
|
|
12218
|
+
const t = setTimeout(() => doInstall(latest, url, current), delay);
|
|
12219
|
+
if (typeof t.unref === "function") t.unref();
|
|
12220
|
+
}
|
|
12221
|
+
function doInstall(latest, url, current) {
|
|
12222
|
+
const prev = readState();
|
|
12223
|
+
writeState({
|
|
12224
|
+
lastTarget: latest,
|
|
12225
|
+
lastAttemptAt: Date.now(),
|
|
12226
|
+
attempts: prev.lastTarget === latest ? (prev.attempts ?? 0) + 1 : 1
|
|
12227
|
+
});
|
|
12228
|
+
console.log(`[self-update] newer version available: v${current} \u2192 v${latest}; re-running installer`);
|
|
12229
|
+
try {
|
|
12230
|
+
runInstaller(url);
|
|
12231
|
+
} catch (err) {
|
|
12232
|
+
upgrading = false;
|
|
12233
|
+
console.warn("[self-update] failed to launch installer:", err?.message || err);
|
|
12234
|
+
}
|
|
12235
|
+
}
|
|
12236
|
+
async function findOrchestratorId() {
|
|
12237
|
+
try {
|
|
12238
|
+
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
12239
|
+
const all = await sessionQueries2.list(500, 0);
|
|
12240
|
+
const orch = all.find((s) => s?.config?.role === "orchestrator");
|
|
12241
|
+
return orch?.id ?? null;
|
|
12242
|
+
} catch {
|
|
12243
|
+
return null;
|
|
12244
|
+
}
|
|
12245
|
+
}
|
|
12246
|
+
async function announceUpdate(target) {
|
|
12247
|
+
try {
|
|
12248
|
+
const { isSlackConfigured: isSlackConfigured2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
12249
|
+
if (!isSlackConfigured2()) return false;
|
|
12250
|
+
const orchId = await findOrchestratorId();
|
|
12251
|
+
if (!orchId) return false;
|
|
12252
|
+
const { pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports));
|
|
12253
|
+
pushToInbox2(orchId, {
|
|
12254
|
+
ref: { channel: "system", kind: "worker.completed", workerId: "self-update", workerName: "self-update" },
|
|
12255
|
+
content: `[SYSTEM self-update] A software update to v${target} will begin in about 5 minutes and the service will restart briefly (expect ~1\u20132 minutes of downtime). Send a short Slack heads-up to whoever you normally talk to / the bot owner that you'll be offline for a quick update, then carry on. Use the messenger tool to deliver it. If you genuinely don't know who to tell, post in the most relevant channel you've recently been active in; if Slack isn't reachable, skip silently \u2014 the update will proceed regardless.`,
|
|
12256
|
+
wake: "now",
|
|
12257
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
12258
|
+
});
|
|
12259
|
+
return true;
|
|
12260
|
+
} catch {
|
|
12261
|
+
return false;
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12264
|
+
function startSelfUpdater() {
|
|
12265
|
+
if (started) return;
|
|
12266
|
+
started = true;
|
|
12267
|
+
if (!isEnabled()) {
|
|
12268
|
+
console.log("[self-update] disabled");
|
|
12269
|
+
return;
|
|
12270
|
+
}
|
|
12271
|
+
if (!isLikelyGlobalInstall()) {
|
|
12272
|
+
console.log("[self-update] skipped (not a global install)");
|
|
12273
|
+
return;
|
|
12274
|
+
}
|
|
12275
|
+
const kickoff = setTimeout(() => {
|
|
12276
|
+
void checkAndUpdate();
|
|
12277
|
+
timer = setInterval(() => {
|
|
12278
|
+
void checkAndUpdate();
|
|
12279
|
+
}, intervalMs());
|
|
12280
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
12281
|
+
}, INITIAL_DELAY_MS);
|
|
12282
|
+
if (typeof kickoff.unref === "function") kickoff.unref();
|
|
12283
|
+
}
|
|
12284
|
+
function stopSelfUpdater() {
|
|
12285
|
+
if (timer) {
|
|
12286
|
+
clearInterval(timer);
|
|
12287
|
+
timer = null;
|
|
12288
|
+
}
|
|
12289
|
+
}
|
|
12290
|
+
var INITIAL_DELAY_MS, DEFAULT_INTERVAL_HOURS, ANNOUNCE_GRACE_MS, RETRY_COOLDOWN_MS, timer, started, upgrading, __test;
|
|
12291
|
+
var init_self_update = __esm({
|
|
12292
|
+
"src/orchestrator/self-update.ts"() {
|
|
12293
|
+
"use strict";
|
|
12294
|
+
init_config();
|
|
12295
|
+
INITIAL_DELAY_MS = 5 * 6e4;
|
|
12296
|
+
DEFAULT_INTERVAL_HOURS = 6;
|
|
12297
|
+
ANNOUNCE_GRACE_MS = 5 * 6e4;
|
|
12298
|
+
RETRY_COOLDOWN_MS = 24 * 60 * 6e4;
|
|
12299
|
+
timer = null;
|
|
12300
|
+
started = false;
|
|
12301
|
+
upgrading = false;
|
|
12302
|
+
__test = { currentVersion: currentVersion2, semverGt, isLikelyGlobalInstall };
|
|
12303
|
+
}
|
|
12304
|
+
});
|
|
12305
|
+
|
|
11198
12306
|
// src/tasks/scheduler.ts
|
|
11199
12307
|
var scheduler_exports = {};
|
|
11200
12308
|
__export(scheduler_exports, {
|
|
@@ -11281,11 +12389,11 @@ import { Hono as Hono10 } from "hono";
|
|
|
11281
12389
|
import { serve } from "@hono/node-server";
|
|
11282
12390
|
import { cors } from "hono/cors";
|
|
11283
12391
|
import { logger } from "hono/logger";
|
|
11284
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
11285
|
-
import { resolve as resolve12, dirname as
|
|
11286
|
-
import { spawn as
|
|
12392
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
12393
|
+
import { resolve as resolve12, dirname as dirname11, join as join19 } from "path";
|
|
12394
|
+
import { spawn as spawn3 } from "child_process";
|
|
11287
12395
|
import { createServer as createNetServer } from "net";
|
|
11288
|
-
import { fileURLToPath as
|
|
12396
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11289
12397
|
|
|
11290
12398
|
// src/server/routes/sessions.ts
|
|
11291
12399
|
init_db();
|
|
@@ -11298,7 +12406,7 @@ import { zValidator } from "@hono/zod-validator";
|
|
|
11298
12406
|
import { z as z16 } from "zod";
|
|
11299
12407
|
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11300
12408
|
import { readdir as readdir6 } from "fs/promises";
|
|
11301
|
-
import { join as
|
|
12409
|
+
import { join as join14, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11302
12410
|
import { nanoid as nanoid10 } from "nanoid";
|
|
11303
12411
|
|
|
11304
12412
|
// src/tasks/agent-status.ts
|
|
@@ -11939,7 +13047,7 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11939
13047
|
});
|
|
11940
13048
|
function getAttachmentsDir(sessionId) {
|
|
11941
13049
|
const appDataDir = getAppDataDirectory();
|
|
11942
|
-
return
|
|
13050
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
11943
13051
|
}
|
|
11944
13052
|
function ensureAttachmentsDir(sessionId) {
|
|
11945
13053
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -11960,7 +13068,7 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11960
13068
|
}
|
|
11961
13069
|
const files = readdirSync3(dir);
|
|
11962
13070
|
const attachments = files.map((filename) => {
|
|
11963
|
-
const filePath =
|
|
13071
|
+
const filePath = join14(dir, filename);
|
|
11964
13072
|
const stats = statSync2(filePath);
|
|
11965
13073
|
return {
|
|
11966
13074
|
id: filename.split("_")[0],
|
|
@@ -11995,7 +13103,7 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11995
13103
|
const id = nanoid10(10);
|
|
11996
13104
|
const ext = extname8(file.name) || "";
|
|
11997
13105
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11998
|
-
const filePath =
|
|
13106
|
+
const filePath = join14(dir, safeFilename);
|
|
11999
13107
|
const arrayBuffer = await file.arrayBuffer();
|
|
12000
13108
|
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
12001
13109
|
return c.json({
|
|
@@ -12021,7 +13129,7 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12021
13129
|
const id = nanoid10(10);
|
|
12022
13130
|
const ext = extname8(body.filename) || "";
|
|
12023
13131
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12024
|
-
const filePath =
|
|
13132
|
+
const filePath = join14(dir, safeFilename);
|
|
12025
13133
|
let base64Data = body.data;
|
|
12026
13134
|
if (base64Data.includes(",")) {
|
|
12027
13135
|
base64Data = base64Data.split(",")[1];
|
|
@@ -12058,7 +13166,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12058
13166
|
if (!file) {
|
|
12059
13167
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12060
13168
|
}
|
|
12061
|
-
const filePath =
|
|
13169
|
+
const filePath = join14(dir, file);
|
|
12062
13170
|
unlinkSync2(filePath);
|
|
12063
13171
|
return c.json({ success: true, id: attachmentId });
|
|
12064
13172
|
});
|
|
@@ -12141,7 +13249,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12141
13249
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12142
13250
|
for (const entry2 of entries) {
|
|
12143
13251
|
if (results.length >= limit * 2) break;
|
|
12144
|
-
const fullPath =
|
|
13252
|
+
const fullPath = join14(currentDir, entry2.name);
|
|
12145
13253
|
const relativePath = relative9(baseDir, fullPath);
|
|
12146
13254
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12147
13255
|
continue;
|
|
@@ -12301,7 +13409,7 @@ import { Hono as Hono2 } from "hono";
|
|
|
12301
13409
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12302
13410
|
import { z as z17 } from "zod";
|
|
12303
13411
|
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
12304
|
-
import { join as
|
|
13412
|
+
import { join as join15 } from "path";
|
|
12305
13413
|
|
|
12306
13414
|
// src/agent/missing-tool-recovery.ts
|
|
12307
13415
|
init_db();
|
|
@@ -12568,6 +13676,7 @@ init_stream_proxy();
|
|
|
12568
13676
|
init_recorder();
|
|
12569
13677
|
init_remote();
|
|
12570
13678
|
init_resize_image();
|
|
13679
|
+
init_local_device_time();
|
|
12571
13680
|
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
12572
13681
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
12573
13682
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
@@ -12683,7 +13792,7 @@ var rejectSchema = z17.object({
|
|
|
12683
13792
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12684
13793
|
function getAttachmentsDirectory(sessionId) {
|
|
12685
13794
|
const appDataDir = getAppDataDirectory();
|
|
12686
|
-
return
|
|
13795
|
+
return join15(appDataDir, "attachments", sessionId);
|
|
12687
13796
|
}
|
|
12688
13797
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12689
13798
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -12706,7 +13815,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12706
13815
|
attachment.mediaType = resized.mediaType;
|
|
12707
13816
|
attachment.data = buffer.toString("base64");
|
|
12708
13817
|
}
|
|
12709
|
-
const filePath =
|
|
13818
|
+
const filePath = join15(attachmentsDir, filename);
|
|
12710
13819
|
writeFileSync6(filePath, buffer);
|
|
12711
13820
|
return filePath;
|
|
12712
13821
|
}
|
|
@@ -13083,9 +14192,12 @@ agents.post(
|
|
|
13083
14192
|
if (!session) {
|
|
13084
14193
|
return c.json({ error: "Session not found" }, 404);
|
|
13085
14194
|
}
|
|
13086
|
-
if (session.config?.role === "orchestrator" && !/^\[\
|
|
14195
|
+
if (session.config?.role === "orchestrator" && !/^\[(WEB|SLACK|SYSTEM|SCHEDULE|WEBHOOK)\b/.test(prompt)) {
|
|
13087
14196
|
prompt = `[WEB] ${prompt}`;
|
|
13088
14197
|
}
|
|
14198
|
+
if (session.config?.role === "orchestrator") {
|
|
14199
|
+
prompt = prependLocalDeviceTimeToUserMessage(prompt);
|
|
14200
|
+
}
|
|
13089
14201
|
const nextSequence = await messageQueries.getNextSequence(id);
|
|
13090
14202
|
await createCheckpoint(id, session.workingDirectory, nextSequence);
|
|
13091
14203
|
let userMessageContent;
|
|
@@ -13695,17 +14807,17 @@ import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
|
13695
14807
|
import { z as z18 } from "zod";
|
|
13696
14808
|
import { readFileSync as readFileSync10 } from "fs";
|
|
13697
14809
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13698
|
-
import { dirname as dirname8, join as
|
|
14810
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
13699
14811
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13700
14812
|
var __dirname = dirname8(__filename);
|
|
13701
14813
|
var possiblePaths = [
|
|
13702
|
-
|
|
14814
|
+
join16(__dirname, "../package.json"),
|
|
13703
14815
|
// From dist/server -> dist/../package.json
|
|
13704
|
-
|
|
14816
|
+
join16(__dirname, "../../package.json"),
|
|
13705
14817
|
// From dist/server (if nested differently)
|
|
13706
|
-
|
|
14818
|
+
join16(__dirname, "../../../package.json"),
|
|
13707
14819
|
// From src/server/routes (development)
|
|
13708
|
-
|
|
14820
|
+
join16(process.cwd(), "package.json")
|
|
13709
14821
|
// From current working directory
|
|
13710
14822
|
];
|
|
13711
14823
|
var currentVersion = "0.0.0";
|
|
@@ -14166,6 +15278,25 @@ import { nanoid as nanoid12 } from "nanoid";
|
|
|
14166
15278
|
init_questions();
|
|
14167
15279
|
var tasks = new Hono5();
|
|
14168
15280
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
15281
|
+
var taskMcpServerSchema = z20.object({
|
|
15282
|
+
name: z20.string().min(1).describe("Tool prefix + display name."),
|
|
15283
|
+
transport: z20.enum(["http", "sse", "stdio"]),
|
|
15284
|
+
url: z20.string().url().optional().describe("http/sse transports."),
|
|
15285
|
+
headers: z20.record(z20.string(), z20.string()).optional().describe("Auth / custom headers for http/sse."),
|
|
15286
|
+
command: z20.string().optional().describe("stdio transport."),
|
|
15287
|
+
args: z20.array(z20.string()).optional(),
|
|
15288
|
+
env: z20.record(z20.string(), z20.string()).optional().describe("Env vars for stdio child process.")
|
|
15289
|
+
}).refine(
|
|
15290
|
+
(s) => s.transport === "stdio" ? !!s.command : !!s.url,
|
|
15291
|
+
{ message: 'http/sse require "url"; stdio requires "command".' }
|
|
15292
|
+
);
|
|
15293
|
+
var taskSkillSchema = z20.object({
|
|
15294
|
+
name: z20.string().min(1),
|
|
15295
|
+
description: z20.string().optional(),
|
|
15296
|
+
content: z20.string().min(1).describe("Full markdown body of the skill."),
|
|
15297
|
+
alwaysApply: z20.boolean().optional().describe("Inject into the system prompt up-front (vs load on demand)."),
|
|
15298
|
+
globs: z20.array(z20.string()).optional()
|
|
15299
|
+
});
|
|
14169
15300
|
var createTaskSchema = z20.object({
|
|
14170
15301
|
prompt: z20.string().min(1),
|
|
14171
15302
|
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
@@ -14177,8 +15308,30 @@ var createTaskSchema = z20.object({
|
|
|
14177
15308
|
parentTaskId: z20.string().optional(),
|
|
14178
15309
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
14179
15310
|
* worker's config so terminal events can wake the orchestrator. */
|
|
14180
|
-
orchestratorSessionId: z20.string().optional()
|
|
15311
|
+
orchestratorSessionId: z20.string().optional(),
|
|
15312
|
+
/** Task-scoped MCP servers — auto-connected for this task only. */
|
|
15313
|
+
mcpServers: z20.array(taskMcpServerSchema).optional(),
|
|
15314
|
+
/** Task-scoped skills — available to this task only. */
|
|
15315
|
+
skills: z20.array(taskSkillSchema).optional()
|
|
14181
15316
|
});
|
|
15317
|
+
function redactMcpServers(servers2) {
|
|
15318
|
+
if (!servers2 || servers2.length === 0) return void 0;
|
|
15319
|
+
return servers2.map((s) => ({
|
|
15320
|
+
name: s.name,
|
|
15321
|
+
transport: s.transport,
|
|
15322
|
+
url: s.url,
|
|
15323
|
+
hasHeaders: !!(s.headers && Object.keys(s.headers).length > 0),
|
|
15324
|
+
command: s.command
|
|
15325
|
+
}));
|
|
15326
|
+
}
|
|
15327
|
+
function redactSkills(skills2) {
|
|
15328
|
+
if (!skills2 || skills2.length === 0) return void 0;
|
|
15329
|
+
return skills2.map((s) => ({
|
|
15330
|
+
name: s.name,
|
|
15331
|
+
description: s.description,
|
|
15332
|
+
alwaysApply: s.alwaysApply
|
|
15333
|
+
}));
|
|
15334
|
+
}
|
|
14182
15335
|
tasks.post(
|
|
14183
15336
|
"/",
|
|
14184
15337
|
zValidator5("json", createTaskSchema),
|
|
@@ -14191,7 +15344,9 @@ tasks.post(
|
|
|
14191
15344
|
webhookUrl: body.webhookUrl,
|
|
14192
15345
|
maxIterations: body.maxIterations ?? 50,
|
|
14193
15346
|
status: "running",
|
|
14194
|
-
parentTaskId: body.parentTaskId
|
|
15347
|
+
parentTaskId: body.parentTaskId,
|
|
15348
|
+
mcpServers: redactMcpServers(body.mcpServers),
|
|
15349
|
+
skills: redactSkills(body.skills)
|
|
14195
15350
|
};
|
|
14196
15351
|
let agent;
|
|
14197
15352
|
if (body.parentTaskId) {
|
|
@@ -14268,7 +15423,9 @@ tasks.post(
|
|
|
14268
15423
|
prompt: body.prompt,
|
|
14269
15424
|
taskConfig,
|
|
14270
15425
|
abortSignal: abortController.signal,
|
|
14271
|
-
writeSSE
|
|
15426
|
+
writeSSE,
|
|
15427
|
+
mcpServers: body.mcpServers,
|
|
15428
|
+
skills: body.skills
|
|
14272
15429
|
});
|
|
14273
15430
|
await writeSSE(JSON.stringify({ type: "finish" }));
|
|
14274
15431
|
} catch (err) {
|
|
@@ -14362,6 +15519,8 @@ tasks.get("/:id", async (c) => {
|
|
|
14362
15519
|
model: session.model,
|
|
14363
15520
|
name: session.name,
|
|
14364
15521
|
parentTaskId: task.parentTaskId,
|
|
15522
|
+
mcpServers: task.mcpServers,
|
|
15523
|
+
skills: task.skills,
|
|
14365
15524
|
createdAt: session.createdAt.toISOString(),
|
|
14366
15525
|
updatedAt: session.updatedAt.toISOString(),
|
|
14367
15526
|
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
@@ -14484,6 +15643,204 @@ function verifySlackSignature(opts) {
|
|
|
14484
15643
|
// src/server/routes/slack.ts
|
|
14485
15644
|
init_client3();
|
|
14486
15645
|
init_slack();
|
|
15646
|
+
|
|
15647
|
+
// src/integrations/slack/files.ts
|
|
15648
|
+
init_client3();
|
|
15649
|
+
var MAX_BYTES = 100 * 1024 * 1024;
|
|
15650
|
+
var INGEST_TIMEOUT_MS = 2500;
|
|
15651
|
+
function inferFileName(file) {
|
|
15652
|
+
return typeof file.name === "string" && file.name || typeof file.title === "string" && file.title || `slack-file-${file.id}`;
|
|
15653
|
+
}
|
|
15654
|
+
function inferContentType(file) {
|
|
15655
|
+
if (typeof file.mimetype === "string" && file.mimetype) return file.mimetype;
|
|
15656
|
+
return "application/octet-stream";
|
|
15657
|
+
}
|
|
15658
|
+
function formatBytes(n) {
|
|
15659
|
+
if (!Number.isFinite(n) || n <= 0) return "?";
|
|
15660
|
+
if (n < 1024) return `${n} B`;
|
|
15661
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
15662
|
+
return `${(n / 1024 / 1024).toFixed(2)} MB`;
|
|
15663
|
+
}
|
|
15664
|
+
function withTimeout(p, ms, label) {
|
|
15665
|
+
return new Promise((resolve13, reject) => {
|
|
15666
|
+
const t = setTimeout(() => reject(new Error(`${label}_timeout`)), ms);
|
|
15667
|
+
p.then(
|
|
15668
|
+
(v) => {
|
|
15669
|
+
clearTimeout(t);
|
|
15670
|
+
resolve13(v);
|
|
15671
|
+
},
|
|
15672
|
+
(e) => {
|
|
15673
|
+
clearTimeout(t);
|
|
15674
|
+
reject(e);
|
|
15675
|
+
}
|
|
15676
|
+
);
|
|
15677
|
+
});
|
|
15678
|
+
}
|
|
15679
|
+
async function ingestOne(file, sessionId, botToken) {
|
|
15680
|
+
const fileName = inferFileName(file);
|
|
15681
|
+
const contentType = inferContentType(file);
|
|
15682
|
+
const declaredSize = typeof file.size === "number" ? file.size : 0;
|
|
15683
|
+
const base = {
|
|
15684
|
+
slackFileId: file.id,
|
|
15685
|
+
fileName,
|
|
15686
|
+
contentType,
|
|
15687
|
+
sizeBytes: declaredSize
|
|
15688
|
+
};
|
|
15689
|
+
const sourceUrl = file.url_private_download || file.url_private;
|
|
15690
|
+
if (!sourceUrl || typeof sourceUrl !== "string") {
|
|
15691
|
+
return { ...base, shortUrl: null, error: "no_source_url" };
|
|
15692
|
+
}
|
|
15693
|
+
if (declaredSize > MAX_BYTES) {
|
|
15694
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
15695
|
+
}
|
|
15696
|
+
let bytes;
|
|
15697
|
+
try {
|
|
15698
|
+
const res = await fetch(sourceUrl, {
|
|
15699
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
15700
|
+
});
|
|
15701
|
+
if (!res.ok) {
|
|
15702
|
+
return { ...base, shortUrl: null, error: `slack_fetch_${res.status}` };
|
|
15703
|
+
}
|
|
15704
|
+
const ab = await res.arrayBuffer();
|
|
15705
|
+
if (ab.byteLength > MAX_BYTES) {
|
|
15706
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
15707
|
+
}
|
|
15708
|
+
bytes = Buffer.from(ab);
|
|
15709
|
+
} catch (err) {
|
|
15710
|
+
return { ...base, shortUrl: null, error: `slack_fetch_error:${err?.message || "unknown"}` };
|
|
15711
|
+
}
|
|
15712
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
15713
|
+
let upload;
|
|
15714
|
+
try {
|
|
15715
|
+
upload = await storageQueries2.getUploadUrl(sessionId, fileName, contentType, "slack");
|
|
15716
|
+
} catch (err) {
|
|
15717
|
+
return { ...base, sizeBytes: bytes.length, shortUrl: null, error: `presign_failed:${err?.message || "unknown"}` };
|
|
15718
|
+
}
|
|
15719
|
+
try {
|
|
15720
|
+
const putRes = await fetch(upload.uploadUrl, {
|
|
15721
|
+
method: "PUT",
|
|
15722
|
+
headers: { "Content-Type": contentType },
|
|
15723
|
+
body: bytes
|
|
15724
|
+
});
|
|
15725
|
+
if (!putRes.ok) {
|
|
15726
|
+
return {
|
|
15727
|
+
...base,
|
|
15728
|
+
sizeBytes: bytes.length,
|
|
15729
|
+
shortUrl: null,
|
|
15730
|
+
error: `gcs_put_${putRes.status}`
|
|
15731
|
+
};
|
|
15732
|
+
}
|
|
15733
|
+
} catch (err) {
|
|
15734
|
+
return {
|
|
15735
|
+
...base,
|
|
15736
|
+
sizeBytes: bytes.length,
|
|
15737
|
+
shortUrl: null,
|
|
15738
|
+
error: `gcs_put_error:${err?.message || "unknown"}`
|
|
15739
|
+
};
|
|
15740
|
+
}
|
|
15741
|
+
try {
|
|
15742
|
+
await storageQueries2.updateFile(upload.fileId, { sizeBytes: bytes.length });
|
|
15743
|
+
} catch (err) {
|
|
15744
|
+
console.warn(`[slack-files] sizeBytes patch failed for ${upload.fileId}:`, err?.message || err);
|
|
15745
|
+
}
|
|
15746
|
+
const shortUrl = upload.shortUrl || // Defensive fallback: build it from the upload URL's origin if the
|
|
15747
|
+
// server somehow forgot to return it (older remote-server versions).
|
|
15748
|
+
inferShortUrlFromUploadUrl(upload.uploadUrl, upload.fileId);
|
|
15749
|
+
return {
|
|
15750
|
+
...base,
|
|
15751
|
+
sizeBytes: bytes.length,
|
|
15752
|
+
shortUrl
|
|
15753
|
+
};
|
|
15754
|
+
}
|
|
15755
|
+
function inferShortUrlFromUploadUrl(uploadUrl, fileId) {
|
|
15756
|
+
try {
|
|
15757
|
+
const u = new URL(uploadUrl);
|
|
15758
|
+
if (u.hostname.endsWith(".googleapis.com")) return null;
|
|
15759
|
+
return `${u.origin}/f/${fileId}`;
|
|
15760
|
+
} catch {
|
|
15761
|
+
return null;
|
|
15762
|
+
}
|
|
15763
|
+
}
|
|
15764
|
+
async function ingestSlackFiles(files, sessionId, options = {}) {
|
|
15765
|
+
if (!Array.isArray(files) || files.length === 0) return [];
|
|
15766
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
15767
|
+
if (!isRemoteConfigured2()) {
|
|
15768
|
+
console.warn(`[slack-files] storage not configured \u2014 skipping ingestion for ${files.length} file(s)`);
|
|
15769
|
+
return files.map((f) => ({
|
|
15770
|
+
slackFileId: f.id,
|
|
15771
|
+
fileName: inferFileName(f),
|
|
15772
|
+
contentType: inferContentType(f),
|
|
15773
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15774
|
+
shortUrl: null,
|
|
15775
|
+
error: "storage_unconfigured"
|
|
15776
|
+
}));
|
|
15777
|
+
}
|
|
15778
|
+
const botToken = getSlackBotToken();
|
|
15779
|
+
if (!botToken) {
|
|
15780
|
+
console.warn("[slack-files] no bot token \u2014 cannot download from Slack");
|
|
15781
|
+
return files.map((f) => ({
|
|
15782
|
+
slackFileId: f.id,
|
|
15783
|
+
fileName: inferFileName(f),
|
|
15784
|
+
contentType: inferContentType(f),
|
|
15785
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15786
|
+
shortUrl: null,
|
|
15787
|
+
error: "no_bot_token"
|
|
15788
|
+
}));
|
|
15789
|
+
}
|
|
15790
|
+
const timeoutMs = options.timeoutMs ?? INGEST_TIMEOUT_MS;
|
|
15791
|
+
const startedAt = Date.now();
|
|
15792
|
+
const pipeline = Promise.allSettled(
|
|
15793
|
+
files.map((f) => ingestOne(f, sessionId, botToken))
|
|
15794
|
+
);
|
|
15795
|
+
let settled;
|
|
15796
|
+
try {
|
|
15797
|
+
settled = await withTimeout(pipeline, timeoutMs, "ingest");
|
|
15798
|
+
} catch (err) {
|
|
15799
|
+
console.warn(`[slack-files] pipeline timeout after ${Date.now() - startedAt}ms (${err?.message || "timeout"})`);
|
|
15800
|
+
return files.map((f) => ({
|
|
15801
|
+
slackFileId: f.id,
|
|
15802
|
+
fileName: inferFileName(f),
|
|
15803
|
+
contentType: inferContentType(f),
|
|
15804
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15805
|
+
shortUrl: null,
|
|
15806
|
+
error: "timeout"
|
|
15807
|
+
}));
|
|
15808
|
+
}
|
|
15809
|
+
const results = settled.map((s, i) => {
|
|
15810
|
+
if (s.status === "fulfilled") return s.value;
|
|
15811
|
+
const f = files[i];
|
|
15812
|
+
return {
|
|
15813
|
+
slackFileId: f.id,
|
|
15814
|
+
fileName: inferFileName(f),
|
|
15815
|
+
contentType: inferContentType(f),
|
|
15816
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15817
|
+
shortUrl: null,
|
|
15818
|
+
error: `unexpected:${s.reason?.message || String(s.reason)}`
|
|
15819
|
+
};
|
|
15820
|
+
});
|
|
15821
|
+
const okCount = results.filter((r) => r.shortUrl).length;
|
|
15822
|
+
console.log(
|
|
15823
|
+
`[slack-files] ingested ${okCount}/${files.length} file(s) in ${Date.now() - startedAt}ms`
|
|
15824
|
+
);
|
|
15825
|
+
return results;
|
|
15826
|
+
}
|
|
15827
|
+
function formatFileBlock(files) {
|
|
15828
|
+
if (!files || files.length === 0) return "";
|
|
15829
|
+
const lines = ["[files]"];
|
|
15830
|
+
for (const f of files) {
|
|
15831
|
+
const sizeLabel = formatBytes(f.sizeBytes);
|
|
15832
|
+
if (f.shortUrl) {
|
|
15833
|
+
lines.push(` - ${f.fileName} (${f.contentType}, ${sizeLabel}): ${f.shortUrl}`);
|
|
15834
|
+
} else {
|
|
15835
|
+
lines.push(
|
|
15836
|
+
` - ${f.fileName} (${f.contentType}, ${sizeLabel}): [ingestion failed: ${f.error || "unknown"}]`
|
|
15837
|
+
);
|
|
15838
|
+
}
|
|
15839
|
+
}
|
|
15840
|
+
return lines.join("\n");
|
|
15841
|
+
}
|
|
15842
|
+
|
|
15843
|
+
// src/server/routes/slack.ts
|
|
14487
15844
|
init_webhook_events();
|
|
14488
15845
|
init_inbox();
|
|
14489
15846
|
var recentlyHandled = /* @__PURE__ */ new Map();
|
|
@@ -14570,9 +15927,43 @@ slack.post("/events", async (c) => {
|
|
|
14570
15927
|
inbound.content = inbound.content.replace(`user=${ev.user}`, () => enriched);
|
|
14571
15928
|
}
|
|
14572
15929
|
}
|
|
14573
|
-
|
|
15930
|
+
const slackFiles = Array.isArray(ev.files) ? ev.files : [];
|
|
14574
15931
|
markHandled(ev.channel, ev.ts);
|
|
14575
|
-
|
|
15932
|
+
if (ev.channel && ev.ts) {
|
|
15933
|
+
void addLoadingReaction(String(ev.channel), String(ev.ts));
|
|
15934
|
+
}
|
|
15935
|
+
let ingestedCount = 0;
|
|
15936
|
+
if (slackFiles.length > 0) {
|
|
15937
|
+
try {
|
|
15938
|
+
const ingested = await ingestSlackFiles(slackFiles, orchestratorId);
|
|
15939
|
+
const block = formatFileBlock(ingested);
|
|
15940
|
+
if (block) inbound.content = `${inbound.content}
|
|
15941
|
+
${block}`;
|
|
15942
|
+
ingestedCount = ingested.filter((f) => f.shortUrl).length;
|
|
15943
|
+
} catch (err) {
|
|
15944
|
+
console.warn("[slack-files] ingestion threw:", err?.message || err);
|
|
15945
|
+
inbound.content = `${inbound.content}
|
|
15946
|
+
[files] (ingestion failed: ${err?.message || "unknown"})`;
|
|
15947
|
+
}
|
|
15948
|
+
}
|
|
15949
|
+
pushToInbox(orchestratorId, inbound);
|
|
15950
|
+
updateEvent(auditId, {
|
|
15951
|
+
status: "routed",
|
|
15952
|
+
sessionId: orchestratorId,
|
|
15953
|
+
...slackFiles.length > 0 ? {
|
|
15954
|
+
// Preserve the original meta (ts, thread_ts, team,
|
|
15955
|
+
// event_subtype) from recordEvent above — updateEvent does a
|
|
15956
|
+
// shallow merge, so we have to re-include them.
|
|
15957
|
+
meta: {
|
|
15958
|
+
ts: ev.ts,
|
|
15959
|
+
thread_ts: ev.thread_ts,
|
|
15960
|
+
team: ev.team,
|
|
15961
|
+
event_subtype: ev.subtype,
|
|
15962
|
+
fileCount: slackFiles.length,
|
|
15963
|
+
ingestedCount
|
|
15964
|
+
}
|
|
15965
|
+
} : {}
|
|
15966
|
+
});
|
|
14576
15967
|
} else {
|
|
14577
15968
|
updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
|
|
14578
15969
|
}
|
|
@@ -14792,10 +16183,35 @@ integrations.get("/", async (c) => {
|
|
|
14792
16183
|
cfAccess: {
|
|
14793
16184
|
enabled: !!cfg?.auth?.cfAccess?.enabled,
|
|
14794
16185
|
teamDomain: cfg?.auth?.cfAccess?.teamDomain || null,
|
|
16186
|
+
audTag: cfg?.auth?.cfAccess?.audTag || null,
|
|
14795
16187
|
allowedEmails: cfg?.auth?.allowedEmails || []
|
|
14796
16188
|
}
|
|
14797
16189
|
});
|
|
14798
16190
|
});
|
|
16191
|
+
var cfAccessSchema = z21.object({
|
|
16192
|
+
enabled: z21.boolean().optional(),
|
|
16193
|
+
teamDomain: z21.string().optional(),
|
|
16194
|
+
audTag: z21.string().optional(),
|
|
16195
|
+
// Email allowlist for the public (cloudflared) surface. Empty array = allow
|
|
16196
|
+
// any email that passes the Cloudflare Access policy (no extra filtering).
|
|
16197
|
+
allowedEmails: z21.array(z21.string().trim().toLowerCase()).optional()
|
|
16198
|
+
});
|
|
16199
|
+
integrations.post("/cf-access", zValidator6("json", cfAccessSchema), async (c) => {
|
|
16200
|
+
const body = c.req.valid("json");
|
|
16201
|
+
if (body.enabled) {
|
|
16202
|
+
const cfg = getConfig();
|
|
16203
|
+
const teamDomain = body.teamDomain ?? cfg?.auth?.cfAccess?.teamDomain;
|
|
16204
|
+
const audTag = body.audTag ?? cfg?.auth?.cfAccess?.audTag;
|
|
16205
|
+
if (!teamDomain || !audTag) {
|
|
16206
|
+
return c.json(
|
|
16207
|
+
{ error: "teamDomain and audTag are required to enable Cloudflare Access" },
|
|
16208
|
+
400
|
|
16209
|
+
);
|
|
16210
|
+
}
|
|
16211
|
+
}
|
|
16212
|
+
setCfAccessConfig(body);
|
|
16213
|
+
return c.json({ ok: true });
|
|
16214
|
+
});
|
|
14799
16215
|
var slackConfigSchema = z21.object({
|
|
14800
16216
|
botToken: z21.string().optional(),
|
|
14801
16217
|
signingSecret: z21.string().optional(),
|
|
@@ -14978,8 +16394,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
14978
16394
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
14979
16395
|
import { z as z22 } from "zod";
|
|
14980
16396
|
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
14981
|
-
import { readFile as readFile12, writeFile as
|
|
14982
|
-
import { resolve as resolve11, join as
|
|
16397
|
+
import { readFile as readFile12, writeFile as writeFile7, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
16398
|
+
import { resolve as resolve11, join as join17, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
14983
16399
|
var skills = new Hono9();
|
|
14984
16400
|
function encodeId(filePath) {
|
|
14985
16401
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -15127,13 +16543,13 @@ skills.post(
|
|
|
15127
16543
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
15128
16544
|
const ext = extname9(safeName).toLowerCase();
|
|
15129
16545
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
15130
|
-
const filePath =
|
|
16546
|
+
const filePath = join17(targetDir, finalName);
|
|
15131
16547
|
if (existsSync21(filePath)) {
|
|
15132
16548
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
15133
16549
|
}
|
|
15134
16550
|
try {
|
|
15135
16551
|
await mkdir5(targetDir, { recursive: true });
|
|
15136
|
-
await
|
|
16552
|
+
await writeFile7(filePath, content, "utf-8");
|
|
15137
16553
|
} catch (err) {
|
|
15138
16554
|
return c.json({ error: err?.message || "write failed" }, 500);
|
|
15139
16555
|
}
|
|
@@ -15149,7 +16565,7 @@ skills.put(
|
|
|
15149
16565
|
if (filePath.includes("/skills/default")) {
|
|
15150
16566
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15151
16567
|
}
|
|
15152
|
-
await
|
|
16568
|
+
await writeFile7(filePath, c.req.valid("json").content, "utf-8");
|
|
15153
16569
|
return c.json({ ok: true });
|
|
15154
16570
|
}
|
|
15155
16571
|
);
|
|
@@ -15193,6 +16609,14 @@ skills.delete("/directories", (c) => {
|
|
|
15193
16609
|
init_config();
|
|
15194
16610
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
15195
16611
|
var EXEMPT_PATH_PREFIXES = ["/health", "/api/slack/events", "/api/inbox/", "/w/"];
|
|
16612
|
+
function isExemptPath(path) {
|
|
16613
|
+
return EXEMPT_PATH_PREFIXES.some((p) => {
|
|
16614
|
+
if (p.endsWith("/")) {
|
|
16615
|
+
return path === p.slice(0, -1) || path === p || path.startsWith(p);
|
|
16616
|
+
}
|
|
16617
|
+
return path === p || path.startsWith(p + "/") || path.startsWith(p + "?");
|
|
16618
|
+
});
|
|
16619
|
+
}
|
|
15196
16620
|
var cachedJWKS = null;
|
|
15197
16621
|
var cachedJWKSUrl = null;
|
|
15198
16622
|
function getOrCreateJWKS(teamDomain) {
|
|
@@ -15222,12 +16646,13 @@ function cfAccessMiddleware() {
|
|
|
15222
16646
|
return next();
|
|
15223
16647
|
}
|
|
15224
16648
|
const path = c.req.path;
|
|
15225
|
-
if (
|
|
16649
|
+
if (isExemptPath(path)) {
|
|
15226
16650
|
return next();
|
|
15227
16651
|
}
|
|
15228
16652
|
const host = c.req.header("host");
|
|
15229
16653
|
const remote = c.req.raw?.socket?.remoteAddress;
|
|
15230
|
-
|
|
16654
|
+
const hasCfJwt = !!c.req.header("cf-access-jwt-assertion");
|
|
16655
|
+
if (!hasCfJwt && isLoopback(host, remote)) {
|
|
15231
16656
|
return next();
|
|
15232
16657
|
}
|
|
15233
16658
|
const teamDomain = cfg.teamDomain;
|
|
@@ -15246,8 +16671,10 @@ function cfAccessMiddleware() {
|
|
|
15246
16671
|
audience: aud
|
|
15247
16672
|
});
|
|
15248
16673
|
const email = String(payload.email || "").toLowerCase();
|
|
15249
|
-
const
|
|
15250
|
-
|
|
16674
|
+
const emailDomain = email.split("@")[1] || "";
|
|
16675
|
+
const allowed = (auth?.allowedEmails || []).map((e) => e.toLowerCase().trim().replace(/^@/, "")).filter(Boolean);
|
|
16676
|
+
const isAllowed = allowed.length === 0 || allowed.some((entry2) => entry2.includes("@") ? entry2 === email : entry2 === emailDomain);
|
|
16677
|
+
if (!isAllowed) {
|
|
15251
16678
|
console.warn(`[cf-access] rejected ${email}: not in allowlist`);
|
|
15252
16679
|
return c.json({ error: "Email not allowed" }, 403);
|
|
15253
16680
|
}
|
|
@@ -15349,13 +16776,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
15349
16776
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
15350
16777
|
function getWebDirectory() {
|
|
15351
16778
|
try {
|
|
15352
|
-
const currentDir =
|
|
16779
|
+
const currentDir = dirname11(fileURLToPath5(import.meta.url));
|
|
15353
16780
|
const webDir = resolve12(currentDir, "..", "web");
|
|
15354
|
-
if (existsSync22(webDir) && existsSync22(
|
|
16781
|
+
if (existsSync22(webDir) && existsSync22(join19(webDir, "package.json"))) {
|
|
15355
16782
|
return webDir;
|
|
15356
16783
|
}
|
|
15357
16784
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
15358
|
-
if (existsSync22(altWebDir) && existsSync22(
|
|
16785
|
+
if (existsSync22(altWebDir) && existsSync22(join19(altWebDir, "package.json"))) {
|
|
15359
16786
|
return altWebDir;
|
|
15360
16787
|
}
|
|
15361
16788
|
return null;
|
|
@@ -15413,20 +16840,20 @@ async function findWebPort(preferredPort) {
|
|
|
15413
16840
|
return { port: preferredPort, alreadyRunning: false };
|
|
15414
16841
|
}
|
|
15415
16842
|
function hasProductionBuild(webDir) {
|
|
15416
|
-
const buildIdPath =
|
|
16843
|
+
const buildIdPath = join19(webDir, ".next", "BUILD_ID");
|
|
15417
16844
|
return existsSync22(buildIdPath);
|
|
15418
16845
|
}
|
|
15419
16846
|
function hasSourceFiles(webDir) {
|
|
15420
|
-
const appDir =
|
|
15421
|
-
const pagesDir =
|
|
15422
|
-
const rootAppDir =
|
|
15423
|
-
const rootPagesDir =
|
|
16847
|
+
const appDir = join19(webDir, "src", "app");
|
|
16848
|
+
const pagesDir = join19(webDir, "src", "pages");
|
|
16849
|
+
const rootAppDir = join19(webDir, "app");
|
|
16850
|
+
const rootPagesDir = join19(webDir, "pages");
|
|
15424
16851
|
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
15425
16852
|
}
|
|
15426
16853
|
function getStandaloneServerPath(webDir) {
|
|
15427
16854
|
const possiblePaths2 = [
|
|
15428
|
-
|
|
15429
|
-
|
|
16855
|
+
join19(webDir, ".next", "standalone", "server.js"),
|
|
16856
|
+
join19(webDir, ".next", "standalone", "web", "server.js")
|
|
15430
16857
|
];
|
|
15431
16858
|
for (const serverPath of possiblePaths2) {
|
|
15432
16859
|
if (existsSync22(serverPath)) {
|
|
@@ -15437,7 +16864,7 @@ function getStandaloneServerPath(webDir) {
|
|
|
15437
16864
|
}
|
|
15438
16865
|
function runCommand(command, args, cwd, env) {
|
|
15439
16866
|
return new Promise((resolve13) => {
|
|
15440
|
-
const child =
|
|
16867
|
+
const child = spawn3(command, args, {
|
|
15441
16868
|
cwd,
|
|
15442
16869
|
stdio: ["ignore", "pipe", "pipe"],
|
|
15443
16870
|
env,
|
|
@@ -15469,15 +16896,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15469
16896
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15470
16897
|
return { process: null, port: actualPort };
|
|
15471
16898
|
}
|
|
15472
|
-
const usePnpm = existsSync22(
|
|
15473
|
-
const useNpm = !usePnpm && existsSync22(
|
|
16899
|
+
const usePnpm = existsSync22(join19(webDir, "pnpm-lock.yaml"));
|
|
16900
|
+
const useNpm = !usePnpm && existsSync22(join19(webDir, "package-lock.json"));
|
|
15474
16901
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15475
16902
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15476
16903
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15477
|
-
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15478
|
-
const runtimeConfigPath =
|
|
16904
|
+
const runtimeConfig = { apiBaseUrl: apiUrl, localApiBaseUrl: `http://127.0.0.1:${apiPort}` };
|
|
16905
|
+
const runtimeConfigPath = join19(webDir, "runtime-config.json");
|
|
15479
16906
|
try {
|
|
15480
|
-
|
|
16907
|
+
writeFileSync8(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15481
16908
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
15482
16909
|
} catch (err) {
|
|
15483
16910
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -15497,7 +16924,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15497
16924
|
if (standaloneServerPath) {
|
|
15498
16925
|
command = "node";
|
|
15499
16926
|
args = ["server.js"];
|
|
15500
|
-
cwd =
|
|
16927
|
+
cwd = dirname11(standaloneServerPath);
|
|
15501
16928
|
webEnv.PORT = String(actualPort);
|
|
15502
16929
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
15503
16930
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -15527,7 +16954,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15527
16954
|
}
|
|
15528
16955
|
return { process: null, port: actualPort };
|
|
15529
16956
|
}
|
|
15530
|
-
const child =
|
|
16957
|
+
const child = spawn3(command, args, {
|
|
15531
16958
|
cwd,
|
|
15532
16959
|
stdio: ["ignore", "pipe", "pipe"],
|
|
15533
16960
|
env: webEnv,
|
|
@@ -15535,12 +16962,12 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15535
16962
|
shell: true
|
|
15536
16963
|
});
|
|
15537
16964
|
const startupTimeout = 3e4;
|
|
15538
|
-
let
|
|
16965
|
+
let started2 = false;
|
|
15539
16966
|
let exited = false;
|
|
15540
16967
|
let exitCode = null;
|
|
15541
16968
|
const startedPromise = new Promise((resolve13) => {
|
|
15542
16969
|
const timeout = setTimeout(() => {
|
|
15543
|
-
if (!
|
|
16970
|
+
if (!started2 && !exited) {
|
|
15544
16971
|
resolve13(false);
|
|
15545
16972
|
}
|
|
15546
16973
|
}, startupTimeout);
|
|
@@ -15552,8 +16979,8 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15552
16979
|
console.log(` Web UI: ${line}`);
|
|
15553
16980
|
}
|
|
15554
16981
|
}
|
|
15555
|
-
if (!
|
|
15556
|
-
|
|
16982
|
+
if (!started2 && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
16983
|
+
started2 = true;
|
|
15557
16984
|
clearTimeout(timeout);
|
|
15558
16985
|
resolve13(true);
|
|
15559
16986
|
}
|
|
@@ -15572,7 +16999,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15572
16999
|
child.on("exit", (code) => {
|
|
15573
17000
|
exited = true;
|
|
15574
17001
|
exitCode = code;
|
|
15575
|
-
if (!
|
|
17002
|
+
if (!started2) {
|
|
15576
17003
|
clearTimeout(timeout);
|
|
15577
17004
|
resolve13(false);
|
|
15578
17005
|
}
|
|
@@ -15692,7 +17119,7 @@ async function startServer(options = {}) {
|
|
|
15692
17119
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
15693
17120
|
}
|
|
15694
17121
|
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
15695
|
-
|
|
17122
|
+
mkdirSync11(config.resolvedWorkingDirectory, { recursive: true });
|
|
15696
17123
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
15697
17124
|
}
|
|
15698
17125
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -15723,9 +17150,17 @@ async function startServer(options = {}) {
|
|
|
15723
17150
|
try {
|
|
15724
17151
|
const { startOrchestratorDaemon: startOrchestratorDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
15725
17152
|
startOrchestratorDaemon2();
|
|
17153
|
+
const { startReconciler: startReconciler2 } = await Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports));
|
|
17154
|
+
startReconciler2();
|
|
15726
17155
|
} catch (err) {
|
|
15727
17156
|
if (!options.quiet) console.warn(`[daemon] start skipped: ${err.message}`);
|
|
15728
17157
|
}
|
|
17158
|
+
try {
|
|
17159
|
+
const { startSelfUpdater: startSelfUpdater2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
|
|
17160
|
+
startSelfUpdater2();
|
|
17161
|
+
} catch (err) {
|
|
17162
|
+
if (!options.quiet) console.warn(`[self-update] start skipped: ${err.message}`);
|
|
17163
|
+
}
|
|
15729
17164
|
try {
|
|
15730
17165
|
const { startScheduler: startScheduler2 } = await Promise.resolve().then(() => (init_scheduler(), scheduler_exports));
|
|
15731
17166
|
startScheduler2({ quiet: options.quiet });
|