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/server/index.js
CHANGED
|
@@ -818,8 +818,8 @@ var init_types = __esm({
|
|
|
818
818
|
authKey: z.string().optional()
|
|
819
819
|
}).optional();
|
|
820
820
|
SparkcoderConfigSchema = z.object({
|
|
821
|
-
// Default model to use (
|
|
822
|
-
defaultModel: z.string().default("
|
|
821
|
+
// Default model to use (LiteLLM model id)
|
|
822
|
+
defaultModel: z.string().default("gpt-5.5"),
|
|
823
823
|
// Working directory for file operations
|
|
824
824
|
workingDirectory: z.string().optional(),
|
|
825
825
|
// Tool approval settings
|
|
@@ -858,6 +858,14 @@ var init_types = __esm({
|
|
|
858
858
|
webhooks: z.object({
|
|
859
859
|
token: z.string().optional()
|
|
860
860
|
}).optional(),
|
|
861
|
+
// Self-update: when running as the managed service, periodically check
|
|
862
|
+
// npm for a newer published version and, if found, re-run the hosted
|
|
863
|
+
// installer (full upgrade + restart). Disabled automatically when not
|
|
864
|
+
// running from a global install (e.g. dev/source checkouts).
|
|
865
|
+
autoUpdate: z.object({
|
|
866
|
+
enabled: z.boolean().optional().default(true),
|
|
867
|
+
intervalHours: z.number().positive().optional().default(6)
|
|
868
|
+
}).optional().default({}),
|
|
861
869
|
// Database path (used for local SQLite - ignored if remoteServer is configured)
|
|
862
870
|
databasePath: z.string().optional().default("./sparkecoder.db"),
|
|
863
871
|
// Remote server configuration (for centralized storage)
|
|
@@ -965,6 +973,7 @@ __export(config_exports, {
|
|
|
965
973
|
requiresApproval: () => requiresApproval,
|
|
966
974
|
saveAuthKey: () => saveAuthKey,
|
|
967
975
|
setApiKey: () => setApiKey,
|
|
976
|
+
setCfAccessConfig: () => setCfAccessConfig,
|
|
968
977
|
setMcpServers: () => setMcpServers,
|
|
969
978
|
setPublicUrl: () => setPublicUrl,
|
|
970
979
|
setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
|
|
@@ -1153,12 +1162,12 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
1153
1162
|
]
|
|
1154
1163
|
};
|
|
1155
1164
|
const DEFAULT_REMOTE_URL = "https://agent-remote-server.sparkecode.com";
|
|
1156
|
-
const
|
|
1165
|
+
const remoteUrl2 = process.env.SPARKECODER_REMOTE_URL || config.remoteServer?.url || DEFAULT_REMOTE_URL;
|
|
1157
1166
|
const remoteAuthKey = process.env.SPARKECODER_AUTH_KEY || config.remoteServer?.authKey || loadStoredAuthKey();
|
|
1158
1167
|
const resolvedRemoteServer = {
|
|
1159
|
-
url:
|
|
1168
|
+
url: remoteUrl2,
|
|
1160
1169
|
authKey: remoteAuthKey,
|
|
1161
|
-
isConfigured: !!
|
|
1170
|
+
isConfigured: !!remoteUrl2 && !!remoteAuthKey
|
|
1162
1171
|
};
|
|
1163
1172
|
const resolved = {
|
|
1164
1173
|
...config,
|
|
@@ -1314,6 +1323,40 @@ function setPublicUrl(publicUrl) {
|
|
|
1314
1323
|
console.warn("[config] failed to persist publicUrl:", err?.message || err);
|
|
1315
1324
|
}
|
|
1316
1325
|
}
|
|
1326
|
+
function setCfAccessConfig(input) {
|
|
1327
|
+
const applyToAuth = (auth) => {
|
|
1328
|
+
const curAuth = auth || {};
|
|
1329
|
+
const curCf = curAuth.cfAccess || {};
|
|
1330
|
+
const nextCf = { ...curCf };
|
|
1331
|
+
if (input.enabled !== void 0) nextCf.enabled = input.enabled;
|
|
1332
|
+
if (input.teamDomain !== void 0) nextCf.teamDomain = input.teamDomain;
|
|
1333
|
+
if (input.audTag !== void 0) nextCf.audTag = input.audTag;
|
|
1334
|
+
const nextAuth = { ...curAuth, cfAccess: nextCf };
|
|
1335
|
+
if (input.allowedEmails !== void 0) nextAuth.allowedEmails = input.allowedEmails;
|
|
1336
|
+
return nextAuth;
|
|
1337
|
+
};
|
|
1338
|
+
if (cachedConfig) {
|
|
1339
|
+
cachedConfig.auth = applyToAuth(cachedConfig.auth);
|
|
1340
|
+
}
|
|
1341
|
+
try {
|
|
1342
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
1343
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
1344
|
+
let raw = {};
|
|
1345
|
+
if (existsSync(target)) {
|
|
1346
|
+
try {
|
|
1347
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
1348
|
+
} catch {
|
|
1349
|
+
raw = {};
|
|
1350
|
+
}
|
|
1351
|
+
} else {
|
|
1352
|
+
raw = createDefaultConfig();
|
|
1353
|
+
}
|
|
1354
|
+
raw.auth = applyToAuth(raw.auth);
|
|
1355
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
console.warn("[config] failed to persist cf-access config:", err?.message || err);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1317
1360
|
function clearSlackConfig() {
|
|
1318
1361
|
if (cachedConfig) cachedConfig.slack = {};
|
|
1319
1362
|
try {
|
|
@@ -1355,7 +1398,7 @@ function autoApproveAllTools(sessionConfig) {
|
|
|
1355
1398
|
}
|
|
1356
1399
|
function createDefaultConfig() {
|
|
1357
1400
|
return {
|
|
1358
|
-
defaultModel: "
|
|
1401
|
+
defaultModel: "gpt-5.5",
|
|
1359
1402
|
// workingDirectory is intentionally not set - defaults to where CLI is run
|
|
1360
1403
|
toolApprovals: {
|
|
1361
1404
|
bash: true,
|
|
@@ -1377,6 +1420,10 @@ function createDefaultConfig() {
|
|
|
1377
1420
|
port: 3141,
|
|
1378
1421
|
host: "0.0.0.0"
|
|
1379
1422
|
},
|
|
1423
|
+
autoUpdate: {
|
|
1424
|
+
enabled: true,
|
|
1425
|
+
intervalHours: 6
|
|
1426
|
+
},
|
|
1380
1427
|
databasePath: "./sparkecoder.db"
|
|
1381
1428
|
};
|
|
1382
1429
|
}
|
|
@@ -1603,6 +1650,7 @@ var init_config = __esm({
|
|
|
1603
1650
|
openai: "OPENAI_API_KEY",
|
|
1604
1651
|
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
1605
1652
|
xai: "XAI_API_KEY",
|
|
1653
|
+
litellm: "LITELLM_API_KEY",
|
|
1606
1654
|
"ai-gateway": "AI_GATEWAY_API_KEY"
|
|
1607
1655
|
};
|
|
1608
1656
|
SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
|
|
@@ -4845,11 +4893,11 @@ async function getRepoNamespace(workingDirectory, configuredNamespace) {
|
|
|
4845
4893
|
if (configuredNamespace) {
|
|
4846
4894
|
return configuredNamespace;
|
|
4847
4895
|
}
|
|
4848
|
-
const
|
|
4849
|
-
if (!
|
|
4896
|
+
const remoteUrl2 = getGitRemoteUrl(workingDirectory);
|
|
4897
|
+
if (!remoteUrl2) {
|
|
4850
4898
|
return null;
|
|
4851
4899
|
}
|
|
4852
|
-
const parsed = parseGitRemoteUrl(
|
|
4900
|
+
const parsed = parseGitRemoteUrl(remoteUrl2);
|
|
4853
4901
|
if (!parsed) {
|
|
4854
4902
|
return null;
|
|
4855
4903
|
}
|
|
@@ -6546,7 +6594,8 @@ async function buildSystemPrompt(options) {
|
|
|
6546
6594
|
sessionId,
|
|
6547
6595
|
discoveredSkills,
|
|
6548
6596
|
activeFiles = [],
|
|
6549
|
-
customInstructions
|
|
6597
|
+
customInstructions,
|
|
6598
|
+
taskScopedSkills
|
|
6550
6599
|
} = options;
|
|
6551
6600
|
let alwaysLoadedContent = "";
|
|
6552
6601
|
let globMatchedContent = "";
|
|
@@ -6567,6 +6616,22 @@ async function buildSystemPrompt(options) {
|
|
|
6567
6616
|
const skills2 = await loadAllSkills2(skillsDirectories);
|
|
6568
6617
|
onDemandSkillsContext = formatSkillsForContext(skills2);
|
|
6569
6618
|
}
|
|
6619
|
+
let taskScopedSkillsBlock = "";
|
|
6620
|
+
if (taskScopedSkills && (taskScopedSkills.always.length > 0 || taskScopedSkills.onDemand.length > 0)) {
|
|
6621
|
+
const parts = ["<task_provided_skills>"];
|
|
6622
|
+
parts.push("These skills were supplied with this task and are available for this run only.");
|
|
6623
|
+
if (taskScopedSkills.always.length > 0) {
|
|
6624
|
+
parts.push(formatAlwaysLoadedSkills(taskScopedSkills.always));
|
|
6625
|
+
}
|
|
6626
|
+
if (taskScopedSkills.onDemand.length > 0) {
|
|
6627
|
+
parts.push("Load any of these on demand with the load_skill tool:");
|
|
6628
|
+
for (const s of taskScopedSkills.onDemand) {
|
|
6629
|
+
parts.push(`- ${s.name}: ${s.description}`);
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
parts.push("</task_provided_skills>");
|
|
6633
|
+
taskScopedSkillsBlock = parts.join("\n");
|
|
6634
|
+
}
|
|
6570
6635
|
const todos = await todoQueries.getBySession(sessionId);
|
|
6571
6636
|
const todosContext = formatTodosForContext(todos);
|
|
6572
6637
|
const plans = await readSessionPlans(workingDirectory, sessionId);
|
|
@@ -6859,6 +6924,8 @@ ${globMatchedContent}
|
|
|
6859
6924
|
${onDemandSkillsContext}
|
|
6860
6925
|
</on_demand_skills>
|
|
6861
6926
|
|
|
6927
|
+
${taskScopedSkillsBlock}
|
|
6928
|
+
|
|
6862
6929
|
<current_task_list>
|
|
6863
6930
|
${todosContext}
|
|
6864
6931
|
</current_task_list>
|
|
@@ -7480,6 +7547,111 @@ var init_sanitize_messages = __esm({
|
|
|
7480
7547
|
}
|
|
7481
7548
|
});
|
|
7482
7549
|
|
|
7550
|
+
// src/utils/cap-image-count.ts
|
|
7551
|
+
function isImagePart(part) {
|
|
7552
|
+
if (!part || typeof part !== "object") return false;
|
|
7553
|
+
const t = part.type;
|
|
7554
|
+
if (t === "image") return true;
|
|
7555
|
+
if (t === "image-data") return true;
|
|
7556
|
+
if (t === "media") {
|
|
7557
|
+
const data = part.data;
|
|
7558
|
+
const mt = part.mediaType;
|
|
7559
|
+
if (typeof data === "string" && typeof mt === "string" && mt.startsWith("image/")) {
|
|
7560
|
+
return true;
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
return false;
|
|
7564
|
+
}
|
|
7565
|
+
function makePlaceholder() {
|
|
7566
|
+
return { type: "text", text: IMAGE_TRUNCATED_PLACEHOLDER };
|
|
7567
|
+
}
|
|
7568
|
+
function countImages(messages) {
|
|
7569
|
+
let n = 0;
|
|
7570
|
+
for (const msg of messages) {
|
|
7571
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7572
|
+
for (const part of msg.content) {
|
|
7573
|
+
if (isImagePart(part)) {
|
|
7574
|
+
n++;
|
|
7575
|
+
continue;
|
|
7576
|
+
}
|
|
7577
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7578
|
+
for (const sub of part.output.value) {
|
|
7579
|
+
if (isImagePart(sub)) n++;
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
}
|
|
7583
|
+
}
|
|
7584
|
+
return n;
|
|
7585
|
+
}
|
|
7586
|
+
function capImageCount(messages, max = MAX_IMAGES_IN_CONTEXT) {
|
|
7587
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7588
|
+
if (max < 0) throw new Error("capImageCount: max must be >= 0");
|
|
7589
|
+
const total = countImages(messages);
|
|
7590
|
+
if (total <= max) return messages;
|
|
7591
|
+
let toDrop = total - max;
|
|
7592
|
+
let mutated = false;
|
|
7593
|
+
const out = messages.slice();
|
|
7594
|
+
for (let i = 0; i < out.length && toDrop > 0; i++) {
|
|
7595
|
+
const msg = out[i];
|
|
7596
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7597
|
+
let contentCloned = false;
|
|
7598
|
+
const ensureContentCloned = () => {
|
|
7599
|
+
if (contentCloned) return;
|
|
7600
|
+
out[i] = { ...msg, content: [...msg.content] };
|
|
7601
|
+
contentCloned = true;
|
|
7602
|
+
};
|
|
7603
|
+
const content = () => out[i].content;
|
|
7604
|
+
for (let j = 0; j < content().length && toDrop > 0; j++) {
|
|
7605
|
+
const part = content()[j];
|
|
7606
|
+
if (isImagePart(part)) {
|
|
7607
|
+
ensureContentCloned();
|
|
7608
|
+
out[i].content[j] = makePlaceholder();
|
|
7609
|
+
toDrop--;
|
|
7610
|
+
mutated = true;
|
|
7611
|
+
continue;
|
|
7612
|
+
}
|
|
7613
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7614
|
+
const innerImages = [];
|
|
7615
|
+
const innerValue = part.output.value;
|
|
7616
|
+
for (let k = 0; k < innerValue.length; k++) {
|
|
7617
|
+
if (isImagePart(innerValue[k])) innerImages.push(k);
|
|
7618
|
+
}
|
|
7619
|
+
if (innerImages.length === 0) continue;
|
|
7620
|
+
const dropHere = Math.min(innerImages.length, toDrop);
|
|
7621
|
+
ensureContentCloned();
|
|
7622
|
+
const newOutputValue = [...innerValue];
|
|
7623
|
+
for (let d = 0; d < dropHere; d++) {
|
|
7624
|
+
newOutputValue[innerImages[d]] = makePlaceholder();
|
|
7625
|
+
}
|
|
7626
|
+
const newPart = {
|
|
7627
|
+
...part,
|
|
7628
|
+
output: {
|
|
7629
|
+
...part.output,
|
|
7630
|
+
value: newOutputValue
|
|
7631
|
+
}
|
|
7632
|
+
};
|
|
7633
|
+
out[i].content[j] = newPart;
|
|
7634
|
+
toDrop -= dropHere;
|
|
7635
|
+
mutated = true;
|
|
7636
|
+
}
|
|
7637
|
+
}
|
|
7638
|
+
}
|
|
7639
|
+
if (mutated) {
|
|
7640
|
+
console.warn(
|
|
7641
|
+
`[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.`
|
|
7642
|
+
);
|
|
7643
|
+
}
|
|
7644
|
+
return mutated ? out : messages;
|
|
7645
|
+
}
|
|
7646
|
+
var MAX_IMAGES_IN_CONTEXT, IMAGE_TRUNCATED_PLACEHOLDER;
|
|
7647
|
+
var init_cap_image_count = __esm({
|
|
7648
|
+
"src/utils/cap-image-count.ts"() {
|
|
7649
|
+
"use strict";
|
|
7650
|
+
MAX_IMAGES_IN_CONTEXT = 11;
|
|
7651
|
+
IMAGE_TRUNCATED_PLACEHOLDER = "[image truncated due to length of conversation]";
|
|
7652
|
+
}
|
|
7653
|
+
});
|
|
7654
|
+
|
|
7483
7655
|
// src/agent/model-limits.ts
|
|
7484
7656
|
function getModelLimits(modelId) {
|
|
7485
7657
|
const normalized = modelId.trim().toLowerCase();
|
|
@@ -7495,18 +7667,9 @@ var init_model_limits = __esm({
|
|
|
7495
7667
|
"src/agent/model-limits.ts"() {
|
|
7496
7668
|
"use strict";
|
|
7497
7669
|
MODEL_LIMITS = {
|
|
7498
|
-
"
|
|
7499
|
-
"
|
|
7500
|
-
"
|
|
7501
|
-
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7502
|
-
"anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7503
|
-
"google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7504
|
-
"google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7505
|
-
"google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7506
|
-
"openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
7507
|
-
"openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
7508
|
-
"openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7509
|
-
"xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
7670
|
+
"claude-opus-4-8": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7671
|
+
"gpt-5.5": { contextWindow: 35e4, rollingTarget: 15e4 },
|
|
7672
|
+
"claude-fable-5": { contextWindow: 2e5, rollingTarget: 15e4 }
|
|
7510
7673
|
};
|
|
7511
7674
|
DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
7512
7675
|
PREFIX_DEFAULTS = {
|
|
@@ -7580,6 +7743,32 @@ var init_conversation_archive = __esm({
|
|
|
7580
7743
|
|
|
7581
7744
|
// src/agent/context.ts
|
|
7582
7745
|
import { generateText as generateText2 } from "ai";
|
|
7746
|
+
function stripBinaryContentForSummary(value) {
|
|
7747
|
+
if (Array.isArray(value)) return value.map(stripBinaryContentForSummary);
|
|
7748
|
+
if (!value || typeof value !== "object") return value;
|
|
7749
|
+
const record = value;
|
|
7750
|
+
const type = record.type;
|
|
7751
|
+
if ((type === "image-data" || type === "file-data" || type === "media") && typeof record.data === "string") {
|
|
7752
|
+
const mediaType = typeof record.mediaType === "string" ? record.mediaType : "unknown media type";
|
|
7753
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
7754
|
+
return {
|
|
7755
|
+
...record,
|
|
7756
|
+
data: `[${type}${filename}; ${mediaType}; ${record.data.length} base64 chars omitted for summary]`
|
|
7757
|
+
};
|
|
7758
|
+
}
|
|
7759
|
+
if (type === "image" && typeof record.image === "string") {
|
|
7760
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
7761
|
+
return {
|
|
7762
|
+
...record,
|
|
7763
|
+
image: `[image${filename}; ${record.image.length} base64 chars omitted for summary]`
|
|
7764
|
+
};
|
|
7765
|
+
}
|
|
7766
|
+
const out = {};
|
|
7767
|
+
for (const [key2, nested] of Object.entries(record)) {
|
|
7768
|
+
out[key2] = stripBinaryContentForSummary(nested);
|
|
7769
|
+
}
|
|
7770
|
+
return out;
|
|
7771
|
+
}
|
|
7583
7772
|
function stripOrphanedToolResults(msg, removedIds) {
|
|
7584
7773
|
if (!Array.isArray(msg.content)) return msg;
|
|
7585
7774
|
const parts = msg.content.filter((part) => {
|
|
@@ -7740,6 +7929,7 @@ var init_context = __esm({
|
|
|
7740
7929
|
init_tokens();
|
|
7741
7930
|
init_prompts();
|
|
7742
7931
|
init_sanitize_messages();
|
|
7932
|
+
init_cap_image_count();
|
|
7743
7933
|
init_model_limits();
|
|
7744
7934
|
TOOL_OUTPUT_TRIM_CHARS = 400;
|
|
7745
7935
|
COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -7789,6 +7979,7 @@ ${summaryContent}`
|
|
|
7789
7979
|
messages = repairToolPairing(messages);
|
|
7790
7980
|
messages = ensureToolResultsFollowCalls(messages);
|
|
7791
7981
|
messages = ensureEndsWithUserOrTool(messages);
|
|
7982
|
+
messages = capImageCount(messages);
|
|
7792
7983
|
return messages;
|
|
7793
7984
|
}
|
|
7794
7985
|
// ---------------------------------------------------------------------------
|
|
@@ -7906,7 +8097,7 @@ ${summaryContent}`
|
|
|
7906
8097
|
}
|
|
7907
8098
|
async summarizeChunk(chunk) {
|
|
7908
8099
|
const historyText = chunk.map((msg) => {
|
|
7909
|
-
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
8100
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(stripBinaryContentForSummary(msg.content));
|
|
7910
8101
|
return `[${msg.role}]: ${content}`;
|
|
7911
8102
|
}).join("\n\n");
|
|
7912
8103
|
try {
|
|
@@ -8154,6 +8345,127 @@ var init_persistence = __esm({
|
|
|
8154
8345
|
});
|
|
8155
8346
|
|
|
8156
8347
|
// src/integrations/slack/client.ts
|
|
8348
|
+
var client_exports = {};
|
|
8349
|
+
__export(client_exports, {
|
|
8350
|
+
LOADING_REACTION: () => LOADING_REACTION,
|
|
8351
|
+
RESULT_REACTIONS: () => RESULT_REACTIONS,
|
|
8352
|
+
addLoadingReaction: () => addLoadingReaction,
|
|
8353
|
+
addResultReaction: () => addResultReaction,
|
|
8354
|
+
botParticipatedInThread: () => botParticipatedInThread,
|
|
8355
|
+
ensureSlackSelfIdentity: () => ensureSlackSelfIdentity,
|
|
8356
|
+
getCachedSlackSelfIdentity: () => getCachedSlackSelfIdentity,
|
|
8357
|
+
getDefaultOrchestratorName: () => getDefaultOrchestratorName,
|
|
8358
|
+
getSlackAdapter: () => getSlackAdapter,
|
|
8359
|
+
getSlackAllowlistPolicy: () => getSlackAllowlistPolicy,
|
|
8360
|
+
getSlackBotToken: () => getSlackBotToken,
|
|
8361
|
+
getSlackDeniedReplyPolicy: () => getSlackDeniedReplyPolicy,
|
|
8362
|
+
getSlackSigningSecret: () => getSlackSigningSecret,
|
|
8363
|
+
isSlackConfigured: () => isSlackConfigured,
|
|
8364
|
+
normalizeSlackMentions: () => normalizeSlackMentions,
|
|
8365
|
+
noteBotPostedInThread: () => noteBotPostedInThread,
|
|
8366
|
+
postThreadMessage: () => postThreadMessage,
|
|
8367
|
+
removeLoadingReaction: () => removeLoadingReaction,
|
|
8368
|
+
resolveSlackUserInfo: () => resolveSlackUserInfo,
|
|
8369
|
+
resolveSlackUserName: () => resolveSlackUserName
|
|
8370
|
+
});
|
|
8371
|
+
function slackBackoffMs(attempt) {
|
|
8372
|
+
const expo = SLACK_BACKOFF_BASE_MS * 2 ** attempt;
|
|
8373
|
+
const jitter = Math.floor(Math.random() * SLACK_BACKOFF_BASE_MS);
|
|
8374
|
+
return Math.min(expo + jitter, SLACK_BACKOFF_CAP_MS);
|
|
8375
|
+
}
|
|
8376
|
+
async function slackFetchWithRetry(url, init, attempts = SLACK_FETCH_ATTEMPTS) {
|
|
8377
|
+
let lastErr;
|
|
8378
|
+
for (let i = 0; i < attempts; i++) {
|
|
8379
|
+
const isLast = i === attempts - 1;
|
|
8380
|
+
try {
|
|
8381
|
+
const res = await fetch(url, init);
|
|
8382
|
+
if ((res.status === 429 || res.status >= 500) && !isLast) {
|
|
8383
|
+
const ra = Number(res.headers.get("retry-after"));
|
|
8384
|
+
const waitMs = Number.isFinite(ra) && ra > 0 ? Math.min(ra * 1e3, SLACK_BACKOFF_CAP_MS) : slackBackoffMs(i);
|
|
8385
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
8386
|
+
continue;
|
|
8387
|
+
}
|
|
8388
|
+
return res;
|
|
8389
|
+
} catch (err) {
|
|
8390
|
+
lastErr = err;
|
|
8391
|
+
if (isLast) throw err;
|
|
8392
|
+
await new Promise((r) => setTimeout(r, slackBackoffMs(i)));
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
throw lastErr ?? new Error("slack fetch failed");
|
|
8396
|
+
}
|
|
8397
|
+
function reactionKey(channel, ts) {
|
|
8398
|
+
return `${channel}\u241F${ts}`;
|
|
8399
|
+
}
|
|
8400
|
+
async function addLoadingReaction(channel, timestamp) {
|
|
8401
|
+
const adapter = getSlackAdapter();
|
|
8402
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8403
|
+
const key2 = reactionKey(channel, timestamp);
|
|
8404
|
+
const inFlight = (async () => {
|
|
8405
|
+
try {
|
|
8406
|
+
const res = await adapter.addReaction({ channel, timestamp, name: LOADING_REACTION });
|
|
8407
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
8408
|
+
console.warn(`[slack] addReaction ${LOADING_REACTION} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
8409
|
+
}
|
|
8410
|
+
return res;
|
|
8411
|
+
} catch (err) {
|
|
8412
|
+
console.warn(`[slack] addReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
8413
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8414
|
+
}
|
|
8415
|
+
})();
|
|
8416
|
+
pendingAdds.set(key2, inFlight);
|
|
8417
|
+
void inFlight.finally(() => {
|
|
8418
|
+
if (pendingAdds.get(key2) === inFlight) pendingAdds.delete(key2);
|
|
8419
|
+
});
|
|
8420
|
+
return inFlight;
|
|
8421
|
+
}
|
|
8422
|
+
async function removeLoadingReaction(channel, timestamp) {
|
|
8423
|
+
const adapter = getSlackAdapter();
|
|
8424
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8425
|
+
const pending = pendingAdds.get(reactionKey(channel, timestamp));
|
|
8426
|
+
if (pending) {
|
|
8427
|
+
try {
|
|
8428
|
+
await pending;
|
|
8429
|
+
} catch {
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
try {
|
|
8433
|
+
const res = await adapter.removeReaction({ channel, timestamp, name: LOADING_REACTION });
|
|
8434
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
8435
|
+
console.warn(`[slack] removeReaction ${LOADING_REACTION} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
8436
|
+
}
|
|
8437
|
+
return res;
|
|
8438
|
+
} catch (err) {
|
|
8439
|
+
console.warn(`[slack] removeReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
8440
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
async function addResultReaction(channel, timestamp, state2) {
|
|
8444
|
+
const name = RESULT_REACTIONS[state2];
|
|
8445
|
+
if (!name) return { ok: false, error: `no_reaction_for_state:${state2}` };
|
|
8446
|
+
const adapter = getSlackAdapter();
|
|
8447
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8448
|
+
try {
|
|
8449
|
+
const res = await adapter.addReaction({ channel, timestamp, name });
|
|
8450
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
8451
|
+
console.warn(`[slack] addReaction ${name} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
8452
|
+
}
|
|
8453
|
+
return res;
|
|
8454
|
+
} catch (err) {
|
|
8455
|
+
console.warn(`[slack] addResultReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
8456
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8457
|
+
}
|
|
8458
|
+
}
|
|
8459
|
+
async function postThreadMessage(channel, threadTs, text) {
|
|
8460
|
+
const adapter = getSlackAdapter();
|
|
8461
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
8462
|
+
try {
|
|
8463
|
+
return await adapter.postMessage({ channel, text, threadTs });
|
|
8464
|
+
} catch (err) {
|
|
8465
|
+
console.warn(`[slack] postThreadMessage threw on ${channel}/${threadTs}:`, err?.message || err);
|
|
8466
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
8467
|
+
}
|
|
8468
|
+
}
|
|
8157
8469
|
function readSlackConfig() {
|
|
8158
8470
|
try {
|
|
8159
8471
|
const cfg = getConfig();
|
|
@@ -8171,9 +8483,25 @@ function readSlackConfig() {
|
|
|
8171
8483
|
function getSlackAdapter() {
|
|
8172
8484
|
const cfg = readSlackConfig();
|
|
8173
8485
|
if (!cfg) return void 0;
|
|
8486
|
+
const slackForm = async (endpoint, params) => {
|
|
8487
|
+
const body = new URLSearchParams(params).toString();
|
|
8488
|
+
const res = await fetch(`https://slack.com/api/${endpoint}`, {
|
|
8489
|
+
method: "POST",
|
|
8490
|
+
headers: {
|
|
8491
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
8492
|
+
Authorization: `Bearer ${cfg.botToken}`
|
|
8493
|
+
},
|
|
8494
|
+
body
|
|
8495
|
+
});
|
|
8496
|
+
const data = await res.json().catch(() => ({}));
|
|
8497
|
+
if (!res.ok || data?.ok === false) {
|
|
8498
|
+
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
8499
|
+
}
|
|
8500
|
+
return { ok: true };
|
|
8501
|
+
};
|
|
8174
8502
|
return {
|
|
8175
8503
|
async postMessage({ channel, text, threadTs }) {
|
|
8176
|
-
const res = await
|
|
8504
|
+
const res = await slackFetchWithRetry("https://slack.com/api/chat.postMessage", {
|
|
8177
8505
|
method: "POST",
|
|
8178
8506
|
headers: {
|
|
8179
8507
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -8186,6 +8514,12 @@ function getSlackAdapter() {
|
|
|
8186
8514
|
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
8187
8515
|
}
|
|
8188
8516
|
return { ok: true, ts: data?.ts };
|
|
8517
|
+
},
|
|
8518
|
+
addReaction({ channel, timestamp, name }) {
|
|
8519
|
+
return slackForm("reactions.add", { channel, timestamp, name });
|
|
8520
|
+
},
|
|
8521
|
+
removeReaction({ channel, timestamp, name }) {
|
|
8522
|
+
return slackForm("reactions.remove", { channel, timestamp, name });
|
|
8189
8523
|
}
|
|
8190
8524
|
};
|
|
8191
8525
|
}
|
|
@@ -8386,12 +8720,31 @@ function getSlackDeniedReplyPolicy() {
|
|
|
8386
8720
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
8387
8721
|
}
|
|
8388
8722
|
}
|
|
8389
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
8723
|
+
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;
|
|
8390
8724
|
var init_client3 = __esm({
|
|
8391
8725
|
"src/integrations/slack/client.ts"() {
|
|
8392
8726
|
"use strict";
|
|
8393
8727
|
init_config();
|
|
8394
8728
|
init_persistence();
|
|
8729
|
+
LOADING_REACTION = "hourglass_flowing_sand";
|
|
8730
|
+
RESULT_REACTIONS = {
|
|
8731
|
+
responded: "white_check_mark",
|
|
8732
|
+
skipped: "zzz",
|
|
8733
|
+
handed_off: "eyes",
|
|
8734
|
+
failed: "warning"
|
|
8735
|
+
};
|
|
8736
|
+
SLACK_FETCH_ATTEMPTS = 3;
|
|
8737
|
+
SLACK_BACKOFF_BASE_MS = 400;
|
|
8738
|
+
SLACK_BACKOFF_CAP_MS = 3e4;
|
|
8739
|
+
REACTION_SOFT_ERRORS = /* @__PURE__ */ new Set([
|
|
8740
|
+
"already_reacted",
|
|
8741
|
+
// add: someone (or we) already added this emoji
|
|
8742
|
+
"no_reaction",
|
|
8743
|
+
// remove: the emoji isn't on the message
|
|
8744
|
+
"message_not_found"
|
|
8745
|
+
// remove/add: original message deleted
|
|
8746
|
+
]);
|
|
8747
|
+
pendingAdds = /* @__PURE__ */ new Map();
|
|
8395
8748
|
cachedSelf = null;
|
|
8396
8749
|
selfInflight = null;
|
|
8397
8750
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -8404,83 +8757,541 @@ var init_client3 = __esm({
|
|
|
8404
8757
|
}
|
|
8405
8758
|
});
|
|
8406
8759
|
|
|
8407
|
-
// src/
|
|
8408
|
-
function
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
}
|
|
8414
|
-
|
|
8415
|
-
|
|
8760
|
+
// src/agent/session-lock.ts
|
|
8761
|
+
async function withSessionLock(sessionId, fn) {
|
|
8762
|
+
let state2 = locks.get(sessionId);
|
|
8763
|
+
if (!state2) {
|
|
8764
|
+
state2 = { tail: Promise.resolve(), pending: 0 };
|
|
8765
|
+
locks.set(sessionId, state2);
|
|
8766
|
+
}
|
|
8767
|
+
state2.pending++;
|
|
8768
|
+
const prev = state2.tail;
|
|
8769
|
+
let release;
|
|
8770
|
+
const next = new Promise((resolve13) => {
|
|
8771
|
+
release = resolve13;
|
|
8772
|
+
});
|
|
8773
|
+
state2.tail = prev.then(() => next);
|
|
8774
|
+
await prev;
|
|
8775
|
+
try {
|
|
8776
|
+
return await fn();
|
|
8777
|
+
} finally {
|
|
8778
|
+
release();
|
|
8779
|
+
state2.pending--;
|
|
8780
|
+
if (state2.pending === 0 && locks.get(sessionId) === state2) {
|
|
8781
|
+
locks.delete(sessionId);
|
|
8782
|
+
}
|
|
8783
|
+
}
|
|
8416
8784
|
}
|
|
8417
|
-
function
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
8421
|
-
return false;
|
|
8785
|
+
function isSessionLocked(sessionId) {
|
|
8786
|
+
const s = locks.get(sessionId);
|
|
8787
|
+
return !!s && s.pending > 0;
|
|
8422
8788
|
}
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
return { event: null, dropReason: "bot_message" };
|
|
8429
|
-
}
|
|
8430
|
-
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
8431
|
-
return { event: null, dropReason: "ignored_subtype" };
|
|
8789
|
+
var locks;
|
|
8790
|
+
var init_session_lock = __esm({
|
|
8791
|
+
"src/agent/session-lock.ts"() {
|
|
8792
|
+
"use strict";
|
|
8793
|
+
locks = /* @__PURE__ */ new Map();
|
|
8432
8794
|
}
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
|
|
8436
|
-
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
|
|
8795
|
+
});
|
|
8796
|
+
|
|
8797
|
+
// src/orchestrator/webhook-events.ts
|
|
8798
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
8799
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
8800
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
8801
|
+
function logFilePath() {
|
|
8802
|
+
return join10(getAppDataDirectory(), "webhook-events.jsonl");
|
|
8803
|
+
}
|
|
8804
|
+
function ensureLoaded() {
|
|
8805
|
+
if (cache !== null) return cache;
|
|
8806
|
+
cache = [];
|
|
8807
|
+
try {
|
|
8808
|
+
const p = logFilePath();
|
|
8809
|
+
if (!existsSync17(p)) return cache;
|
|
8810
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
8811
|
+
for (const line of lines) {
|
|
8812
|
+
try {
|
|
8813
|
+
cache.push(JSON.parse(line));
|
|
8814
|
+
} catch {
|
|
8815
|
+
}
|
|
8440
8816
|
}
|
|
8441
|
-
if (
|
|
8442
|
-
|
|
8817
|
+
if (cache.length > MAX_EVENTS) {
|
|
8818
|
+
cache = cache.slice(-MAX_EVENTS);
|
|
8819
|
+
try {
|
|
8820
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
8821
|
+
} catch {
|
|
8822
|
+
}
|
|
8443
8823
|
}
|
|
8444
|
-
|
|
8824
|
+
} catch {
|
|
8445
8825
|
}
|
|
8446
|
-
|
|
8447
|
-
|
|
8448
|
-
|
|
8449
|
-
const
|
|
8450
|
-
|
|
8451
|
-
if (
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
}
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
if (channelAllowlistActive && !policy.allowedChannels.includes(event.channel)) {
|
|
8458
|
-
return { event: null, dropReason: "channel_not_allowed" };
|
|
8459
|
-
}
|
|
8460
|
-
if (!userOk) {
|
|
8461
|
-
return { event: null, dropReason: "user_not_allowed" };
|
|
8462
|
-
}
|
|
8826
|
+
return cache;
|
|
8827
|
+
}
|
|
8828
|
+
function appendEvent(ev) {
|
|
8829
|
+
const list = ensureLoaded();
|
|
8830
|
+
list.push(ev);
|
|
8831
|
+
if (list.length > MAX_EVENTS) list.shift();
|
|
8832
|
+
try {
|
|
8833
|
+
const p = logFilePath();
|
|
8834
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
8835
|
+
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
8836
|
+
} catch {
|
|
8463
8837
|
}
|
|
8464
|
-
const ref = {
|
|
8465
|
-
channel: "slack",
|
|
8466
|
-
slackChannel: event.channel,
|
|
8467
|
-
threadTs: event.thread_ts || event.ts,
|
|
8468
|
-
teamId: event.team,
|
|
8469
|
-
user: event.user
|
|
8470
|
-
};
|
|
8471
|
-
const label = slackChannel.displayLabel(ref);
|
|
8472
|
-
return {
|
|
8473
|
-
event: {
|
|
8474
|
-
ref,
|
|
8475
|
-
content: `[${label}] ${text}`,
|
|
8476
|
-
wake: "now",
|
|
8477
|
-
enqueuedAt: /* @__PURE__ */ new Date()
|
|
8478
|
-
}
|
|
8479
|
-
};
|
|
8480
8838
|
}
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8839
|
+
function recordEvent(ev) {
|
|
8840
|
+
const full = {
|
|
8841
|
+
id: ev.id ?? nanoid4(),
|
|
8842
|
+
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
8843
|
+
source: ev.source,
|
|
8844
|
+
status: ev.status,
|
|
8845
|
+
subtype: ev.subtype,
|
|
8846
|
+
channel: ev.channel,
|
|
8847
|
+
user: ev.user,
|
|
8848
|
+
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
8849
|
+
dropReason: ev.dropReason,
|
|
8850
|
+
error: ev.error,
|
|
8851
|
+
sessionId: ev.sessionId,
|
|
8852
|
+
durationMs: ev.durationMs,
|
|
8853
|
+
meta: ev.meta
|
|
8854
|
+
};
|
|
8855
|
+
appendEvent(full);
|
|
8856
|
+
return full.id;
|
|
8857
|
+
}
|
|
8858
|
+
function updateEvent(id, patch) {
|
|
8859
|
+
const list = ensureLoaded();
|
|
8860
|
+
const i = list.findIndex((e) => e.id === id);
|
|
8861
|
+
if (i < 0) return;
|
|
8862
|
+
list[i] = { ...list[i], ...patch };
|
|
8863
|
+
try {
|
|
8864
|
+
const p = logFilePath();
|
|
8865
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
8866
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
8867
|
+
} catch {
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8870
|
+
function listEvents(filter = {}) {
|
|
8871
|
+
const list = ensureLoaded();
|
|
8872
|
+
const q = filter.q?.toLowerCase();
|
|
8873
|
+
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
8874
|
+
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
8875
|
+
const matched = list.filter((e) => {
|
|
8876
|
+
if (filter.source && e.source !== filter.source) return false;
|
|
8877
|
+
if (filter.status && e.status !== filter.status) return false;
|
|
8878
|
+
const t = Date.parse(e.ts);
|
|
8879
|
+
if (t < sinceTs) return false;
|
|
8880
|
+
if (t >= beforeTs) return false;
|
|
8881
|
+
if (q) {
|
|
8882
|
+
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
8883
|
+
if (!hay.includes(q)) return false;
|
|
8884
|
+
}
|
|
8885
|
+
return true;
|
|
8886
|
+
});
|
|
8887
|
+
matched.reverse();
|
|
8888
|
+
const offset = Math.max(0, filter.offset ?? 0);
|
|
8889
|
+
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
8890
|
+
return {
|
|
8891
|
+
events: matched.slice(offset, offset + limit),
|
|
8892
|
+
total: matched.length
|
|
8893
|
+
};
|
|
8894
|
+
}
|
|
8895
|
+
function clearAllEvents() {
|
|
8896
|
+
cache = [];
|
|
8897
|
+
try {
|
|
8898
|
+
writeFileSync4(logFilePath(), "");
|
|
8899
|
+
} catch {
|
|
8900
|
+
}
|
|
8901
|
+
}
|
|
8902
|
+
var MAX_EVENTS, cache;
|
|
8903
|
+
var init_webhook_events = __esm({
|
|
8904
|
+
"src/orchestrator/webhook-events.ts"() {
|
|
8905
|
+
"use strict";
|
|
8906
|
+
init_config();
|
|
8907
|
+
MAX_EVENTS = 1e3;
|
|
8908
|
+
cache = null;
|
|
8909
|
+
}
|
|
8910
|
+
});
|
|
8911
|
+
|
|
8912
|
+
// src/orchestrator/inbox.ts
|
|
8913
|
+
var inbox_exports = {};
|
|
8914
|
+
__export(inbox_exports, {
|
|
8915
|
+
clearInbox: () => clearInbox,
|
|
8916
|
+
flush: () => flush,
|
|
8917
|
+
peekInbox: () => peekInbox,
|
|
8918
|
+
pushToInbox: () => pushToInbox,
|
|
8919
|
+
setFlushHandler: () => setFlushHandler
|
|
8920
|
+
});
|
|
8921
|
+
function setFlushHandler(fn) {
|
|
8922
|
+
flushHandler = fn;
|
|
8923
|
+
}
|
|
8924
|
+
function entryFor(sessionId) {
|
|
8925
|
+
let e = inboxes.get(sessionId);
|
|
8926
|
+
if (!e) {
|
|
8927
|
+
e = { pending: [] };
|
|
8928
|
+
inboxes.set(sessionId, e);
|
|
8929
|
+
}
|
|
8930
|
+
return e;
|
|
8931
|
+
}
|
|
8932
|
+
function pushToInbox(orchestratorSessionId, event) {
|
|
8933
|
+
const e = entryFor(orchestratorSessionId);
|
|
8934
|
+
e.pending.push(event);
|
|
8935
|
+
try {
|
|
8936
|
+
trackInbound(orchestratorSessionId, event);
|
|
8937
|
+
} catch {
|
|
8938
|
+
}
|
|
8939
|
+
if (event.wake === "now") {
|
|
8940
|
+
scheduleFlush(orchestratorSessionId);
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
function scheduleFlush(sessionId) {
|
|
8944
|
+
const e = inboxes.get(sessionId);
|
|
8945
|
+
if (!e) return;
|
|
8946
|
+
if (e.timer) clearTimeout(e.timer);
|
|
8947
|
+
e.timer = setTimeout(() => {
|
|
8948
|
+
void flush(sessionId);
|
|
8949
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
8950
|
+
}
|
|
8951
|
+
async function flush(sessionId) {
|
|
8952
|
+
const e = inboxes.get(sessionId);
|
|
8953
|
+
if (!e) return;
|
|
8954
|
+
if (e.timer) {
|
|
8955
|
+
clearTimeout(e.timer);
|
|
8956
|
+
e.timer = void 0;
|
|
8957
|
+
}
|
|
8958
|
+
const events = e.pending.splice(0);
|
|
8959
|
+
if (events.length === 0) return;
|
|
8960
|
+
if (!flushHandler) {
|
|
8961
|
+
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
8962
|
+
return;
|
|
8963
|
+
}
|
|
8964
|
+
try {
|
|
8965
|
+
await flushHandler(sessionId, events);
|
|
8966
|
+
} catch (err) {
|
|
8967
|
+
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
function peekInbox(sessionId) {
|
|
8971
|
+
return inboxes.get(sessionId)?.pending.slice() ?? [];
|
|
8972
|
+
}
|
|
8973
|
+
function clearInbox(sessionId) {
|
|
8974
|
+
const e = inboxes.get(sessionId);
|
|
8975
|
+
if (!e) return;
|
|
8976
|
+
if (e.timer) {
|
|
8977
|
+
clearTimeout(e.timer);
|
|
8978
|
+
e.timer = void 0;
|
|
8979
|
+
}
|
|
8980
|
+
e.pending.length = 0;
|
|
8981
|
+
}
|
|
8982
|
+
var inboxes, FLUSH_DEBOUNCE_MS, flushHandler;
|
|
8983
|
+
var init_inbox = __esm({
|
|
8984
|
+
"src/orchestrator/inbox.ts"() {
|
|
8985
|
+
"use strict";
|
|
8986
|
+
init_inbox_acks();
|
|
8987
|
+
inboxes = /* @__PURE__ */ new Map();
|
|
8988
|
+
FLUSH_DEBOUNCE_MS = 200;
|
|
8989
|
+
flushHandler = null;
|
|
8990
|
+
}
|
|
8991
|
+
});
|
|
8992
|
+
|
|
8993
|
+
// src/orchestrator/inbox-acks.ts
|
|
8994
|
+
var inbox_acks_exports = {};
|
|
8995
|
+
__export(inbox_acks_exports, {
|
|
8996
|
+
MAX_ATTEMPTS: () => MAX_ATTEMPTS,
|
|
8997
|
+
RECONCILE_EVERY_MS: () => RECONCILE_EVERY_MS,
|
|
8998
|
+
REPLAY_AFTER_MS: () => REPLAY_AFTER_MS,
|
|
8999
|
+
__getAck: () => __getAck,
|
|
9000
|
+
__listAcks: () => __listAcks,
|
|
9001
|
+
__resetAcks: () => __resetAcks,
|
|
9002
|
+
eventKey: () => eventKey,
|
|
9003
|
+
markRespondedForThread: () => markRespondedForThread,
|
|
9004
|
+
markState: () => markState,
|
|
9005
|
+
reconcileOnce: () => reconcileOnce,
|
|
9006
|
+
resolveBatchOnTurnEnd: () => resolveBatchOnTurnEnd,
|
|
9007
|
+
startReconciler: () => startReconciler,
|
|
9008
|
+
stopReconciler: () => stopReconciler,
|
|
9009
|
+
trackInbound: () => trackInbound
|
|
9010
|
+
});
|
|
9011
|
+
function eventKey(event) {
|
|
9012
|
+
const ref = event.ref;
|
|
9013
|
+
const ch = ref?.channel ?? "unknown";
|
|
9014
|
+
switch (ch) {
|
|
9015
|
+
case "slack":
|
|
9016
|
+
return `slack${SEP}${ref.slackChannel}${SEP}${ref.messageTs ?? ref.threadTs ?? ""}`;
|
|
9017
|
+
case "system":
|
|
9018
|
+
return `system${SEP}${ref.workerId}${SEP}${ref.kind}`;
|
|
9019
|
+
case "webhook":
|
|
9020
|
+
return `webhook${SEP}${ref.webhookId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
9021
|
+
case "schedule":
|
|
9022
|
+
return `schedule${SEP}${ref.scheduleId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
9023
|
+
default:
|
|
9024
|
+
return `${ch}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}${SEP}${event.content.slice(0, 40)}`;
|
|
9025
|
+
}
|
|
9026
|
+
}
|
|
9027
|
+
function trackInbound(sessionId, event) {
|
|
9028
|
+
if (event.wake === "never") return null;
|
|
9029
|
+
const key2 = eventKey(event);
|
|
9030
|
+
const existing = ledger.get(key2);
|
|
9031
|
+
if (existing) return key2;
|
|
9032
|
+
const ref = event.ref;
|
|
9033
|
+
const now = Date.now();
|
|
9034
|
+
const entry2 = {
|
|
9035
|
+
key: key2,
|
|
9036
|
+
sessionId,
|
|
9037
|
+
event,
|
|
9038
|
+
channel: ref?.channel ?? "unknown",
|
|
9039
|
+
state: "working",
|
|
9040
|
+
attempts: 0,
|
|
9041
|
+
trackedAt: now,
|
|
9042
|
+
updatedAt: now
|
|
9043
|
+
};
|
|
9044
|
+
if (ref?.channel === "slack") {
|
|
9045
|
+
entry2.slackChannel = ref.slackChannel;
|
|
9046
|
+
entry2.threadTs = ref.threadTs;
|
|
9047
|
+
entry2.messageTs = ref.messageTs;
|
|
9048
|
+
}
|
|
9049
|
+
ledger.set(key2, entry2);
|
|
9050
|
+
if (ledger.size > MAX_ENTRIES) pruneOldest();
|
|
9051
|
+
return key2;
|
|
9052
|
+
}
|
|
9053
|
+
function markState(key2, state2) {
|
|
9054
|
+
const entry2 = ledger.get(key2);
|
|
9055
|
+
if (!entry2) return;
|
|
9056
|
+
if (TERMINAL.has(entry2.state)) return;
|
|
9057
|
+
entry2.state = state2;
|
|
9058
|
+
entry2.updatedAt = Date.now();
|
|
9059
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
9060
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, state2);
|
|
9061
|
+
}
|
|
9062
|
+
}
|
|
9063
|
+
function markRespondedForThread(slackChannel2, threadTs) {
|
|
9064
|
+
if (!slackChannel2 || !threadTs) return;
|
|
9065
|
+
for (const entry2 of ledger.values()) {
|
|
9066
|
+
if (entry2.channel === "slack" && entry2.state === "working" && entry2.slackChannel === slackChannel2 && entry2.threadTs === threadTs) {
|
|
9067
|
+
markState(entry2.key, "responded");
|
|
9068
|
+
}
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
function resolveBatchOnTurnEnd(events, ok) {
|
|
9072
|
+
if (!ok) return;
|
|
9073
|
+
for (const ev of events) {
|
|
9074
|
+
const key2 = eventKey(ev);
|
|
9075
|
+
const entry2 = ledger.get(key2);
|
|
9076
|
+
if (!entry2 || entry2.state !== "working") continue;
|
|
9077
|
+
if (entry2.channel === "slack") continue;
|
|
9078
|
+
markState(key2, "responded");
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
async function reconcileOnce(now = Date.now()) {
|
|
9082
|
+
let pushToInbox2 = null;
|
|
9083
|
+
const toReplay = [];
|
|
9084
|
+
for (const entry2 of ledger.values()) {
|
|
9085
|
+
if (TERMINAL.has(entry2.state)) {
|
|
9086
|
+
if (now - entry2.updatedAt > PRUNE_AFTER_MS) ledger.delete(entry2.key);
|
|
9087
|
+
continue;
|
|
9088
|
+
}
|
|
9089
|
+
if (isSessionLocked(entry2.sessionId)) continue;
|
|
9090
|
+
if (now - entry2.updatedAt < REPLAY_AFTER_MS) continue;
|
|
9091
|
+
if (entry2.attempts >= MAX_ATTEMPTS) {
|
|
9092
|
+
failEntry(entry2);
|
|
9093
|
+
continue;
|
|
9094
|
+
}
|
|
9095
|
+
toReplay.push(entry2);
|
|
9096
|
+
}
|
|
9097
|
+
if (toReplay.length === 0) return;
|
|
9098
|
+
try {
|
|
9099
|
+
({ pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports)));
|
|
9100
|
+
} catch {
|
|
9101
|
+
return;
|
|
9102
|
+
}
|
|
9103
|
+
for (const entry2 of toReplay) {
|
|
9104
|
+
entry2.attempts += 1;
|
|
9105
|
+
entry2.updatedAt = Date.now();
|
|
9106
|
+
const nudged = {
|
|
9107
|
+
...entry2.event,
|
|
9108
|
+
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.]
|
|
9109
|
+
${entry2.event.content}`,
|
|
9110
|
+
wake: "now"
|
|
9111
|
+
};
|
|
9112
|
+
try {
|
|
9113
|
+
pushToInbox2(entry2.sessionId, nudged);
|
|
9114
|
+
} catch {
|
|
9115
|
+
}
|
|
9116
|
+
}
|
|
9117
|
+
}
|
|
9118
|
+
function failEntry(entry2) {
|
|
9119
|
+
entry2.state = "failed";
|
|
9120
|
+
entry2.updatedAt = Date.now();
|
|
9121
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
9122
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, "failed");
|
|
9123
|
+
if (entry2.threadTs) {
|
|
9124
|
+
fireFallback(
|
|
9125
|
+
entry2.slackChannel,
|
|
9126
|
+
entry2.threadTs,
|
|
9127
|
+
`: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.`
|
|
9128
|
+
);
|
|
9129
|
+
}
|
|
9130
|
+
}
|
|
9131
|
+
recordEvent({
|
|
9132
|
+
source: "daemon",
|
|
9133
|
+
status: "failed",
|
|
9134
|
+
channel: entry2.channel,
|
|
9135
|
+
sessionId: entry2.sessionId,
|
|
9136
|
+
error: `unacknowledged after ${entry2.attempts} replay attempt(s)`,
|
|
9137
|
+
textSnippet: entry2.event.content.slice(0, 200),
|
|
9138
|
+
meta: { ackKey: entry2.key, ackState: "failed" }
|
|
9139
|
+
});
|
|
9140
|
+
}
|
|
9141
|
+
function pruneOldest() {
|
|
9142
|
+
const terminal = [];
|
|
9143
|
+
for (const e of ledger.values()) if (TERMINAL.has(e.state)) terminal.push(e);
|
|
9144
|
+
terminal.sort((a, b) => a.updatedAt - b.updatedAt);
|
|
9145
|
+
for (const e of terminal) {
|
|
9146
|
+
if (ledger.size <= MAX_ENTRIES) break;
|
|
9147
|
+
ledger.delete(e.key);
|
|
9148
|
+
}
|
|
9149
|
+
while (ledger.size > MAX_ENTRIES) {
|
|
9150
|
+
const oldest = ledger.keys().next().value;
|
|
9151
|
+
if (!oldest) break;
|
|
9152
|
+
ledger.delete(oldest);
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
function fireResultReaction(channel, ts, state2) {
|
|
9156
|
+
if (typeof addResultReaction !== "function") return;
|
|
9157
|
+
try {
|
|
9158
|
+
void Promise.resolve(addResultReaction(channel, ts, state2)).catch(() => {
|
|
9159
|
+
});
|
|
9160
|
+
} catch {
|
|
9161
|
+
}
|
|
9162
|
+
}
|
|
9163
|
+
function fireFallback(channel, threadTs, text) {
|
|
9164
|
+
if (typeof postThreadMessage !== "function") return;
|
|
9165
|
+
try {
|
|
9166
|
+
void Promise.resolve(postThreadMessage(channel, threadTs, text)).catch(() => {
|
|
9167
|
+
});
|
|
9168
|
+
} catch {
|
|
9169
|
+
}
|
|
9170
|
+
}
|
|
9171
|
+
function startReconciler() {
|
|
9172
|
+
if (reconcileTimer) return;
|
|
9173
|
+
reconcileTimer = setInterval(() => {
|
|
9174
|
+
void reconcileOnce();
|
|
9175
|
+
}, RECONCILE_EVERY_MS);
|
|
9176
|
+
if (typeof reconcileTimer.unref === "function") reconcileTimer.unref();
|
|
9177
|
+
}
|
|
9178
|
+
function stopReconciler() {
|
|
9179
|
+
if (reconcileTimer) {
|
|
9180
|
+
clearInterval(reconcileTimer);
|
|
9181
|
+
reconcileTimer = null;
|
|
9182
|
+
}
|
|
9183
|
+
}
|
|
9184
|
+
function __getAck(key2) {
|
|
9185
|
+
return ledger.get(key2);
|
|
9186
|
+
}
|
|
9187
|
+
function __listAcks() {
|
|
9188
|
+
return [...ledger.values()];
|
|
9189
|
+
}
|
|
9190
|
+
function __resetAcks() {
|
|
9191
|
+
ledger.clear();
|
|
9192
|
+
}
|
|
9193
|
+
var REPLAY_AFTER_MS, RECONCILE_EVERY_MS, MAX_ATTEMPTS, PRUNE_AFTER_MS, MAX_ENTRIES, TERMINAL, SEP, ledger, reconcileTimer;
|
|
9194
|
+
var init_inbox_acks = __esm({
|
|
9195
|
+
"src/orchestrator/inbox-acks.ts"() {
|
|
9196
|
+
"use strict";
|
|
9197
|
+
init_session_lock();
|
|
9198
|
+
init_webhook_events();
|
|
9199
|
+
init_client3();
|
|
9200
|
+
REPLAY_AFTER_MS = 3 * 6e4;
|
|
9201
|
+
RECONCILE_EVERY_MS = 6e4;
|
|
9202
|
+
MAX_ATTEMPTS = 2;
|
|
9203
|
+
PRUNE_AFTER_MS = 60 * 6e4;
|
|
9204
|
+
MAX_ENTRIES = 5e3;
|
|
9205
|
+
TERMINAL = /* @__PURE__ */ new Set(["responded", "skipped", "handed_off", "failed"]);
|
|
9206
|
+
SEP = "\u241F";
|
|
9207
|
+
ledger = /* @__PURE__ */ new Map();
|
|
9208
|
+
reconcileTimer = null;
|
|
9209
|
+
}
|
|
9210
|
+
});
|
|
9211
|
+
|
|
9212
|
+
// src/integrations/channels/slack.ts
|
|
9213
|
+
function threadKey(channel, threadTs) {
|
|
9214
|
+
return `${channel}\u241F${threadTs}`;
|
|
9215
|
+
}
|
|
9216
|
+
function markThreadOwned(channel, threadTs) {
|
|
9217
|
+
ownedThreads.add(threadKey(channel, threadTs));
|
|
9218
|
+
}
|
|
9219
|
+
function isThreadOwned(channel, threadTs) {
|
|
9220
|
+
return ownedThreads.has(threadKey(channel, threadTs));
|
|
9221
|
+
}
|
|
9222
|
+
function isSelfAuthored(event, self) {
|
|
9223
|
+
if (!self) return true;
|
|
9224
|
+
if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
|
|
9225
|
+
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
9226
|
+
return false;
|
|
9227
|
+
}
|
|
9228
|
+
function slackEventToInboundResult(event, opts = {}) {
|
|
9229
|
+
if (!event) return { event: null, dropReason: "empty_text" };
|
|
9230
|
+
const self = opts.self ?? getCachedSlackSelfIdentity();
|
|
9231
|
+
const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
|
|
9232
|
+
if (isBotAuthored && isSelfAuthored(event, self)) {
|
|
9233
|
+
return { event: null, dropReason: "bot_message" };
|
|
9234
|
+
}
|
|
9235
|
+
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
9236
|
+
return { event: null, dropReason: "ignored_subtype" };
|
|
9237
|
+
}
|
|
9238
|
+
const isDm = event.type === "message" && event.channel_type === "im";
|
|
9239
|
+
const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
|
|
9240
|
+
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.
|
|
9241
|
+
typeof event.channel === "string");
|
|
9242
|
+
if (event.type !== "app_mention" && !isDm && !isThreadReply) {
|
|
9243
|
+
if (isNonThreadChannelMsg) {
|
|
9244
|
+
return { event: null, dropReason: "non_thread_channel_msg" };
|
|
9245
|
+
}
|
|
9246
|
+
if (event.type !== "message") {
|
|
9247
|
+
return { event: null, dropReason: "non_message_event" };
|
|
9248
|
+
}
|
|
9249
|
+
return { event: null, dropReason: "unsupported_type" };
|
|
9250
|
+
}
|
|
9251
|
+
const text = (event.text ?? "").trim();
|
|
9252
|
+
const hasFiles = Array.isArray(event.files) && event.files.length > 0;
|
|
9253
|
+
if (!text && !hasFiles) return { event: null, dropReason: "empty_text" };
|
|
9254
|
+
const policy = getSlackAllowlistPolicy();
|
|
9255
|
+
const userAllowlistActive = policy.allowedUsers.length > 0;
|
|
9256
|
+
const userOk = !userAllowlistActive || event.user && policy.allowedUsers.includes(event.user);
|
|
9257
|
+
if (isDm) {
|
|
9258
|
+
if (!policy.allowDmsFromAnyone && !(event.user && policy.allowedUsers.includes(event.user))) {
|
|
9259
|
+
return { event: null, dropReason: "dm_blocked" };
|
|
9260
|
+
}
|
|
9261
|
+
} else {
|
|
9262
|
+
const channelAllowlistActive = policy.allowedChannels.length > 0;
|
|
9263
|
+
if (channelAllowlistActive && !policy.allowedChannels.includes(event.channel)) {
|
|
9264
|
+
return { event: null, dropReason: "channel_not_allowed" };
|
|
9265
|
+
}
|
|
9266
|
+
if (!userOk) {
|
|
9267
|
+
return { event: null, dropReason: "user_not_allowed" };
|
|
9268
|
+
}
|
|
9269
|
+
}
|
|
9270
|
+
const ref = {
|
|
9271
|
+
channel: "slack",
|
|
9272
|
+
slackChannel: event.channel,
|
|
9273
|
+
// For thread replies, threadTs points at the parent (so our reply
|
|
9274
|
+
// continues the thread). messageTs is the inbound message's own ts —
|
|
9275
|
+
// used by reaction add/remove (which target the message itself, not
|
|
9276
|
+
// its parent) and any future "edit this message" operations.
|
|
9277
|
+
threadTs: event.thread_ts || event.ts,
|
|
9278
|
+
messageTs: event.ts,
|
|
9279
|
+
teamId: event.team,
|
|
9280
|
+
user: event.user
|
|
9281
|
+
};
|
|
9282
|
+
const label = slackChannel.displayLabel(ref);
|
|
9283
|
+
return {
|
|
9284
|
+
event: {
|
|
9285
|
+
ref,
|
|
9286
|
+
content: `[${label}] ${text}`,
|
|
9287
|
+
wake: "now",
|
|
9288
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
9289
|
+
}
|
|
9290
|
+
};
|
|
9291
|
+
}
|
|
9292
|
+
var ownedThreads, slackChannel, IGNORED_MESSAGE_SUBTYPES;
|
|
9293
|
+
var init_slack = __esm({
|
|
9294
|
+
"src/integrations/channels/slack.ts"() {
|
|
8484
9295
|
"use strict";
|
|
8485
9296
|
init_client3();
|
|
8486
9297
|
ownedThreads = /* @__PURE__ */ new Set();
|
|
@@ -8500,6 +9311,8 @@ var init_slack = __esm({
|
|
|
8500
9311
|
if (r.slackChannel && r.threadTs) {
|
|
8501
9312
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
8502
9313
|
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
9314
|
+
void Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports)).then((m) => m.markRespondedForThread(r.slackChannel, r.threadTs)).catch(() => {
|
|
9315
|
+
});
|
|
8503
9316
|
}
|
|
8504
9317
|
},
|
|
8505
9318
|
displayLabel(ref) {
|
|
@@ -8532,8 +9345,14 @@ var init_slack = __esm({
|
|
|
8532
9345
|
// also-broadcast-to-channel replies; the regular thread reply already fires
|
|
8533
9346
|
"message_replied",
|
|
8534
9347
|
// legacy parent-thread bump
|
|
8535
|
-
|
|
8536
|
-
//
|
|
9348
|
+
// NOTE: `file_share` is intentionally NOT ignored. It's the subtype Slack
|
|
9349
|
+
// attaches to a normal user message that includes file uploads — the
|
|
9350
|
+
// event still has `text`, `user`, `channel`, and `files: [...]`. The
|
|
9351
|
+
// route handler (src/server/routes/slack.ts) hands the `files[]` array
|
|
9352
|
+
// to `ingestSlackFiles` (src/integrations/slack/files.ts), which
|
|
9353
|
+
// downloads each file using the bot token, re-uploads it to GCS via
|
|
9354
|
+
// the remote-server's /storage/upload-url endpoint, and appends the
|
|
9355
|
+
// resulting short public URLs to the inbound message content.
|
|
8537
9356
|
"reply_broadcast",
|
|
8538
9357
|
"tombstone",
|
|
8539
9358
|
"huddle_thread"
|
|
@@ -8742,7 +9561,7 @@ var init_messenger = __esm({
|
|
|
8742
9561
|
});
|
|
8743
9562
|
|
|
8744
9563
|
// src/orchestrator/schedules-store.ts
|
|
8745
|
-
import { nanoid as
|
|
9564
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
8746
9565
|
async function readOrch(orchestratorSessionId) {
|
|
8747
9566
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
8748
9567
|
if (!s) return null;
|
|
@@ -8757,7 +9576,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
8757
9576
|
const data = await readOrch(orchestratorSessionId);
|
|
8758
9577
|
if (!data) throw new Error("orchestrator session not found");
|
|
8759
9578
|
const row = {
|
|
8760
|
-
id: `sch_${
|
|
9579
|
+
id: `sch_${nanoid5(10)}`,
|
|
8761
9580
|
name: input.name,
|
|
8762
9581
|
cron: input.cron,
|
|
8763
9582
|
prompt: input.prompt,
|
|
@@ -8794,7 +9613,7 @@ var init_schedules_store = __esm({
|
|
|
8794
9613
|
|
|
8795
9614
|
// src/orchestrator/webhooks-store.ts
|
|
8796
9615
|
import { randomBytes } from "crypto";
|
|
8797
|
-
import { nanoid as
|
|
9616
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
8798
9617
|
function newToken() {
|
|
8799
9618
|
return randomBytes(24).toString("base64url");
|
|
8800
9619
|
}
|
|
@@ -8811,7 +9630,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
8811
9630
|
const data = await readOrch2(orchestratorSessionId);
|
|
8812
9631
|
if (!data) throw new Error("orchestrator session not found");
|
|
8813
9632
|
const row = {
|
|
8814
|
-
id: `whk_${
|
|
9633
|
+
id: `whk_${nanoid6(10)}`,
|
|
8815
9634
|
name: input.name,
|
|
8816
9635
|
token: newToken(),
|
|
8817
9636
|
wake: input.wake ?? "now",
|
|
@@ -8956,7 +9775,9 @@ function buildAgentTool(opts) {
|
|
|
8956
9775
|
workingDirectory: input.workingDirectory ?? opts.defaultWorkingDirectory,
|
|
8957
9776
|
name: input.name,
|
|
8958
9777
|
maxIterations: input.maxIterations ?? 100,
|
|
8959
|
-
orchestratorSessionId: opts.orchestratorSessionId
|
|
9778
|
+
orchestratorSessionId: opts.orchestratorSessionId,
|
|
9779
|
+
...input.mcpServers ? { mcpServers: input.mcpServers } : {},
|
|
9780
|
+
...input.skills ? { skills: input.skills } : {}
|
|
8960
9781
|
}
|
|
8961
9782
|
});
|
|
8962
9783
|
return {
|
|
@@ -9129,6 +9950,26 @@ var init_orchestrator_actions = __esm({
|
|
|
9129
9950
|
model: z14.string().optional().describe("spawn only: model override."),
|
|
9130
9951
|
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
9131
9952
|
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
9953
|
+
mcpServers: z14.array(
|
|
9954
|
+
z14.object({
|
|
9955
|
+
name: z14.string(),
|
|
9956
|
+
transport: z14.enum(["http", "sse", "stdio"]),
|
|
9957
|
+
url: z14.string().optional(),
|
|
9958
|
+
headers: z14.record(z14.string(), z14.string()).optional(),
|
|
9959
|
+
command: z14.string().optional(),
|
|
9960
|
+
args: z14.array(z14.string()).optional(),
|
|
9961
|
+
env: z14.record(z14.string(), z14.string()).optional()
|
|
9962
|
+
})
|
|
9963
|
+
).optional().describe("spawn only: task-scoped MCP servers (with auth headers) connected for this worker only, tools exposed as mcp_<name>_<tool>."),
|
|
9964
|
+
skills: z14.array(
|
|
9965
|
+
z14.object({
|
|
9966
|
+
name: z14.string(),
|
|
9967
|
+
description: z14.string().optional(),
|
|
9968
|
+
content: z14.string(),
|
|
9969
|
+
alwaysApply: z14.boolean().optional(),
|
|
9970
|
+
globs: z14.array(z14.string()).optional()
|
|
9971
|
+
})
|
|
9972
|
+
).optional().describe("spawn only: task-scoped skills (inline markdown) available to this worker only."),
|
|
9132
9973
|
// message
|
|
9133
9974
|
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
9134
9975
|
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
@@ -9168,9 +10009,9 @@ var init_orchestrator_actions = __esm({
|
|
|
9168
10009
|
});
|
|
9169
10010
|
|
|
9170
10011
|
// src/integrations/mcp/store.ts
|
|
9171
|
-
import { nanoid as
|
|
9172
|
-
import { existsSync as
|
|
9173
|
-
import { resolve as resolve10, join as
|
|
10012
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
10013
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
|
|
10014
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
9174
10015
|
function readServers() {
|
|
9175
10016
|
try {
|
|
9176
10017
|
const cfg = getConfig();
|
|
@@ -9182,12 +10023,12 @@ function readServers() {
|
|
|
9182
10023
|
function refreshMcpServersFromDisk() {
|
|
9183
10024
|
const candidates = [
|
|
9184
10025
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9185
|
-
|
|
10026
|
+
join11(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9186
10027
|
];
|
|
9187
10028
|
for (const path of candidates) {
|
|
9188
|
-
if (!
|
|
10029
|
+
if (!existsSync18(path)) continue;
|
|
9189
10030
|
try {
|
|
9190
|
-
const raw = JSON.parse(
|
|
10031
|
+
const raw = JSON.parse(readFileSync9(path, "utf-8"));
|
|
9191
10032
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9192
10033
|
setMcpServers(servers2);
|
|
9193
10034
|
return servers2;
|
|
@@ -9206,7 +10047,7 @@ function createMcpServer(input) {
|
|
|
9206
10047
|
const all = readServers();
|
|
9207
10048
|
validateInput(input);
|
|
9208
10049
|
const row = {
|
|
9209
|
-
id: `mcp_${
|
|
10050
|
+
id: `mcp_${nanoid7(10)}`,
|
|
9210
10051
|
name: sanitizeName(input.name),
|
|
9211
10052
|
transport: input.transport,
|
|
9212
10053
|
url: input.url,
|
|
@@ -9377,6 +10218,159 @@ var init_pool = __esm({
|
|
|
9377
10218
|
}
|
|
9378
10219
|
});
|
|
9379
10220
|
|
|
10221
|
+
// src/integrations/mcp/task-scoped.ts
|
|
10222
|
+
import { createMCPClient as createMCPClient2 } from "@ai-sdk/mcp";
|
|
10223
|
+
function sanitizeName2(raw) {
|
|
10224
|
+
return raw.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/_+/g, "_");
|
|
10225
|
+
}
|
|
10226
|
+
function buildHttpLikeTransport(server) {
|
|
10227
|
+
if (!server.url) {
|
|
10228
|
+
throw new Error(`${server.transport} transport requires a url`);
|
|
10229
|
+
}
|
|
10230
|
+
return {
|
|
10231
|
+
type: server.transport,
|
|
10232
|
+
url: server.url,
|
|
10233
|
+
headers: server.headers
|
|
10234
|
+
};
|
|
10235
|
+
}
|
|
10236
|
+
async function buildStdioTransport2(server) {
|
|
10237
|
+
if (!server.command) {
|
|
10238
|
+
throw new Error("stdio transport requires a command");
|
|
10239
|
+
}
|
|
10240
|
+
const mod = await import("@ai-sdk/mcp/mcp-stdio");
|
|
10241
|
+
const Cls = mod.Experimental_StdioMCPTransport || mod.StdioClientTransport;
|
|
10242
|
+
if (!Cls) throw new Error("@ai-sdk/mcp/mcp-stdio is missing the stdio transport class");
|
|
10243
|
+
return new Cls({
|
|
10244
|
+
command: server.command,
|
|
10245
|
+
args: server.args ?? [],
|
|
10246
|
+
env: server.env
|
|
10247
|
+
});
|
|
10248
|
+
}
|
|
10249
|
+
async function buildTransport(server) {
|
|
10250
|
+
return server.transport === "stdio" ? await buildStdioTransport2(server) : buildHttpLikeTransport(server);
|
|
10251
|
+
}
|
|
10252
|
+
async function connectTaskMcpServers(servers2, opts = {}) {
|
|
10253
|
+
const tools = {};
|
|
10254
|
+
const connected = [];
|
|
10255
|
+
const errors = [];
|
|
10256
|
+
const clients2 = [];
|
|
10257
|
+
for (const raw of servers2 ?? []) {
|
|
10258
|
+
const name = sanitizeName2(raw.name || "");
|
|
10259
|
+
if (!name) {
|
|
10260
|
+
errors.push({ name: raw.name || "(unnamed)", error: "server name is required" });
|
|
10261
|
+
continue;
|
|
10262
|
+
}
|
|
10263
|
+
let client = null;
|
|
10264
|
+
try {
|
|
10265
|
+
const transport = await buildTransport(raw);
|
|
10266
|
+
client = await createMCPClient2({ transport });
|
|
10267
|
+
clients2.push(client);
|
|
10268
|
+
const serverTools = await client.tools();
|
|
10269
|
+
for (const [toolName, t] of Object.entries(serverTools)) {
|
|
10270
|
+
tools[`mcp_${name}_${toolName}`] = t;
|
|
10271
|
+
}
|
|
10272
|
+
connected.push(name);
|
|
10273
|
+
} catch (err) {
|
|
10274
|
+
const message = err?.message || String(err);
|
|
10275
|
+
errors.push({ name, error: message });
|
|
10276
|
+
if (!opts.quiet) {
|
|
10277
|
+
console.warn(`[mcp:task] connecting "${name}" failed: ${message}`);
|
|
10278
|
+
}
|
|
10279
|
+
if (client) {
|
|
10280
|
+
try {
|
|
10281
|
+
await client.close();
|
|
10282
|
+
} catch {
|
|
10283
|
+
}
|
|
10284
|
+
const idx = clients2.indexOf(client);
|
|
10285
|
+
if (idx >= 0) clients2.splice(idx, 1);
|
|
10286
|
+
}
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
10289
|
+
let closed = false;
|
|
10290
|
+
const close = async () => {
|
|
10291
|
+
if (closed) return;
|
|
10292
|
+
closed = true;
|
|
10293
|
+
await Promise.all(
|
|
10294
|
+
clients2.map(async (c) => {
|
|
10295
|
+
try {
|
|
10296
|
+
await c.close();
|
|
10297
|
+
} catch {
|
|
10298
|
+
}
|
|
10299
|
+
})
|
|
10300
|
+
);
|
|
10301
|
+
};
|
|
10302
|
+
return { tools, connected, errors, close };
|
|
10303
|
+
}
|
|
10304
|
+
var init_task_scoped = __esm({
|
|
10305
|
+
"src/integrations/mcp/task-scoped.ts"() {
|
|
10306
|
+
"use strict";
|
|
10307
|
+
}
|
|
10308
|
+
});
|
|
10309
|
+
|
|
10310
|
+
// src/skills/task-scoped.ts
|
|
10311
|
+
import { mkdtemp, writeFile as writeFile5, rm } from "fs/promises";
|
|
10312
|
+
import { tmpdir } from "os";
|
|
10313
|
+
import { join as join12 } from "path";
|
|
10314
|
+
function safeFileName(name, index) {
|
|
10315
|
+
const base = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
10316
|
+
return `${base || `skill-${index + 1}`}.md`;
|
|
10317
|
+
}
|
|
10318
|
+
function escapeFrontmatterValue(value) {
|
|
10319
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
10320
|
+
}
|
|
10321
|
+
function buildSkillFile(skill) {
|
|
10322
|
+
const lines = ["---"];
|
|
10323
|
+
lines.push(`name: ${escapeFrontmatterValue(skill.name)}`);
|
|
10324
|
+
lines.push(`description: ${escapeFrontmatterValue(skill.description || skill.name)}`);
|
|
10325
|
+
if (skill.alwaysApply) lines.push("alwaysApply: true");
|
|
10326
|
+
if (skill.globs && skill.globs.length > 0) {
|
|
10327
|
+
lines.push(`globs: [${skill.globs.map((g) => escapeFrontmatterValue(g)).join(", ")}]`);
|
|
10328
|
+
}
|
|
10329
|
+
lines.push("---");
|
|
10330
|
+
lines.push("");
|
|
10331
|
+
lines.push(skill.content);
|
|
10332
|
+
return lines.join("\n");
|
|
10333
|
+
}
|
|
10334
|
+
async function materializeTaskSkills(skills2, taskId) {
|
|
10335
|
+
if (!skills2 || skills2.length === 0) return null;
|
|
10336
|
+
const safeTaskId = taskId.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
10337
|
+
const dir = await mkdtemp(join12(tmpdir(), `sparkecoder-task-skills-${safeTaskId}-`));
|
|
10338
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10339
|
+
await Promise.all(
|
|
10340
|
+
skills2.map(async (skill, i) => {
|
|
10341
|
+
let fileName = safeFileName(skill.name, i);
|
|
10342
|
+
while (seen.has(fileName)) fileName = `dup-${i}-${fileName}`;
|
|
10343
|
+
seen.add(fileName);
|
|
10344
|
+
await writeFile5(join12(dir, fileName), buildSkillFile(skill), "utf-8");
|
|
10345
|
+
})
|
|
10346
|
+
);
|
|
10347
|
+
const loaded2 = await loadSkillsFromDirectory(dir, { priority: 1, defaultLoadType: "on_demand" });
|
|
10348
|
+
const alwaysSkills = loaded2.filter((s) => s.alwaysApply || s.loadType === "always");
|
|
10349
|
+
const onDemand = loaded2.filter((s) => !(s.alwaysApply || s.loadType === "always"));
|
|
10350
|
+
const always = (await Promise.all(
|
|
10351
|
+
alwaysSkills.map(async (s) => {
|
|
10352
|
+
const withContent = await loadSkillContent(s.name, [dir]);
|
|
10353
|
+
return withContent ? { ...s, content: withContent.content } : null;
|
|
10354
|
+
})
|
|
10355
|
+
)).filter((s) => s !== null);
|
|
10356
|
+
let cleaned = false;
|
|
10357
|
+
const cleanup2 = async () => {
|
|
10358
|
+
if (cleaned) return;
|
|
10359
|
+
cleaned = true;
|
|
10360
|
+
try {
|
|
10361
|
+
await rm(dir, { recursive: true, force: true });
|
|
10362
|
+
} catch {
|
|
10363
|
+
}
|
|
10364
|
+
};
|
|
10365
|
+
return { dir, always, onDemand, cleanup: cleanup2 };
|
|
10366
|
+
}
|
|
10367
|
+
var init_task_scoped2 = __esm({
|
|
10368
|
+
"src/skills/task-scoped.ts"() {
|
|
10369
|
+
"use strict";
|
|
10370
|
+
init_skills();
|
|
10371
|
+
}
|
|
10372
|
+
});
|
|
10373
|
+
|
|
9380
10374
|
// src/utils/webhook.ts
|
|
9381
10375
|
var webhook_exports = {};
|
|
9382
10376
|
__export(webhook_exports, {
|
|
@@ -9535,79 +10529,57 @@ var init_pending_input = __esm({
|
|
|
9535
10529
|
}
|
|
9536
10530
|
});
|
|
9537
10531
|
|
|
9538
|
-
// src/
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
if (!e) {
|
|
9553
|
-
e = { pending: [] };
|
|
9554
|
-
inboxes.set(sessionId, e);
|
|
9555
|
-
}
|
|
9556
|
-
return e;
|
|
10532
|
+
// src/utils/local-device-time.ts
|
|
10533
|
+
function formatLocalDeviceTimeLine(now = /* @__PURE__ */ new Date()) {
|
|
10534
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
10535
|
+
const formatted = now.toLocaleString("en-US", {
|
|
10536
|
+
weekday: "long",
|
|
10537
|
+
year: "numeric",
|
|
10538
|
+
month: "long",
|
|
10539
|
+
day: "numeric",
|
|
10540
|
+
hour: "numeric",
|
|
10541
|
+
minute: "2-digit",
|
|
10542
|
+
second: "2-digit",
|
|
10543
|
+
timeZoneName: "short"
|
|
10544
|
+
});
|
|
10545
|
+
return `${LOCAL_TIME_MARKER} ${formatted} (${timeZone})]`;
|
|
9557
10546
|
}
|
|
9558
|
-
function
|
|
9559
|
-
|
|
9560
|
-
e.pending.push(event);
|
|
9561
|
-
if (event.wake === "now") {
|
|
9562
|
-
scheduleFlush(orchestratorSessionId);
|
|
9563
|
-
}
|
|
10547
|
+
function hasLocalDeviceTimeLine(text) {
|
|
10548
|
+
return text.includes(LOCAL_TIME_MARKER);
|
|
9564
10549
|
}
|
|
9565
|
-
function
|
|
9566
|
-
const
|
|
9567
|
-
if (!
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
void flush(sessionId);
|
|
9571
|
-
}, FLUSH_DEBOUNCE_MS);
|
|
10550
|
+
function prependLocalDeviceTimeToUserMessage(text, now) {
|
|
10551
|
+
const trimmed = text.trim();
|
|
10552
|
+
if (!trimmed || hasLocalDeviceTimeLine(text)) return text;
|
|
10553
|
+
return `${formatLocalDeviceTimeLine(now)}
|
|
10554
|
+
${text}`;
|
|
9572
10555
|
}
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
if (e.timer) {
|
|
9577
|
-
clearTimeout(e.timer);
|
|
9578
|
-
e.timer = void 0;
|
|
9579
|
-
}
|
|
9580
|
-
const events = e.pending.splice(0);
|
|
9581
|
-
if (events.length === 0) return;
|
|
9582
|
-
if (!flushHandler) {
|
|
9583
|
-
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
9584
|
-
return;
|
|
10556
|
+
function prependLocalDeviceTimeToUserContent(content, now) {
|
|
10557
|
+
if (typeof content === "string") {
|
|
10558
|
+
return prependLocalDeviceTimeToUserMessage(content, now);
|
|
9585
10559
|
}
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
10560
|
+
const line = formatLocalDeviceTimeLine(now);
|
|
10561
|
+
if (content.some((p) => p.type === "text" && p.text && hasLocalDeviceTimeLine(p.text))) {
|
|
10562
|
+
return content;
|
|
9590
10563
|
}
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
10564
|
+
const userIdx = content.findIndex(
|
|
10565
|
+
(p) => p.type === "text" && p.text?.includes("[USER MESSAGE]")
|
|
10566
|
+
);
|
|
10567
|
+
if (userIdx >= 0 && content[userIdx].text) {
|
|
10568
|
+
const copy = content.map((p) => ({ ...p }));
|
|
10569
|
+
copy[userIdx] = {
|
|
10570
|
+
...copy[userIdx],
|
|
10571
|
+
text: `${line}
|
|
10572
|
+
${copy[userIdx].text}`
|
|
10573
|
+
};
|
|
10574
|
+
return copy;
|
|
9601
10575
|
}
|
|
9602
|
-
|
|
10576
|
+
return [{ type: "text", text: line }, ...content];
|
|
9603
10577
|
}
|
|
9604
|
-
var
|
|
9605
|
-
var
|
|
9606
|
-
"src/
|
|
10578
|
+
var LOCAL_TIME_MARKER;
|
|
10579
|
+
var init_local_device_time = __esm({
|
|
10580
|
+
"src/utils/local-device-time.ts"() {
|
|
9607
10581
|
"use strict";
|
|
9608
|
-
|
|
9609
|
-
FLUSH_DEBOUNCE_MS = 200;
|
|
9610
|
-
flushHandler = null;
|
|
10582
|
+
LOCAL_TIME_MARKER = "[Local device time:";
|
|
9611
10583
|
}
|
|
9612
10584
|
});
|
|
9613
10585
|
|
|
@@ -9819,10 +10791,10 @@ __export(recorder_exports, {
|
|
|
9819
10791
|
});
|
|
9820
10792
|
import { exec as exec5 } from "child_process";
|
|
9821
10793
|
import { promisify as promisify5 } from "util";
|
|
9822
|
-
import { writeFile as
|
|
9823
|
-
import { join as
|
|
9824
|
-
import { tmpdir } from "os";
|
|
9825
|
-
import { nanoid as
|
|
10794
|
+
import { writeFile as writeFile6, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm as rm2 } from "fs/promises";
|
|
10795
|
+
import { join as join13 } from "path";
|
|
10796
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
10797
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
9826
10798
|
async function checkFfmpeg() {
|
|
9827
10799
|
try {
|
|
9828
10800
|
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
@@ -9833,7 +10805,7 @@ async function checkFfmpeg() {
|
|
|
9833
10805
|
}
|
|
9834
10806
|
async function cleanup(dir) {
|
|
9835
10807
|
try {
|
|
9836
|
-
await
|
|
10808
|
+
await rm2(dir, { recursive: true, force: true });
|
|
9837
10809
|
} catch {
|
|
9838
10810
|
}
|
|
9839
10811
|
}
|
|
@@ -9877,21 +10849,21 @@ var init_recorder = __esm({
|
|
|
9877
10849
|
*/
|
|
9878
10850
|
async encode() {
|
|
9879
10851
|
if (this.frames.length === 0) return null;
|
|
9880
|
-
const workDir =
|
|
10852
|
+
const workDir = join13(tmpdir2(), `sparkecoder-recording-${nanoid8(8)}`);
|
|
9881
10853
|
await mkdir4(workDir, { recursive: true });
|
|
9882
10854
|
try {
|
|
9883
10855
|
for (let i = 0; i < this.frames.length; i++) {
|
|
9884
|
-
const framePath =
|
|
9885
|
-
await
|
|
10856
|
+
const framePath = join13(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
10857
|
+
await writeFile6(framePath, this.frames[i].data);
|
|
9886
10858
|
}
|
|
9887
10859
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
9888
10860
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
9889
10861
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
9890
|
-
const outputPath =
|
|
10862
|
+
const outputPath = join13(workDir, `recording_${this.sessionId}.mp4`);
|
|
9891
10863
|
const hasFfmpeg = await checkFfmpeg();
|
|
9892
10864
|
if (hasFfmpeg) {
|
|
9893
10865
|
await execAsync5(
|
|
9894
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
10866
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join13(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
9895
10867
|
{ timeout: 12e4 }
|
|
9896
10868
|
);
|
|
9897
10869
|
} else {
|
|
@@ -9903,7 +10875,7 @@ var init_recorder = __esm({
|
|
|
9903
10875
|
const files = await readdir5(workDir);
|
|
9904
10876
|
for (const f of files) {
|
|
9905
10877
|
if (f.startsWith("frame_")) {
|
|
9906
|
-
await unlink2(
|
|
10878
|
+
await unlink2(join13(workDir, f)).catch(() => {
|
|
9907
10879
|
});
|
|
9908
10880
|
}
|
|
9909
10881
|
}
|
|
@@ -9932,7 +10904,7 @@ import {
|
|
|
9932
10904
|
stepCountIs as stepCountIs2
|
|
9933
10905
|
} from "ai";
|
|
9934
10906
|
import { z as z15 } from "zod";
|
|
9935
|
-
import { nanoid as
|
|
10907
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
9936
10908
|
function anySignal(signals) {
|
|
9937
10909
|
const ctrl = new AbortController();
|
|
9938
10910
|
for (const s of signals) {
|
|
@@ -9976,10 +10948,13 @@ var init_agent = __esm({
|
|
|
9976
10948
|
init_prompts();
|
|
9977
10949
|
init_orchestrator_actions();
|
|
9978
10950
|
init_pool();
|
|
10951
|
+
init_task_scoped();
|
|
10952
|
+
init_task_scoped2();
|
|
9979
10953
|
init_webhook2();
|
|
9980
10954
|
init_questions();
|
|
9981
10955
|
init_pending_input();
|
|
9982
10956
|
init_inbox();
|
|
10957
|
+
init_local_device_time();
|
|
9983
10958
|
init_system();
|
|
9984
10959
|
init_context();
|
|
9985
10960
|
init_prompts();
|
|
@@ -10142,9 +11117,11 @@ ${prompt}` });
|
|
|
10142
11117
|
*/
|
|
10143
11118
|
async stream(options) {
|
|
10144
11119
|
const config = getConfig();
|
|
10145
|
-
const
|
|
11120
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
11121
|
+
const userContent = this.buildUserMessageContent(prompt, options.attachments);
|
|
11122
|
+
const persistedUserContent = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserContent(userContent) : userContent;
|
|
10146
11123
|
if (!options.skipSaveUserMessage) {
|
|
10147
|
-
await this.context.addUserMessage(
|
|
11124
|
+
await this.context.addUserMessage(persistedUserContent);
|
|
10148
11125
|
}
|
|
10149
11126
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10150
11127
|
let systemPrompt = await buildSystemPrompt({
|
|
@@ -10222,7 +11199,8 @@ ${personality.trim()}
|
|
|
10222
11199
|
*/
|
|
10223
11200
|
async run(options) {
|
|
10224
11201
|
const config = getConfig();
|
|
10225
|
-
|
|
11202
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
11203
|
+
await this.context.addUserMessage(prompt);
|
|
10226
11204
|
const systemPrompt = await buildSystemPrompt({
|
|
10227
11205
|
workingDirectory: this.session.workingDirectory,
|
|
10228
11206
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -10266,355 +11244,387 @@ ${personality.trim()}
|
|
|
10266
11244
|
*/
|
|
10267
11245
|
async runTask(options) {
|
|
10268
11246
|
const config = getConfig();
|
|
10269
|
-
const
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
const completion = { signal: null };
|
|
10284
|
-
const onComplete = (signal) => {
|
|
10285
|
-
completion.signal = signal;
|
|
10286
|
-
};
|
|
10287
|
-
let taskRecorder = null;
|
|
10288
|
-
const sessionId = this.session.id;
|
|
10289
|
-
const emit = options.writeSSE;
|
|
10290
|
-
const bashProgressHandler = (progress) => {
|
|
10291
|
-
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
10292
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
10293
|
-
});
|
|
10294
|
-
const port = progress.browserStreamPort;
|
|
10295
|
-
if (port && progress.status === "started") {
|
|
10296
|
-
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
10297
|
-
const proxy = getOrCreateProxy2(sessionId, port);
|
|
10298
|
-
if (!taskRecorder) {
|
|
10299
|
-
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
10300
|
-
taskRecorder = new FrameRecorder2(sessionId);
|
|
10301
|
-
taskRecorder.start();
|
|
10302
|
-
});
|
|
10303
|
-
}
|
|
10304
|
-
if (proxy.listenerCount("frame") === 0) {
|
|
10305
|
-
proxy.on("frame", (frame) => {
|
|
10306
|
-
taskRecorder?.addFrame(frame);
|
|
10307
|
-
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
10308
|
-
});
|
|
10309
|
-
});
|
|
10310
|
-
proxy.on("status", (s) => {
|
|
10311
|
-
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
10312
|
-
});
|
|
10313
|
-
});
|
|
10314
|
-
}
|
|
11247
|
+
const taskScopedCleanups = [];
|
|
11248
|
+
try {
|
|
11249
|
+
const maxIterations = options.taskConfig.maxIterations ?? 50;
|
|
11250
|
+
const webhookUrl = options.taskConfig.webhookUrl;
|
|
11251
|
+
const parentTaskId = options.taskConfig.parentTaskId;
|
|
11252
|
+
const fireWebhook = (type, data) => {
|
|
11253
|
+
if (!webhookUrl) return;
|
|
11254
|
+
sendWebhook(webhookUrl, {
|
|
11255
|
+
type,
|
|
11256
|
+
taskId: this.session.id,
|
|
11257
|
+
sessionId: this.session.id,
|
|
11258
|
+
...parentTaskId ? { parentTaskId } : {},
|
|
11259
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11260
|
+
data
|
|
10315
11261
|
});
|
|
11262
|
+
};
|
|
11263
|
+
const completion = { signal: null };
|
|
11264
|
+
const onComplete = (signal) => {
|
|
11265
|
+
completion.signal = signal;
|
|
11266
|
+
};
|
|
11267
|
+
let taskMcpTools = {};
|
|
11268
|
+
if (options.mcpServers && options.mcpServers.length > 0) {
|
|
11269
|
+
const mcpConnection = await connectTaskMcpServers(options.mcpServers, { quiet: true });
|
|
11270
|
+
taskScopedCleanups.push(mcpConnection.close);
|
|
11271
|
+
taskMcpTools = mcpConnection.tools;
|
|
11272
|
+
if (mcpConnection.connected.length > 0) {
|
|
11273
|
+
console.log(`[TASK] connected ${mcpConnection.connected.length} task-scoped MCP server(s): ${mcpConnection.connected.join(", ")}`);
|
|
11274
|
+
}
|
|
11275
|
+
for (const e of mcpConnection.errors) {
|
|
11276
|
+
console.warn(`[TASK] task-scoped MCP server "${e.name}" failed to connect: ${e.error}`);
|
|
11277
|
+
if (options.writeSSE) await options.writeSSE(JSON.stringify({ type: "task-mcp-error", data: { name: e.name, error: e.error } }));
|
|
11278
|
+
}
|
|
10316
11279
|
}
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
|
|
10324
|
-
options.onToolProgress?.({ toolName: "
|
|
10325
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "
|
|
10326
|
-
});
|
|
10327
|
-
},
|
|
10328
|
-
onSearchProgress: (progress) => {
|
|
10329
|
-
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
10330
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
11280
|
+
const materializedSkills = await materializeTaskSkills(options.skills, this.session.id);
|
|
11281
|
+
if (materializedSkills) taskScopedCleanups.push(materializedSkills.cleanup);
|
|
11282
|
+
const taskSkillsDir = materializedSkills?.dir;
|
|
11283
|
+
let taskRecorder = null;
|
|
11284
|
+
const sessionId = this.session.id;
|
|
11285
|
+
const emit = options.writeSSE;
|
|
11286
|
+
const bashProgressHandler = (progress) => {
|
|
11287
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
11288
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
10331
11289
|
});
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
11290
|
+
const port = progress.browserStreamPort;
|
|
11291
|
+
if (port && progress.status === "started") {
|
|
11292
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
11293
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
11294
|
+
if (!taskRecorder) {
|
|
11295
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
11296
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
11297
|
+
taskRecorder.start();
|
|
11298
|
+
});
|
|
11299
|
+
}
|
|
11300
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
11301
|
+
proxy.on("frame", (frame) => {
|
|
11302
|
+
taskRecorder?.addFrame(frame);
|
|
11303
|
+
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
11304
|
+
});
|
|
11305
|
+
});
|
|
11306
|
+
proxy.on("status", (s) => {
|
|
11307
|
+
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
11308
|
+
});
|
|
11309
|
+
});
|
|
11310
|
+
}
|
|
10350
11311
|
});
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
}
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
11312
|
+
}
|
|
11313
|
+
};
|
|
11314
|
+
const taskTools = await createTools({
|
|
11315
|
+
sessionId: this.session.id,
|
|
11316
|
+
workingDirectory: this.session.workingDirectory,
|
|
11317
|
+
onBashProgress: bashProgressHandler,
|
|
11318
|
+
onWriteFileProgress: (progress) => {
|
|
11319
|
+
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
11320
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "write_file", data: progress })).catch(() => {
|
|
11321
|
+
});
|
|
11322
|
+
},
|
|
11323
|
+
onSearchProgress: (progress) => {
|
|
11324
|
+
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
11325
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
11326
|
+
});
|
|
11327
|
+
},
|
|
11328
|
+
// Add the task-scoped skills temp dir (if any) so load_skill can list
|
|
11329
|
+
// and load the inline skills supplied with this task.
|
|
11330
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
11331
|
+
taskTools: {
|
|
11332
|
+
outputSchema: options.taskConfig.outputSchema,
|
|
11333
|
+
onComplete,
|
|
11334
|
+
onQuestion: async (question) => {
|
|
11335
|
+
const payload = {
|
|
11336
|
+
questionId: question.questionId,
|
|
11337
|
+
question: question.question,
|
|
11338
|
+
context: question.context,
|
|
11339
|
+
choices: question.choices,
|
|
11340
|
+
status: "pending"
|
|
11341
|
+
};
|
|
11342
|
+
const answerPromise = waitForTaskQuestionAnswer({
|
|
11343
|
+
taskId: this.session.id,
|
|
11344
|
+
questionId: question.questionId,
|
|
11345
|
+
question: question.question,
|
|
11346
|
+
context: question.context,
|
|
11347
|
+
choices: question.choices
|
|
11348
|
+
});
|
|
11349
|
+
fireWebhook("task.question", payload);
|
|
11350
|
+
if (emit) {
|
|
11351
|
+
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
11352
|
+
}
|
|
11353
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
11354
|
+
if (orchId) {
|
|
11355
|
+
pushToInbox(orchId, workerQuestionEvent(
|
|
11356
|
+
this.session.id,
|
|
11357
|
+
this.session.name || "worker",
|
|
11358
|
+
question.question,
|
|
11359
|
+
question.questionId
|
|
11360
|
+
));
|
|
11361
|
+
}
|
|
11362
|
+
const answer = await answerPromise;
|
|
11363
|
+
const answeredPayload = {
|
|
11364
|
+
questionId: question.questionId,
|
|
11365
|
+
answer: answer.answer,
|
|
11366
|
+
answeredBy: answer.answeredBy
|
|
11367
|
+
};
|
|
11368
|
+
fireWebhook("task.question_answered", answeredPayload);
|
|
11369
|
+
if (emit) {
|
|
11370
|
+
await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
|
|
11371
|
+
}
|
|
11372
|
+
return answer;
|
|
10373
11373
|
}
|
|
10374
|
-
return answer;
|
|
10375
11374
|
}
|
|
11375
|
+
});
|
|
11376
|
+
for (const [name, t] of Object.entries(taskMcpTools)) {
|
|
11377
|
+
taskTools[name] = t;
|
|
10376
11378
|
}
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
11379
|
+
const baseSystemPrompt = await buildSystemPrompt({
|
|
11380
|
+
workingDirectory: this.session.workingDirectory,
|
|
11381
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
11382
|
+
sessionId: this.session.id,
|
|
11383
|
+
discoveredSkills: config.discoveredSkills,
|
|
11384
|
+
activeFiles: [],
|
|
11385
|
+
taskScopedSkills: materializedSkills ? { always: materializedSkills.always, onDemand: materializedSkills.onDemand } : void 0
|
|
11386
|
+
});
|
|
11387
|
+
const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
|
|
11388
|
+
const systemPrompt = `${baseSystemPrompt}
|
|
10387
11389
|
|
|
10388
11390
|
${taskAddendum}`;
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
}
|
|
10393
|
-
await this.context.addUserMessage(options.prompt);
|
|
10394
|
-
let iteration = 0;
|
|
10395
|
-
while (iteration < maxIterations) {
|
|
10396
|
-
iteration++;
|
|
10397
|
-
if (options.abortSignal?.aborted) {
|
|
10398
|
-
const cancelError = "Task was cancelled";
|
|
10399
|
-
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
10400
|
-
clearInterruptController(this.session.id);
|
|
10401
|
-
return { status: "failed", error: cancelError, iterations: iteration };
|
|
11391
|
+
fireWebhook("task.started", { prompt: options.prompt });
|
|
11392
|
+
if (emit) {
|
|
11393
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: options.prompt } }));
|
|
10402
11394
|
}
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
11395
|
+
await this.context.addUserMessage(options.prompt);
|
|
11396
|
+
let iteration = 0;
|
|
11397
|
+
while (iteration < maxIterations) {
|
|
11398
|
+
iteration++;
|
|
11399
|
+
if (options.abortSignal?.aborted) {
|
|
11400
|
+
const cancelError = "Task was cancelled";
|
|
11401
|
+
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
11402
|
+
clearInterruptController(this.session.id);
|
|
11403
|
+
return { status: "failed", error: cancelError, iterations: iteration };
|
|
11404
|
+
}
|
|
11405
|
+
const pending = drainInputs(this.session.id);
|
|
11406
|
+
for (const p of pending) {
|
|
11407
|
+
const labelled = p.source === "orchestrator" ? `[message from orchestrator]
|
|
10406
11408
|
${p.text}` : p.source === "system" ? `[system note]
|
|
10407
11409
|
${p.text}` : p.text;
|
|
10408
|
-
if (emit) {
|
|
10409
|
-
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
10410
|
-
}
|
|
10411
|
-
await this.context.addUserMessage(labelled);
|
|
10412
|
-
}
|
|
10413
|
-
const interruptController = new AbortController();
|
|
10414
|
-
registerInterruptController(this.session.id, interruptController);
|
|
10415
|
-
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
10416
|
-
const messages = await this.context.getMessages();
|
|
10417
|
-
const useAnthropic = isAnthropicModel(this.session.model);
|
|
10418
|
-
if (emit) {
|
|
10419
|
-
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
10420
|
-
}
|
|
10421
|
-
let textStarted = false;
|
|
10422
|
-
let textId = `text_${Date.now()}`;
|
|
10423
|
-
let reasoningId = `reasoning_${Date.now()}`;
|
|
10424
|
-
let reasoningStarted = false;
|
|
10425
|
-
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
10426
|
-
const iterStream = streamText2({
|
|
10427
|
-
model: resolveModel(this.session.model),
|
|
10428
|
-
system: systemPrompt,
|
|
10429
|
-
messages,
|
|
10430
|
-
tools: wrapToolsNeverThrow(taskTools),
|
|
10431
|
-
stopWhen: stepCountIs2(500),
|
|
10432
|
-
abortSignal: combinedAbort,
|
|
10433
|
-
providerOptions: useAnthropic ? {
|
|
10434
|
-
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
10435
|
-
} : void 0,
|
|
10436
|
-
// See the matching note in `stream()` — repair tool pairing before
|
|
10437
|
-
// every step so we never feed the model an orphan tool-call.
|
|
10438
|
-
prepareStep: async ({ messages: stepMessages }) => {
|
|
10439
|
-
const paired = repairToolPairing(stepMessages);
|
|
10440
|
-
const ordered = ensureToolResultsFollowCalls(paired);
|
|
10441
|
-
if (ordered === stepMessages) return {};
|
|
10442
|
-
return { messages: ordered };
|
|
10443
|
-
},
|
|
10444
|
-
onStepFinish: async (step) => {
|
|
10445
|
-
options.onStepFinish?.(step);
|
|
10446
|
-
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
10447
11410
|
if (emit) {
|
|
10448
|
-
|
|
10449
|
-
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
10450
|
-
textStarted = false;
|
|
10451
|
-
textId = `text_${Date.now()}`;
|
|
10452
|
-
}
|
|
10453
|
-
await emit(JSON.stringify({ type: "finish-step" }));
|
|
11411
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
10454
11412
|
}
|
|
11413
|
+
await this.context.addUserMessage(labelled);
|
|
10455
11414
|
}
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
11415
|
+
const interruptController = new AbortController();
|
|
11416
|
+
registerInterruptController(this.session.id, interruptController);
|
|
11417
|
+
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
11418
|
+
const messages = await this.context.getMessages();
|
|
11419
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
11420
|
+
if (emit) {
|
|
11421
|
+
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
11422
|
+
}
|
|
11423
|
+
let textStarted = false;
|
|
11424
|
+
let textId = `text_${Date.now()}`;
|
|
11425
|
+
let reasoningId = `reasoning_${Date.now()}`;
|
|
11426
|
+
let reasoningStarted = false;
|
|
11427
|
+
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
11428
|
+
const iterStream = streamText2({
|
|
11429
|
+
model: resolveModel(this.session.model),
|
|
11430
|
+
system: systemPrompt,
|
|
11431
|
+
messages,
|
|
11432
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
11433
|
+
stopWhen: stepCountIs2(500),
|
|
11434
|
+
abortSignal: combinedAbort,
|
|
11435
|
+
providerOptions: useAnthropic ? {
|
|
11436
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
11437
|
+
} : void 0,
|
|
11438
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
11439
|
+
// every step so we never feed the model an orphan tool-call.
|
|
11440
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
11441
|
+
const paired = repairToolPairing(stepMessages);
|
|
11442
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
11443
|
+
if (ordered === stepMessages) return {};
|
|
11444
|
+
return { messages: ordered };
|
|
11445
|
+
},
|
|
11446
|
+
onStepFinish: async (step) => {
|
|
11447
|
+
options.onStepFinish?.(step);
|
|
11448
|
+
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
11449
|
+
if (emit) {
|
|
11450
|
+
if (textStarted) {
|
|
11451
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
11452
|
+
textStarted = false;
|
|
11453
|
+
textId = `text_${Date.now()}`;
|
|
11454
|
+
}
|
|
11455
|
+
await emit(JSON.stringify({ type: "finish-step" }));
|
|
10463
11456
|
}
|
|
10464
|
-
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
10465
|
-
}
|
|
10466
|
-
} else if (part.type === "reasoning-start") {
|
|
10467
|
-
if (emit) {
|
|
10468
|
-
await emit(JSON.stringify({ type: "reasoning-start", id: reasoningId }));
|
|
10469
|
-
reasoningStarted = true;
|
|
10470
|
-
}
|
|
10471
|
-
} else if (part.type === "reasoning-delta") {
|
|
10472
|
-
if (emit) {
|
|
10473
|
-
await emit(JSON.stringify({ type: "reasoning-delta", id: reasoningId, delta: part.text }));
|
|
10474
|
-
}
|
|
10475
|
-
} else if (part.type === "reasoning-end") {
|
|
10476
|
-
if (emit && reasoningStarted) {
|
|
10477
|
-
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
10478
|
-
reasoningStarted = false;
|
|
10479
|
-
reasoningId = `reasoning_${Date.now()}`;
|
|
10480
|
-
}
|
|
10481
|
-
} else if (part.type === "tool-call-streaming-start") {
|
|
10482
|
-
if (emit) {
|
|
10483
|
-
const p = part;
|
|
10484
|
-
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: p.toolCallId, toolName: p.toolName }));
|
|
10485
|
-
toolCallStarts.add(p.toolCallId);
|
|
10486
|
-
}
|
|
10487
|
-
} else if (part.type === "tool-call-delta") {
|
|
10488
|
-
if (emit) {
|
|
10489
|
-
const p = part;
|
|
10490
|
-
await emit(JSON.stringify({ type: "tool-input-delta", toolCallId: p.toolCallId, argsTextDelta: p.argsTextDelta }));
|
|
10491
11457
|
}
|
|
10492
|
-
}
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
11458
|
+
});
|
|
11459
|
+
for await (const part of iterStream.fullStream) {
|
|
11460
|
+
if (part.type === "text-delta") {
|
|
11461
|
+
if (emit) {
|
|
11462
|
+
if (!textStarted) {
|
|
11463
|
+
await emit(JSON.stringify({ type: "text-start", id: textId }));
|
|
11464
|
+
textStarted = true;
|
|
11465
|
+
}
|
|
11466
|
+
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
11467
|
+
}
|
|
11468
|
+
} else if (part.type === "reasoning-start") {
|
|
11469
|
+
if (emit) {
|
|
11470
|
+
await emit(JSON.stringify({ type: "reasoning-start", id: reasoningId }));
|
|
11471
|
+
reasoningStarted = true;
|
|
11472
|
+
}
|
|
11473
|
+
} else if (part.type === "reasoning-delta") {
|
|
11474
|
+
if (emit) {
|
|
11475
|
+
await emit(JSON.stringify({ type: "reasoning-delta", id: reasoningId, delta: part.text }));
|
|
11476
|
+
}
|
|
11477
|
+
} else if (part.type === "reasoning-end") {
|
|
11478
|
+
if (emit && reasoningStarted) {
|
|
11479
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
11480
|
+
reasoningStarted = false;
|
|
11481
|
+
reasoningId = `reasoning_${Date.now()}`;
|
|
11482
|
+
}
|
|
11483
|
+
} else if (part.type === "tool-call-streaming-start") {
|
|
11484
|
+
if (emit) {
|
|
11485
|
+
const p = part;
|
|
11486
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: p.toolCallId, toolName: p.toolName }));
|
|
11487
|
+
toolCallStarts.add(p.toolCallId);
|
|
11488
|
+
}
|
|
11489
|
+
} else if (part.type === "tool-call-delta") {
|
|
11490
|
+
if (emit) {
|
|
11491
|
+
const p = part;
|
|
11492
|
+
await emit(JSON.stringify({ type: "tool-input-delta", toolCallId: p.toolCallId, argsTextDelta: p.argsTextDelta }));
|
|
11493
|
+
}
|
|
11494
|
+
} else if (part.type === "tool-call") {
|
|
11495
|
+
if (emit) {
|
|
11496
|
+
if (!toolCallStarts.has(part.toolCallId)) {
|
|
11497
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: part.toolCallId, toolName: part.toolName }));
|
|
11498
|
+
toolCallStarts.add(part.toolCallId);
|
|
11499
|
+
}
|
|
11500
|
+
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
11501
|
+
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
11502
|
+
}
|
|
11503
|
+
} else if (part.type === "tool-result") {
|
|
11504
|
+
if (emit) {
|
|
11505
|
+
await emit(JSON.stringify({ type: "tool-output-available", toolCallId: part.toolCallId, output: part.output }));
|
|
11506
|
+
}
|
|
11507
|
+
} else if (part.type === "error") {
|
|
11508
|
+
console.error("Task stream error:", part.error);
|
|
11509
|
+
if (emit) {
|
|
11510
|
+
await emit(JSON.stringify({ type: "error", errorText: String(part.error) }));
|
|
10497
11511
|
}
|
|
10498
|
-
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
10499
|
-
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
10500
11512
|
}
|
|
10501
|
-
}
|
|
10502
|
-
|
|
10503
|
-
|
|
11513
|
+
}
|
|
11514
|
+
if (emit && textStarted) {
|
|
11515
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
11516
|
+
}
|
|
11517
|
+
if (emit && reasoningStarted) {
|
|
11518
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
11519
|
+
}
|
|
11520
|
+
const interrupted = interruptController.signal.aborted;
|
|
11521
|
+
clearInterruptController(this.session.id);
|
|
11522
|
+
const iterResponse = await iterStream.response;
|
|
11523
|
+
const responseMessages = iterResponse.messages;
|
|
11524
|
+
await this.context.addResponseMessages(responseMessages);
|
|
11525
|
+
const resultText = await iterStream.text;
|
|
11526
|
+
const resultSteps = await iterStream.steps;
|
|
11527
|
+
if (resultText) {
|
|
11528
|
+
options.onText?.(resultText);
|
|
11529
|
+
fireWebhook("task.message", { iteration, text: resultText });
|
|
11530
|
+
}
|
|
11531
|
+
for (const step of resultSteps) {
|
|
11532
|
+
if (step.toolCalls) {
|
|
11533
|
+
for (const tc of step.toolCalls) {
|
|
11534
|
+
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
11535
|
+
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
11536
|
+
}
|
|
10504
11537
|
}
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
11538
|
+
if (step.toolResults) {
|
|
11539
|
+
for (const tr of step.toolResults) {
|
|
11540
|
+
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.output });
|
|
11541
|
+
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.output });
|
|
11542
|
+
}
|
|
10509
11543
|
}
|
|
10510
11544
|
}
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
const responseMessages = iterResponse.messages;
|
|
10522
|
-
await this.context.addResponseMessages(responseMessages);
|
|
10523
|
-
const resultText = await iterStream.text;
|
|
10524
|
-
const resultSteps = await iterStream.steps;
|
|
10525
|
-
if (resultText) {
|
|
10526
|
-
options.onText?.(resultText);
|
|
10527
|
-
fireWebhook("task.message", { iteration, text: resultText });
|
|
10528
|
-
}
|
|
10529
|
-
for (const step of resultSteps) {
|
|
10530
|
-
if (step.toolCalls) {
|
|
10531
|
-
for (const tc of step.toolCalls) {
|
|
10532
|
-
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
10533
|
-
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
11545
|
+
if (completion.signal) {
|
|
11546
|
+
const sig = completion.signal;
|
|
11547
|
+
const finalStatus = sig.status;
|
|
11548
|
+
let fileUrls;
|
|
11549
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
11550
|
+
const resultObj = sig.result;
|
|
11551
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
11552
|
+
if (filePaths.length > 0) {
|
|
11553
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
11554
|
+
}
|
|
10534
11555
|
}
|
|
10535
|
-
|
|
10536
|
-
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
|
|
11556
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
11557
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
11558
|
+
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
11559
|
+
fireWebhook(eventType, {
|
|
11560
|
+
status: finalStatus,
|
|
11561
|
+
result: sig.result,
|
|
11562
|
+
error: sig.error,
|
|
11563
|
+
iterations: iteration,
|
|
11564
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
11565
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
11566
|
+
});
|
|
11567
|
+
const updatedTask2 = {
|
|
11568
|
+
...options.taskConfig,
|
|
11569
|
+
status: finalStatus,
|
|
11570
|
+
result: sig.result,
|
|
11571
|
+
error: sig.error,
|
|
11572
|
+
iterations: iteration
|
|
11573
|
+
};
|
|
11574
|
+
await sessionQueries.update(this.session.id, {
|
|
11575
|
+
config: { ...this.session.config, task: updatedTask2 }
|
|
11576
|
+
});
|
|
11577
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
11578
|
+
if (orchId) {
|
|
11579
|
+
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
11580
|
+
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
10540
11581
|
}
|
|
11582
|
+
return {
|
|
11583
|
+
status: finalStatus,
|
|
11584
|
+
result: sig.result,
|
|
11585
|
+
error: sig.error,
|
|
11586
|
+
iterations: iteration
|
|
11587
|
+
};
|
|
10541
11588
|
}
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
let fileUrls;
|
|
10547
|
-
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
10548
|
-
const resultObj = sig.result;
|
|
10549
|
-
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
10550
|
-
if (filePaths.length > 0) {
|
|
10551
|
-
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
11589
|
+
if (!interrupted) {
|
|
11590
|
+
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.";
|
|
11591
|
+
if (emit) {
|
|
11592
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: continuationPrompt } }));
|
|
10552
11593
|
}
|
|
11594
|
+
await this.context.addUserMessage(continuationPrompt);
|
|
10553
11595
|
}
|
|
10554
|
-
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
10555
|
-
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
10556
|
-
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
10557
|
-
fireWebhook(eventType, {
|
|
10558
|
-
status: finalStatus,
|
|
10559
|
-
result: sig.result,
|
|
10560
|
-
error: sig.error,
|
|
10561
|
-
iterations: iteration,
|
|
10562
|
-
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
10563
|
-
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
10564
|
-
});
|
|
10565
|
-
const updatedTask2 = {
|
|
10566
|
-
...options.taskConfig,
|
|
10567
|
-
status: finalStatus,
|
|
10568
|
-
result: sig.result,
|
|
10569
|
-
error: sig.error,
|
|
10570
|
-
iterations: iteration
|
|
10571
|
-
};
|
|
10572
|
-
await sessionQueries.update(this.session.id, {
|
|
10573
|
-
config: { ...this.session.config, task: updatedTask2 }
|
|
10574
|
-
});
|
|
10575
|
-
const orchId = this.session.config?.orchestratorSessionId;
|
|
10576
|
-
if (orchId) {
|
|
10577
|
-
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
10578
|
-
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
10579
|
-
}
|
|
10580
|
-
return {
|
|
10581
|
-
status: finalStatus,
|
|
10582
|
-
result: sig.result,
|
|
10583
|
-
error: sig.error,
|
|
10584
|
-
iterations: iteration
|
|
10585
|
-
};
|
|
10586
11596
|
}
|
|
10587
|
-
|
|
10588
|
-
|
|
10589
|
-
|
|
10590
|
-
|
|
11597
|
+
clearInterruptController(this.session.id);
|
|
11598
|
+
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
11599
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
11600
|
+
fireWebhook("task.failed", {
|
|
11601
|
+
status: "failed",
|
|
11602
|
+
error: timeoutError,
|
|
11603
|
+
iterations: iteration,
|
|
11604
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
11605
|
+
});
|
|
11606
|
+
const updatedTask = {
|
|
11607
|
+
...options.taskConfig,
|
|
11608
|
+
status: "failed",
|
|
11609
|
+
error: timeoutError,
|
|
11610
|
+
iterations: iteration
|
|
11611
|
+
};
|
|
11612
|
+
await sessionQueries.update(this.session.id, {
|
|
11613
|
+
config: { ...this.session.config, task: updatedTask }
|
|
11614
|
+
});
|
|
11615
|
+
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
11616
|
+
if (orchIdTimeout) {
|
|
11617
|
+
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
11618
|
+
}
|
|
11619
|
+
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
11620
|
+
} finally {
|
|
11621
|
+
for (const cleanup2 of taskScopedCleanups) {
|
|
11622
|
+
try {
|
|
11623
|
+
await cleanup2();
|
|
11624
|
+
} catch {
|
|
10591
11625
|
}
|
|
10592
|
-
await this.context.addUserMessage(continuationPrompt);
|
|
10593
11626
|
}
|
|
10594
11627
|
}
|
|
10595
|
-
clearInterruptController(this.session.id);
|
|
10596
|
-
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
10597
|
-
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
10598
|
-
fireWebhook("task.failed", {
|
|
10599
|
-
status: "failed",
|
|
10600
|
-
error: timeoutError,
|
|
10601
|
-
iterations: iteration,
|
|
10602
|
-
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
10603
|
-
});
|
|
10604
|
-
const updatedTask = {
|
|
10605
|
-
...options.taskConfig,
|
|
10606
|
-
status: "failed",
|
|
10607
|
-
error: timeoutError,
|
|
10608
|
-
iterations: iteration
|
|
10609
|
-
};
|
|
10610
|
-
await sessionQueries.update(this.session.id, {
|
|
10611
|
-
config: { ...this.session.config, task: updatedTask }
|
|
10612
|
-
});
|
|
10613
|
-
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
10614
|
-
if (orchIdTimeout) {
|
|
10615
|
-
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
10616
|
-
}
|
|
10617
|
-
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
10618
11628
|
}
|
|
10619
11629
|
/**
|
|
10620
11630
|
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
@@ -10674,11 +11684,11 @@ ${p.text}` : p.text;
|
|
|
10674
11684
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
10675
11685
|
if (!isRemoteConfigured2()) return [];
|
|
10676
11686
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
10677
|
-
const { join:
|
|
11687
|
+
const { join: join20, basename: basename7 } = await import("path");
|
|
10678
11688
|
const urls = [];
|
|
10679
11689
|
for (const filePath of filePaths) {
|
|
10680
11690
|
try {
|
|
10681
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
11691
|
+
const fullPath = filePath.startsWith("/") ? filePath : join20(this.session.workingDirectory, filePath);
|
|
10682
11692
|
const fileName = basename7(fullPath);
|
|
10683
11693
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
10684
11694
|
const mimeMap = {
|
|
@@ -10740,7 +11750,7 @@ ${p.text}` : p.text;
|
|
|
10740
11750
|
description: originalTool.description || "",
|
|
10741
11751
|
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
10742
11752
|
execute: async (input, toolOptions) => {
|
|
10743
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
11753
|
+
const toolCallId = toolOptions.toolCallId || nanoid9();
|
|
10744
11754
|
const execution = toolExecutionQueries.create({
|
|
10745
11755
|
sessionId: this.session.id,
|
|
10746
11756
|
toolName: name,
|
|
@@ -10781,219 +11791,71 @@ ${p.text}` : p.text;
|
|
|
10781
11791
|
await toolExecutionQueries.complete(exec7.id, null, error.message);
|
|
10782
11792
|
throw error;
|
|
10783
11793
|
}
|
|
10784
|
-
}
|
|
10785
|
-
});
|
|
10786
|
-
}
|
|
10787
|
-
return wrappedTools;
|
|
10788
|
-
}
|
|
10789
|
-
/**
|
|
10790
|
-
* Wait for all pending approvals
|
|
10791
|
-
*/
|
|
10792
|
-
async waitForApprovals() {
|
|
10793
|
-
return Array.from(this.pendingApprovals.values());
|
|
10794
|
-
}
|
|
10795
|
-
/**
|
|
10796
|
-
* Approve a pending tool execution
|
|
10797
|
-
*/
|
|
10798
|
-
async approve(toolCallId) {
|
|
10799
|
-
const resolver = approvalResolvers.get(toolCallId);
|
|
10800
|
-
if (resolver) {
|
|
10801
|
-
resolver.resolve(true);
|
|
10802
|
-
return { approved: true };
|
|
10803
|
-
}
|
|
10804
|
-
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
10805
|
-
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
10806
|
-
if (!execution) {
|
|
10807
|
-
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
10808
|
-
}
|
|
10809
|
-
await toolExecutionQueries.approve(execution.id);
|
|
10810
|
-
return { approved: true };
|
|
10811
|
-
}
|
|
10812
|
-
/**
|
|
10813
|
-
* Reject a pending tool execution
|
|
10814
|
-
*/
|
|
10815
|
-
async reject(toolCallId, reason) {
|
|
10816
|
-
const resolver = approvalResolvers.get(toolCallId);
|
|
10817
|
-
if (resolver) {
|
|
10818
|
-
resolver.reason = reason;
|
|
10819
|
-
resolver.resolve(false);
|
|
10820
|
-
return { rejected: true };
|
|
10821
|
-
}
|
|
10822
|
-
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
10823
|
-
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
10824
|
-
if (!execution) {
|
|
10825
|
-
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
10826
|
-
}
|
|
10827
|
-
await toolExecutionQueries.reject(execution.id);
|
|
10828
|
-
return { rejected: true };
|
|
10829
|
-
}
|
|
10830
|
-
/**
|
|
10831
|
-
* Get pending approvals
|
|
10832
|
-
*/
|
|
10833
|
-
async getPendingApprovals() {
|
|
10834
|
-
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
10835
|
-
}
|
|
10836
|
-
/**
|
|
10837
|
-
* Get context statistics
|
|
10838
|
-
*/
|
|
10839
|
-
getContextStats() {
|
|
10840
|
-
return this.context.getStats();
|
|
10841
|
-
}
|
|
10842
|
-
/**
|
|
10843
|
-
* Clear conversation context (start fresh)
|
|
10844
|
-
*/
|
|
10845
|
-
clearContext() {
|
|
10846
|
-
this.context.clear();
|
|
10847
|
-
}
|
|
10848
|
-
};
|
|
10849
|
-
}
|
|
10850
|
-
});
|
|
10851
|
-
|
|
10852
|
-
// src/agent/session-lock.ts
|
|
10853
|
-
async function withSessionLock(sessionId, fn) {
|
|
10854
|
-
let state2 = locks.get(sessionId);
|
|
10855
|
-
if (!state2) {
|
|
10856
|
-
state2 = { tail: Promise.resolve(), pending: 0 };
|
|
10857
|
-
locks.set(sessionId, state2);
|
|
10858
|
-
}
|
|
10859
|
-
state2.pending++;
|
|
10860
|
-
const prev = state2.tail;
|
|
10861
|
-
let release;
|
|
10862
|
-
const next = new Promise((resolve13) => {
|
|
10863
|
-
release = resolve13;
|
|
10864
|
-
});
|
|
10865
|
-
state2.tail = prev.then(() => next);
|
|
10866
|
-
await prev;
|
|
10867
|
-
try {
|
|
10868
|
-
return await fn();
|
|
10869
|
-
} finally {
|
|
10870
|
-
release();
|
|
10871
|
-
state2.pending--;
|
|
10872
|
-
if (state2.pending === 0 && locks.get(sessionId) === state2) {
|
|
10873
|
-
locks.delete(sessionId);
|
|
10874
|
-
}
|
|
10875
|
-
}
|
|
10876
|
-
}
|
|
10877
|
-
var locks;
|
|
10878
|
-
var init_session_lock = __esm({
|
|
10879
|
-
"src/agent/session-lock.ts"() {
|
|
10880
|
-
"use strict";
|
|
10881
|
-
locks = /* @__PURE__ */ new Map();
|
|
10882
|
-
}
|
|
10883
|
-
});
|
|
10884
|
-
|
|
10885
|
-
// src/orchestrator/webhook-events.ts
|
|
10886
|
-
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
10887
|
-
import { dirname as dirname7, join as join12 } from "path";
|
|
10888
|
-
import { nanoid as nanoid9 } from "nanoid";
|
|
10889
|
-
function logFilePath() {
|
|
10890
|
-
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
10891
|
-
}
|
|
10892
|
-
function ensureLoaded() {
|
|
10893
|
-
if (cache !== null) return cache;
|
|
10894
|
-
cache = [];
|
|
10895
|
-
try {
|
|
10896
|
-
const p = logFilePath();
|
|
10897
|
-
if (!existsSync18(p)) return cache;
|
|
10898
|
-
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
10899
|
-
for (const line of lines) {
|
|
10900
|
-
try {
|
|
10901
|
-
cache.push(JSON.parse(line));
|
|
10902
|
-
} catch {
|
|
10903
|
-
}
|
|
10904
|
-
}
|
|
10905
|
-
if (cache.length > MAX_EVENTS) {
|
|
10906
|
-
cache = cache.slice(-MAX_EVENTS);
|
|
10907
|
-
try {
|
|
10908
|
-
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10909
|
-
} catch {
|
|
10910
|
-
}
|
|
10911
|
-
}
|
|
10912
|
-
} catch {
|
|
10913
|
-
}
|
|
10914
|
-
return cache;
|
|
10915
|
-
}
|
|
10916
|
-
function appendEvent(ev) {
|
|
10917
|
-
const list = ensureLoaded();
|
|
10918
|
-
list.push(ev);
|
|
10919
|
-
if (list.length > MAX_EVENTS) list.shift();
|
|
10920
|
-
try {
|
|
10921
|
-
const p = logFilePath();
|
|
10922
|
-
mkdirSync7(dirname7(p), { recursive: true });
|
|
10923
|
-
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
10924
|
-
} catch {
|
|
10925
|
-
}
|
|
10926
|
-
}
|
|
10927
|
-
function recordEvent(ev) {
|
|
10928
|
-
const full = {
|
|
10929
|
-
id: ev.id ?? nanoid9(),
|
|
10930
|
-
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10931
|
-
source: ev.source,
|
|
10932
|
-
status: ev.status,
|
|
10933
|
-
subtype: ev.subtype,
|
|
10934
|
-
channel: ev.channel,
|
|
10935
|
-
user: ev.user,
|
|
10936
|
-
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
10937
|
-
dropReason: ev.dropReason,
|
|
10938
|
-
error: ev.error,
|
|
10939
|
-
sessionId: ev.sessionId,
|
|
10940
|
-
durationMs: ev.durationMs,
|
|
10941
|
-
meta: ev.meta
|
|
10942
|
-
};
|
|
10943
|
-
appendEvent(full);
|
|
10944
|
-
return full.id;
|
|
10945
|
-
}
|
|
10946
|
-
function updateEvent(id, patch) {
|
|
10947
|
-
const list = ensureLoaded();
|
|
10948
|
-
const i = list.findIndex((e) => e.id === id);
|
|
10949
|
-
if (i < 0) return;
|
|
10950
|
-
list[i] = { ...list[i], ...patch };
|
|
10951
|
-
try {
|
|
10952
|
-
const p = logFilePath();
|
|
10953
|
-
mkdirSync7(dirname7(p), { recursive: true });
|
|
10954
|
-
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
10955
|
-
} catch {
|
|
10956
|
-
}
|
|
10957
|
-
}
|
|
10958
|
-
function listEvents(filter = {}) {
|
|
10959
|
-
const list = ensureLoaded();
|
|
10960
|
-
const q = filter.q?.toLowerCase();
|
|
10961
|
-
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
10962
|
-
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
10963
|
-
const matched = list.filter((e) => {
|
|
10964
|
-
if (filter.source && e.source !== filter.source) return false;
|
|
10965
|
-
if (filter.status && e.status !== filter.status) return false;
|
|
10966
|
-
const t = Date.parse(e.ts);
|
|
10967
|
-
if (t < sinceTs) return false;
|
|
10968
|
-
if (t >= beforeTs) return false;
|
|
10969
|
-
if (q) {
|
|
10970
|
-
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
10971
|
-
if (!hay.includes(q)) return false;
|
|
10972
|
-
}
|
|
10973
|
-
return true;
|
|
10974
|
-
});
|
|
10975
|
-
matched.reverse();
|
|
10976
|
-
const offset = Math.max(0, filter.offset ?? 0);
|
|
10977
|
-
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
10978
|
-
return {
|
|
10979
|
-
events: matched.slice(offset, offset + limit),
|
|
10980
|
-
total: matched.length
|
|
10981
|
-
};
|
|
10982
|
-
}
|
|
10983
|
-
function clearAllEvents() {
|
|
10984
|
-
cache = [];
|
|
10985
|
-
try {
|
|
10986
|
-
writeFileSync4(logFilePath(), "");
|
|
10987
|
-
} catch {
|
|
10988
|
-
}
|
|
10989
|
-
}
|
|
10990
|
-
var MAX_EVENTS, cache;
|
|
10991
|
-
var init_webhook_events = __esm({
|
|
10992
|
-
"src/orchestrator/webhook-events.ts"() {
|
|
10993
|
-
"use strict";
|
|
10994
|
-
init_config();
|
|
10995
|
-
MAX_EVENTS = 1e3;
|
|
10996
|
-
cache = null;
|
|
11794
|
+
}
|
|
11795
|
+
});
|
|
11796
|
+
}
|
|
11797
|
+
return wrappedTools;
|
|
11798
|
+
}
|
|
11799
|
+
/**
|
|
11800
|
+
* Wait for all pending approvals
|
|
11801
|
+
*/
|
|
11802
|
+
async waitForApprovals() {
|
|
11803
|
+
return Array.from(this.pendingApprovals.values());
|
|
11804
|
+
}
|
|
11805
|
+
/**
|
|
11806
|
+
* Approve a pending tool execution
|
|
11807
|
+
*/
|
|
11808
|
+
async approve(toolCallId) {
|
|
11809
|
+
const resolver = approvalResolvers.get(toolCallId);
|
|
11810
|
+
if (resolver) {
|
|
11811
|
+
resolver.resolve(true);
|
|
11812
|
+
return { approved: true };
|
|
11813
|
+
}
|
|
11814
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11815
|
+
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
11816
|
+
if (!execution) {
|
|
11817
|
+
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
11818
|
+
}
|
|
11819
|
+
await toolExecutionQueries.approve(execution.id);
|
|
11820
|
+
return { approved: true };
|
|
11821
|
+
}
|
|
11822
|
+
/**
|
|
11823
|
+
* Reject a pending tool execution
|
|
11824
|
+
*/
|
|
11825
|
+
async reject(toolCallId, reason) {
|
|
11826
|
+
const resolver = approvalResolvers.get(toolCallId);
|
|
11827
|
+
if (resolver) {
|
|
11828
|
+
resolver.reason = reason;
|
|
11829
|
+
resolver.resolve(false);
|
|
11830
|
+
return { rejected: true };
|
|
11831
|
+
}
|
|
11832
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11833
|
+
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
11834
|
+
if (!execution) {
|
|
11835
|
+
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
11836
|
+
}
|
|
11837
|
+
await toolExecutionQueries.reject(execution.id);
|
|
11838
|
+
return { rejected: true };
|
|
11839
|
+
}
|
|
11840
|
+
/**
|
|
11841
|
+
* Get pending approvals
|
|
11842
|
+
*/
|
|
11843
|
+
async getPendingApprovals() {
|
|
11844
|
+
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11845
|
+
}
|
|
11846
|
+
/**
|
|
11847
|
+
* Get context statistics
|
|
11848
|
+
*/
|
|
11849
|
+
getContextStats() {
|
|
11850
|
+
return this.context.getStats();
|
|
11851
|
+
}
|
|
11852
|
+
/**
|
|
11853
|
+
* Clear conversation context (start fresh)
|
|
11854
|
+
*/
|
|
11855
|
+
clearContext() {
|
|
11856
|
+
this.context.clear();
|
|
11857
|
+
}
|
|
11858
|
+
};
|
|
10997
11859
|
}
|
|
10998
11860
|
});
|
|
10999
11861
|
|
|
@@ -11069,7 +11931,24 @@ async function runDaemonTurn(sessionId, events) {
|
|
|
11069
11931
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11070
11932
|
meta: { triggeredBy: events.map((e) => e.content?.slice(0, 80)) }
|
|
11071
11933
|
});
|
|
11934
|
+
try {
|
|
11935
|
+
resolveBatchOnTurnEnd(events, !error);
|
|
11936
|
+
} catch (err) {
|
|
11937
|
+
console.error("[daemon] ack bookkeeping threw:", err?.message || err);
|
|
11938
|
+
}
|
|
11072
11939
|
broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
|
|
11940
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11941
|
+
for (const ev of events) {
|
|
11942
|
+
if (ev.ref?.channel !== "slack") continue;
|
|
11943
|
+
const ref = ev.ref;
|
|
11944
|
+
const channel = ref.slackChannel;
|
|
11945
|
+
const ts = ref.messageTs;
|
|
11946
|
+
if (!channel || !ts) continue;
|
|
11947
|
+
const key2 = `${channel}\u241F${ts}`;
|
|
11948
|
+
if (seen.has(key2)) continue;
|
|
11949
|
+
seen.add(key2);
|
|
11950
|
+
void removeLoadingReaction(channel, ts);
|
|
11951
|
+
}
|
|
11073
11952
|
}
|
|
11074
11953
|
var listeners;
|
|
11075
11954
|
var init_daemon = __esm({
|
|
@@ -11080,6 +11959,8 @@ var init_daemon = __esm({
|
|
|
11080
11959
|
init_db();
|
|
11081
11960
|
init_inbox();
|
|
11082
11961
|
init_webhook_events();
|
|
11962
|
+
init_inbox_acks();
|
|
11963
|
+
init_client3();
|
|
11083
11964
|
listeners = /* @__PURE__ */ new Map();
|
|
11084
11965
|
}
|
|
11085
11966
|
});
|
|
@@ -11178,6 +12059,233 @@ var init_ensure_orchestrator = __esm({
|
|
|
11178
12059
|
}
|
|
11179
12060
|
});
|
|
11180
12061
|
|
|
12062
|
+
// src/orchestrator/self-update.ts
|
|
12063
|
+
var self_update_exports = {};
|
|
12064
|
+
__export(self_update_exports, {
|
|
12065
|
+
__test: () => __test,
|
|
12066
|
+
startSelfUpdater: () => startSelfUpdater,
|
|
12067
|
+
stopSelfUpdater: () => stopSelfUpdater
|
|
12068
|
+
});
|
|
12069
|
+
import { spawn as spawn2, execFile } from "child_process";
|
|
12070
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10 } from "fs";
|
|
12071
|
+
import { dirname as dirname10, join as join18 } from "path";
|
|
12072
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12073
|
+
function currentVersion2() {
|
|
12074
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
12075
|
+
const candidates = [
|
|
12076
|
+
join18(here, "..", "..", "package.json"),
|
|
12077
|
+
join18(here, "..", "package.json"),
|
|
12078
|
+
join18(process.cwd(), "package.json")
|
|
12079
|
+
];
|
|
12080
|
+
for (const p of candidates) {
|
|
12081
|
+
try {
|
|
12082
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
12083
|
+
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
12084
|
+
} catch {
|
|
12085
|
+
}
|
|
12086
|
+
}
|
|
12087
|
+
return "0.0.0";
|
|
12088
|
+
}
|
|
12089
|
+
function isLikelyGlobalInstall() {
|
|
12090
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
12091
|
+
return here.includes("/node_modules/sparkecoder/") || here.includes("\\node_modules\\sparkecoder\\");
|
|
12092
|
+
}
|
|
12093
|
+
function isEnabled() {
|
|
12094
|
+
if (process.env.SPARKECODER_AUTO_UPDATE === "false" || process.env.SPARKECODER_AUTO_UPDATE === "0") return false;
|
|
12095
|
+
try {
|
|
12096
|
+
const cfg = getConfig();
|
|
12097
|
+
if (cfg?.autoUpdate?.enabled === false) return false;
|
|
12098
|
+
} catch {
|
|
12099
|
+
}
|
|
12100
|
+
return true;
|
|
12101
|
+
}
|
|
12102
|
+
function remoteUrl() {
|
|
12103
|
+
try {
|
|
12104
|
+
const cfg = getConfig();
|
|
12105
|
+
const url = cfg?.remoteServer?.url;
|
|
12106
|
+
return typeof url === "string" && url.length > 0 ? url.replace(/\/+$/, "") : null;
|
|
12107
|
+
} catch {
|
|
12108
|
+
return null;
|
|
12109
|
+
}
|
|
12110
|
+
}
|
|
12111
|
+
function intervalMs() {
|
|
12112
|
+
try {
|
|
12113
|
+
const h = getConfig()?.autoUpdate?.intervalHours;
|
|
12114
|
+
if (typeof h === "number" && h > 0) return h * 60 * 6e4;
|
|
12115
|
+
} catch {
|
|
12116
|
+
}
|
|
12117
|
+
return DEFAULT_INTERVAL_HOURS * 60 * 6e4;
|
|
12118
|
+
}
|
|
12119
|
+
function semverGt(a, b) {
|
|
12120
|
+
const parse = (v) => v.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
12121
|
+
const pa = parse(a);
|
|
12122
|
+
const pb = parse(b);
|
|
12123
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
12124
|
+
const x = pa[i] ?? 0;
|
|
12125
|
+
const y = pb[i] ?? 0;
|
|
12126
|
+
if (x > y) return true;
|
|
12127
|
+
if (x < y) return false;
|
|
12128
|
+
}
|
|
12129
|
+
return false;
|
|
12130
|
+
}
|
|
12131
|
+
function statePath() {
|
|
12132
|
+
try {
|
|
12133
|
+
return join18(getAppDataDirectory(), "self-update-state.json");
|
|
12134
|
+
} catch {
|
|
12135
|
+
return null;
|
|
12136
|
+
}
|
|
12137
|
+
}
|
|
12138
|
+
function readState() {
|
|
12139
|
+
const p = statePath();
|
|
12140
|
+
if (!p) return {};
|
|
12141
|
+
try {
|
|
12142
|
+
return JSON.parse(readFileSync11(p, "utf8"));
|
|
12143
|
+
} catch {
|
|
12144
|
+
return {};
|
|
12145
|
+
}
|
|
12146
|
+
}
|
|
12147
|
+
function writeState(s) {
|
|
12148
|
+
const p = statePath();
|
|
12149
|
+
if (!p) return;
|
|
12150
|
+
try {
|
|
12151
|
+
mkdirSync10(dirname10(p), { recursive: true });
|
|
12152
|
+
writeFileSync7(p, JSON.stringify(s));
|
|
12153
|
+
} catch {
|
|
12154
|
+
}
|
|
12155
|
+
}
|
|
12156
|
+
function attemptedRecently(target, now) {
|
|
12157
|
+
const s = readState();
|
|
12158
|
+
return s.lastTarget === target && typeof s.lastAttemptAt === "number" && now - s.lastAttemptAt < RETRY_COOLDOWN_MS;
|
|
12159
|
+
}
|
|
12160
|
+
function latestPublishedVersion() {
|
|
12161
|
+
return new Promise((resolve13) => {
|
|
12162
|
+
execFile("npm", ["view", "sparkecoder", "version"], { timeout: 3e4 }, (err, stdout) => {
|
|
12163
|
+
if (err) {
|
|
12164
|
+
resolve13(null);
|
|
12165
|
+
return;
|
|
12166
|
+
}
|
|
12167
|
+
const v = String(stdout).trim();
|
|
12168
|
+
resolve13(/^\d+\.\d+\.\d+/.test(v) ? v : null);
|
|
12169
|
+
});
|
|
12170
|
+
});
|
|
12171
|
+
}
|
|
12172
|
+
function runInstaller(url) {
|
|
12173
|
+
const secret = process.env.SPARKECODER_SETUP_SECRET || process.env.SPARKECODER_TUNNEL_SECRET || "";
|
|
12174
|
+
const query = secret ? `?secret=${encodeURIComponent(secret)}` : "";
|
|
12175
|
+
const oneLiner = `bash -c "$(curl -fsSL '${url}/install.sh${query}')" >/tmp/sparkecoder-selfupdate.log 2>&1`;
|
|
12176
|
+
const child = spawn2("bash", ["-lc", oneLiner], {
|
|
12177
|
+
detached: true,
|
|
12178
|
+
stdio: "ignore"
|
|
12179
|
+
});
|
|
12180
|
+
child.unref();
|
|
12181
|
+
}
|
|
12182
|
+
async function checkAndUpdate() {
|
|
12183
|
+
if (upgrading || !isEnabled()) return;
|
|
12184
|
+
const url = remoteUrl();
|
|
12185
|
+
if (!url) return;
|
|
12186
|
+
const latest = await latestPublishedVersion();
|
|
12187
|
+
if (!latest) return;
|
|
12188
|
+
const current = currentVersion2();
|
|
12189
|
+
if (!semverGt(latest, current)) return;
|
|
12190
|
+
const now = Date.now();
|
|
12191
|
+
if (attemptedRecently(latest, now)) {
|
|
12192
|
+
console.log(`[self-update] v${latest} already attempted recently; skipping until cooldown elapses`);
|
|
12193
|
+
return;
|
|
12194
|
+
}
|
|
12195
|
+
upgrading = true;
|
|
12196
|
+
const announced = await announceUpdate(latest);
|
|
12197
|
+
const delay = announced ? ANNOUNCE_GRACE_MS : 0;
|
|
12198
|
+
if (announced) {
|
|
12199
|
+
console.log(`[self-update] announced v${latest} in Slack; updating in ${Math.round(delay / 6e4)}m`);
|
|
12200
|
+
}
|
|
12201
|
+
const t = setTimeout(() => doInstall(latest, url, current), delay);
|
|
12202
|
+
if (typeof t.unref === "function") t.unref();
|
|
12203
|
+
}
|
|
12204
|
+
function doInstall(latest, url, current) {
|
|
12205
|
+
const prev = readState();
|
|
12206
|
+
writeState({
|
|
12207
|
+
lastTarget: latest,
|
|
12208
|
+
lastAttemptAt: Date.now(),
|
|
12209
|
+
attempts: prev.lastTarget === latest ? (prev.attempts ?? 0) + 1 : 1
|
|
12210
|
+
});
|
|
12211
|
+
console.log(`[self-update] newer version available: v${current} \u2192 v${latest}; re-running installer`);
|
|
12212
|
+
try {
|
|
12213
|
+
runInstaller(url);
|
|
12214
|
+
} catch (err) {
|
|
12215
|
+
upgrading = false;
|
|
12216
|
+
console.warn("[self-update] failed to launch installer:", err?.message || err);
|
|
12217
|
+
}
|
|
12218
|
+
}
|
|
12219
|
+
async function findOrchestratorId() {
|
|
12220
|
+
try {
|
|
12221
|
+
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
12222
|
+
const all = await sessionQueries2.list(500, 0);
|
|
12223
|
+
const orch = all.find((s) => s?.config?.role === "orchestrator");
|
|
12224
|
+
return orch?.id ?? null;
|
|
12225
|
+
} catch {
|
|
12226
|
+
return null;
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12229
|
+
async function announceUpdate(target) {
|
|
12230
|
+
try {
|
|
12231
|
+
const { isSlackConfigured: isSlackConfigured2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
12232
|
+
if (!isSlackConfigured2()) return false;
|
|
12233
|
+
const orchId = await findOrchestratorId();
|
|
12234
|
+
if (!orchId) return false;
|
|
12235
|
+
const { pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports));
|
|
12236
|
+
pushToInbox2(orchId, {
|
|
12237
|
+
ref: { channel: "system", kind: "worker.completed", workerId: "self-update", workerName: "self-update" },
|
|
12238
|
+
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.`,
|
|
12239
|
+
wake: "now",
|
|
12240
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
12241
|
+
});
|
|
12242
|
+
return true;
|
|
12243
|
+
} catch {
|
|
12244
|
+
return false;
|
|
12245
|
+
}
|
|
12246
|
+
}
|
|
12247
|
+
function startSelfUpdater() {
|
|
12248
|
+
if (started) return;
|
|
12249
|
+
started = true;
|
|
12250
|
+
if (!isEnabled()) {
|
|
12251
|
+
console.log("[self-update] disabled");
|
|
12252
|
+
return;
|
|
12253
|
+
}
|
|
12254
|
+
if (!isLikelyGlobalInstall()) {
|
|
12255
|
+
console.log("[self-update] skipped (not a global install)");
|
|
12256
|
+
return;
|
|
12257
|
+
}
|
|
12258
|
+
const kickoff = setTimeout(() => {
|
|
12259
|
+
void checkAndUpdate();
|
|
12260
|
+
timer = setInterval(() => {
|
|
12261
|
+
void checkAndUpdate();
|
|
12262
|
+
}, intervalMs());
|
|
12263
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
12264
|
+
}, INITIAL_DELAY_MS);
|
|
12265
|
+
if (typeof kickoff.unref === "function") kickoff.unref();
|
|
12266
|
+
}
|
|
12267
|
+
function stopSelfUpdater() {
|
|
12268
|
+
if (timer) {
|
|
12269
|
+
clearInterval(timer);
|
|
12270
|
+
timer = null;
|
|
12271
|
+
}
|
|
12272
|
+
}
|
|
12273
|
+
var INITIAL_DELAY_MS, DEFAULT_INTERVAL_HOURS, ANNOUNCE_GRACE_MS, RETRY_COOLDOWN_MS, timer, started, upgrading, __test;
|
|
12274
|
+
var init_self_update = __esm({
|
|
12275
|
+
"src/orchestrator/self-update.ts"() {
|
|
12276
|
+
"use strict";
|
|
12277
|
+
init_config();
|
|
12278
|
+
INITIAL_DELAY_MS = 5 * 6e4;
|
|
12279
|
+
DEFAULT_INTERVAL_HOURS = 6;
|
|
12280
|
+
ANNOUNCE_GRACE_MS = 5 * 6e4;
|
|
12281
|
+
RETRY_COOLDOWN_MS = 24 * 60 * 6e4;
|
|
12282
|
+
timer = null;
|
|
12283
|
+
started = false;
|
|
12284
|
+
upgrading = false;
|
|
12285
|
+
__test = { currentVersion: currentVersion2, semverGt, isLikelyGlobalInstall };
|
|
12286
|
+
}
|
|
12287
|
+
});
|
|
12288
|
+
|
|
11181
12289
|
// src/tasks/scheduler.ts
|
|
11182
12290
|
var scheduler_exports = {};
|
|
11183
12291
|
__export(scheduler_exports, {
|
|
@@ -11261,11 +12369,11 @@ import { Hono as Hono10 } from "hono";
|
|
|
11261
12369
|
import { serve } from "@hono/node-server";
|
|
11262
12370
|
import { cors } from "hono/cors";
|
|
11263
12371
|
import { logger } from "hono/logger";
|
|
11264
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
11265
|
-
import { resolve as resolve12, dirname as
|
|
11266
|
-
import { spawn as
|
|
12372
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
12373
|
+
import { resolve as resolve12, dirname as dirname11, join as join19 } from "path";
|
|
12374
|
+
import { spawn as spawn3 } from "child_process";
|
|
11267
12375
|
import { createServer as createNetServer } from "net";
|
|
11268
|
-
import { fileURLToPath as
|
|
12376
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11269
12377
|
|
|
11270
12378
|
// src/server/routes/sessions.ts
|
|
11271
12379
|
init_db();
|
|
@@ -11278,7 +12386,7 @@ import { zValidator } from "@hono/zod-validator";
|
|
|
11278
12386
|
import { z as z16 } from "zod";
|
|
11279
12387
|
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
11280
12388
|
import { readdir as readdir6 } from "fs/promises";
|
|
11281
|
-
import { join as
|
|
12389
|
+
import { join as join14, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
11282
12390
|
import { nanoid as nanoid10 } from "nanoid";
|
|
11283
12391
|
|
|
11284
12392
|
// src/tasks/agent-status.ts
|
|
@@ -11919,7 +13027,7 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
11919
13027
|
});
|
|
11920
13028
|
function getAttachmentsDir(sessionId) {
|
|
11921
13029
|
const appDataDir = getAppDataDirectory();
|
|
11922
|
-
return
|
|
13030
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
11923
13031
|
}
|
|
11924
13032
|
function ensureAttachmentsDir(sessionId) {
|
|
11925
13033
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -11940,7 +13048,7 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
11940
13048
|
}
|
|
11941
13049
|
const files = readdirSync3(dir);
|
|
11942
13050
|
const attachments = files.map((filename) => {
|
|
11943
|
-
const filePath =
|
|
13051
|
+
const filePath = join14(dir, filename);
|
|
11944
13052
|
const stats = statSync2(filePath);
|
|
11945
13053
|
return {
|
|
11946
13054
|
id: filename.split("_")[0],
|
|
@@ -11975,7 +13083,7 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
11975
13083
|
const id = nanoid10(10);
|
|
11976
13084
|
const ext = extname8(file.name) || "";
|
|
11977
13085
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
11978
|
-
const filePath =
|
|
13086
|
+
const filePath = join14(dir, safeFilename);
|
|
11979
13087
|
const arrayBuffer = await file.arrayBuffer();
|
|
11980
13088
|
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
11981
13089
|
return c.json({
|
|
@@ -12001,7 +13109,7 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12001
13109
|
const id = nanoid10(10);
|
|
12002
13110
|
const ext = extname8(body.filename) || "";
|
|
12003
13111
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12004
|
-
const filePath =
|
|
13112
|
+
const filePath = join14(dir, safeFilename);
|
|
12005
13113
|
let base64Data = body.data;
|
|
12006
13114
|
if (base64Data.includes(",")) {
|
|
12007
13115
|
base64Data = base64Data.split(",")[1];
|
|
@@ -12038,7 +13146,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
12038
13146
|
if (!file) {
|
|
12039
13147
|
return c.json({ error: "Attachment not found" }, 404);
|
|
12040
13148
|
}
|
|
12041
|
-
const filePath =
|
|
13149
|
+
const filePath = join14(dir, file);
|
|
12042
13150
|
unlinkSync2(filePath);
|
|
12043
13151
|
return c.json({ success: true, id: attachmentId });
|
|
12044
13152
|
});
|
|
@@ -12121,7 +13229,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
12121
13229
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
12122
13230
|
for (const entry2 of entries) {
|
|
12123
13231
|
if (results.length >= limit * 2) break;
|
|
12124
|
-
const fullPath =
|
|
13232
|
+
const fullPath = join14(currentDir, entry2.name);
|
|
12125
13233
|
const relativePath = relative9(baseDir, fullPath);
|
|
12126
13234
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
12127
13235
|
continue;
|
|
@@ -12281,7 +13389,7 @@ import { Hono as Hono2 } from "hono";
|
|
|
12281
13389
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
12282
13390
|
import { z as z17 } from "zod";
|
|
12283
13391
|
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
12284
|
-
import { join as
|
|
13392
|
+
import { join as join15 } from "path";
|
|
12285
13393
|
|
|
12286
13394
|
// src/agent/missing-tool-recovery.ts
|
|
12287
13395
|
init_db();
|
|
@@ -12548,6 +13656,7 @@ init_stream_proxy();
|
|
|
12548
13656
|
init_recorder();
|
|
12549
13657
|
init_remote();
|
|
12550
13658
|
init_resize_image();
|
|
13659
|
+
init_local_device_time();
|
|
12551
13660
|
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
12552
13661
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
12553
13662
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
@@ -12663,7 +13772,7 @@ var rejectSchema = z17.object({
|
|
|
12663
13772
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
12664
13773
|
function getAttachmentsDirectory(sessionId) {
|
|
12665
13774
|
const appDataDir = getAppDataDirectory();
|
|
12666
|
-
return
|
|
13775
|
+
return join15(appDataDir, "attachments", sessionId);
|
|
12667
13776
|
}
|
|
12668
13777
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
12669
13778
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -12686,7 +13795,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
12686
13795
|
attachment.mediaType = resized.mediaType;
|
|
12687
13796
|
attachment.data = buffer.toString("base64");
|
|
12688
13797
|
}
|
|
12689
|
-
const filePath =
|
|
13798
|
+
const filePath = join15(attachmentsDir, filename);
|
|
12690
13799
|
writeFileSync6(filePath, buffer);
|
|
12691
13800
|
return filePath;
|
|
12692
13801
|
}
|
|
@@ -13063,9 +14172,12 @@ agents.post(
|
|
|
13063
14172
|
if (!session) {
|
|
13064
14173
|
return c.json({ error: "Session not found" }, 404);
|
|
13065
14174
|
}
|
|
13066
|
-
if (session.config?.role === "orchestrator" && !/^\[\
|
|
14175
|
+
if (session.config?.role === "orchestrator" && !/^\[(WEB|SLACK|SYSTEM|SCHEDULE|WEBHOOK)\b/.test(prompt)) {
|
|
13067
14176
|
prompt = `[WEB] ${prompt}`;
|
|
13068
14177
|
}
|
|
14178
|
+
if (session.config?.role === "orchestrator") {
|
|
14179
|
+
prompt = prependLocalDeviceTimeToUserMessage(prompt);
|
|
14180
|
+
}
|
|
13069
14181
|
const nextSequence = await messageQueries.getNextSequence(id);
|
|
13070
14182
|
await createCheckpoint(id, session.workingDirectory, nextSequence);
|
|
13071
14183
|
let userMessageContent;
|
|
@@ -13675,17 +14787,17 @@ import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
|
13675
14787
|
import { z as z18 } from "zod";
|
|
13676
14788
|
import { readFileSync as readFileSync10 } from "fs";
|
|
13677
14789
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13678
|
-
import { dirname as dirname8, join as
|
|
14790
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
13679
14791
|
var __filename = fileURLToPath3(import.meta.url);
|
|
13680
14792
|
var __dirname = dirname8(__filename);
|
|
13681
14793
|
var possiblePaths = [
|
|
13682
|
-
|
|
14794
|
+
join16(__dirname, "../package.json"),
|
|
13683
14795
|
// From dist/server -> dist/../package.json
|
|
13684
|
-
|
|
14796
|
+
join16(__dirname, "../../package.json"),
|
|
13685
14797
|
// From dist/server (if nested differently)
|
|
13686
|
-
|
|
14798
|
+
join16(__dirname, "../../../package.json"),
|
|
13687
14799
|
// From src/server/routes (development)
|
|
13688
|
-
|
|
14800
|
+
join16(process.cwd(), "package.json")
|
|
13689
14801
|
// From current working directory
|
|
13690
14802
|
];
|
|
13691
14803
|
var currentVersion = "0.0.0";
|
|
@@ -14146,6 +15258,25 @@ import { nanoid as nanoid12 } from "nanoid";
|
|
|
14146
15258
|
init_questions();
|
|
14147
15259
|
var tasks = new Hono5();
|
|
14148
15260
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
15261
|
+
var taskMcpServerSchema = z20.object({
|
|
15262
|
+
name: z20.string().min(1).describe("Tool prefix + display name."),
|
|
15263
|
+
transport: z20.enum(["http", "sse", "stdio"]),
|
|
15264
|
+
url: z20.string().url().optional().describe("http/sse transports."),
|
|
15265
|
+
headers: z20.record(z20.string(), z20.string()).optional().describe("Auth / custom headers for http/sse."),
|
|
15266
|
+
command: z20.string().optional().describe("stdio transport."),
|
|
15267
|
+
args: z20.array(z20.string()).optional(),
|
|
15268
|
+
env: z20.record(z20.string(), z20.string()).optional().describe("Env vars for stdio child process.")
|
|
15269
|
+
}).refine(
|
|
15270
|
+
(s) => s.transport === "stdio" ? !!s.command : !!s.url,
|
|
15271
|
+
{ message: 'http/sse require "url"; stdio requires "command".' }
|
|
15272
|
+
);
|
|
15273
|
+
var taskSkillSchema = z20.object({
|
|
15274
|
+
name: z20.string().min(1),
|
|
15275
|
+
description: z20.string().optional(),
|
|
15276
|
+
content: z20.string().min(1).describe("Full markdown body of the skill."),
|
|
15277
|
+
alwaysApply: z20.boolean().optional().describe("Inject into the system prompt up-front (vs load on demand)."),
|
|
15278
|
+
globs: z20.array(z20.string()).optional()
|
|
15279
|
+
});
|
|
14149
15280
|
var createTaskSchema = z20.object({
|
|
14150
15281
|
prompt: z20.string().min(1),
|
|
14151
15282
|
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
@@ -14157,8 +15288,30 @@ var createTaskSchema = z20.object({
|
|
|
14157
15288
|
parentTaskId: z20.string().optional(),
|
|
14158
15289
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
14159
15290
|
* worker's config so terminal events can wake the orchestrator. */
|
|
14160
|
-
orchestratorSessionId: z20.string().optional()
|
|
15291
|
+
orchestratorSessionId: z20.string().optional(),
|
|
15292
|
+
/** Task-scoped MCP servers — auto-connected for this task only. */
|
|
15293
|
+
mcpServers: z20.array(taskMcpServerSchema).optional(),
|
|
15294
|
+
/** Task-scoped skills — available to this task only. */
|
|
15295
|
+
skills: z20.array(taskSkillSchema).optional()
|
|
14161
15296
|
});
|
|
15297
|
+
function redactMcpServers(servers2) {
|
|
15298
|
+
if (!servers2 || servers2.length === 0) return void 0;
|
|
15299
|
+
return servers2.map((s) => ({
|
|
15300
|
+
name: s.name,
|
|
15301
|
+
transport: s.transport,
|
|
15302
|
+
url: s.url,
|
|
15303
|
+
hasHeaders: !!(s.headers && Object.keys(s.headers).length > 0),
|
|
15304
|
+
command: s.command
|
|
15305
|
+
}));
|
|
15306
|
+
}
|
|
15307
|
+
function redactSkills(skills2) {
|
|
15308
|
+
if (!skills2 || skills2.length === 0) return void 0;
|
|
15309
|
+
return skills2.map((s) => ({
|
|
15310
|
+
name: s.name,
|
|
15311
|
+
description: s.description,
|
|
15312
|
+
alwaysApply: s.alwaysApply
|
|
15313
|
+
}));
|
|
15314
|
+
}
|
|
14162
15315
|
tasks.post(
|
|
14163
15316
|
"/",
|
|
14164
15317
|
zValidator5("json", createTaskSchema),
|
|
@@ -14171,7 +15324,9 @@ tasks.post(
|
|
|
14171
15324
|
webhookUrl: body.webhookUrl,
|
|
14172
15325
|
maxIterations: body.maxIterations ?? 50,
|
|
14173
15326
|
status: "running",
|
|
14174
|
-
parentTaskId: body.parentTaskId
|
|
15327
|
+
parentTaskId: body.parentTaskId,
|
|
15328
|
+
mcpServers: redactMcpServers(body.mcpServers),
|
|
15329
|
+
skills: redactSkills(body.skills)
|
|
14175
15330
|
};
|
|
14176
15331
|
let agent;
|
|
14177
15332
|
if (body.parentTaskId) {
|
|
@@ -14248,7 +15403,9 @@ tasks.post(
|
|
|
14248
15403
|
prompt: body.prompt,
|
|
14249
15404
|
taskConfig,
|
|
14250
15405
|
abortSignal: abortController.signal,
|
|
14251
|
-
writeSSE
|
|
15406
|
+
writeSSE,
|
|
15407
|
+
mcpServers: body.mcpServers,
|
|
15408
|
+
skills: body.skills
|
|
14252
15409
|
});
|
|
14253
15410
|
await writeSSE(JSON.stringify({ type: "finish" }));
|
|
14254
15411
|
} catch (err) {
|
|
@@ -14342,6 +15499,8 @@ tasks.get("/:id", async (c) => {
|
|
|
14342
15499
|
model: session.model,
|
|
14343
15500
|
name: session.name,
|
|
14344
15501
|
parentTaskId: task.parentTaskId,
|
|
15502
|
+
mcpServers: task.mcpServers,
|
|
15503
|
+
skills: task.skills,
|
|
14345
15504
|
createdAt: session.createdAt.toISOString(),
|
|
14346
15505
|
updatedAt: session.updatedAt.toISOString(),
|
|
14347
15506
|
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
@@ -14464,6 +15623,204 @@ function verifySlackSignature(opts) {
|
|
|
14464
15623
|
// src/server/routes/slack.ts
|
|
14465
15624
|
init_client3();
|
|
14466
15625
|
init_slack();
|
|
15626
|
+
|
|
15627
|
+
// src/integrations/slack/files.ts
|
|
15628
|
+
init_client3();
|
|
15629
|
+
var MAX_BYTES = 100 * 1024 * 1024;
|
|
15630
|
+
var INGEST_TIMEOUT_MS = 2500;
|
|
15631
|
+
function inferFileName(file) {
|
|
15632
|
+
return typeof file.name === "string" && file.name || typeof file.title === "string" && file.title || `slack-file-${file.id}`;
|
|
15633
|
+
}
|
|
15634
|
+
function inferContentType(file) {
|
|
15635
|
+
if (typeof file.mimetype === "string" && file.mimetype) return file.mimetype;
|
|
15636
|
+
return "application/octet-stream";
|
|
15637
|
+
}
|
|
15638
|
+
function formatBytes(n) {
|
|
15639
|
+
if (!Number.isFinite(n) || n <= 0) return "?";
|
|
15640
|
+
if (n < 1024) return `${n} B`;
|
|
15641
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
15642
|
+
return `${(n / 1024 / 1024).toFixed(2)} MB`;
|
|
15643
|
+
}
|
|
15644
|
+
function withTimeout(p, ms, label) {
|
|
15645
|
+
return new Promise((resolve13, reject) => {
|
|
15646
|
+
const t = setTimeout(() => reject(new Error(`${label}_timeout`)), ms);
|
|
15647
|
+
p.then(
|
|
15648
|
+
(v) => {
|
|
15649
|
+
clearTimeout(t);
|
|
15650
|
+
resolve13(v);
|
|
15651
|
+
},
|
|
15652
|
+
(e) => {
|
|
15653
|
+
clearTimeout(t);
|
|
15654
|
+
reject(e);
|
|
15655
|
+
}
|
|
15656
|
+
);
|
|
15657
|
+
});
|
|
15658
|
+
}
|
|
15659
|
+
async function ingestOne(file, sessionId, botToken) {
|
|
15660
|
+
const fileName = inferFileName(file);
|
|
15661
|
+
const contentType = inferContentType(file);
|
|
15662
|
+
const declaredSize = typeof file.size === "number" ? file.size : 0;
|
|
15663
|
+
const base = {
|
|
15664
|
+
slackFileId: file.id,
|
|
15665
|
+
fileName,
|
|
15666
|
+
contentType,
|
|
15667
|
+
sizeBytes: declaredSize
|
|
15668
|
+
};
|
|
15669
|
+
const sourceUrl = file.url_private_download || file.url_private;
|
|
15670
|
+
if (!sourceUrl || typeof sourceUrl !== "string") {
|
|
15671
|
+
return { ...base, shortUrl: null, error: "no_source_url" };
|
|
15672
|
+
}
|
|
15673
|
+
if (declaredSize > MAX_BYTES) {
|
|
15674
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
15675
|
+
}
|
|
15676
|
+
let bytes;
|
|
15677
|
+
try {
|
|
15678
|
+
const res = await fetch(sourceUrl, {
|
|
15679
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
15680
|
+
});
|
|
15681
|
+
if (!res.ok) {
|
|
15682
|
+
return { ...base, shortUrl: null, error: `slack_fetch_${res.status}` };
|
|
15683
|
+
}
|
|
15684
|
+
const ab = await res.arrayBuffer();
|
|
15685
|
+
if (ab.byteLength > MAX_BYTES) {
|
|
15686
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
15687
|
+
}
|
|
15688
|
+
bytes = Buffer.from(ab);
|
|
15689
|
+
} catch (err) {
|
|
15690
|
+
return { ...base, shortUrl: null, error: `slack_fetch_error:${err?.message || "unknown"}` };
|
|
15691
|
+
}
|
|
15692
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
15693
|
+
let upload;
|
|
15694
|
+
try {
|
|
15695
|
+
upload = await storageQueries2.getUploadUrl(sessionId, fileName, contentType, "slack");
|
|
15696
|
+
} catch (err) {
|
|
15697
|
+
return { ...base, sizeBytes: bytes.length, shortUrl: null, error: `presign_failed:${err?.message || "unknown"}` };
|
|
15698
|
+
}
|
|
15699
|
+
try {
|
|
15700
|
+
const putRes = await fetch(upload.uploadUrl, {
|
|
15701
|
+
method: "PUT",
|
|
15702
|
+
headers: { "Content-Type": contentType },
|
|
15703
|
+
body: bytes
|
|
15704
|
+
});
|
|
15705
|
+
if (!putRes.ok) {
|
|
15706
|
+
return {
|
|
15707
|
+
...base,
|
|
15708
|
+
sizeBytes: bytes.length,
|
|
15709
|
+
shortUrl: null,
|
|
15710
|
+
error: `gcs_put_${putRes.status}`
|
|
15711
|
+
};
|
|
15712
|
+
}
|
|
15713
|
+
} catch (err) {
|
|
15714
|
+
return {
|
|
15715
|
+
...base,
|
|
15716
|
+
sizeBytes: bytes.length,
|
|
15717
|
+
shortUrl: null,
|
|
15718
|
+
error: `gcs_put_error:${err?.message || "unknown"}`
|
|
15719
|
+
};
|
|
15720
|
+
}
|
|
15721
|
+
try {
|
|
15722
|
+
await storageQueries2.updateFile(upload.fileId, { sizeBytes: bytes.length });
|
|
15723
|
+
} catch (err) {
|
|
15724
|
+
console.warn(`[slack-files] sizeBytes patch failed for ${upload.fileId}:`, err?.message || err);
|
|
15725
|
+
}
|
|
15726
|
+
const shortUrl = upload.shortUrl || // Defensive fallback: build it from the upload URL's origin if the
|
|
15727
|
+
// server somehow forgot to return it (older remote-server versions).
|
|
15728
|
+
inferShortUrlFromUploadUrl(upload.uploadUrl, upload.fileId);
|
|
15729
|
+
return {
|
|
15730
|
+
...base,
|
|
15731
|
+
sizeBytes: bytes.length,
|
|
15732
|
+
shortUrl
|
|
15733
|
+
};
|
|
15734
|
+
}
|
|
15735
|
+
function inferShortUrlFromUploadUrl(uploadUrl, fileId) {
|
|
15736
|
+
try {
|
|
15737
|
+
const u = new URL(uploadUrl);
|
|
15738
|
+
if (u.hostname.endsWith(".googleapis.com")) return null;
|
|
15739
|
+
return `${u.origin}/f/${fileId}`;
|
|
15740
|
+
} catch {
|
|
15741
|
+
return null;
|
|
15742
|
+
}
|
|
15743
|
+
}
|
|
15744
|
+
async function ingestSlackFiles(files, sessionId, options = {}) {
|
|
15745
|
+
if (!Array.isArray(files) || files.length === 0) return [];
|
|
15746
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
15747
|
+
if (!isRemoteConfigured2()) {
|
|
15748
|
+
console.warn(`[slack-files] storage not configured \u2014 skipping ingestion for ${files.length} file(s)`);
|
|
15749
|
+
return files.map((f) => ({
|
|
15750
|
+
slackFileId: f.id,
|
|
15751
|
+
fileName: inferFileName(f),
|
|
15752
|
+
contentType: inferContentType(f),
|
|
15753
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15754
|
+
shortUrl: null,
|
|
15755
|
+
error: "storage_unconfigured"
|
|
15756
|
+
}));
|
|
15757
|
+
}
|
|
15758
|
+
const botToken = getSlackBotToken();
|
|
15759
|
+
if (!botToken) {
|
|
15760
|
+
console.warn("[slack-files] no bot token \u2014 cannot download from Slack");
|
|
15761
|
+
return files.map((f) => ({
|
|
15762
|
+
slackFileId: f.id,
|
|
15763
|
+
fileName: inferFileName(f),
|
|
15764
|
+
contentType: inferContentType(f),
|
|
15765
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15766
|
+
shortUrl: null,
|
|
15767
|
+
error: "no_bot_token"
|
|
15768
|
+
}));
|
|
15769
|
+
}
|
|
15770
|
+
const timeoutMs = options.timeoutMs ?? INGEST_TIMEOUT_MS;
|
|
15771
|
+
const startedAt = Date.now();
|
|
15772
|
+
const pipeline = Promise.allSettled(
|
|
15773
|
+
files.map((f) => ingestOne(f, sessionId, botToken))
|
|
15774
|
+
);
|
|
15775
|
+
let settled;
|
|
15776
|
+
try {
|
|
15777
|
+
settled = await withTimeout(pipeline, timeoutMs, "ingest");
|
|
15778
|
+
} catch (err) {
|
|
15779
|
+
console.warn(`[slack-files] pipeline timeout after ${Date.now() - startedAt}ms (${err?.message || "timeout"})`);
|
|
15780
|
+
return files.map((f) => ({
|
|
15781
|
+
slackFileId: f.id,
|
|
15782
|
+
fileName: inferFileName(f),
|
|
15783
|
+
contentType: inferContentType(f),
|
|
15784
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15785
|
+
shortUrl: null,
|
|
15786
|
+
error: "timeout"
|
|
15787
|
+
}));
|
|
15788
|
+
}
|
|
15789
|
+
const results = settled.map((s, i) => {
|
|
15790
|
+
if (s.status === "fulfilled") return s.value;
|
|
15791
|
+
const f = files[i];
|
|
15792
|
+
return {
|
|
15793
|
+
slackFileId: f.id,
|
|
15794
|
+
fileName: inferFileName(f),
|
|
15795
|
+
contentType: inferContentType(f),
|
|
15796
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
15797
|
+
shortUrl: null,
|
|
15798
|
+
error: `unexpected:${s.reason?.message || String(s.reason)}`
|
|
15799
|
+
};
|
|
15800
|
+
});
|
|
15801
|
+
const okCount = results.filter((r) => r.shortUrl).length;
|
|
15802
|
+
console.log(
|
|
15803
|
+
`[slack-files] ingested ${okCount}/${files.length} file(s) in ${Date.now() - startedAt}ms`
|
|
15804
|
+
);
|
|
15805
|
+
return results;
|
|
15806
|
+
}
|
|
15807
|
+
function formatFileBlock(files) {
|
|
15808
|
+
if (!files || files.length === 0) return "";
|
|
15809
|
+
const lines = ["[files]"];
|
|
15810
|
+
for (const f of files) {
|
|
15811
|
+
const sizeLabel = formatBytes(f.sizeBytes);
|
|
15812
|
+
if (f.shortUrl) {
|
|
15813
|
+
lines.push(` - ${f.fileName} (${f.contentType}, ${sizeLabel}): ${f.shortUrl}`);
|
|
15814
|
+
} else {
|
|
15815
|
+
lines.push(
|
|
15816
|
+
` - ${f.fileName} (${f.contentType}, ${sizeLabel}): [ingestion failed: ${f.error || "unknown"}]`
|
|
15817
|
+
);
|
|
15818
|
+
}
|
|
15819
|
+
}
|
|
15820
|
+
return lines.join("\n");
|
|
15821
|
+
}
|
|
15822
|
+
|
|
15823
|
+
// src/server/routes/slack.ts
|
|
14467
15824
|
init_webhook_events();
|
|
14468
15825
|
init_inbox();
|
|
14469
15826
|
var recentlyHandled = /* @__PURE__ */ new Map();
|
|
@@ -14550,9 +15907,43 @@ slack.post("/events", async (c) => {
|
|
|
14550
15907
|
inbound.content = inbound.content.replace(`user=${ev.user}`, () => enriched);
|
|
14551
15908
|
}
|
|
14552
15909
|
}
|
|
14553
|
-
|
|
15910
|
+
const slackFiles = Array.isArray(ev.files) ? ev.files : [];
|
|
14554
15911
|
markHandled(ev.channel, ev.ts);
|
|
14555
|
-
|
|
15912
|
+
if (ev.channel && ev.ts) {
|
|
15913
|
+
void addLoadingReaction(String(ev.channel), String(ev.ts));
|
|
15914
|
+
}
|
|
15915
|
+
let ingestedCount = 0;
|
|
15916
|
+
if (slackFiles.length > 0) {
|
|
15917
|
+
try {
|
|
15918
|
+
const ingested = await ingestSlackFiles(slackFiles, orchestratorId);
|
|
15919
|
+
const block = formatFileBlock(ingested);
|
|
15920
|
+
if (block) inbound.content = `${inbound.content}
|
|
15921
|
+
${block}`;
|
|
15922
|
+
ingestedCount = ingested.filter((f) => f.shortUrl).length;
|
|
15923
|
+
} catch (err) {
|
|
15924
|
+
console.warn("[slack-files] ingestion threw:", err?.message || err);
|
|
15925
|
+
inbound.content = `${inbound.content}
|
|
15926
|
+
[files] (ingestion failed: ${err?.message || "unknown"})`;
|
|
15927
|
+
}
|
|
15928
|
+
}
|
|
15929
|
+
pushToInbox(orchestratorId, inbound);
|
|
15930
|
+
updateEvent(auditId, {
|
|
15931
|
+
status: "routed",
|
|
15932
|
+
sessionId: orchestratorId,
|
|
15933
|
+
...slackFiles.length > 0 ? {
|
|
15934
|
+
// Preserve the original meta (ts, thread_ts, team,
|
|
15935
|
+
// event_subtype) from recordEvent above — updateEvent does a
|
|
15936
|
+
// shallow merge, so we have to re-include them.
|
|
15937
|
+
meta: {
|
|
15938
|
+
ts: ev.ts,
|
|
15939
|
+
thread_ts: ev.thread_ts,
|
|
15940
|
+
team: ev.team,
|
|
15941
|
+
event_subtype: ev.subtype,
|
|
15942
|
+
fileCount: slackFiles.length,
|
|
15943
|
+
ingestedCount
|
|
15944
|
+
}
|
|
15945
|
+
} : {}
|
|
15946
|
+
});
|
|
14556
15947
|
} else {
|
|
14557
15948
|
updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
|
|
14558
15949
|
}
|
|
@@ -14772,10 +16163,35 @@ integrations.get("/", async (c) => {
|
|
|
14772
16163
|
cfAccess: {
|
|
14773
16164
|
enabled: !!cfg?.auth?.cfAccess?.enabled,
|
|
14774
16165
|
teamDomain: cfg?.auth?.cfAccess?.teamDomain || null,
|
|
16166
|
+
audTag: cfg?.auth?.cfAccess?.audTag || null,
|
|
14775
16167
|
allowedEmails: cfg?.auth?.allowedEmails || []
|
|
14776
16168
|
}
|
|
14777
16169
|
});
|
|
14778
16170
|
});
|
|
16171
|
+
var cfAccessSchema = z21.object({
|
|
16172
|
+
enabled: z21.boolean().optional(),
|
|
16173
|
+
teamDomain: z21.string().optional(),
|
|
16174
|
+
audTag: z21.string().optional(),
|
|
16175
|
+
// Email allowlist for the public (cloudflared) surface. Empty array = allow
|
|
16176
|
+
// any email that passes the Cloudflare Access policy (no extra filtering).
|
|
16177
|
+
allowedEmails: z21.array(z21.string().trim().toLowerCase()).optional()
|
|
16178
|
+
});
|
|
16179
|
+
integrations.post("/cf-access", zValidator6("json", cfAccessSchema), async (c) => {
|
|
16180
|
+
const body = c.req.valid("json");
|
|
16181
|
+
if (body.enabled) {
|
|
16182
|
+
const cfg = getConfig();
|
|
16183
|
+
const teamDomain = body.teamDomain ?? cfg?.auth?.cfAccess?.teamDomain;
|
|
16184
|
+
const audTag = body.audTag ?? cfg?.auth?.cfAccess?.audTag;
|
|
16185
|
+
if (!teamDomain || !audTag) {
|
|
16186
|
+
return c.json(
|
|
16187
|
+
{ error: "teamDomain and audTag are required to enable Cloudflare Access" },
|
|
16188
|
+
400
|
|
16189
|
+
);
|
|
16190
|
+
}
|
|
16191
|
+
}
|
|
16192
|
+
setCfAccessConfig(body);
|
|
16193
|
+
return c.json({ ok: true });
|
|
16194
|
+
});
|
|
14779
16195
|
var slackConfigSchema = z21.object({
|
|
14780
16196
|
botToken: z21.string().optional(),
|
|
14781
16197
|
signingSecret: z21.string().optional(),
|
|
@@ -14958,8 +16374,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
14958
16374
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
14959
16375
|
import { z as z22 } from "zod";
|
|
14960
16376
|
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
14961
|
-
import { readFile as readFile12, writeFile as
|
|
14962
|
-
import { resolve as resolve11, join as
|
|
16377
|
+
import { readFile as readFile12, writeFile as writeFile7, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
16378
|
+
import { resolve as resolve11, join as join17, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
14963
16379
|
var skills = new Hono9();
|
|
14964
16380
|
function encodeId(filePath) {
|
|
14965
16381
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -15107,13 +16523,13 @@ skills.post(
|
|
|
15107
16523
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
15108
16524
|
const ext = extname9(safeName).toLowerCase();
|
|
15109
16525
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
15110
|
-
const filePath =
|
|
16526
|
+
const filePath = join17(targetDir, finalName);
|
|
15111
16527
|
if (existsSync21(filePath)) {
|
|
15112
16528
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
15113
16529
|
}
|
|
15114
16530
|
try {
|
|
15115
16531
|
await mkdir5(targetDir, { recursive: true });
|
|
15116
|
-
await
|
|
16532
|
+
await writeFile7(filePath, content, "utf-8");
|
|
15117
16533
|
} catch (err) {
|
|
15118
16534
|
return c.json({ error: err?.message || "write failed" }, 500);
|
|
15119
16535
|
}
|
|
@@ -15129,7 +16545,7 @@ skills.put(
|
|
|
15129
16545
|
if (filePath.includes("/skills/default")) {
|
|
15130
16546
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
15131
16547
|
}
|
|
15132
|
-
await
|
|
16548
|
+
await writeFile7(filePath, c.req.valid("json").content, "utf-8");
|
|
15133
16549
|
return c.json({ ok: true });
|
|
15134
16550
|
}
|
|
15135
16551
|
);
|
|
@@ -15173,6 +16589,14 @@ skills.delete("/directories", (c) => {
|
|
|
15173
16589
|
init_config();
|
|
15174
16590
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
15175
16591
|
var EXEMPT_PATH_PREFIXES = ["/health", "/api/slack/events", "/api/inbox/", "/w/"];
|
|
16592
|
+
function isExemptPath(path) {
|
|
16593
|
+
return EXEMPT_PATH_PREFIXES.some((p) => {
|
|
16594
|
+
if (p.endsWith("/")) {
|
|
16595
|
+
return path === p.slice(0, -1) || path === p || path.startsWith(p);
|
|
16596
|
+
}
|
|
16597
|
+
return path === p || path.startsWith(p + "/") || path.startsWith(p + "?");
|
|
16598
|
+
});
|
|
16599
|
+
}
|
|
15176
16600
|
var cachedJWKS = null;
|
|
15177
16601
|
var cachedJWKSUrl = null;
|
|
15178
16602
|
function getOrCreateJWKS(teamDomain) {
|
|
@@ -15202,12 +16626,13 @@ function cfAccessMiddleware() {
|
|
|
15202
16626
|
return next();
|
|
15203
16627
|
}
|
|
15204
16628
|
const path = c.req.path;
|
|
15205
|
-
if (
|
|
16629
|
+
if (isExemptPath(path)) {
|
|
15206
16630
|
return next();
|
|
15207
16631
|
}
|
|
15208
16632
|
const host = c.req.header("host");
|
|
15209
16633
|
const remote = c.req.raw?.socket?.remoteAddress;
|
|
15210
|
-
|
|
16634
|
+
const hasCfJwt = !!c.req.header("cf-access-jwt-assertion");
|
|
16635
|
+
if (!hasCfJwt && isLoopback(host, remote)) {
|
|
15211
16636
|
return next();
|
|
15212
16637
|
}
|
|
15213
16638
|
const teamDomain = cfg.teamDomain;
|
|
@@ -15226,8 +16651,10 @@ function cfAccessMiddleware() {
|
|
|
15226
16651
|
audience: aud
|
|
15227
16652
|
});
|
|
15228
16653
|
const email = String(payload.email || "").toLowerCase();
|
|
15229
|
-
const
|
|
15230
|
-
|
|
16654
|
+
const emailDomain = email.split("@")[1] || "";
|
|
16655
|
+
const allowed = (auth?.allowedEmails || []).map((e) => e.toLowerCase().trim().replace(/^@/, "")).filter(Boolean);
|
|
16656
|
+
const isAllowed = allowed.length === 0 || allowed.some((entry2) => entry2.includes("@") ? entry2 === email : entry2 === emailDomain);
|
|
16657
|
+
if (!isAllowed) {
|
|
15231
16658
|
console.warn(`[cf-access] rejected ${email}: not in allowlist`);
|
|
15232
16659
|
return c.json({ error: "Email not allowed" }, 403);
|
|
15233
16660
|
}
|
|
@@ -15329,13 +16756,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
15329
16756
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
15330
16757
|
function getWebDirectory() {
|
|
15331
16758
|
try {
|
|
15332
|
-
const currentDir =
|
|
16759
|
+
const currentDir = dirname11(fileURLToPath5(import.meta.url));
|
|
15333
16760
|
const webDir = resolve12(currentDir, "..", "web");
|
|
15334
|
-
if (existsSync22(webDir) && existsSync22(
|
|
16761
|
+
if (existsSync22(webDir) && existsSync22(join19(webDir, "package.json"))) {
|
|
15335
16762
|
return webDir;
|
|
15336
16763
|
}
|
|
15337
16764
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
15338
|
-
if (existsSync22(altWebDir) && existsSync22(
|
|
16765
|
+
if (existsSync22(altWebDir) && existsSync22(join19(altWebDir, "package.json"))) {
|
|
15339
16766
|
return altWebDir;
|
|
15340
16767
|
}
|
|
15341
16768
|
return null;
|
|
@@ -15393,20 +16820,20 @@ async function findWebPort(preferredPort) {
|
|
|
15393
16820
|
return { port: preferredPort, alreadyRunning: false };
|
|
15394
16821
|
}
|
|
15395
16822
|
function hasProductionBuild(webDir) {
|
|
15396
|
-
const buildIdPath =
|
|
16823
|
+
const buildIdPath = join19(webDir, ".next", "BUILD_ID");
|
|
15397
16824
|
return existsSync22(buildIdPath);
|
|
15398
16825
|
}
|
|
15399
16826
|
function hasSourceFiles(webDir) {
|
|
15400
|
-
const appDir =
|
|
15401
|
-
const pagesDir =
|
|
15402
|
-
const rootAppDir =
|
|
15403
|
-
const rootPagesDir =
|
|
16827
|
+
const appDir = join19(webDir, "src", "app");
|
|
16828
|
+
const pagesDir = join19(webDir, "src", "pages");
|
|
16829
|
+
const rootAppDir = join19(webDir, "app");
|
|
16830
|
+
const rootPagesDir = join19(webDir, "pages");
|
|
15404
16831
|
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
15405
16832
|
}
|
|
15406
16833
|
function getStandaloneServerPath(webDir) {
|
|
15407
16834
|
const possiblePaths2 = [
|
|
15408
|
-
|
|
15409
|
-
|
|
16835
|
+
join19(webDir, ".next", "standalone", "server.js"),
|
|
16836
|
+
join19(webDir, ".next", "standalone", "web", "server.js")
|
|
15410
16837
|
];
|
|
15411
16838
|
for (const serverPath of possiblePaths2) {
|
|
15412
16839
|
if (existsSync22(serverPath)) {
|
|
@@ -15417,7 +16844,7 @@ function getStandaloneServerPath(webDir) {
|
|
|
15417
16844
|
}
|
|
15418
16845
|
function runCommand(command, args, cwd, env) {
|
|
15419
16846
|
return new Promise((resolve13) => {
|
|
15420
|
-
const child =
|
|
16847
|
+
const child = spawn3(command, args, {
|
|
15421
16848
|
cwd,
|
|
15422
16849
|
stdio: ["ignore", "pipe", "pipe"],
|
|
15423
16850
|
env,
|
|
@@ -15449,15 +16876,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15449
16876
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
15450
16877
|
return { process: null, port: actualPort };
|
|
15451
16878
|
}
|
|
15452
|
-
const usePnpm = existsSync22(
|
|
15453
|
-
const useNpm = !usePnpm && existsSync22(
|
|
16879
|
+
const usePnpm = existsSync22(join19(webDir, "pnpm-lock.yaml"));
|
|
16880
|
+
const useNpm = !usePnpm && existsSync22(join19(webDir, "package-lock.json"));
|
|
15454
16881
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
15455
16882
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
15456
16883
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
15457
|
-
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
15458
|
-
const runtimeConfigPath =
|
|
16884
|
+
const runtimeConfig = { apiBaseUrl: apiUrl, localApiBaseUrl: `http://127.0.0.1:${apiPort}` };
|
|
16885
|
+
const runtimeConfigPath = join19(webDir, "runtime-config.json");
|
|
15459
16886
|
try {
|
|
15460
|
-
|
|
16887
|
+
writeFileSync8(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
15461
16888
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
15462
16889
|
} catch (err) {
|
|
15463
16890
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -15477,7 +16904,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15477
16904
|
if (standaloneServerPath) {
|
|
15478
16905
|
command = "node";
|
|
15479
16906
|
args = ["server.js"];
|
|
15480
|
-
cwd =
|
|
16907
|
+
cwd = dirname11(standaloneServerPath);
|
|
15481
16908
|
webEnv.PORT = String(actualPort);
|
|
15482
16909
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
15483
16910
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -15507,7 +16934,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15507
16934
|
}
|
|
15508
16935
|
return { process: null, port: actualPort };
|
|
15509
16936
|
}
|
|
15510
|
-
const child =
|
|
16937
|
+
const child = spawn3(command, args, {
|
|
15511
16938
|
cwd,
|
|
15512
16939
|
stdio: ["ignore", "pipe", "pipe"],
|
|
15513
16940
|
env: webEnv,
|
|
@@ -15515,12 +16942,12 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15515
16942
|
shell: true
|
|
15516
16943
|
});
|
|
15517
16944
|
const startupTimeout = 3e4;
|
|
15518
|
-
let
|
|
16945
|
+
let started2 = false;
|
|
15519
16946
|
let exited = false;
|
|
15520
16947
|
let exitCode = null;
|
|
15521
16948
|
const startedPromise = new Promise((resolve13) => {
|
|
15522
16949
|
const timeout = setTimeout(() => {
|
|
15523
|
-
if (!
|
|
16950
|
+
if (!started2 && !exited) {
|
|
15524
16951
|
resolve13(false);
|
|
15525
16952
|
}
|
|
15526
16953
|
}, startupTimeout);
|
|
@@ -15532,8 +16959,8 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15532
16959
|
console.log(` Web UI: ${line}`);
|
|
15533
16960
|
}
|
|
15534
16961
|
}
|
|
15535
|
-
if (!
|
|
15536
|
-
|
|
16962
|
+
if (!started2 && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
16963
|
+
started2 = true;
|
|
15537
16964
|
clearTimeout(timeout);
|
|
15538
16965
|
resolve13(true);
|
|
15539
16966
|
}
|
|
@@ -15552,7 +16979,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
15552
16979
|
child.on("exit", (code) => {
|
|
15553
16980
|
exited = true;
|
|
15554
16981
|
exitCode = code;
|
|
15555
|
-
if (!
|
|
16982
|
+
if (!started2) {
|
|
15556
16983
|
clearTimeout(timeout);
|
|
15557
16984
|
resolve13(false);
|
|
15558
16985
|
}
|
|
@@ -15672,7 +17099,7 @@ async function startServer(options = {}) {
|
|
|
15672
17099
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
15673
17100
|
}
|
|
15674
17101
|
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
15675
|
-
|
|
17102
|
+
mkdirSync11(config.resolvedWorkingDirectory, { recursive: true });
|
|
15676
17103
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
15677
17104
|
}
|
|
15678
17105
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -15703,9 +17130,17 @@ async function startServer(options = {}) {
|
|
|
15703
17130
|
try {
|
|
15704
17131
|
const { startOrchestratorDaemon: startOrchestratorDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
15705
17132
|
startOrchestratorDaemon2();
|
|
17133
|
+
const { startReconciler: startReconciler2 } = await Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports));
|
|
17134
|
+
startReconciler2();
|
|
15706
17135
|
} catch (err) {
|
|
15707
17136
|
if (!options.quiet) console.warn(`[daemon] start skipped: ${err.message}`);
|
|
15708
17137
|
}
|
|
17138
|
+
try {
|
|
17139
|
+
const { startSelfUpdater: startSelfUpdater2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
|
|
17140
|
+
startSelfUpdater2();
|
|
17141
|
+
} catch (err) {
|
|
17142
|
+
if (!options.quiet) console.warn(`[self-update] start skipped: ${err.message}`);
|
|
17143
|
+
}
|
|
15709
17144
|
try {
|
|
15710
17145
|
const { startScheduler: startScheduler2 } = await Promise.resolve().then(() => (init_scheduler(), scheduler_exports));
|
|
15711
17146
|
startScheduler2({ quiet: options.quiet });
|