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/cli.js
CHANGED
|
@@ -819,8 +819,8 @@ var init_types = __esm({
|
|
|
819
819
|
authKey: z.string().optional()
|
|
820
820
|
}).optional();
|
|
821
821
|
SparkcoderConfigSchema = z.object({
|
|
822
|
-
// Default model to use (
|
|
823
|
-
defaultModel: z.string().default("
|
|
822
|
+
// Default model to use (LiteLLM model id)
|
|
823
|
+
defaultModel: z.string().default("gpt-5.5"),
|
|
824
824
|
// Working directory for file operations
|
|
825
825
|
workingDirectory: z.string().optional(),
|
|
826
826
|
// Tool approval settings
|
|
@@ -859,6 +859,14 @@ var init_types = __esm({
|
|
|
859
859
|
webhooks: z.object({
|
|
860
860
|
token: z.string().optional()
|
|
861
861
|
}).optional(),
|
|
862
|
+
// Self-update: when running as the managed service, periodically check
|
|
863
|
+
// npm for a newer published version and, if found, re-run the hosted
|
|
864
|
+
// installer (full upgrade + restart). Disabled automatically when not
|
|
865
|
+
// running from a global install (e.g. dev/source checkouts).
|
|
866
|
+
autoUpdate: z.object({
|
|
867
|
+
enabled: z.boolean().optional().default(true),
|
|
868
|
+
intervalHours: z.number().positive().optional().default(6)
|
|
869
|
+
}).optional().default({}),
|
|
862
870
|
// Database path (used for local SQLite - ignored if remoteServer is configured)
|
|
863
871
|
databasePath: z.string().optional().default("./sparkecoder.db"),
|
|
864
872
|
// Remote server configuration (for centralized storage)
|
|
@@ -966,6 +974,7 @@ __export(config_exports, {
|
|
|
966
974
|
requiresApproval: () => requiresApproval,
|
|
967
975
|
saveAuthKey: () => saveAuthKey,
|
|
968
976
|
setApiKey: () => setApiKey,
|
|
977
|
+
setCfAccessConfig: () => setCfAccessConfig,
|
|
969
978
|
setMcpServers: () => setMcpServers,
|
|
970
979
|
setPublicUrl: () => setPublicUrl,
|
|
971
980
|
setSkillsAdditionalDirectories: () => setSkillsAdditionalDirectories,
|
|
@@ -1154,12 +1163,12 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
1154
1163
|
]
|
|
1155
1164
|
};
|
|
1156
1165
|
const DEFAULT_REMOTE_URL = "https://agent-remote-server.sparkecode.com";
|
|
1157
|
-
const
|
|
1166
|
+
const remoteUrl2 = process.env.SPARKECODER_REMOTE_URL || config.remoteServer?.url || DEFAULT_REMOTE_URL;
|
|
1158
1167
|
const remoteAuthKey = process.env.SPARKECODER_AUTH_KEY || config.remoteServer?.authKey || loadStoredAuthKey();
|
|
1159
1168
|
const resolvedRemoteServer = {
|
|
1160
|
-
url:
|
|
1169
|
+
url: remoteUrl2,
|
|
1161
1170
|
authKey: remoteAuthKey,
|
|
1162
|
-
isConfigured: !!
|
|
1171
|
+
isConfigured: !!remoteUrl2 && !!remoteAuthKey
|
|
1163
1172
|
};
|
|
1164
1173
|
const resolved = {
|
|
1165
1174
|
...config,
|
|
@@ -1315,6 +1324,40 @@ function setPublicUrl(publicUrl) {
|
|
|
1315
1324
|
console.warn("[config] failed to persist publicUrl:", err?.message || err);
|
|
1316
1325
|
}
|
|
1317
1326
|
}
|
|
1327
|
+
function setCfAccessConfig(input) {
|
|
1328
|
+
const applyToAuth = (auth) => {
|
|
1329
|
+
const curAuth = auth || {};
|
|
1330
|
+
const curCf = curAuth.cfAccess || {};
|
|
1331
|
+
const nextCf = { ...curCf };
|
|
1332
|
+
if (input.enabled !== void 0) nextCf.enabled = input.enabled;
|
|
1333
|
+
if (input.teamDomain !== void 0) nextCf.teamDomain = input.teamDomain;
|
|
1334
|
+
if (input.audTag !== void 0) nextCf.audTag = input.audTag;
|
|
1335
|
+
const nextAuth = { ...curAuth, cfAccess: nextCf };
|
|
1336
|
+
if (input.allowedEmails !== void 0) nextAuth.allowedEmails = input.allowedEmails;
|
|
1337
|
+
return nextAuth;
|
|
1338
|
+
};
|
|
1339
|
+
if (cachedConfig) {
|
|
1340
|
+
cachedConfig.auth = applyToAuth(cachedConfig.auth);
|
|
1341
|
+
}
|
|
1342
|
+
try {
|
|
1343
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
1344
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
1345
|
+
let raw = {};
|
|
1346
|
+
if (existsSync(target)) {
|
|
1347
|
+
try {
|
|
1348
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
1349
|
+
} catch {
|
|
1350
|
+
raw = {};
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
raw = createDefaultConfig();
|
|
1354
|
+
}
|
|
1355
|
+
raw.auth = applyToAuth(raw.auth);
|
|
1356
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
console.warn("[config] failed to persist cf-access config:", err?.message || err);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1318
1361
|
function clearSlackConfig() {
|
|
1319
1362
|
if (cachedConfig) cachedConfig.slack = {};
|
|
1320
1363
|
try {
|
|
@@ -1356,7 +1399,7 @@ function autoApproveAllTools(sessionConfig) {
|
|
|
1356
1399
|
}
|
|
1357
1400
|
function createDefaultConfig() {
|
|
1358
1401
|
return {
|
|
1359
|
-
defaultModel: "
|
|
1402
|
+
defaultModel: "gpt-5.5",
|
|
1360
1403
|
// workingDirectory is intentionally not set - defaults to where CLI is run
|
|
1361
1404
|
toolApprovals: {
|
|
1362
1405
|
bash: true,
|
|
@@ -1378,6 +1421,10 @@ function createDefaultConfig() {
|
|
|
1378
1421
|
port: 3141,
|
|
1379
1422
|
host: "0.0.0.0"
|
|
1380
1423
|
},
|
|
1424
|
+
autoUpdate: {
|
|
1425
|
+
enabled: true,
|
|
1426
|
+
intervalHours: 6
|
|
1427
|
+
},
|
|
1381
1428
|
databasePath: "./sparkecoder.db"
|
|
1382
1429
|
};
|
|
1383
1430
|
}
|
|
@@ -1604,6 +1651,7 @@ var init_config = __esm({
|
|
|
1604
1651
|
openai: "OPENAI_API_KEY",
|
|
1605
1652
|
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
1606
1653
|
xai: "XAI_API_KEY",
|
|
1654
|
+
litellm: "LITELLM_API_KEY",
|
|
1607
1655
|
"ai-gateway": "AI_GATEWAY_API_KEY"
|
|
1608
1656
|
};
|
|
1609
1657
|
SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
|
|
@@ -4846,11 +4894,11 @@ async function getRepoNamespace(workingDirectory, configuredNamespace) {
|
|
|
4846
4894
|
if (configuredNamespace) {
|
|
4847
4895
|
return configuredNamespace;
|
|
4848
4896
|
}
|
|
4849
|
-
const
|
|
4850
|
-
if (!
|
|
4897
|
+
const remoteUrl2 = getGitRemoteUrl(workingDirectory);
|
|
4898
|
+
if (!remoteUrl2) {
|
|
4851
4899
|
return null;
|
|
4852
4900
|
}
|
|
4853
|
-
const parsed = parseGitRemoteUrl(
|
|
4901
|
+
const parsed = parseGitRemoteUrl(remoteUrl2);
|
|
4854
4902
|
if (!parsed) {
|
|
4855
4903
|
return null;
|
|
4856
4904
|
}
|
|
@@ -5449,7 +5497,7 @@ function isPathExcluded(relativePath, exclude) {
|
|
|
5449
5497
|
}
|
|
5450
5498
|
async function walkDirectory(dir, include, exclude, baseDir) {
|
|
5451
5499
|
const { readdirSync: readdirSync4 } = await import("fs");
|
|
5452
|
-
const { join:
|
|
5500
|
+
const { join: join21, relative: relative10 } = await import("path");
|
|
5453
5501
|
const files = [];
|
|
5454
5502
|
function walk(currentDir) {
|
|
5455
5503
|
let entries;
|
|
@@ -5459,7 +5507,7 @@ async function walkDirectory(dir, include, exclude, baseDir) {
|
|
|
5459
5507
|
return;
|
|
5460
5508
|
}
|
|
5461
5509
|
for (const entry2 of entries) {
|
|
5462
|
-
const fullPath =
|
|
5510
|
+
const fullPath = join21(currentDir, entry2.name);
|
|
5463
5511
|
const relativePath = relative10(baseDir, fullPath);
|
|
5464
5512
|
if (isPathExcluded(relativePath, exclude)) {
|
|
5465
5513
|
continue;
|
|
@@ -7289,7 +7337,8 @@ async function buildSystemPrompt(options) {
|
|
|
7289
7337
|
sessionId,
|
|
7290
7338
|
discoveredSkills,
|
|
7291
7339
|
activeFiles = [],
|
|
7292
|
-
customInstructions
|
|
7340
|
+
customInstructions,
|
|
7341
|
+
taskScopedSkills
|
|
7293
7342
|
} = options;
|
|
7294
7343
|
let alwaysLoadedContent = "";
|
|
7295
7344
|
let globMatchedContent = "";
|
|
@@ -7310,6 +7359,22 @@ async function buildSystemPrompt(options) {
|
|
|
7310
7359
|
const skills2 = await loadAllSkills2(skillsDirectories);
|
|
7311
7360
|
onDemandSkillsContext = formatSkillsForContext(skills2);
|
|
7312
7361
|
}
|
|
7362
|
+
let taskScopedSkillsBlock = "";
|
|
7363
|
+
if (taskScopedSkills && (taskScopedSkills.always.length > 0 || taskScopedSkills.onDemand.length > 0)) {
|
|
7364
|
+
const parts = ["<task_provided_skills>"];
|
|
7365
|
+
parts.push("These skills were supplied with this task and are available for this run only.");
|
|
7366
|
+
if (taskScopedSkills.always.length > 0) {
|
|
7367
|
+
parts.push(formatAlwaysLoadedSkills(taskScopedSkills.always));
|
|
7368
|
+
}
|
|
7369
|
+
if (taskScopedSkills.onDemand.length > 0) {
|
|
7370
|
+
parts.push("Load any of these on demand with the load_skill tool:");
|
|
7371
|
+
for (const s of taskScopedSkills.onDemand) {
|
|
7372
|
+
parts.push(`- ${s.name}: ${s.description}`);
|
|
7373
|
+
}
|
|
7374
|
+
}
|
|
7375
|
+
parts.push("</task_provided_skills>");
|
|
7376
|
+
taskScopedSkillsBlock = parts.join("\n");
|
|
7377
|
+
}
|
|
7313
7378
|
const todos = await todoQueries.getBySession(sessionId);
|
|
7314
7379
|
const todosContext = formatTodosForContext(todos);
|
|
7315
7380
|
const plans = await readSessionPlans(workingDirectory, sessionId);
|
|
@@ -7602,6 +7667,8 @@ ${globMatchedContent}
|
|
|
7602
7667
|
${onDemandSkillsContext}
|
|
7603
7668
|
</on_demand_skills>
|
|
7604
7669
|
|
|
7670
|
+
${taskScopedSkillsBlock}
|
|
7671
|
+
|
|
7605
7672
|
<current_task_list>
|
|
7606
7673
|
${todosContext}
|
|
7607
7674
|
</current_task_list>
|
|
@@ -8223,6 +8290,111 @@ var init_sanitize_messages = __esm({
|
|
|
8223
8290
|
}
|
|
8224
8291
|
});
|
|
8225
8292
|
|
|
8293
|
+
// src/utils/cap-image-count.ts
|
|
8294
|
+
function isImagePart(part) {
|
|
8295
|
+
if (!part || typeof part !== "object") return false;
|
|
8296
|
+
const t = part.type;
|
|
8297
|
+
if (t === "image") return true;
|
|
8298
|
+
if (t === "image-data") return true;
|
|
8299
|
+
if (t === "media") {
|
|
8300
|
+
const data = part.data;
|
|
8301
|
+
const mt = part.mediaType;
|
|
8302
|
+
if (typeof data === "string" && typeof mt === "string" && mt.startsWith("image/")) {
|
|
8303
|
+
return true;
|
|
8304
|
+
}
|
|
8305
|
+
}
|
|
8306
|
+
return false;
|
|
8307
|
+
}
|
|
8308
|
+
function makePlaceholder() {
|
|
8309
|
+
return { type: "text", text: IMAGE_TRUNCATED_PLACEHOLDER };
|
|
8310
|
+
}
|
|
8311
|
+
function countImages(messages) {
|
|
8312
|
+
let n = 0;
|
|
8313
|
+
for (const msg of messages) {
|
|
8314
|
+
if (!Array.isArray(msg.content)) continue;
|
|
8315
|
+
for (const part of msg.content) {
|
|
8316
|
+
if (isImagePart(part)) {
|
|
8317
|
+
n++;
|
|
8318
|
+
continue;
|
|
8319
|
+
}
|
|
8320
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
8321
|
+
for (const sub of part.output.value) {
|
|
8322
|
+
if (isImagePart(sub)) n++;
|
|
8323
|
+
}
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
return n;
|
|
8328
|
+
}
|
|
8329
|
+
function capImageCount(messages, max = MAX_IMAGES_IN_CONTEXT) {
|
|
8330
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
8331
|
+
if (max < 0) throw new Error("capImageCount: max must be >= 0");
|
|
8332
|
+
const total = countImages(messages);
|
|
8333
|
+
if (total <= max) return messages;
|
|
8334
|
+
let toDrop = total - max;
|
|
8335
|
+
let mutated = false;
|
|
8336
|
+
const out = messages.slice();
|
|
8337
|
+
for (let i = 0; i < out.length && toDrop > 0; i++) {
|
|
8338
|
+
const msg = out[i];
|
|
8339
|
+
if (!Array.isArray(msg.content)) continue;
|
|
8340
|
+
let contentCloned = false;
|
|
8341
|
+
const ensureContentCloned = () => {
|
|
8342
|
+
if (contentCloned) return;
|
|
8343
|
+
out[i] = { ...msg, content: [...msg.content] };
|
|
8344
|
+
contentCloned = true;
|
|
8345
|
+
};
|
|
8346
|
+
const content = () => out[i].content;
|
|
8347
|
+
for (let j = 0; j < content().length && toDrop > 0; j++) {
|
|
8348
|
+
const part = content()[j];
|
|
8349
|
+
if (isImagePart(part)) {
|
|
8350
|
+
ensureContentCloned();
|
|
8351
|
+
out[i].content[j] = makePlaceholder();
|
|
8352
|
+
toDrop--;
|
|
8353
|
+
mutated = true;
|
|
8354
|
+
continue;
|
|
8355
|
+
}
|
|
8356
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
8357
|
+
const innerImages = [];
|
|
8358
|
+
const innerValue = part.output.value;
|
|
8359
|
+
for (let k = 0; k < innerValue.length; k++) {
|
|
8360
|
+
if (isImagePart(innerValue[k])) innerImages.push(k);
|
|
8361
|
+
}
|
|
8362
|
+
if (innerImages.length === 0) continue;
|
|
8363
|
+
const dropHere = Math.min(innerImages.length, toDrop);
|
|
8364
|
+
ensureContentCloned();
|
|
8365
|
+
const newOutputValue = [...innerValue];
|
|
8366
|
+
for (let d = 0; d < dropHere; d++) {
|
|
8367
|
+
newOutputValue[innerImages[d]] = makePlaceholder();
|
|
8368
|
+
}
|
|
8369
|
+
const newPart = {
|
|
8370
|
+
...part,
|
|
8371
|
+
output: {
|
|
8372
|
+
...part.output,
|
|
8373
|
+
value: newOutputValue
|
|
8374
|
+
}
|
|
8375
|
+
};
|
|
8376
|
+
out[i].content[j] = newPart;
|
|
8377
|
+
toDrop -= dropHere;
|
|
8378
|
+
mutated = true;
|
|
8379
|
+
}
|
|
8380
|
+
}
|
|
8381
|
+
}
|
|
8382
|
+
if (mutated) {
|
|
8383
|
+
console.warn(
|
|
8384
|
+
`[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.`
|
|
8385
|
+
);
|
|
8386
|
+
}
|
|
8387
|
+
return mutated ? out : messages;
|
|
8388
|
+
}
|
|
8389
|
+
var MAX_IMAGES_IN_CONTEXT, IMAGE_TRUNCATED_PLACEHOLDER;
|
|
8390
|
+
var init_cap_image_count = __esm({
|
|
8391
|
+
"src/utils/cap-image-count.ts"() {
|
|
8392
|
+
"use strict";
|
|
8393
|
+
MAX_IMAGES_IN_CONTEXT = 11;
|
|
8394
|
+
IMAGE_TRUNCATED_PLACEHOLDER = "[image truncated due to length of conversation]";
|
|
8395
|
+
}
|
|
8396
|
+
});
|
|
8397
|
+
|
|
8226
8398
|
// src/agent/model-limits.ts
|
|
8227
8399
|
function getModelLimits(modelId) {
|
|
8228
8400
|
const normalized = modelId.trim().toLowerCase();
|
|
@@ -8238,18 +8410,9 @@ var init_model_limits = __esm({
|
|
|
8238
8410
|
"src/agent/model-limits.ts"() {
|
|
8239
8411
|
"use strict";
|
|
8240
8412
|
MODEL_LIMITS = {
|
|
8241
|
-
"
|
|
8242
|
-
"
|
|
8243
|
-
"
|
|
8244
|
-
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8245
|
-
"anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8246
|
-
"google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
8247
|
-
"google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
8248
|
-
"google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
8249
|
-
"openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
8250
|
-
"openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
8251
|
-
"openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8252
|
-
"xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
8413
|
+
"claude-opus-4-8": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
8414
|
+
"gpt-5.5": { contextWindow: 35e4, rollingTarget: 15e4 },
|
|
8415
|
+
"claude-fable-5": { contextWindow: 2e5, rollingTarget: 15e4 }
|
|
8253
8416
|
};
|
|
8254
8417
|
DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
8255
8418
|
PREFIX_DEFAULTS = {
|
|
@@ -8323,6 +8486,32 @@ var init_conversation_archive = __esm({
|
|
|
8323
8486
|
|
|
8324
8487
|
// src/agent/context.ts
|
|
8325
8488
|
import { generateText as generateText2 } from "ai";
|
|
8489
|
+
function stripBinaryContentForSummary(value) {
|
|
8490
|
+
if (Array.isArray(value)) return value.map(stripBinaryContentForSummary);
|
|
8491
|
+
if (!value || typeof value !== "object") return value;
|
|
8492
|
+
const record = value;
|
|
8493
|
+
const type = record.type;
|
|
8494
|
+
if ((type === "image-data" || type === "file-data" || type === "media") && typeof record.data === "string") {
|
|
8495
|
+
const mediaType = typeof record.mediaType === "string" ? record.mediaType : "unknown media type";
|
|
8496
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
8497
|
+
return {
|
|
8498
|
+
...record,
|
|
8499
|
+
data: `[${type}${filename}; ${mediaType}; ${record.data.length} base64 chars omitted for summary]`
|
|
8500
|
+
};
|
|
8501
|
+
}
|
|
8502
|
+
if (type === "image" && typeof record.image === "string") {
|
|
8503
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
8504
|
+
return {
|
|
8505
|
+
...record,
|
|
8506
|
+
image: `[image${filename}; ${record.image.length} base64 chars omitted for summary]`
|
|
8507
|
+
};
|
|
8508
|
+
}
|
|
8509
|
+
const out = {};
|
|
8510
|
+
for (const [key2, nested] of Object.entries(record)) {
|
|
8511
|
+
out[key2] = stripBinaryContentForSummary(nested);
|
|
8512
|
+
}
|
|
8513
|
+
return out;
|
|
8514
|
+
}
|
|
8326
8515
|
function stripOrphanedToolResults(msg, removedIds) {
|
|
8327
8516
|
if (!Array.isArray(msg.content)) return msg;
|
|
8328
8517
|
const parts = msg.content.filter((part) => {
|
|
@@ -8483,6 +8672,7 @@ var init_context = __esm({
|
|
|
8483
8672
|
init_tokens();
|
|
8484
8673
|
init_prompts();
|
|
8485
8674
|
init_sanitize_messages();
|
|
8675
|
+
init_cap_image_count();
|
|
8486
8676
|
init_model_limits();
|
|
8487
8677
|
TOOL_OUTPUT_TRIM_CHARS = 400;
|
|
8488
8678
|
COMPACTABLE_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -8532,6 +8722,7 @@ ${summaryContent}`
|
|
|
8532
8722
|
messages = repairToolPairing(messages);
|
|
8533
8723
|
messages = ensureToolResultsFollowCalls(messages);
|
|
8534
8724
|
messages = ensureEndsWithUserOrTool(messages);
|
|
8725
|
+
messages = capImageCount(messages);
|
|
8535
8726
|
return messages;
|
|
8536
8727
|
}
|
|
8537
8728
|
// ---------------------------------------------------------------------------
|
|
@@ -8649,7 +8840,7 @@ ${summaryContent}`
|
|
|
8649
8840
|
}
|
|
8650
8841
|
async summarizeChunk(chunk) {
|
|
8651
8842
|
const historyText = chunk.map((msg) => {
|
|
8652
|
-
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
8843
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(stripBinaryContentForSummary(msg.content));
|
|
8653
8844
|
return `[${msg.role}]: ${content}`;
|
|
8654
8845
|
}).join("\n\n");
|
|
8655
8846
|
try {
|
|
@@ -8897,6 +9088,127 @@ var init_persistence = __esm({
|
|
|
8897
9088
|
});
|
|
8898
9089
|
|
|
8899
9090
|
// src/integrations/slack/client.ts
|
|
9091
|
+
var client_exports = {};
|
|
9092
|
+
__export(client_exports, {
|
|
9093
|
+
LOADING_REACTION: () => LOADING_REACTION,
|
|
9094
|
+
RESULT_REACTIONS: () => RESULT_REACTIONS,
|
|
9095
|
+
addLoadingReaction: () => addLoadingReaction,
|
|
9096
|
+
addResultReaction: () => addResultReaction,
|
|
9097
|
+
botParticipatedInThread: () => botParticipatedInThread,
|
|
9098
|
+
ensureSlackSelfIdentity: () => ensureSlackSelfIdentity,
|
|
9099
|
+
getCachedSlackSelfIdentity: () => getCachedSlackSelfIdentity,
|
|
9100
|
+
getDefaultOrchestratorName: () => getDefaultOrchestratorName,
|
|
9101
|
+
getSlackAdapter: () => getSlackAdapter,
|
|
9102
|
+
getSlackAllowlistPolicy: () => getSlackAllowlistPolicy,
|
|
9103
|
+
getSlackBotToken: () => getSlackBotToken,
|
|
9104
|
+
getSlackDeniedReplyPolicy: () => getSlackDeniedReplyPolicy,
|
|
9105
|
+
getSlackSigningSecret: () => getSlackSigningSecret,
|
|
9106
|
+
isSlackConfigured: () => isSlackConfigured,
|
|
9107
|
+
normalizeSlackMentions: () => normalizeSlackMentions,
|
|
9108
|
+
noteBotPostedInThread: () => noteBotPostedInThread,
|
|
9109
|
+
postThreadMessage: () => postThreadMessage,
|
|
9110
|
+
removeLoadingReaction: () => removeLoadingReaction,
|
|
9111
|
+
resolveSlackUserInfo: () => resolveSlackUserInfo,
|
|
9112
|
+
resolveSlackUserName: () => resolveSlackUserName
|
|
9113
|
+
});
|
|
9114
|
+
function slackBackoffMs(attempt) {
|
|
9115
|
+
const expo = SLACK_BACKOFF_BASE_MS * 2 ** attempt;
|
|
9116
|
+
const jitter = Math.floor(Math.random() * SLACK_BACKOFF_BASE_MS);
|
|
9117
|
+
return Math.min(expo + jitter, SLACK_BACKOFF_CAP_MS);
|
|
9118
|
+
}
|
|
9119
|
+
async function slackFetchWithRetry(url, init, attempts = SLACK_FETCH_ATTEMPTS) {
|
|
9120
|
+
let lastErr;
|
|
9121
|
+
for (let i = 0; i < attempts; i++) {
|
|
9122
|
+
const isLast = i === attempts - 1;
|
|
9123
|
+
try {
|
|
9124
|
+
const res = await fetch(url, init);
|
|
9125
|
+
if ((res.status === 429 || res.status >= 500) && !isLast) {
|
|
9126
|
+
const ra = Number(res.headers.get("retry-after"));
|
|
9127
|
+
const waitMs = Number.isFinite(ra) && ra > 0 ? Math.min(ra * 1e3, SLACK_BACKOFF_CAP_MS) : slackBackoffMs(i);
|
|
9128
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
9129
|
+
continue;
|
|
9130
|
+
}
|
|
9131
|
+
return res;
|
|
9132
|
+
} catch (err) {
|
|
9133
|
+
lastErr = err;
|
|
9134
|
+
if (isLast) throw err;
|
|
9135
|
+
await new Promise((r) => setTimeout(r, slackBackoffMs(i)));
|
|
9136
|
+
}
|
|
9137
|
+
}
|
|
9138
|
+
throw lastErr ?? new Error("slack fetch failed");
|
|
9139
|
+
}
|
|
9140
|
+
function reactionKey(channel, ts) {
|
|
9141
|
+
return `${channel}\u241F${ts}`;
|
|
9142
|
+
}
|
|
9143
|
+
async function addLoadingReaction(channel, timestamp) {
|
|
9144
|
+
const adapter = getSlackAdapter();
|
|
9145
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
9146
|
+
const key2 = reactionKey(channel, timestamp);
|
|
9147
|
+
const inFlight = (async () => {
|
|
9148
|
+
try {
|
|
9149
|
+
const res = await adapter.addReaction({ channel, timestamp, name: LOADING_REACTION });
|
|
9150
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
9151
|
+
console.warn(`[slack] addReaction ${LOADING_REACTION} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
9152
|
+
}
|
|
9153
|
+
return res;
|
|
9154
|
+
} catch (err) {
|
|
9155
|
+
console.warn(`[slack] addReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
9156
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
9157
|
+
}
|
|
9158
|
+
})();
|
|
9159
|
+
pendingAdds.set(key2, inFlight);
|
|
9160
|
+
void inFlight.finally(() => {
|
|
9161
|
+
if (pendingAdds.get(key2) === inFlight) pendingAdds.delete(key2);
|
|
9162
|
+
});
|
|
9163
|
+
return inFlight;
|
|
9164
|
+
}
|
|
9165
|
+
async function removeLoadingReaction(channel, timestamp) {
|
|
9166
|
+
const adapter = getSlackAdapter();
|
|
9167
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
9168
|
+
const pending = pendingAdds.get(reactionKey(channel, timestamp));
|
|
9169
|
+
if (pending) {
|
|
9170
|
+
try {
|
|
9171
|
+
await pending;
|
|
9172
|
+
} catch {
|
|
9173
|
+
}
|
|
9174
|
+
}
|
|
9175
|
+
try {
|
|
9176
|
+
const res = await adapter.removeReaction({ channel, timestamp, name: LOADING_REACTION });
|
|
9177
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
9178
|
+
console.warn(`[slack] removeReaction ${LOADING_REACTION} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
9179
|
+
}
|
|
9180
|
+
return res;
|
|
9181
|
+
} catch (err) {
|
|
9182
|
+
console.warn(`[slack] removeReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
9183
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
9184
|
+
}
|
|
9185
|
+
}
|
|
9186
|
+
async function addResultReaction(channel, timestamp, state2) {
|
|
9187
|
+
const name = RESULT_REACTIONS[state2];
|
|
9188
|
+
if (!name) return { ok: false, error: `no_reaction_for_state:${state2}` };
|
|
9189
|
+
const adapter = getSlackAdapter();
|
|
9190
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
9191
|
+
try {
|
|
9192
|
+
const res = await adapter.addReaction({ channel, timestamp, name });
|
|
9193
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
9194
|
+
console.warn(`[slack] addReaction ${name} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
9195
|
+
}
|
|
9196
|
+
return res;
|
|
9197
|
+
} catch (err) {
|
|
9198
|
+
console.warn(`[slack] addResultReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
9199
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
9200
|
+
}
|
|
9201
|
+
}
|
|
9202
|
+
async function postThreadMessage(channel, threadTs, text) {
|
|
9203
|
+
const adapter = getSlackAdapter();
|
|
9204
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
9205
|
+
try {
|
|
9206
|
+
return await adapter.postMessage({ channel, text, threadTs });
|
|
9207
|
+
} catch (err) {
|
|
9208
|
+
console.warn(`[slack] postThreadMessage threw on ${channel}/${threadTs}:`, err?.message || err);
|
|
9209
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
9210
|
+
}
|
|
9211
|
+
}
|
|
8900
9212
|
function readSlackConfig() {
|
|
8901
9213
|
try {
|
|
8902
9214
|
const cfg = getConfig();
|
|
@@ -8914,9 +9226,25 @@ function readSlackConfig() {
|
|
|
8914
9226
|
function getSlackAdapter() {
|
|
8915
9227
|
const cfg = readSlackConfig();
|
|
8916
9228
|
if (!cfg) return void 0;
|
|
9229
|
+
const slackForm = async (endpoint, params) => {
|
|
9230
|
+
const body = new URLSearchParams(params).toString();
|
|
9231
|
+
const res = await fetch(`https://slack.com/api/${endpoint}`, {
|
|
9232
|
+
method: "POST",
|
|
9233
|
+
headers: {
|
|
9234
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
9235
|
+
Authorization: `Bearer ${cfg.botToken}`
|
|
9236
|
+
},
|
|
9237
|
+
body
|
|
9238
|
+
});
|
|
9239
|
+
const data = await res.json().catch(() => ({}));
|
|
9240
|
+
if (!res.ok || data?.ok === false) {
|
|
9241
|
+
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
9242
|
+
}
|
|
9243
|
+
return { ok: true };
|
|
9244
|
+
};
|
|
8917
9245
|
return {
|
|
8918
9246
|
async postMessage({ channel, text, threadTs }) {
|
|
8919
|
-
const res = await
|
|
9247
|
+
const res = await slackFetchWithRetry("https://slack.com/api/chat.postMessage", {
|
|
8920
9248
|
method: "POST",
|
|
8921
9249
|
headers: {
|
|
8922
9250
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -8929,6 +9257,12 @@ function getSlackAdapter() {
|
|
|
8929
9257
|
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
8930
9258
|
}
|
|
8931
9259
|
return { ok: true, ts: data?.ts };
|
|
9260
|
+
},
|
|
9261
|
+
addReaction({ channel, timestamp, name }) {
|
|
9262
|
+
return slackForm("reactions.add", { channel, timestamp, name });
|
|
9263
|
+
},
|
|
9264
|
+
removeReaction({ channel, timestamp, name }) {
|
|
9265
|
+
return slackForm("reactions.remove", { channel, timestamp, name });
|
|
8932
9266
|
}
|
|
8933
9267
|
};
|
|
8934
9268
|
}
|
|
@@ -9129,12 +9463,31 @@ function getSlackDeniedReplyPolicy() {
|
|
|
9129
9463
|
return { enabled: true, template: DEFAULT_DENIED_TEMPLATE };
|
|
9130
9464
|
}
|
|
9131
9465
|
}
|
|
9132
|
-
var cachedSelf, selfInflight, USER_TTL_MS, USER_FAIL_TTL_MS, userInflight, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS, threadOwnedInflight, DEFAULT_DENIED_TEMPLATE;
|
|
9466
|
+
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;
|
|
9133
9467
|
var init_client3 = __esm({
|
|
9134
9468
|
"src/integrations/slack/client.ts"() {
|
|
9135
9469
|
"use strict";
|
|
9136
9470
|
init_config();
|
|
9137
9471
|
init_persistence();
|
|
9472
|
+
LOADING_REACTION = "hourglass_flowing_sand";
|
|
9473
|
+
RESULT_REACTIONS = {
|
|
9474
|
+
responded: "white_check_mark",
|
|
9475
|
+
skipped: "zzz",
|
|
9476
|
+
handed_off: "eyes",
|
|
9477
|
+
failed: "warning"
|
|
9478
|
+
};
|
|
9479
|
+
SLACK_FETCH_ATTEMPTS = 3;
|
|
9480
|
+
SLACK_BACKOFF_BASE_MS = 400;
|
|
9481
|
+
SLACK_BACKOFF_CAP_MS = 3e4;
|
|
9482
|
+
REACTION_SOFT_ERRORS = /* @__PURE__ */ new Set([
|
|
9483
|
+
"already_reacted",
|
|
9484
|
+
// add: someone (or we) already added this emoji
|
|
9485
|
+
"no_reaction",
|
|
9486
|
+
// remove: the emoji isn't on the message
|
|
9487
|
+
"message_not_found"
|
|
9488
|
+
// remove/add: original message deleted
|
|
9489
|
+
]);
|
|
9490
|
+
pendingAdds = /* @__PURE__ */ new Map();
|
|
9138
9491
|
cachedSelf = null;
|
|
9139
9492
|
selfInflight = null;
|
|
9140
9493
|
USER_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -9147,83 +9500,541 @@ var init_client3 = __esm({
|
|
|
9147
9500
|
}
|
|
9148
9501
|
});
|
|
9149
9502
|
|
|
9150
|
-
// src/
|
|
9151
|
-
function
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
}
|
|
9157
|
-
|
|
9158
|
-
|
|
9503
|
+
// src/agent/session-lock.ts
|
|
9504
|
+
async function withSessionLock(sessionId, fn) {
|
|
9505
|
+
let state2 = locks.get(sessionId);
|
|
9506
|
+
if (!state2) {
|
|
9507
|
+
state2 = { tail: Promise.resolve(), pending: 0 };
|
|
9508
|
+
locks.set(sessionId, state2);
|
|
9509
|
+
}
|
|
9510
|
+
state2.pending++;
|
|
9511
|
+
const prev = state2.tail;
|
|
9512
|
+
let release;
|
|
9513
|
+
const next = new Promise((resolve14) => {
|
|
9514
|
+
release = resolve14;
|
|
9515
|
+
});
|
|
9516
|
+
state2.tail = prev.then(() => next);
|
|
9517
|
+
await prev;
|
|
9518
|
+
try {
|
|
9519
|
+
return await fn();
|
|
9520
|
+
} finally {
|
|
9521
|
+
release();
|
|
9522
|
+
state2.pending--;
|
|
9523
|
+
if (state2.pending === 0 && locks.get(sessionId) === state2) {
|
|
9524
|
+
locks.delete(sessionId);
|
|
9525
|
+
}
|
|
9526
|
+
}
|
|
9159
9527
|
}
|
|
9160
|
-
function
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
9164
|
-
return false;
|
|
9528
|
+
function isSessionLocked(sessionId) {
|
|
9529
|
+
const s = locks.get(sessionId);
|
|
9530
|
+
return !!s && s.pending > 0;
|
|
9165
9531
|
}
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
return { event: null, dropReason: "bot_message" };
|
|
9172
|
-
}
|
|
9173
|
-
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
9174
|
-
return { event: null, dropReason: "ignored_subtype" };
|
|
9532
|
+
var locks;
|
|
9533
|
+
var init_session_lock = __esm({
|
|
9534
|
+
"src/agent/session-lock.ts"() {
|
|
9535
|
+
"use strict";
|
|
9536
|
+
locks = /* @__PURE__ */ new Map();
|
|
9175
9537
|
}
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9538
|
+
});
|
|
9539
|
+
|
|
9540
|
+
// src/orchestrator/webhook-events.ts
|
|
9541
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
9542
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
9543
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
9544
|
+
function logFilePath() {
|
|
9545
|
+
return join10(getAppDataDirectory(), "webhook-events.jsonl");
|
|
9546
|
+
}
|
|
9547
|
+
function ensureLoaded() {
|
|
9548
|
+
if (cache !== null) return cache;
|
|
9549
|
+
cache = [];
|
|
9550
|
+
try {
|
|
9551
|
+
const p = logFilePath();
|
|
9552
|
+
if (!existsSync17(p)) return cache;
|
|
9553
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
9554
|
+
for (const line of lines) {
|
|
9555
|
+
try {
|
|
9556
|
+
cache.push(JSON.parse(line));
|
|
9557
|
+
} catch {
|
|
9558
|
+
}
|
|
9183
9559
|
}
|
|
9184
|
-
if (
|
|
9185
|
-
|
|
9560
|
+
if (cache.length > MAX_EVENTS) {
|
|
9561
|
+
cache = cache.slice(-MAX_EVENTS);
|
|
9562
|
+
try {
|
|
9563
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
9564
|
+
} catch {
|
|
9565
|
+
}
|
|
9186
9566
|
}
|
|
9187
|
-
|
|
9567
|
+
} catch {
|
|
9188
9568
|
}
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
const
|
|
9193
|
-
|
|
9194
|
-
if (
|
|
9195
|
-
|
|
9196
|
-
|
|
9197
|
-
}
|
|
9198
|
-
|
|
9199
|
-
|
|
9200
|
-
if (channelAllowlistActive && !policy.allowedChannels.includes(event.channel)) {
|
|
9201
|
-
return { event: null, dropReason: "channel_not_allowed" };
|
|
9202
|
-
}
|
|
9203
|
-
if (!userOk) {
|
|
9204
|
-
return { event: null, dropReason: "user_not_allowed" };
|
|
9205
|
-
}
|
|
9569
|
+
return cache;
|
|
9570
|
+
}
|
|
9571
|
+
function appendEvent(ev) {
|
|
9572
|
+
const list = ensureLoaded();
|
|
9573
|
+
list.push(ev);
|
|
9574
|
+
if (list.length > MAX_EVENTS) list.shift();
|
|
9575
|
+
try {
|
|
9576
|
+
const p = logFilePath();
|
|
9577
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
9578
|
+
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
9579
|
+
} catch {
|
|
9206
9580
|
}
|
|
9207
|
-
const ref = {
|
|
9208
|
-
channel: "slack",
|
|
9209
|
-
slackChannel: event.channel,
|
|
9210
|
-
threadTs: event.thread_ts || event.ts,
|
|
9211
|
-
teamId: event.team,
|
|
9212
|
-
user: event.user
|
|
9213
|
-
};
|
|
9214
|
-
const label = slackChannel.displayLabel(ref);
|
|
9215
|
-
return {
|
|
9216
|
-
event: {
|
|
9217
|
-
ref,
|
|
9218
|
-
content: `[${label}] ${text}`,
|
|
9219
|
-
wake: "now",
|
|
9220
|
-
enqueuedAt: /* @__PURE__ */ new Date()
|
|
9221
|
-
}
|
|
9222
|
-
};
|
|
9223
9581
|
}
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9582
|
+
function recordEvent(ev) {
|
|
9583
|
+
const full = {
|
|
9584
|
+
id: ev.id ?? nanoid4(),
|
|
9585
|
+
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9586
|
+
source: ev.source,
|
|
9587
|
+
status: ev.status,
|
|
9588
|
+
subtype: ev.subtype,
|
|
9589
|
+
channel: ev.channel,
|
|
9590
|
+
user: ev.user,
|
|
9591
|
+
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
9592
|
+
dropReason: ev.dropReason,
|
|
9593
|
+
error: ev.error,
|
|
9594
|
+
sessionId: ev.sessionId,
|
|
9595
|
+
durationMs: ev.durationMs,
|
|
9596
|
+
meta: ev.meta
|
|
9597
|
+
};
|
|
9598
|
+
appendEvent(full);
|
|
9599
|
+
return full.id;
|
|
9600
|
+
}
|
|
9601
|
+
function updateEvent(id, patch) {
|
|
9602
|
+
const list = ensureLoaded();
|
|
9603
|
+
const i = list.findIndex((e) => e.id === id);
|
|
9604
|
+
if (i < 0) return;
|
|
9605
|
+
list[i] = { ...list[i], ...patch };
|
|
9606
|
+
try {
|
|
9607
|
+
const p = logFilePath();
|
|
9608
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
9609
|
+
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
9610
|
+
} catch {
|
|
9611
|
+
}
|
|
9612
|
+
}
|
|
9613
|
+
function listEvents(filter = {}) {
|
|
9614
|
+
const list = ensureLoaded();
|
|
9615
|
+
const q = filter.q?.toLowerCase();
|
|
9616
|
+
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
9617
|
+
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
9618
|
+
const matched = list.filter((e) => {
|
|
9619
|
+
if (filter.source && e.source !== filter.source) return false;
|
|
9620
|
+
if (filter.status && e.status !== filter.status) return false;
|
|
9621
|
+
const t = Date.parse(e.ts);
|
|
9622
|
+
if (t < sinceTs) return false;
|
|
9623
|
+
if (t >= beforeTs) return false;
|
|
9624
|
+
if (q) {
|
|
9625
|
+
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
9626
|
+
if (!hay.includes(q)) return false;
|
|
9627
|
+
}
|
|
9628
|
+
return true;
|
|
9629
|
+
});
|
|
9630
|
+
matched.reverse();
|
|
9631
|
+
const offset = Math.max(0, filter.offset ?? 0);
|
|
9632
|
+
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
9633
|
+
return {
|
|
9634
|
+
events: matched.slice(offset, offset + limit),
|
|
9635
|
+
total: matched.length
|
|
9636
|
+
};
|
|
9637
|
+
}
|
|
9638
|
+
function clearAllEvents() {
|
|
9639
|
+
cache = [];
|
|
9640
|
+
try {
|
|
9641
|
+
writeFileSync4(logFilePath(), "");
|
|
9642
|
+
} catch {
|
|
9643
|
+
}
|
|
9644
|
+
}
|
|
9645
|
+
var MAX_EVENTS, cache;
|
|
9646
|
+
var init_webhook_events = __esm({
|
|
9647
|
+
"src/orchestrator/webhook-events.ts"() {
|
|
9648
|
+
"use strict";
|
|
9649
|
+
init_config();
|
|
9650
|
+
MAX_EVENTS = 1e3;
|
|
9651
|
+
cache = null;
|
|
9652
|
+
}
|
|
9653
|
+
});
|
|
9654
|
+
|
|
9655
|
+
// src/orchestrator/inbox.ts
|
|
9656
|
+
var inbox_exports = {};
|
|
9657
|
+
__export(inbox_exports, {
|
|
9658
|
+
clearInbox: () => clearInbox,
|
|
9659
|
+
flush: () => flush,
|
|
9660
|
+
peekInbox: () => peekInbox,
|
|
9661
|
+
pushToInbox: () => pushToInbox,
|
|
9662
|
+
setFlushHandler: () => setFlushHandler
|
|
9663
|
+
});
|
|
9664
|
+
function setFlushHandler(fn) {
|
|
9665
|
+
flushHandler = fn;
|
|
9666
|
+
}
|
|
9667
|
+
function entryFor(sessionId) {
|
|
9668
|
+
let e = inboxes.get(sessionId);
|
|
9669
|
+
if (!e) {
|
|
9670
|
+
e = { pending: [] };
|
|
9671
|
+
inboxes.set(sessionId, e);
|
|
9672
|
+
}
|
|
9673
|
+
return e;
|
|
9674
|
+
}
|
|
9675
|
+
function pushToInbox(orchestratorSessionId, event) {
|
|
9676
|
+
const e = entryFor(orchestratorSessionId);
|
|
9677
|
+
e.pending.push(event);
|
|
9678
|
+
try {
|
|
9679
|
+
trackInbound(orchestratorSessionId, event);
|
|
9680
|
+
} catch {
|
|
9681
|
+
}
|
|
9682
|
+
if (event.wake === "now") {
|
|
9683
|
+
scheduleFlush(orchestratorSessionId);
|
|
9684
|
+
}
|
|
9685
|
+
}
|
|
9686
|
+
function scheduleFlush(sessionId) {
|
|
9687
|
+
const e = inboxes.get(sessionId);
|
|
9688
|
+
if (!e) return;
|
|
9689
|
+
if (e.timer) clearTimeout(e.timer);
|
|
9690
|
+
e.timer = setTimeout(() => {
|
|
9691
|
+
void flush(sessionId);
|
|
9692
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
9693
|
+
}
|
|
9694
|
+
async function flush(sessionId) {
|
|
9695
|
+
const e = inboxes.get(sessionId);
|
|
9696
|
+
if (!e) return;
|
|
9697
|
+
if (e.timer) {
|
|
9698
|
+
clearTimeout(e.timer);
|
|
9699
|
+
e.timer = void 0;
|
|
9700
|
+
}
|
|
9701
|
+
const events = e.pending.splice(0);
|
|
9702
|
+
if (events.length === 0) return;
|
|
9703
|
+
if (!flushHandler) {
|
|
9704
|
+
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
9705
|
+
return;
|
|
9706
|
+
}
|
|
9707
|
+
try {
|
|
9708
|
+
await flushHandler(sessionId, events);
|
|
9709
|
+
} catch (err) {
|
|
9710
|
+
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
9711
|
+
}
|
|
9712
|
+
}
|
|
9713
|
+
function peekInbox(sessionId) {
|
|
9714
|
+
return inboxes.get(sessionId)?.pending.slice() ?? [];
|
|
9715
|
+
}
|
|
9716
|
+
function clearInbox(sessionId) {
|
|
9717
|
+
const e = inboxes.get(sessionId);
|
|
9718
|
+
if (!e) return;
|
|
9719
|
+
if (e.timer) {
|
|
9720
|
+
clearTimeout(e.timer);
|
|
9721
|
+
e.timer = void 0;
|
|
9722
|
+
}
|
|
9723
|
+
e.pending.length = 0;
|
|
9724
|
+
}
|
|
9725
|
+
var inboxes, FLUSH_DEBOUNCE_MS, flushHandler;
|
|
9726
|
+
var init_inbox = __esm({
|
|
9727
|
+
"src/orchestrator/inbox.ts"() {
|
|
9728
|
+
"use strict";
|
|
9729
|
+
init_inbox_acks();
|
|
9730
|
+
inboxes = /* @__PURE__ */ new Map();
|
|
9731
|
+
FLUSH_DEBOUNCE_MS = 200;
|
|
9732
|
+
flushHandler = null;
|
|
9733
|
+
}
|
|
9734
|
+
});
|
|
9735
|
+
|
|
9736
|
+
// src/orchestrator/inbox-acks.ts
|
|
9737
|
+
var inbox_acks_exports = {};
|
|
9738
|
+
__export(inbox_acks_exports, {
|
|
9739
|
+
MAX_ATTEMPTS: () => MAX_ATTEMPTS,
|
|
9740
|
+
RECONCILE_EVERY_MS: () => RECONCILE_EVERY_MS,
|
|
9741
|
+
REPLAY_AFTER_MS: () => REPLAY_AFTER_MS,
|
|
9742
|
+
__getAck: () => __getAck,
|
|
9743
|
+
__listAcks: () => __listAcks,
|
|
9744
|
+
__resetAcks: () => __resetAcks,
|
|
9745
|
+
eventKey: () => eventKey,
|
|
9746
|
+
markRespondedForThread: () => markRespondedForThread,
|
|
9747
|
+
markState: () => markState,
|
|
9748
|
+
reconcileOnce: () => reconcileOnce,
|
|
9749
|
+
resolveBatchOnTurnEnd: () => resolveBatchOnTurnEnd,
|
|
9750
|
+
startReconciler: () => startReconciler,
|
|
9751
|
+
stopReconciler: () => stopReconciler,
|
|
9752
|
+
trackInbound: () => trackInbound
|
|
9753
|
+
});
|
|
9754
|
+
function eventKey(event) {
|
|
9755
|
+
const ref = event.ref;
|
|
9756
|
+
const ch = ref?.channel ?? "unknown";
|
|
9757
|
+
switch (ch) {
|
|
9758
|
+
case "slack":
|
|
9759
|
+
return `slack${SEP}${ref.slackChannel}${SEP}${ref.messageTs ?? ref.threadTs ?? ""}`;
|
|
9760
|
+
case "system":
|
|
9761
|
+
return `system${SEP}${ref.workerId}${SEP}${ref.kind}`;
|
|
9762
|
+
case "webhook":
|
|
9763
|
+
return `webhook${SEP}${ref.webhookId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
9764
|
+
case "schedule":
|
|
9765
|
+
return `schedule${SEP}${ref.scheduleId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
9766
|
+
default:
|
|
9767
|
+
return `${ch}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}${SEP}${event.content.slice(0, 40)}`;
|
|
9768
|
+
}
|
|
9769
|
+
}
|
|
9770
|
+
function trackInbound(sessionId, event) {
|
|
9771
|
+
if (event.wake === "never") return null;
|
|
9772
|
+
const key2 = eventKey(event);
|
|
9773
|
+
const existing = ledger.get(key2);
|
|
9774
|
+
if (existing) return key2;
|
|
9775
|
+
const ref = event.ref;
|
|
9776
|
+
const now = Date.now();
|
|
9777
|
+
const entry2 = {
|
|
9778
|
+
key: key2,
|
|
9779
|
+
sessionId,
|
|
9780
|
+
event,
|
|
9781
|
+
channel: ref?.channel ?? "unknown",
|
|
9782
|
+
state: "working",
|
|
9783
|
+
attempts: 0,
|
|
9784
|
+
trackedAt: now,
|
|
9785
|
+
updatedAt: now
|
|
9786
|
+
};
|
|
9787
|
+
if (ref?.channel === "slack") {
|
|
9788
|
+
entry2.slackChannel = ref.slackChannel;
|
|
9789
|
+
entry2.threadTs = ref.threadTs;
|
|
9790
|
+
entry2.messageTs = ref.messageTs;
|
|
9791
|
+
}
|
|
9792
|
+
ledger.set(key2, entry2);
|
|
9793
|
+
if (ledger.size > MAX_ENTRIES) pruneOldest();
|
|
9794
|
+
return key2;
|
|
9795
|
+
}
|
|
9796
|
+
function markState(key2, state2) {
|
|
9797
|
+
const entry2 = ledger.get(key2);
|
|
9798
|
+
if (!entry2) return;
|
|
9799
|
+
if (TERMINAL.has(entry2.state)) return;
|
|
9800
|
+
entry2.state = state2;
|
|
9801
|
+
entry2.updatedAt = Date.now();
|
|
9802
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
9803
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, state2);
|
|
9804
|
+
}
|
|
9805
|
+
}
|
|
9806
|
+
function markRespondedForThread(slackChannel2, threadTs) {
|
|
9807
|
+
if (!slackChannel2 || !threadTs) return;
|
|
9808
|
+
for (const entry2 of ledger.values()) {
|
|
9809
|
+
if (entry2.channel === "slack" && entry2.state === "working" && entry2.slackChannel === slackChannel2 && entry2.threadTs === threadTs) {
|
|
9810
|
+
markState(entry2.key, "responded");
|
|
9811
|
+
}
|
|
9812
|
+
}
|
|
9813
|
+
}
|
|
9814
|
+
function resolveBatchOnTurnEnd(events, ok) {
|
|
9815
|
+
if (!ok) return;
|
|
9816
|
+
for (const ev of events) {
|
|
9817
|
+
const key2 = eventKey(ev);
|
|
9818
|
+
const entry2 = ledger.get(key2);
|
|
9819
|
+
if (!entry2 || entry2.state !== "working") continue;
|
|
9820
|
+
if (entry2.channel === "slack") continue;
|
|
9821
|
+
markState(key2, "responded");
|
|
9822
|
+
}
|
|
9823
|
+
}
|
|
9824
|
+
async function reconcileOnce(now = Date.now()) {
|
|
9825
|
+
let pushToInbox2 = null;
|
|
9826
|
+
const toReplay = [];
|
|
9827
|
+
for (const entry2 of ledger.values()) {
|
|
9828
|
+
if (TERMINAL.has(entry2.state)) {
|
|
9829
|
+
if (now - entry2.updatedAt > PRUNE_AFTER_MS) ledger.delete(entry2.key);
|
|
9830
|
+
continue;
|
|
9831
|
+
}
|
|
9832
|
+
if (isSessionLocked(entry2.sessionId)) continue;
|
|
9833
|
+
if (now - entry2.updatedAt < REPLAY_AFTER_MS) continue;
|
|
9834
|
+
if (entry2.attempts >= MAX_ATTEMPTS) {
|
|
9835
|
+
failEntry(entry2);
|
|
9836
|
+
continue;
|
|
9837
|
+
}
|
|
9838
|
+
toReplay.push(entry2);
|
|
9839
|
+
}
|
|
9840
|
+
if (toReplay.length === 0) return;
|
|
9841
|
+
try {
|
|
9842
|
+
({ pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports)));
|
|
9843
|
+
} catch {
|
|
9844
|
+
return;
|
|
9845
|
+
}
|
|
9846
|
+
for (const entry2 of toReplay) {
|
|
9847
|
+
entry2.attempts += 1;
|
|
9848
|
+
entry2.updatedAt = Date.now();
|
|
9849
|
+
const nudged = {
|
|
9850
|
+
...entry2.event,
|
|
9851
|
+
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.]
|
|
9852
|
+
${entry2.event.content}`,
|
|
9853
|
+
wake: "now"
|
|
9854
|
+
};
|
|
9855
|
+
try {
|
|
9856
|
+
pushToInbox2(entry2.sessionId, nudged);
|
|
9857
|
+
} catch {
|
|
9858
|
+
}
|
|
9859
|
+
}
|
|
9860
|
+
}
|
|
9861
|
+
function failEntry(entry2) {
|
|
9862
|
+
entry2.state = "failed";
|
|
9863
|
+
entry2.updatedAt = Date.now();
|
|
9864
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
9865
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, "failed");
|
|
9866
|
+
if (entry2.threadTs) {
|
|
9867
|
+
fireFallback(
|
|
9868
|
+
entry2.slackChannel,
|
|
9869
|
+
entry2.threadTs,
|
|
9870
|
+
`: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.`
|
|
9871
|
+
);
|
|
9872
|
+
}
|
|
9873
|
+
}
|
|
9874
|
+
recordEvent({
|
|
9875
|
+
source: "daemon",
|
|
9876
|
+
status: "failed",
|
|
9877
|
+
channel: entry2.channel,
|
|
9878
|
+
sessionId: entry2.sessionId,
|
|
9879
|
+
error: `unacknowledged after ${entry2.attempts} replay attempt(s)`,
|
|
9880
|
+
textSnippet: entry2.event.content.slice(0, 200),
|
|
9881
|
+
meta: { ackKey: entry2.key, ackState: "failed" }
|
|
9882
|
+
});
|
|
9883
|
+
}
|
|
9884
|
+
function pruneOldest() {
|
|
9885
|
+
const terminal = [];
|
|
9886
|
+
for (const e of ledger.values()) if (TERMINAL.has(e.state)) terminal.push(e);
|
|
9887
|
+
terminal.sort((a, b) => a.updatedAt - b.updatedAt);
|
|
9888
|
+
for (const e of terminal) {
|
|
9889
|
+
if (ledger.size <= MAX_ENTRIES) break;
|
|
9890
|
+
ledger.delete(e.key);
|
|
9891
|
+
}
|
|
9892
|
+
while (ledger.size > MAX_ENTRIES) {
|
|
9893
|
+
const oldest = ledger.keys().next().value;
|
|
9894
|
+
if (!oldest) break;
|
|
9895
|
+
ledger.delete(oldest);
|
|
9896
|
+
}
|
|
9897
|
+
}
|
|
9898
|
+
function fireResultReaction(channel, ts, state2) {
|
|
9899
|
+
if (typeof addResultReaction !== "function") return;
|
|
9900
|
+
try {
|
|
9901
|
+
void Promise.resolve(addResultReaction(channel, ts, state2)).catch(() => {
|
|
9902
|
+
});
|
|
9903
|
+
} catch {
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
function fireFallback(channel, threadTs, text) {
|
|
9907
|
+
if (typeof postThreadMessage !== "function") return;
|
|
9908
|
+
try {
|
|
9909
|
+
void Promise.resolve(postThreadMessage(channel, threadTs, text)).catch(() => {
|
|
9910
|
+
});
|
|
9911
|
+
} catch {
|
|
9912
|
+
}
|
|
9913
|
+
}
|
|
9914
|
+
function startReconciler() {
|
|
9915
|
+
if (reconcileTimer) return;
|
|
9916
|
+
reconcileTimer = setInterval(() => {
|
|
9917
|
+
void reconcileOnce();
|
|
9918
|
+
}, RECONCILE_EVERY_MS);
|
|
9919
|
+
if (typeof reconcileTimer.unref === "function") reconcileTimer.unref();
|
|
9920
|
+
}
|
|
9921
|
+
function stopReconciler() {
|
|
9922
|
+
if (reconcileTimer) {
|
|
9923
|
+
clearInterval(reconcileTimer);
|
|
9924
|
+
reconcileTimer = null;
|
|
9925
|
+
}
|
|
9926
|
+
}
|
|
9927
|
+
function __getAck(key2) {
|
|
9928
|
+
return ledger.get(key2);
|
|
9929
|
+
}
|
|
9930
|
+
function __listAcks() {
|
|
9931
|
+
return [...ledger.values()];
|
|
9932
|
+
}
|
|
9933
|
+
function __resetAcks() {
|
|
9934
|
+
ledger.clear();
|
|
9935
|
+
}
|
|
9936
|
+
var REPLAY_AFTER_MS, RECONCILE_EVERY_MS, MAX_ATTEMPTS, PRUNE_AFTER_MS, MAX_ENTRIES, TERMINAL, SEP, ledger, reconcileTimer;
|
|
9937
|
+
var init_inbox_acks = __esm({
|
|
9938
|
+
"src/orchestrator/inbox-acks.ts"() {
|
|
9939
|
+
"use strict";
|
|
9940
|
+
init_session_lock();
|
|
9941
|
+
init_webhook_events();
|
|
9942
|
+
init_client3();
|
|
9943
|
+
REPLAY_AFTER_MS = 3 * 6e4;
|
|
9944
|
+
RECONCILE_EVERY_MS = 6e4;
|
|
9945
|
+
MAX_ATTEMPTS = 2;
|
|
9946
|
+
PRUNE_AFTER_MS = 60 * 6e4;
|
|
9947
|
+
MAX_ENTRIES = 5e3;
|
|
9948
|
+
TERMINAL = /* @__PURE__ */ new Set(["responded", "skipped", "handed_off", "failed"]);
|
|
9949
|
+
SEP = "\u241F";
|
|
9950
|
+
ledger = /* @__PURE__ */ new Map();
|
|
9951
|
+
reconcileTimer = null;
|
|
9952
|
+
}
|
|
9953
|
+
});
|
|
9954
|
+
|
|
9955
|
+
// src/integrations/channels/slack.ts
|
|
9956
|
+
function threadKey(channel, threadTs) {
|
|
9957
|
+
return `${channel}\u241F${threadTs}`;
|
|
9958
|
+
}
|
|
9959
|
+
function markThreadOwned(channel, threadTs) {
|
|
9960
|
+
ownedThreads.add(threadKey(channel, threadTs));
|
|
9961
|
+
}
|
|
9962
|
+
function isThreadOwned(channel, threadTs) {
|
|
9963
|
+
return ownedThreads.has(threadKey(channel, threadTs));
|
|
9964
|
+
}
|
|
9965
|
+
function isSelfAuthored(event, self) {
|
|
9966
|
+
if (!self) return true;
|
|
9967
|
+
if (self.botId && event.bot_id && event.bot_id === self.botId) return true;
|
|
9968
|
+
if (self.botUserId && event.user && event.user === self.botUserId) return true;
|
|
9969
|
+
return false;
|
|
9970
|
+
}
|
|
9971
|
+
function slackEventToInboundResult(event, opts = {}) {
|
|
9972
|
+
if (!event) return { event: null, dropReason: "empty_text" };
|
|
9973
|
+
const self = opts.self ?? getCachedSlackSelfIdentity();
|
|
9974
|
+
const isBotAuthored = !!event.bot_id || event.type === "message" && event.subtype === "bot_message";
|
|
9975
|
+
if (isBotAuthored && isSelfAuthored(event, self)) {
|
|
9976
|
+
return { event: null, dropReason: "bot_message" };
|
|
9977
|
+
}
|
|
9978
|
+
if (event.type === "message" && event.subtype && IGNORED_MESSAGE_SUBTYPES.has(event.subtype)) {
|
|
9979
|
+
return { event: null, dropReason: "ignored_subtype" };
|
|
9980
|
+
}
|
|
9981
|
+
const isDm = event.type === "message" && event.channel_type === "im";
|
|
9982
|
+
const isThreadReply = event.type === "message" && !isDm && typeof event.thread_ts === "string" && event.thread_ts !== event.ts;
|
|
9983
|
+
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.
|
|
9984
|
+
typeof event.channel === "string");
|
|
9985
|
+
if (event.type !== "app_mention" && !isDm && !isThreadReply) {
|
|
9986
|
+
if (isNonThreadChannelMsg) {
|
|
9987
|
+
return { event: null, dropReason: "non_thread_channel_msg" };
|
|
9988
|
+
}
|
|
9989
|
+
if (event.type !== "message") {
|
|
9990
|
+
return { event: null, dropReason: "non_message_event" };
|
|
9991
|
+
}
|
|
9992
|
+
return { event: null, dropReason: "unsupported_type" };
|
|
9993
|
+
}
|
|
9994
|
+
const text = (event.text ?? "").trim();
|
|
9995
|
+
const hasFiles = Array.isArray(event.files) && event.files.length > 0;
|
|
9996
|
+
if (!text && !hasFiles) return { event: null, dropReason: "empty_text" };
|
|
9997
|
+
const policy = getSlackAllowlistPolicy();
|
|
9998
|
+
const userAllowlistActive = policy.allowedUsers.length > 0;
|
|
9999
|
+
const userOk = !userAllowlistActive || event.user && policy.allowedUsers.includes(event.user);
|
|
10000
|
+
if (isDm) {
|
|
10001
|
+
if (!policy.allowDmsFromAnyone && !(event.user && policy.allowedUsers.includes(event.user))) {
|
|
10002
|
+
return { event: null, dropReason: "dm_blocked" };
|
|
10003
|
+
}
|
|
10004
|
+
} else {
|
|
10005
|
+
const channelAllowlistActive = policy.allowedChannels.length > 0;
|
|
10006
|
+
if (channelAllowlistActive && !policy.allowedChannels.includes(event.channel)) {
|
|
10007
|
+
return { event: null, dropReason: "channel_not_allowed" };
|
|
10008
|
+
}
|
|
10009
|
+
if (!userOk) {
|
|
10010
|
+
return { event: null, dropReason: "user_not_allowed" };
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
const ref = {
|
|
10014
|
+
channel: "slack",
|
|
10015
|
+
slackChannel: event.channel,
|
|
10016
|
+
// For thread replies, threadTs points at the parent (so our reply
|
|
10017
|
+
// continues the thread). messageTs is the inbound message's own ts —
|
|
10018
|
+
// used by reaction add/remove (which target the message itself, not
|
|
10019
|
+
// its parent) and any future "edit this message" operations.
|
|
10020
|
+
threadTs: event.thread_ts || event.ts,
|
|
10021
|
+
messageTs: event.ts,
|
|
10022
|
+
teamId: event.team,
|
|
10023
|
+
user: event.user
|
|
10024
|
+
};
|
|
10025
|
+
const label = slackChannel.displayLabel(ref);
|
|
10026
|
+
return {
|
|
10027
|
+
event: {
|
|
10028
|
+
ref,
|
|
10029
|
+
content: `[${label}] ${text}`,
|
|
10030
|
+
wake: "now",
|
|
10031
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
10032
|
+
}
|
|
10033
|
+
};
|
|
10034
|
+
}
|
|
10035
|
+
var ownedThreads, slackChannel, IGNORED_MESSAGE_SUBTYPES;
|
|
10036
|
+
var init_slack = __esm({
|
|
10037
|
+
"src/integrations/channels/slack.ts"() {
|
|
9227
10038
|
"use strict";
|
|
9228
10039
|
init_client3();
|
|
9229
10040
|
ownedThreads = /* @__PURE__ */ new Set();
|
|
@@ -9243,6 +10054,8 @@ var init_slack = __esm({
|
|
|
9243
10054
|
if (r.slackChannel && r.threadTs) {
|
|
9244
10055
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
9245
10056
|
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
10057
|
+
void Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports)).then((m) => m.markRespondedForThread(r.slackChannel, r.threadTs)).catch(() => {
|
|
10058
|
+
});
|
|
9246
10059
|
}
|
|
9247
10060
|
},
|
|
9248
10061
|
displayLabel(ref) {
|
|
@@ -9275,8 +10088,14 @@ var init_slack = __esm({
|
|
|
9275
10088
|
// also-broadcast-to-channel replies; the regular thread reply already fires
|
|
9276
10089
|
"message_replied",
|
|
9277
10090
|
// legacy parent-thread bump
|
|
9278
|
-
|
|
9279
|
-
//
|
|
10091
|
+
// NOTE: `file_share` is intentionally NOT ignored. It's the subtype Slack
|
|
10092
|
+
// attaches to a normal user message that includes file uploads — the
|
|
10093
|
+
// event still has `text`, `user`, `channel`, and `files: [...]`. The
|
|
10094
|
+
// route handler (src/server/routes/slack.ts) hands the `files[]` array
|
|
10095
|
+
// to `ingestSlackFiles` (src/integrations/slack/files.ts), which
|
|
10096
|
+
// downloads each file using the bot token, re-uploads it to GCS via
|
|
10097
|
+
// the remote-server's /storage/upload-url endpoint, and appends the
|
|
10098
|
+
// resulting short public URLs to the inbound message content.
|
|
9280
10099
|
"reply_broadcast",
|
|
9281
10100
|
"tombstone",
|
|
9282
10101
|
"huddle_thread"
|
|
@@ -9485,7 +10304,7 @@ var init_messenger = __esm({
|
|
|
9485
10304
|
});
|
|
9486
10305
|
|
|
9487
10306
|
// src/orchestrator/schedules-store.ts
|
|
9488
|
-
import { nanoid as
|
|
10307
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
9489
10308
|
async function readOrch(orchestratorSessionId) {
|
|
9490
10309
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
9491
10310
|
if (!s) return null;
|
|
@@ -9500,7 +10319,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
9500
10319
|
const data = await readOrch(orchestratorSessionId);
|
|
9501
10320
|
if (!data) throw new Error("orchestrator session not found");
|
|
9502
10321
|
const row = {
|
|
9503
|
-
id: `sch_${
|
|
10322
|
+
id: `sch_${nanoid5(10)}`,
|
|
9504
10323
|
name: input.name,
|
|
9505
10324
|
cron: input.cron,
|
|
9506
10325
|
prompt: input.prompt,
|
|
@@ -9537,7 +10356,7 @@ var init_schedules_store = __esm({
|
|
|
9537
10356
|
|
|
9538
10357
|
// src/orchestrator/webhooks-store.ts
|
|
9539
10358
|
import { randomBytes } from "crypto";
|
|
9540
|
-
import { nanoid as
|
|
10359
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
9541
10360
|
function newToken() {
|
|
9542
10361
|
return randomBytes(24).toString("base64url");
|
|
9543
10362
|
}
|
|
@@ -9554,7 +10373,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
9554
10373
|
const data = await readOrch2(orchestratorSessionId);
|
|
9555
10374
|
if (!data) throw new Error("orchestrator session not found");
|
|
9556
10375
|
const row = {
|
|
9557
|
-
id: `whk_${
|
|
10376
|
+
id: `whk_${nanoid6(10)}`,
|
|
9558
10377
|
name: input.name,
|
|
9559
10378
|
token: newToken(),
|
|
9560
10379
|
wake: input.wake ?? "now",
|
|
@@ -9699,7 +10518,9 @@ function buildAgentTool(opts) {
|
|
|
9699
10518
|
workingDirectory: input.workingDirectory ?? opts.defaultWorkingDirectory,
|
|
9700
10519
|
name: input.name,
|
|
9701
10520
|
maxIterations: input.maxIterations ?? 100,
|
|
9702
|
-
orchestratorSessionId: opts.orchestratorSessionId
|
|
10521
|
+
orchestratorSessionId: opts.orchestratorSessionId,
|
|
10522
|
+
...input.mcpServers ? { mcpServers: input.mcpServers } : {},
|
|
10523
|
+
...input.skills ? { skills: input.skills } : {}
|
|
9703
10524
|
}
|
|
9704
10525
|
});
|
|
9705
10526
|
return {
|
|
@@ -9872,6 +10693,26 @@ var init_orchestrator_actions = __esm({
|
|
|
9872
10693
|
model: z14.string().optional().describe("spawn only: model override."),
|
|
9873
10694
|
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
9874
10695
|
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
10696
|
+
mcpServers: z14.array(
|
|
10697
|
+
z14.object({
|
|
10698
|
+
name: z14.string(),
|
|
10699
|
+
transport: z14.enum(["http", "sse", "stdio"]),
|
|
10700
|
+
url: z14.string().optional(),
|
|
10701
|
+
headers: z14.record(z14.string(), z14.string()).optional(),
|
|
10702
|
+
command: z14.string().optional(),
|
|
10703
|
+
args: z14.array(z14.string()).optional(),
|
|
10704
|
+
env: z14.record(z14.string(), z14.string()).optional()
|
|
10705
|
+
})
|
|
10706
|
+
).optional().describe("spawn only: task-scoped MCP servers (with auth headers) connected for this worker only, tools exposed as mcp_<name>_<tool>."),
|
|
10707
|
+
skills: z14.array(
|
|
10708
|
+
z14.object({
|
|
10709
|
+
name: z14.string(),
|
|
10710
|
+
description: z14.string().optional(),
|
|
10711
|
+
content: z14.string(),
|
|
10712
|
+
alwaysApply: z14.boolean().optional(),
|
|
10713
|
+
globs: z14.array(z14.string()).optional()
|
|
10714
|
+
})
|
|
10715
|
+
).optional().describe("spawn only: task-scoped skills (inline markdown) available to this worker only."),
|
|
9875
10716
|
// message
|
|
9876
10717
|
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
9877
10718
|
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
@@ -9911,9 +10752,9 @@ var init_orchestrator_actions = __esm({
|
|
|
9911
10752
|
});
|
|
9912
10753
|
|
|
9913
10754
|
// src/integrations/mcp/store.ts
|
|
9914
|
-
import { nanoid as
|
|
9915
|
-
import { existsSync as
|
|
9916
|
-
import { resolve as resolve10, join as
|
|
10755
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
10756
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
|
|
10757
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
9917
10758
|
function readServers() {
|
|
9918
10759
|
try {
|
|
9919
10760
|
const cfg = getConfig();
|
|
@@ -9925,12 +10766,12 @@ function readServers() {
|
|
|
9925
10766
|
function refreshMcpServersFromDisk() {
|
|
9926
10767
|
const candidates = [
|
|
9927
10768
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
9928
|
-
|
|
10769
|
+
join11(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
9929
10770
|
];
|
|
9930
10771
|
for (const path of candidates) {
|
|
9931
|
-
if (!
|
|
10772
|
+
if (!existsSync18(path)) continue;
|
|
9932
10773
|
try {
|
|
9933
|
-
const raw = JSON.parse(
|
|
10774
|
+
const raw = JSON.parse(readFileSync9(path, "utf-8"));
|
|
9934
10775
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
9935
10776
|
setMcpServers(servers2);
|
|
9936
10777
|
return servers2;
|
|
@@ -9949,7 +10790,7 @@ function createMcpServer(input) {
|
|
|
9949
10790
|
const all = readServers();
|
|
9950
10791
|
validateInput(input);
|
|
9951
10792
|
const row = {
|
|
9952
|
-
id: `mcp_${
|
|
10793
|
+
id: `mcp_${nanoid7(10)}`,
|
|
9953
10794
|
name: sanitizeName(input.name),
|
|
9954
10795
|
transport: input.transport,
|
|
9955
10796
|
url: input.url,
|
|
@@ -10120,6 +10961,159 @@ var init_pool = __esm({
|
|
|
10120
10961
|
}
|
|
10121
10962
|
});
|
|
10122
10963
|
|
|
10964
|
+
// src/integrations/mcp/task-scoped.ts
|
|
10965
|
+
import { createMCPClient as createMCPClient2 } from "@ai-sdk/mcp";
|
|
10966
|
+
function sanitizeName2(raw) {
|
|
10967
|
+
return raw.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/_+/g, "_");
|
|
10968
|
+
}
|
|
10969
|
+
function buildHttpLikeTransport(server) {
|
|
10970
|
+
if (!server.url) {
|
|
10971
|
+
throw new Error(`${server.transport} transport requires a url`);
|
|
10972
|
+
}
|
|
10973
|
+
return {
|
|
10974
|
+
type: server.transport,
|
|
10975
|
+
url: server.url,
|
|
10976
|
+
headers: server.headers
|
|
10977
|
+
};
|
|
10978
|
+
}
|
|
10979
|
+
async function buildStdioTransport2(server) {
|
|
10980
|
+
if (!server.command) {
|
|
10981
|
+
throw new Error("stdio transport requires a command");
|
|
10982
|
+
}
|
|
10983
|
+
const mod = await import("@ai-sdk/mcp/mcp-stdio");
|
|
10984
|
+
const Cls = mod.Experimental_StdioMCPTransport || mod.StdioClientTransport;
|
|
10985
|
+
if (!Cls) throw new Error("@ai-sdk/mcp/mcp-stdio is missing the stdio transport class");
|
|
10986
|
+
return new Cls({
|
|
10987
|
+
command: server.command,
|
|
10988
|
+
args: server.args ?? [],
|
|
10989
|
+
env: server.env
|
|
10990
|
+
});
|
|
10991
|
+
}
|
|
10992
|
+
async function buildTransport(server) {
|
|
10993
|
+
return server.transport === "stdio" ? await buildStdioTransport2(server) : buildHttpLikeTransport(server);
|
|
10994
|
+
}
|
|
10995
|
+
async function connectTaskMcpServers(servers2, opts = {}) {
|
|
10996
|
+
const tools = {};
|
|
10997
|
+
const connected = [];
|
|
10998
|
+
const errors = [];
|
|
10999
|
+
const clients2 = [];
|
|
11000
|
+
for (const raw of servers2 ?? []) {
|
|
11001
|
+
const name = sanitizeName2(raw.name || "");
|
|
11002
|
+
if (!name) {
|
|
11003
|
+
errors.push({ name: raw.name || "(unnamed)", error: "server name is required" });
|
|
11004
|
+
continue;
|
|
11005
|
+
}
|
|
11006
|
+
let client = null;
|
|
11007
|
+
try {
|
|
11008
|
+
const transport = await buildTransport(raw);
|
|
11009
|
+
client = await createMCPClient2({ transport });
|
|
11010
|
+
clients2.push(client);
|
|
11011
|
+
const serverTools = await client.tools();
|
|
11012
|
+
for (const [toolName, t] of Object.entries(serverTools)) {
|
|
11013
|
+
tools[`mcp_${name}_${toolName}`] = t;
|
|
11014
|
+
}
|
|
11015
|
+
connected.push(name);
|
|
11016
|
+
} catch (err) {
|
|
11017
|
+
const message = err?.message || String(err);
|
|
11018
|
+
errors.push({ name, error: message });
|
|
11019
|
+
if (!opts.quiet) {
|
|
11020
|
+
console.warn(`[mcp:task] connecting "${name}" failed: ${message}`);
|
|
11021
|
+
}
|
|
11022
|
+
if (client) {
|
|
11023
|
+
try {
|
|
11024
|
+
await client.close();
|
|
11025
|
+
} catch {
|
|
11026
|
+
}
|
|
11027
|
+
const idx = clients2.indexOf(client);
|
|
11028
|
+
if (idx >= 0) clients2.splice(idx, 1);
|
|
11029
|
+
}
|
|
11030
|
+
}
|
|
11031
|
+
}
|
|
11032
|
+
let closed = false;
|
|
11033
|
+
const close = async () => {
|
|
11034
|
+
if (closed) return;
|
|
11035
|
+
closed = true;
|
|
11036
|
+
await Promise.all(
|
|
11037
|
+
clients2.map(async (c) => {
|
|
11038
|
+
try {
|
|
11039
|
+
await c.close();
|
|
11040
|
+
} catch {
|
|
11041
|
+
}
|
|
11042
|
+
})
|
|
11043
|
+
);
|
|
11044
|
+
};
|
|
11045
|
+
return { tools, connected, errors, close };
|
|
11046
|
+
}
|
|
11047
|
+
var init_task_scoped = __esm({
|
|
11048
|
+
"src/integrations/mcp/task-scoped.ts"() {
|
|
11049
|
+
"use strict";
|
|
11050
|
+
}
|
|
11051
|
+
});
|
|
11052
|
+
|
|
11053
|
+
// src/skills/task-scoped.ts
|
|
11054
|
+
import { mkdtemp, writeFile as writeFile5, rm } from "fs/promises";
|
|
11055
|
+
import { tmpdir } from "os";
|
|
11056
|
+
import { join as join12 } from "path";
|
|
11057
|
+
function safeFileName(name, index) {
|
|
11058
|
+
const base = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
11059
|
+
return `${base || `skill-${index + 1}`}.md`;
|
|
11060
|
+
}
|
|
11061
|
+
function escapeFrontmatterValue(value) {
|
|
11062
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
11063
|
+
}
|
|
11064
|
+
function buildSkillFile(skill) {
|
|
11065
|
+
const lines = ["---"];
|
|
11066
|
+
lines.push(`name: ${escapeFrontmatterValue(skill.name)}`);
|
|
11067
|
+
lines.push(`description: ${escapeFrontmatterValue(skill.description || skill.name)}`);
|
|
11068
|
+
if (skill.alwaysApply) lines.push("alwaysApply: true");
|
|
11069
|
+
if (skill.globs && skill.globs.length > 0) {
|
|
11070
|
+
lines.push(`globs: [${skill.globs.map((g) => escapeFrontmatterValue(g)).join(", ")}]`);
|
|
11071
|
+
}
|
|
11072
|
+
lines.push("---");
|
|
11073
|
+
lines.push("");
|
|
11074
|
+
lines.push(skill.content);
|
|
11075
|
+
return lines.join("\n");
|
|
11076
|
+
}
|
|
11077
|
+
async function materializeTaskSkills(skills2, taskId) {
|
|
11078
|
+
if (!skills2 || skills2.length === 0) return null;
|
|
11079
|
+
const safeTaskId = taskId.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
11080
|
+
const dir = await mkdtemp(join12(tmpdir(), `sparkecoder-task-skills-${safeTaskId}-`));
|
|
11081
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11082
|
+
await Promise.all(
|
|
11083
|
+
skills2.map(async (skill, i) => {
|
|
11084
|
+
let fileName = safeFileName(skill.name, i);
|
|
11085
|
+
while (seen.has(fileName)) fileName = `dup-${i}-${fileName}`;
|
|
11086
|
+
seen.add(fileName);
|
|
11087
|
+
await writeFile5(join12(dir, fileName), buildSkillFile(skill), "utf-8");
|
|
11088
|
+
})
|
|
11089
|
+
);
|
|
11090
|
+
const loaded2 = await loadSkillsFromDirectory(dir, { priority: 1, defaultLoadType: "on_demand" });
|
|
11091
|
+
const alwaysSkills = loaded2.filter((s) => s.alwaysApply || s.loadType === "always");
|
|
11092
|
+
const onDemand = loaded2.filter((s) => !(s.alwaysApply || s.loadType === "always"));
|
|
11093
|
+
const always = (await Promise.all(
|
|
11094
|
+
alwaysSkills.map(async (s) => {
|
|
11095
|
+
const withContent = await loadSkillContent(s.name, [dir]);
|
|
11096
|
+
return withContent ? { ...s, content: withContent.content } : null;
|
|
11097
|
+
})
|
|
11098
|
+
)).filter((s) => s !== null);
|
|
11099
|
+
let cleaned = false;
|
|
11100
|
+
const cleanup2 = async () => {
|
|
11101
|
+
if (cleaned) return;
|
|
11102
|
+
cleaned = true;
|
|
11103
|
+
try {
|
|
11104
|
+
await rm(dir, { recursive: true, force: true });
|
|
11105
|
+
} catch {
|
|
11106
|
+
}
|
|
11107
|
+
};
|
|
11108
|
+
return { dir, always, onDemand, cleanup: cleanup2 };
|
|
11109
|
+
}
|
|
11110
|
+
var init_task_scoped2 = __esm({
|
|
11111
|
+
"src/skills/task-scoped.ts"() {
|
|
11112
|
+
"use strict";
|
|
11113
|
+
init_skills();
|
|
11114
|
+
}
|
|
11115
|
+
});
|
|
11116
|
+
|
|
10123
11117
|
// src/utils/webhook.ts
|
|
10124
11118
|
var webhook_exports = {};
|
|
10125
11119
|
__export(webhook_exports, {
|
|
@@ -10278,79 +11272,57 @@ var init_pending_input = __esm({
|
|
|
10278
11272
|
}
|
|
10279
11273
|
});
|
|
10280
11274
|
|
|
10281
|
-
// src/
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
if (!e) {
|
|
10296
|
-
e = { pending: [] };
|
|
10297
|
-
inboxes.set(sessionId, e);
|
|
10298
|
-
}
|
|
10299
|
-
return e;
|
|
11275
|
+
// src/utils/local-device-time.ts
|
|
11276
|
+
function formatLocalDeviceTimeLine(now = /* @__PURE__ */ new Date()) {
|
|
11277
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
11278
|
+
const formatted = now.toLocaleString("en-US", {
|
|
11279
|
+
weekday: "long",
|
|
11280
|
+
year: "numeric",
|
|
11281
|
+
month: "long",
|
|
11282
|
+
day: "numeric",
|
|
11283
|
+
hour: "numeric",
|
|
11284
|
+
minute: "2-digit",
|
|
11285
|
+
second: "2-digit",
|
|
11286
|
+
timeZoneName: "short"
|
|
11287
|
+
});
|
|
11288
|
+
return `${LOCAL_TIME_MARKER} ${formatted} (${timeZone})]`;
|
|
10300
11289
|
}
|
|
10301
|
-
function
|
|
10302
|
-
|
|
10303
|
-
e.pending.push(event);
|
|
10304
|
-
if (event.wake === "now") {
|
|
10305
|
-
scheduleFlush(orchestratorSessionId);
|
|
10306
|
-
}
|
|
11290
|
+
function hasLocalDeviceTimeLine(text) {
|
|
11291
|
+
return text.includes(LOCAL_TIME_MARKER);
|
|
10307
11292
|
}
|
|
10308
|
-
function
|
|
10309
|
-
const
|
|
10310
|
-
if (!
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
void flush(sessionId);
|
|
10314
|
-
}, FLUSH_DEBOUNCE_MS);
|
|
11293
|
+
function prependLocalDeviceTimeToUserMessage(text, now) {
|
|
11294
|
+
const trimmed = text.trim();
|
|
11295
|
+
if (!trimmed || hasLocalDeviceTimeLine(text)) return text;
|
|
11296
|
+
return `${formatLocalDeviceTimeLine(now)}
|
|
11297
|
+
${text}`;
|
|
10315
11298
|
}
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
if (e.timer) {
|
|
10320
|
-
clearTimeout(e.timer);
|
|
10321
|
-
e.timer = void 0;
|
|
10322
|
-
}
|
|
10323
|
-
const events = e.pending.splice(0);
|
|
10324
|
-
if (events.length === 0) return;
|
|
10325
|
-
if (!flushHandler) {
|
|
10326
|
-
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
10327
|
-
return;
|
|
11299
|
+
function prependLocalDeviceTimeToUserContent(content, now) {
|
|
11300
|
+
if (typeof content === "string") {
|
|
11301
|
+
return prependLocalDeviceTimeToUserMessage(content, now);
|
|
10328
11302
|
}
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
11303
|
+
const line = formatLocalDeviceTimeLine(now);
|
|
11304
|
+
if (content.some((p) => p.type === "text" && p.text && hasLocalDeviceTimeLine(p.text))) {
|
|
11305
|
+
return content;
|
|
10333
11306
|
}
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
11307
|
+
const userIdx = content.findIndex(
|
|
11308
|
+
(p) => p.type === "text" && p.text?.includes("[USER MESSAGE]")
|
|
11309
|
+
);
|
|
11310
|
+
if (userIdx >= 0 && content[userIdx].text) {
|
|
11311
|
+
const copy = content.map((p) => ({ ...p }));
|
|
11312
|
+
copy[userIdx] = {
|
|
11313
|
+
...copy[userIdx],
|
|
11314
|
+
text: `${line}
|
|
11315
|
+
${copy[userIdx].text}`
|
|
11316
|
+
};
|
|
11317
|
+
return copy;
|
|
10344
11318
|
}
|
|
10345
|
-
|
|
11319
|
+
return [{ type: "text", text: line }, ...content];
|
|
10346
11320
|
}
|
|
10347
|
-
var
|
|
10348
|
-
var
|
|
10349
|
-
"src/
|
|
11321
|
+
var LOCAL_TIME_MARKER;
|
|
11322
|
+
var init_local_device_time = __esm({
|
|
11323
|
+
"src/utils/local-device-time.ts"() {
|
|
10350
11324
|
"use strict";
|
|
10351
|
-
|
|
10352
|
-
FLUSH_DEBOUNCE_MS = 200;
|
|
10353
|
-
flushHandler = null;
|
|
11325
|
+
LOCAL_TIME_MARKER = "[Local device time:";
|
|
10354
11326
|
}
|
|
10355
11327
|
});
|
|
10356
11328
|
|
|
@@ -10562,10 +11534,10 @@ __export(recorder_exports, {
|
|
|
10562
11534
|
});
|
|
10563
11535
|
import { exec as exec5 } from "child_process";
|
|
10564
11536
|
import { promisify as promisify5 } from "util";
|
|
10565
|
-
import { writeFile as
|
|
10566
|
-
import { join as
|
|
10567
|
-
import { tmpdir } from "os";
|
|
10568
|
-
import { nanoid as
|
|
11537
|
+
import { writeFile as writeFile6, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm as rm2 } from "fs/promises";
|
|
11538
|
+
import { join as join13 } from "path";
|
|
11539
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
11540
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
10569
11541
|
async function checkFfmpeg() {
|
|
10570
11542
|
try {
|
|
10571
11543
|
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
@@ -10576,7 +11548,7 @@ async function checkFfmpeg() {
|
|
|
10576
11548
|
}
|
|
10577
11549
|
async function cleanup(dir) {
|
|
10578
11550
|
try {
|
|
10579
|
-
await
|
|
11551
|
+
await rm2(dir, { recursive: true, force: true });
|
|
10580
11552
|
} catch {
|
|
10581
11553
|
}
|
|
10582
11554
|
}
|
|
@@ -10620,21 +11592,21 @@ var init_recorder = __esm({
|
|
|
10620
11592
|
*/
|
|
10621
11593
|
async encode() {
|
|
10622
11594
|
if (this.frames.length === 0) return null;
|
|
10623
|
-
const workDir =
|
|
11595
|
+
const workDir = join13(tmpdir2(), `sparkecoder-recording-${nanoid8(8)}`);
|
|
10624
11596
|
await mkdir4(workDir, { recursive: true });
|
|
10625
11597
|
try {
|
|
10626
11598
|
for (let i = 0; i < this.frames.length; i++) {
|
|
10627
|
-
const framePath =
|
|
10628
|
-
await
|
|
11599
|
+
const framePath = join13(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
11600
|
+
await writeFile6(framePath, this.frames[i].data);
|
|
10629
11601
|
}
|
|
10630
11602
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
10631
11603
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
10632
11604
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
10633
|
-
const outputPath =
|
|
11605
|
+
const outputPath = join13(workDir, `recording_${this.sessionId}.mp4`);
|
|
10634
11606
|
const hasFfmpeg = await checkFfmpeg();
|
|
10635
11607
|
if (hasFfmpeg) {
|
|
10636
11608
|
await execAsync5(
|
|
10637
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
11609
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join13(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
10638
11610
|
{ timeout: 12e4 }
|
|
10639
11611
|
);
|
|
10640
11612
|
} else {
|
|
@@ -10646,7 +11618,7 @@ var init_recorder = __esm({
|
|
|
10646
11618
|
const files = await readdir5(workDir);
|
|
10647
11619
|
for (const f of files) {
|
|
10648
11620
|
if (f.startsWith("frame_")) {
|
|
10649
|
-
await unlink2(
|
|
11621
|
+
await unlink2(join13(workDir, f)).catch(() => {
|
|
10650
11622
|
});
|
|
10651
11623
|
}
|
|
10652
11624
|
}
|
|
@@ -10675,7 +11647,7 @@ import {
|
|
|
10675
11647
|
stepCountIs as stepCountIs2
|
|
10676
11648
|
} from "ai";
|
|
10677
11649
|
import { z as z15 } from "zod";
|
|
10678
|
-
import { nanoid as
|
|
11650
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
10679
11651
|
function anySignal(signals) {
|
|
10680
11652
|
const ctrl = new AbortController();
|
|
10681
11653
|
for (const s of signals) {
|
|
@@ -10719,10 +11691,13 @@ var init_agent = __esm({
|
|
|
10719
11691
|
init_prompts();
|
|
10720
11692
|
init_orchestrator_actions();
|
|
10721
11693
|
init_pool();
|
|
11694
|
+
init_task_scoped();
|
|
11695
|
+
init_task_scoped2();
|
|
10722
11696
|
init_webhook2();
|
|
10723
11697
|
init_questions();
|
|
10724
11698
|
init_pending_input();
|
|
10725
11699
|
init_inbox();
|
|
11700
|
+
init_local_device_time();
|
|
10726
11701
|
init_system();
|
|
10727
11702
|
init_context();
|
|
10728
11703
|
init_prompts();
|
|
@@ -10885,9 +11860,11 @@ ${prompt}` });
|
|
|
10885
11860
|
*/
|
|
10886
11861
|
async stream(options) {
|
|
10887
11862
|
const config = getConfig();
|
|
10888
|
-
const
|
|
11863
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
11864
|
+
const userContent = this.buildUserMessageContent(prompt, options.attachments);
|
|
11865
|
+
const persistedUserContent = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserContent(userContent) : userContent;
|
|
10889
11866
|
if (!options.skipSaveUserMessage) {
|
|
10890
|
-
await this.context.addUserMessage(
|
|
11867
|
+
await this.context.addUserMessage(persistedUserContent);
|
|
10891
11868
|
}
|
|
10892
11869
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
10893
11870
|
let systemPrompt = await buildSystemPrompt({
|
|
@@ -10965,7 +11942,8 @@ ${personality.trim()}
|
|
|
10965
11942
|
*/
|
|
10966
11943
|
async run(options) {
|
|
10967
11944
|
const config = getConfig();
|
|
10968
|
-
|
|
11945
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
11946
|
+
await this.context.addUserMessage(prompt);
|
|
10969
11947
|
const systemPrompt = await buildSystemPrompt({
|
|
10970
11948
|
workingDirectory: this.session.workingDirectory,
|
|
10971
11949
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -11009,355 +11987,387 @@ ${personality.trim()}
|
|
|
11009
11987
|
*/
|
|
11010
11988
|
async runTask(options) {
|
|
11011
11989
|
const config = getConfig();
|
|
11012
|
-
const
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
|
|
11026
|
-
const completion = { signal: null };
|
|
11027
|
-
const onComplete = (signal) => {
|
|
11028
|
-
completion.signal = signal;
|
|
11029
|
-
};
|
|
11030
|
-
let taskRecorder = null;
|
|
11031
|
-
const sessionId = this.session.id;
|
|
11032
|
-
const emit = options.writeSSE;
|
|
11033
|
-
const bashProgressHandler = (progress) => {
|
|
11034
|
-
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
11035
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
11036
|
-
});
|
|
11037
|
-
const port = progress.browserStreamPort;
|
|
11038
|
-
if (port && progress.status === "started") {
|
|
11039
|
-
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
11040
|
-
const proxy = getOrCreateProxy2(sessionId, port);
|
|
11041
|
-
if (!taskRecorder) {
|
|
11042
|
-
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
11043
|
-
taskRecorder = new FrameRecorder2(sessionId);
|
|
11044
|
-
taskRecorder.start();
|
|
11045
|
-
});
|
|
11046
|
-
}
|
|
11047
|
-
if (proxy.listenerCount("frame") === 0) {
|
|
11048
|
-
proxy.on("frame", (frame) => {
|
|
11049
|
-
taskRecorder?.addFrame(frame);
|
|
11050
|
-
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
11051
|
-
});
|
|
11052
|
-
});
|
|
11053
|
-
proxy.on("status", (s) => {
|
|
11054
|
-
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
11055
|
-
});
|
|
11056
|
-
});
|
|
11057
|
-
}
|
|
11990
|
+
const taskScopedCleanups = [];
|
|
11991
|
+
try {
|
|
11992
|
+
const maxIterations = options.taskConfig.maxIterations ?? 50;
|
|
11993
|
+
const webhookUrl = options.taskConfig.webhookUrl;
|
|
11994
|
+
const parentTaskId = options.taskConfig.parentTaskId;
|
|
11995
|
+
const fireWebhook = (type, data) => {
|
|
11996
|
+
if (!webhookUrl) return;
|
|
11997
|
+
sendWebhook(webhookUrl, {
|
|
11998
|
+
type,
|
|
11999
|
+
taskId: this.session.id,
|
|
12000
|
+
sessionId: this.session.id,
|
|
12001
|
+
...parentTaskId ? { parentTaskId } : {},
|
|
12002
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12003
|
+
data
|
|
11058
12004
|
});
|
|
12005
|
+
};
|
|
12006
|
+
const completion = { signal: null };
|
|
12007
|
+
const onComplete = (signal) => {
|
|
12008
|
+
completion.signal = signal;
|
|
12009
|
+
};
|
|
12010
|
+
let taskMcpTools = {};
|
|
12011
|
+
if (options.mcpServers && options.mcpServers.length > 0) {
|
|
12012
|
+
const mcpConnection = await connectTaskMcpServers(options.mcpServers, { quiet: true });
|
|
12013
|
+
taskScopedCleanups.push(mcpConnection.close);
|
|
12014
|
+
taskMcpTools = mcpConnection.tools;
|
|
12015
|
+
if (mcpConnection.connected.length > 0) {
|
|
12016
|
+
console.log(`[TASK] connected ${mcpConnection.connected.length} task-scoped MCP server(s): ${mcpConnection.connected.join(", ")}`);
|
|
12017
|
+
}
|
|
12018
|
+
for (const e of mcpConnection.errors) {
|
|
12019
|
+
console.warn(`[TASK] task-scoped MCP server "${e.name}" failed to connect: ${e.error}`);
|
|
12020
|
+
if (options.writeSSE) await options.writeSSE(JSON.stringify({ type: "task-mcp-error", data: { name: e.name, error: e.error } }));
|
|
12021
|
+
}
|
|
11059
12022
|
}
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
11067
|
-
options.onToolProgress?.({ toolName: "
|
|
11068
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "
|
|
11069
|
-
});
|
|
11070
|
-
},
|
|
11071
|
-
onSearchProgress: (progress) => {
|
|
11072
|
-
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
11073
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
12023
|
+
const materializedSkills = await materializeTaskSkills(options.skills, this.session.id);
|
|
12024
|
+
if (materializedSkills) taskScopedCleanups.push(materializedSkills.cleanup);
|
|
12025
|
+
const taskSkillsDir = materializedSkills?.dir;
|
|
12026
|
+
let taskRecorder = null;
|
|
12027
|
+
const sessionId = this.session.id;
|
|
12028
|
+
const emit = options.writeSSE;
|
|
12029
|
+
const bashProgressHandler = (progress) => {
|
|
12030
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
12031
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
11074
12032
|
});
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
12033
|
+
const port = progress.browserStreamPort;
|
|
12034
|
+
if (port && progress.status === "started") {
|
|
12035
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
12036
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
12037
|
+
if (!taskRecorder) {
|
|
12038
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
12039
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
12040
|
+
taskRecorder.start();
|
|
12041
|
+
});
|
|
12042
|
+
}
|
|
12043
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
12044
|
+
proxy.on("frame", (frame) => {
|
|
12045
|
+
taskRecorder?.addFrame(frame);
|
|
12046
|
+
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
12047
|
+
});
|
|
12048
|
+
});
|
|
12049
|
+
proxy.on("status", (s) => {
|
|
12050
|
+
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
12051
|
+
});
|
|
12052
|
+
});
|
|
12053
|
+
}
|
|
11093
12054
|
});
|
|
11094
|
-
|
|
11095
|
-
|
|
11096
|
-
|
|
11097
|
-
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
}
|
|
11107
|
-
|
|
11108
|
-
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
12055
|
+
}
|
|
12056
|
+
};
|
|
12057
|
+
const taskTools = await createTools({
|
|
12058
|
+
sessionId: this.session.id,
|
|
12059
|
+
workingDirectory: this.session.workingDirectory,
|
|
12060
|
+
onBashProgress: bashProgressHandler,
|
|
12061
|
+
onWriteFileProgress: (progress) => {
|
|
12062
|
+
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
12063
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "write_file", data: progress })).catch(() => {
|
|
12064
|
+
});
|
|
12065
|
+
},
|
|
12066
|
+
onSearchProgress: (progress) => {
|
|
12067
|
+
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
12068
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
12069
|
+
});
|
|
12070
|
+
},
|
|
12071
|
+
// Add the task-scoped skills temp dir (if any) so load_skill can list
|
|
12072
|
+
// and load the inline skills supplied with this task.
|
|
12073
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
12074
|
+
taskTools: {
|
|
12075
|
+
outputSchema: options.taskConfig.outputSchema,
|
|
12076
|
+
onComplete,
|
|
12077
|
+
onQuestion: async (question) => {
|
|
12078
|
+
const payload = {
|
|
12079
|
+
questionId: question.questionId,
|
|
12080
|
+
question: question.question,
|
|
12081
|
+
context: question.context,
|
|
12082
|
+
choices: question.choices,
|
|
12083
|
+
status: "pending"
|
|
12084
|
+
};
|
|
12085
|
+
const answerPromise = waitForTaskQuestionAnswer({
|
|
12086
|
+
taskId: this.session.id,
|
|
12087
|
+
questionId: question.questionId,
|
|
12088
|
+
question: question.question,
|
|
12089
|
+
context: question.context,
|
|
12090
|
+
choices: question.choices
|
|
12091
|
+
});
|
|
12092
|
+
fireWebhook("task.question", payload);
|
|
12093
|
+
if (emit) {
|
|
12094
|
+
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
12095
|
+
}
|
|
12096
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
12097
|
+
if (orchId) {
|
|
12098
|
+
pushToInbox(orchId, workerQuestionEvent(
|
|
12099
|
+
this.session.id,
|
|
12100
|
+
this.session.name || "worker",
|
|
12101
|
+
question.question,
|
|
12102
|
+
question.questionId
|
|
12103
|
+
));
|
|
12104
|
+
}
|
|
12105
|
+
const answer = await answerPromise;
|
|
12106
|
+
const answeredPayload = {
|
|
12107
|
+
questionId: question.questionId,
|
|
12108
|
+
answer: answer.answer,
|
|
12109
|
+
answeredBy: answer.answeredBy
|
|
12110
|
+
};
|
|
12111
|
+
fireWebhook("task.question_answered", answeredPayload);
|
|
12112
|
+
if (emit) {
|
|
12113
|
+
await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
|
|
12114
|
+
}
|
|
12115
|
+
return answer;
|
|
11116
12116
|
}
|
|
11117
|
-
return answer;
|
|
11118
12117
|
}
|
|
12118
|
+
});
|
|
12119
|
+
for (const [name, t] of Object.entries(taskMcpTools)) {
|
|
12120
|
+
taskTools[name] = t;
|
|
11119
12121
|
}
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
11123
|
-
|
|
11124
|
-
|
|
11125
|
-
|
|
11126
|
-
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
12122
|
+
const baseSystemPrompt = await buildSystemPrompt({
|
|
12123
|
+
workingDirectory: this.session.workingDirectory,
|
|
12124
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
12125
|
+
sessionId: this.session.id,
|
|
12126
|
+
discoveredSkills: config.discoveredSkills,
|
|
12127
|
+
activeFiles: [],
|
|
12128
|
+
taskScopedSkills: materializedSkills ? { always: materializedSkills.always, onDemand: materializedSkills.onDemand } : void 0
|
|
12129
|
+
});
|
|
12130
|
+
const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
|
|
12131
|
+
const systemPrompt = `${baseSystemPrompt}
|
|
11130
12132
|
|
|
11131
12133
|
${taskAddendum}`;
|
|
11132
|
-
|
|
11133
|
-
if (emit) {
|
|
11134
|
-
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: options.prompt } }));
|
|
11135
|
-
}
|
|
11136
|
-
await this.context.addUserMessage(options.prompt);
|
|
11137
|
-
let iteration = 0;
|
|
11138
|
-
while (iteration < maxIterations) {
|
|
11139
|
-
iteration++;
|
|
11140
|
-
if (options.abortSignal?.aborted) {
|
|
11141
|
-
const cancelError = "Task was cancelled";
|
|
11142
|
-
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
11143
|
-
clearInterruptController(this.session.id);
|
|
11144
|
-
return { status: "failed", error: cancelError, iterations: iteration };
|
|
11145
|
-
}
|
|
11146
|
-
const pending = drainInputs(this.session.id);
|
|
11147
|
-
for (const p of pending) {
|
|
11148
|
-
const labelled = p.source === "orchestrator" ? `[message from orchestrator]
|
|
11149
|
-
${p.text}` : p.source === "system" ? `[system note]
|
|
11150
|
-
${p.text}` : p.text;
|
|
11151
|
-
if (emit) {
|
|
11152
|
-
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
11153
|
-
}
|
|
11154
|
-
await this.context.addUserMessage(labelled);
|
|
11155
|
-
}
|
|
11156
|
-
const interruptController = new AbortController();
|
|
11157
|
-
registerInterruptController(this.session.id, interruptController);
|
|
11158
|
-
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
11159
|
-
const messages = await this.context.getMessages();
|
|
11160
|
-
const useAnthropic = isAnthropicModel(this.session.model);
|
|
12134
|
+
fireWebhook("task.started", { prompt: options.prompt });
|
|
11161
12135
|
if (emit) {
|
|
11162
|
-
await emit(JSON.stringify({ type: "
|
|
12136
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: options.prompt } }));
|
|
11163
12137
|
}
|
|
11164
|
-
|
|
11165
|
-
let
|
|
11166
|
-
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11170
|
-
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
// See the matching note in `stream()` — repair tool pairing before
|
|
11180
|
-
// every step so we never feed the model an orphan tool-call.
|
|
11181
|
-
prepareStep: async ({ messages: stepMessages }) => {
|
|
11182
|
-
const paired = repairToolPairing(stepMessages);
|
|
11183
|
-
const ordered = ensureToolResultsFollowCalls(paired);
|
|
11184
|
-
if (ordered === stepMessages) return {};
|
|
11185
|
-
return { messages: ordered };
|
|
11186
|
-
},
|
|
11187
|
-
onStepFinish: async (step) => {
|
|
11188
|
-
options.onStepFinish?.(step);
|
|
11189
|
-
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
11190
|
-
if (emit) {
|
|
11191
|
-
if (textStarted) {
|
|
11192
|
-
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
11193
|
-
textStarted = false;
|
|
11194
|
-
textId = `text_${Date.now()}`;
|
|
11195
|
-
}
|
|
11196
|
-
await emit(JSON.stringify({ type: "finish-step" }));
|
|
11197
|
-
}
|
|
11198
|
-
}
|
|
11199
|
-
});
|
|
11200
|
-
for await (const part of iterStream.fullStream) {
|
|
11201
|
-
if (part.type === "text-delta") {
|
|
11202
|
-
if (emit) {
|
|
11203
|
-
if (!textStarted) {
|
|
11204
|
-
await emit(JSON.stringify({ type: "text-start", id: textId }));
|
|
11205
|
-
textStarted = true;
|
|
11206
|
-
}
|
|
11207
|
-
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
11208
|
-
}
|
|
11209
|
-
} else if (part.type === "reasoning-start") {
|
|
12138
|
+
await this.context.addUserMessage(options.prompt);
|
|
12139
|
+
let iteration = 0;
|
|
12140
|
+
while (iteration < maxIterations) {
|
|
12141
|
+
iteration++;
|
|
12142
|
+
if (options.abortSignal?.aborted) {
|
|
12143
|
+
const cancelError = "Task was cancelled";
|
|
12144
|
+
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
12145
|
+
clearInterruptController(this.session.id);
|
|
12146
|
+
return { status: "failed", error: cancelError, iterations: iteration };
|
|
12147
|
+
}
|
|
12148
|
+
const pending = drainInputs(this.session.id);
|
|
12149
|
+
for (const p of pending) {
|
|
12150
|
+
const labelled = p.source === "orchestrator" ? `[message from orchestrator]
|
|
12151
|
+
${p.text}` : p.source === "system" ? `[system note]
|
|
12152
|
+
${p.text}` : p.text;
|
|
11210
12153
|
if (emit) {
|
|
11211
|
-
await emit(JSON.stringify({ type: "
|
|
11212
|
-
reasoningStarted = true;
|
|
12154
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
11213
12155
|
}
|
|
11214
|
-
|
|
11215
|
-
|
|
11216
|
-
|
|
12156
|
+
await this.context.addUserMessage(labelled);
|
|
12157
|
+
}
|
|
12158
|
+
const interruptController = new AbortController();
|
|
12159
|
+
registerInterruptController(this.session.id, interruptController);
|
|
12160
|
+
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
12161
|
+
const messages = await this.context.getMessages();
|
|
12162
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
12163
|
+
if (emit) {
|
|
12164
|
+
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
12165
|
+
}
|
|
12166
|
+
let textStarted = false;
|
|
12167
|
+
let textId = `text_${Date.now()}`;
|
|
12168
|
+
let reasoningId = `reasoning_${Date.now()}`;
|
|
12169
|
+
let reasoningStarted = false;
|
|
12170
|
+
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
12171
|
+
const iterStream = streamText2({
|
|
12172
|
+
model: resolveModel(this.session.model),
|
|
12173
|
+
system: systemPrompt,
|
|
12174
|
+
messages,
|
|
12175
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
12176
|
+
stopWhen: stepCountIs2(500),
|
|
12177
|
+
abortSignal: combinedAbort,
|
|
12178
|
+
providerOptions: useAnthropic ? {
|
|
12179
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
12180
|
+
} : void 0,
|
|
12181
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
12182
|
+
// every step so we never feed the model an orphan tool-call.
|
|
12183
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
12184
|
+
const paired = repairToolPairing(stepMessages);
|
|
12185
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
12186
|
+
if (ordered === stepMessages) return {};
|
|
12187
|
+
return { messages: ordered };
|
|
12188
|
+
},
|
|
12189
|
+
onStepFinish: async (step) => {
|
|
12190
|
+
options.onStepFinish?.(step);
|
|
12191
|
+
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
12192
|
+
if (emit) {
|
|
12193
|
+
if (textStarted) {
|
|
12194
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
12195
|
+
textStarted = false;
|
|
12196
|
+
textId = `text_${Date.now()}`;
|
|
12197
|
+
}
|
|
12198
|
+
await emit(JSON.stringify({ type: "finish-step" }));
|
|
12199
|
+
}
|
|
11217
12200
|
}
|
|
11218
|
-
}
|
|
11219
|
-
|
|
11220
|
-
|
|
11221
|
-
|
|
11222
|
-
|
|
12201
|
+
});
|
|
12202
|
+
for await (const part of iterStream.fullStream) {
|
|
12203
|
+
if (part.type === "text-delta") {
|
|
12204
|
+
if (emit) {
|
|
12205
|
+
if (!textStarted) {
|
|
12206
|
+
await emit(JSON.stringify({ type: "text-start", id: textId }));
|
|
12207
|
+
textStarted = true;
|
|
12208
|
+
}
|
|
12209
|
+
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
12210
|
+
}
|
|
12211
|
+
} else if (part.type === "reasoning-start") {
|
|
12212
|
+
if (emit) {
|
|
12213
|
+
await emit(JSON.stringify({ type: "reasoning-start", id: reasoningId }));
|
|
12214
|
+
reasoningStarted = true;
|
|
12215
|
+
}
|
|
12216
|
+
} else if (part.type === "reasoning-delta") {
|
|
12217
|
+
if (emit) {
|
|
12218
|
+
await emit(JSON.stringify({ type: "reasoning-delta", id: reasoningId, delta: part.text }));
|
|
12219
|
+
}
|
|
12220
|
+
} else if (part.type === "reasoning-end") {
|
|
12221
|
+
if (emit && reasoningStarted) {
|
|
12222
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
12223
|
+
reasoningStarted = false;
|
|
12224
|
+
reasoningId = `reasoning_${Date.now()}`;
|
|
12225
|
+
}
|
|
12226
|
+
} else if (part.type === "tool-call-streaming-start") {
|
|
12227
|
+
if (emit) {
|
|
12228
|
+
const p = part;
|
|
12229
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: p.toolCallId, toolName: p.toolName }));
|
|
12230
|
+
toolCallStarts.add(p.toolCallId);
|
|
12231
|
+
}
|
|
12232
|
+
} else if (part.type === "tool-call-delta") {
|
|
12233
|
+
if (emit) {
|
|
12234
|
+
const p = part;
|
|
12235
|
+
await emit(JSON.stringify({ type: "tool-input-delta", toolCallId: p.toolCallId, argsTextDelta: p.argsTextDelta }));
|
|
12236
|
+
}
|
|
12237
|
+
} else if (part.type === "tool-call") {
|
|
12238
|
+
if (emit) {
|
|
12239
|
+
if (!toolCallStarts.has(part.toolCallId)) {
|
|
12240
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: part.toolCallId, toolName: part.toolName }));
|
|
12241
|
+
toolCallStarts.add(part.toolCallId);
|
|
12242
|
+
}
|
|
12243
|
+
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
12244
|
+
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
12245
|
+
}
|
|
12246
|
+
} else if (part.type === "tool-result") {
|
|
12247
|
+
if (emit) {
|
|
12248
|
+
await emit(JSON.stringify({ type: "tool-output-available", toolCallId: part.toolCallId, output: part.output }));
|
|
12249
|
+
}
|
|
12250
|
+
} else if (part.type === "error") {
|
|
12251
|
+
console.error("Task stream error:", part.error);
|
|
12252
|
+
if (emit) {
|
|
12253
|
+
await emit(JSON.stringify({ type: "error", errorText: String(part.error) }));
|
|
12254
|
+
}
|
|
11223
12255
|
}
|
|
11224
|
-
}
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
12256
|
+
}
|
|
12257
|
+
if (emit && textStarted) {
|
|
12258
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
12259
|
+
}
|
|
12260
|
+
if (emit && reasoningStarted) {
|
|
12261
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
12262
|
+
}
|
|
12263
|
+
const interrupted = interruptController.signal.aborted;
|
|
12264
|
+
clearInterruptController(this.session.id);
|
|
12265
|
+
const iterResponse = await iterStream.response;
|
|
12266
|
+
const responseMessages = iterResponse.messages;
|
|
12267
|
+
await this.context.addResponseMessages(responseMessages);
|
|
12268
|
+
const resultText = await iterStream.text;
|
|
12269
|
+
const resultSteps = await iterStream.steps;
|
|
12270
|
+
if (resultText) {
|
|
12271
|
+
options.onText?.(resultText);
|
|
12272
|
+
fireWebhook("task.message", { iteration, text: resultText });
|
|
12273
|
+
}
|
|
12274
|
+
for (const step of resultSteps) {
|
|
12275
|
+
if (step.toolCalls) {
|
|
12276
|
+
for (const tc of step.toolCalls) {
|
|
12277
|
+
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
12278
|
+
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
12279
|
+
}
|
|
11229
12280
|
}
|
|
11230
|
-
|
|
11231
|
-
|
|
11232
|
-
|
|
11233
|
-
|
|
12281
|
+
if (step.toolResults) {
|
|
12282
|
+
for (const tr of step.toolResults) {
|
|
12283
|
+
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.output });
|
|
12284
|
+
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.output });
|
|
12285
|
+
}
|
|
11234
12286
|
}
|
|
11235
|
-
}
|
|
11236
|
-
|
|
11237
|
-
|
|
11238
|
-
|
|
11239
|
-
|
|
12287
|
+
}
|
|
12288
|
+
if (completion.signal) {
|
|
12289
|
+
const sig = completion.signal;
|
|
12290
|
+
const finalStatus = sig.status;
|
|
12291
|
+
let fileUrls;
|
|
12292
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
12293
|
+
const resultObj = sig.result;
|
|
12294
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
12295
|
+
if (filePaths.length > 0) {
|
|
12296
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
11240
12297
|
}
|
|
11241
|
-
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
11242
|
-
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
11243
12298
|
}
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
12299
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
12300
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
12301
|
+
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
12302
|
+
fireWebhook(eventType, {
|
|
12303
|
+
status: finalStatus,
|
|
12304
|
+
result: sig.result,
|
|
12305
|
+
error: sig.error,
|
|
12306
|
+
iterations: iteration,
|
|
12307
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
12308
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
12309
|
+
});
|
|
12310
|
+
const updatedTask2 = {
|
|
12311
|
+
...options.taskConfig,
|
|
12312
|
+
status: finalStatus,
|
|
12313
|
+
result: sig.result,
|
|
12314
|
+
error: sig.error,
|
|
12315
|
+
iterations: iteration
|
|
12316
|
+
};
|
|
12317
|
+
await sessionQueries.update(this.session.id, {
|
|
12318
|
+
config: { ...this.session.config, task: updatedTask2 }
|
|
12319
|
+
});
|
|
12320
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
12321
|
+
if (orchId) {
|
|
12322
|
+
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
12323
|
+
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
11247
12324
|
}
|
|
11248
|
-
|
|
11249
|
-
|
|
12325
|
+
return {
|
|
12326
|
+
status: finalStatus,
|
|
12327
|
+
result: sig.result,
|
|
12328
|
+
error: sig.error,
|
|
12329
|
+
iterations: iteration
|
|
12330
|
+
};
|
|
12331
|
+
}
|
|
12332
|
+
if (!interrupted) {
|
|
12333
|
+
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.";
|
|
11250
12334
|
if (emit) {
|
|
11251
|
-
await emit(JSON.stringify({ type: "
|
|
12335
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: continuationPrompt } }));
|
|
11252
12336
|
}
|
|
12337
|
+
await this.context.addUserMessage(continuationPrompt);
|
|
11253
12338
|
}
|
|
11254
12339
|
}
|
|
11255
|
-
if (emit && textStarted) {
|
|
11256
|
-
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
11257
|
-
}
|
|
11258
|
-
if (emit && reasoningStarted) {
|
|
11259
|
-
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
11260
|
-
}
|
|
11261
|
-
const interrupted = interruptController.signal.aborted;
|
|
11262
12340
|
clearInterruptController(this.session.id);
|
|
11263
|
-
const
|
|
11264
|
-
const
|
|
11265
|
-
|
|
11266
|
-
|
|
11267
|
-
|
|
11268
|
-
|
|
11269
|
-
|
|
11270
|
-
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
}
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
}
|
|
11284
|
-
}
|
|
11285
|
-
}
|
|
11286
|
-
if (completion.signal) {
|
|
11287
|
-
const sig = completion.signal;
|
|
11288
|
-
const finalStatus = sig.status;
|
|
11289
|
-
let fileUrls;
|
|
11290
|
-
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
11291
|
-
const resultObj = sig.result;
|
|
11292
|
-
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
11293
|
-
if (filePaths.length > 0) {
|
|
11294
|
-
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
11295
|
-
}
|
|
11296
|
-
}
|
|
11297
|
-
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
11298
|
-
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
11299
|
-
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
11300
|
-
fireWebhook(eventType, {
|
|
11301
|
-
status: finalStatus,
|
|
11302
|
-
result: sig.result,
|
|
11303
|
-
error: sig.error,
|
|
11304
|
-
iterations: iteration,
|
|
11305
|
-
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
11306
|
-
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
11307
|
-
});
|
|
11308
|
-
const updatedTask2 = {
|
|
11309
|
-
...options.taskConfig,
|
|
11310
|
-
status: finalStatus,
|
|
11311
|
-
result: sig.result,
|
|
11312
|
-
error: sig.error,
|
|
11313
|
-
iterations: iteration
|
|
11314
|
-
};
|
|
11315
|
-
await sessionQueries.update(this.session.id, {
|
|
11316
|
-
config: { ...this.session.config, task: updatedTask2 }
|
|
11317
|
-
});
|
|
11318
|
-
const orchId = this.session.config?.orchestratorSessionId;
|
|
11319
|
-
if (orchId) {
|
|
11320
|
-
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
11321
|
-
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
11322
|
-
}
|
|
11323
|
-
return {
|
|
11324
|
-
status: finalStatus,
|
|
11325
|
-
result: sig.result,
|
|
11326
|
-
error: sig.error,
|
|
11327
|
-
iterations: iteration
|
|
11328
|
-
};
|
|
12341
|
+
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
12342
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
12343
|
+
fireWebhook("task.failed", {
|
|
12344
|
+
status: "failed",
|
|
12345
|
+
error: timeoutError,
|
|
12346
|
+
iterations: iteration,
|
|
12347
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
12348
|
+
});
|
|
12349
|
+
const updatedTask = {
|
|
12350
|
+
...options.taskConfig,
|
|
12351
|
+
status: "failed",
|
|
12352
|
+
error: timeoutError,
|
|
12353
|
+
iterations: iteration
|
|
12354
|
+
};
|
|
12355
|
+
await sessionQueries.update(this.session.id, {
|
|
12356
|
+
config: { ...this.session.config, task: updatedTask }
|
|
12357
|
+
});
|
|
12358
|
+
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
12359
|
+
if (orchIdTimeout) {
|
|
12360
|
+
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
11329
12361
|
}
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11333
|
-
|
|
12362
|
+
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
12363
|
+
} finally {
|
|
12364
|
+
for (const cleanup2 of taskScopedCleanups) {
|
|
12365
|
+
try {
|
|
12366
|
+
await cleanup2();
|
|
12367
|
+
} catch {
|
|
11334
12368
|
}
|
|
11335
|
-
await this.context.addUserMessage(continuationPrompt);
|
|
11336
12369
|
}
|
|
11337
12370
|
}
|
|
11338
|
-
clearInterruptController(this.session.id);
|
|
11339
|
-
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
11340
|
-
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
11341
|
-
fireWebhook("task.failed", {
|
|
11342
|
-
status: "failed",
|
|
11343
|
-
error: timeoutError,
|
|
11344
|
-
iterations: iteration,
|
|
11345
|
-
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
11346
|
-
});
|
|
11347
|
-
const updatedTask = {
|
|
11348
|
-
...options.taskConfig,
|
|
11349
|
-
status: "failed",
|
|
11350
|
-
error: timeoutError,
|
|
11351
|
-
iterations: iteration
|
|
11352
|
-
};
|
|
11353
|
-
await sessionQueries.update(this.session.id, {
|
|
11354
|
-
config: { ...this.session.config, task: updatedTask }
|
|
11355
|
-
});
|
|
11356
|
-
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
11357
|
-
if (orchIdTimeout) {
|
|
11358
|
-
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
11359
|
-
}
|
|
11360
|
-
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
11361
12371
|
}
|
|
11362
12372
|
/**
|
|
11363
12373
|
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
@@ -11417,11 +12427,11 @@ ${p.text}` : p.text;
|
|
|
11417
12427
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
11418
12428
|
if (!isRemoteConfigured2()) return [];
|
|
11419
12429
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
11420
|
-
const { join:
|
|
12430
|
+
const { join: join21, basename: basename7 } = await import("path");
|
|
11421
12431
|
const urls = [];
|
|
11422
12432
|
for (const filePath of filePaths) {
|
|
11423
12433
|
try {
|
|
11424
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
12434
|
+
const fullPath = filePath.startsWith("/") ? filePath : join21(this.session.workingDirectory, filePath);
|
|
11425
12435
|
const fileName = basename7(fullPath);
|
|
11426
12436
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
11427
12437
|
const mimeMap = {
|
|
@@ -11483,7 +12493,7 @@ ${p.text}` : p.text;
|
|
|
11483
12493
|
description: originalTool.description || "",
|
|
11484
12494
|
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
11485
12495
|
execute: async (input, toolOptions) => {
|
|
11486
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
12496
|
+
const toolCallId = toolOptions.toolCallId || nanoid9();
|
|
11487
12497
|
const execution = toolExecutionQueries.create({
|
|
11488
12498
|
sessionId: this.session.id,
|
|
11489
12499
|
toolName: name,
|
|
@@ -11555,188 +12565,40 @@ ${p.text}` : p.text;
|
|
|
11555
12565
|
/**
|
|
11556
12566
|
* Reject a pending tool execution
|
|
11557
12567
|
*/
|
|
11558
|
-
async reject(toolCallId, reason) {
|
|
11559
|
-
const resolver = approvalResolvers.get(toolCallId);
|
|
11560
|
-
if (resolver) {
|
|
11561
|
-
resolver.reason = reason;
|
|
11562
|
-
resolver.resolve(false);
|
|
11563
|
-
return { rejected: true };
|
|
11564
|
-
}
|
|
11565
|
-
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11566
|
-
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
11567
|
-
if (!execution) {
|
|
11568
|
-
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
11569
|
-
}
|
|
11570
|
-
await toolExecutionQueries.reject(execution.id);
|
|
11571
|
-
return { rejected: true };
|
|
11572
|
-
}
|
|
11573
|
-
/**
|
|
11574
|
-
* Get pending approvals
|
|
11575
|
-
*/
|
|
11576
|
-
async getPendingApprovals() {
|
|
11577
|
-
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
11578
|
-
}
|
|
11579
|
-
/**
|
|
11580
|
-
* Get context statistics
|
|
11581
|
-
*/
|
|
11582
|
-
getContextStats() {
|
|
11583
|
-
return this.context.getStats();
|
|
11584
|
-
}
|
|
11585
|
-
/**
|
|
11586
|
-
* Clear conversation context (start fresh)
|
|
11587
|
-
*/
|
|
11588
|
-
clearContext() {
|
|
11589
|
-
this.context.clear();
|
|
11590
|
-
}
|
|
11591
|
-
};
|
|
11592
|
-
}
|
|
11593
|
-
});
|
|
11594
|
-
|
|
11595
|
-
// src/agent/session-lock.ts
|
|
11596
|
-
async function withSessionLock(sessionId, fn) {
|
|
11597
|
-
let state2 = locks.get(sessionId);
|
|
11598
|
-
if (!state2) {
|
|
11599
|
-
state2 = { tail: Promise.resolve(), pending: 0 };
|
|
11600
|
-
locks.set(sessionId, state2);
|
|
11601
|
-
}
|
|
11602
|
-
state2.pending++;
|
|
11603
|
-
const prev = state2.tail;
|
|
11604
|
-
let release;
|
|
11605
|
-
const next = new Promise((resolve14) => {
|
|
11606
|
-
release = resolve14;
|
|
11607
|
-
});
|
|
11608
|
-
state2.tail = prev.then(() => next);
|
|
11609
|
-
await prev;
|
|
11610
|
-
try {
|
|
11611
|
-
return await fn();
|
|
11612
|
-
} finally {
|
|
11613
|
-
release();
|
|
11614
|
-
state2.pending--;
|
|
11615
|
-
if (state2.pending === 0 && locks.get(sessionId) === state2) {
|
|
11616
|
-
locks.delete(sessionId);
|
|
11617
|
-
}
|
|
11618
|
-
}
|
|
11619
|
-
}
|
|
11620
|
-
var locks;
|
|
11621
|
-
var init_session_lock = __esm({
|
|
11622
|
-
"src/agent/session-lock.ts"() {
|
|
11623
|
-
"use strict";
|
|
11624
|
-
locks = /* @__PURE__ */ new Map();
|
|
11625
|
-
}
|
|
11626
|
-
});
|
|
11627
|
-
|
|
11628
|
-
// src/orchestrator/webhook-events.ts
|
|
11629
|
-
import { existsSync as existsSync18, readFileSync as readFileSync9, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
11630
|
-
import { dirname as dirname7, join as join12 } from "path";
|
|
11631
|
-
import { nanoid as nanoid9 } from "nanoid";
|
|
11632
|
-
function logFilePath() {
|
|
11633
|
-
return join12(getAppDataDirectory(), "webhook-events.jsonl");
|
|
11634
|
-
}
|
|
11635
|
-
function ensureLoaded() {
|
|
11636
|
-
if (cache !== null) return cache;
|
|
11637
|
-
cache = [];
|
|
11638
|
-
try {
|
|
11639
|
-
const p = logFilePath();
|
|
11640
|
-
if (!existsSync18(p)) return cache;
|
|
11641
|
-
const lines = readFileSync9(p, "utf-8").split("\n").filter(Boolean);
|
|
11642
|
-
for (const line of lines) {
|
|
11643
|
-
try {
|
|
11644
|
-
cache.push(JSON.parse(line));
|
|
11645
|
-
} catch {
|
|
11646
|
-
}
|
|
11647
|
-
}
|
|
11648
|
-
if (cache.length > MAX_EVENTS) {
|
|
11649
|
-
cache = cache.slice(-MAX_EVENTS);
|
|
11650
|
-
try {
|
|
11651
|
-
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11652
|
-
} catch {
|
|
11653
|
-
}
|
|
11654
|
-
}
|
|
11655
|
-
} catch {
|
|
11656
|
-
}
|
|
11657
|
-
return cache;
|
|
11658
|
-
}
|
|
11659
|
-
function appendEvent(ev) {
|
|
11660
|
-
const list = ensureLoaded();
|
|
11661
|
-
list.push(ev);
|
|
11662
|
-
if (list.length > MAX_EVENTS) list.shift();
|
|
11663
|
-
try {
|
|
11664
|
-
const p = logFilePath();
|
|
11665
|
-
mkdirSync7(dirname7(p), { recursive: true });
|
|
11666
|
-
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
11667
|
-
} catch {
|
|
11668
|
-
}
|
|
11669
|
-
}
|
|
11670
|
-
function recordEvent(ev) {
|
|
11671
|
-
const full = {
|
|
11672
|
-
id: ev.id ?? nanoid9(),
|
|
11673
|
-
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
11674
|
-
source: ev.source,
|
|
11675
|
-
status: ev.status,
|
|
11676
|
-
subtype: ev.subtype,
|
|
11677
|
-
channel: ev.channel,
|
|
11678
|
-
user: ev.user,
|
|
11679
|
-
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
11680
|
-
dropReason: ev.dropReason,
|
|
11681
|
-
error: ev.error,
|
|
11682
|
-
sessionId: ev.sessionId,
|
|
11683
|
-
durationMs: ev.durationMs,
|
|
11684
|
-
meta: ev.meta
|
|
11685
|
-
};
|
|
11686
|
-
appendEvent(full);
|
|
11687
|
-
return full.id;
|
|
11688
|
-
}
|
|
11689
|
-
function updateEvent(id, patch) {
|
|
11690
|
-
const list = ensureLoaded();
|
|
11691
|
-
const i = list.findIndex((e) => e.id === id);
|
|
11692
|
-
if (i < 0) return;
|
|
11693
|
-
list[i] = { ...list[i], ...patch };
|
|
11694
|
-
try {
|
|
11695
|
-
const p = logFilePath();
|
|
11696
|
-
mkdirSync7(dirname7(p), { recursive: true });
|
|
11697
|
-
writeFileSync4(p, list.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
11698
|
-
} catch {
|
|
11699
|
-
}
|
|
11700
|
-
}
|
|
11701
|
-
function listEvents(filter = {}) {
|
|
11702
|
-
const list = ensureLoaded();
|
|
11703
|
-
const q = filter.q?.toLowerCase();
|
|
11704
|
-
const sinceTs = filter.since ? Date.parse(filter.since) : -Infinity;
|
|
11705
|
-
const beforeTs = filter.before ? Date.parse(filter.before) : Infinity;
|
|
11706
|
-
const matched = list.filter((e) => {
|
|
11707
|
-
if (filter.source && e.source !== filter.source) return false;
|
|
11708
|
-
if (filter.status && e.status !== filter.status) return false;
|
|
11709
|
-
const t = Date.parse(e.ts);
|
|
11710
|
-
if (t < sinceTs) return false;
|
|
11711
|
-
if (t >= beforeTs) return false;
|
|
11712
|
-
if (q) {
|
|
11713
|
-
const hay = `${e.channel ?? ""} ${e.user ?? ""} ${e.textSnippet ?? ""} ${e.dropReason ?? ""} ${e.error ?? ""} ${e.subtype ?? ""}`.toLowerCase();
|
|
11714
|
-
if (!hay.includes(q)) return false;
|
|
11715
|
-
}
|
|
11716
|
-
return true;
|
|
11717
|
-
});
|
|
11718
|
-
matched.reverse();
|
|
11719
|
-
const offset = Math.max(0, filter.offset ?? 0);
|
|
11720
|
-
const limit = Math.min(500, Math.max(1, filter.limit ?? 50));
|
|
11721
|
-
return {
|
|
11722
|
-
events: matched.slice(offset, offset + limit),
|
|
11723
|
-
total: matched.length
|
|
11724
|
-
};
|
|
11725
|
-
}
|
|
11726
|
-
function clearAllEvents() {
|
|
11727
|
-
cache = [];
|
|
11728
|
-
try {
|
|
11729
|
-
writeFileSync4(logFilePath(), "");
|
|
11730
|
-
} catch {
|
|
11731
|
-
}
|
|
11732
|
-
}
|
|
11733
|
-
var MAX_EVENTS, cache;
|
|
11734
|
-
var init_webhook_events = __esm({
|
|
11735
|
-
"src/orchestrator/webhook-events.ts"() {
|
|
11736
|
-
"use strict";
|
|
11737
|
-
init_config();
|
|
11738
|
-
MAX_EVENTS = 1e3;
|
|
11739
|
-
cache = null;
|
|
12568
|
+
async reject(toolCallId, reason) {
|
|
12569
|
+
const resolver = approvalResolvers.get(toolCallId);
|
|
12570
|
+
if (resolver) {
|
|
12571
|
+
resolver.reason = reason;
|
|
12572
|
+
resolver.resolve(false);
|
|
12573
|
+
return { rejected: true };
|
|
12574
|
+
}
|
|
12575
|
+
const pendingFromDb = await toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
12576
|
+
const execution = pendingFromDb.find((e) => e.toolCallId === toolCallId);
|
|
12577
|
+
if (!execution) {
|
|
12578
|
+
throw new Error(`No pending approval for tool call: ${toolCallId}`);
|
|
12579
|
+
}
|
|
12580
|
+
await toolExecutionQueries.reject(execution.id);
|
|
12581
|
+
return { rejected: true };
|
|
12582
|
+
}
|
|
12583
|
+
/**
|
|
12584
|
+
* Get pending approvals
|
|
12585
|
+
*/
|
|
12586
|
+
async getPendingApprovals() {
|
|
12587
|
+
return toolExecutionQueries.getPendingApprovals(this.session.id);
|
|
12588
|
+
}
|
|
12589
|
+
/**
|
|
12590
|
+
* Get context statistics
|
|
12591
|
+
*/
|
|
12592
|
+
getContextStats() {
|
|
12593
|
+
return this.context.getStats();
|
|
12594
|
+
}
|
|
12595
|
+
/**
|
|
12596
|
+
* Clear conversation context (start fresh)
|
|
12597
|
+
*/
|
|
12598
|
+
clearContext() {
|
|
12599
|
+
this.context.clear();
|
|
12600
|
+
}
|
|
12601
|
+
};
|
|
11740
12602
|
}
|
|
11741
12603
|
});
|
|
11742
12604
|
|
|
@@ -11812,7 +12674,24 @@ async function runDaemonTurn(sessionId, events) {
|
|
|
11812
12674
|
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
11813
12675
|
meta: { triggeredBy: events.map((e) => e.content?.slice(0, 80)) }
|
|
11814
12676
|
});
|
|
12677
|
+
try {
|
|
12678
|
+
resolveBatchOnTurnEnd(events, !error);
|
|
12679
|
+
} catch (err) {
|
|
12680
|
+
console.error("[daemon] ack bookkeeping threw:", err?.message || err);
|
|
12681
|
+
}
|
|
11815
12682
|
broadcast({ sessionId, text: trimmed, triggeredBy: events, startedAt, finishedAt, error });
|
|
12683
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12684
|
+
for (const ev of events) {
|
|
12685
|
+
if (ev.ref?.channel !== "slack") continue;
|
|
12686
|
+
const ref = ev.ref;
|
|
12687
|
+
const channel = ref.slackChannel;
|
|
12688
|
+
const ts = ref.messageTs;
|
|
12689
|
+
if (!channel || !ts) continue;
|
|
12690
|
+
const key2 = `${channel}\u241F${ts}`;
|
|
12691
|
+
if (seen.has(key2)) continue;
|
|
12692
|
+
seen.add(key2);
|
|
12693
|
+
void removeLoadingReaction(channel, ts);
|
|
12694
|
+
}
|
|
11816
12695
|
}
|
|
11817
12696
|
var listeners;
|
|
11818
12697
|
var init_daemon = __esm({
|
|
@@ -11823,6 +12702,8 @@ var init_daemon = __esm({
|
|
|
11823
12702
|
init_db();
|
|
11824
12703
|
init_inbox();
|
|
11825
12704
|
init_webhook_events();
|
|
12705
|
+
init_inbox_acks();
|
|
12706
|
+
init_client3();
|
|
11826
12707
|
listeners = /* @__PURE__ */ new Map();
|
|
11827
12708
|
}
|
|
11828
12709
|
});
|
|
@@ -11921,6 +12802,233 @@ var init_ensure_orchestrator = __esm({
|
|
|
11921
12802
|
}
|
|
11922
12803
|
});
|
|
11923
12804
|
|
|
12805
|
+
// src/orchestrator/self-update.ts
|
|
12806
|
+
var self_update_exports = {};
|
|
12807
|
+
__export(self_update_exports, {
|
|
12808
|
+
__test: () => __test,
|
|
12809
|
+
startSelfUpdater: () => startSelfUpdater,
|
|
12810
|
+
stopSelfUpdater: () => stopSelfUpdater
|
|
12811
|
+
});
|
|
12812
|
+
import { spawn as spawn2, execFile } from "child_process";
|
|
12813
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10 } from "fs";
|
|
12814
|
+
import { dirname as dirname10, join as join18 } from "path";
|
|
12815
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12816
|
+
function currentVersion2() {
|
|
12817
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
12818
|
+
const candidates = [
|
|
12819
|
+
join18(here, "..", "..", "package.json"),
|
|
12820
|
+
join18(here, "..", "package.json"),
|
|
12821
|
+
join18(process.cwd(), "package.json")
|
|
12822
|
+
];
|
|
12823
|
+
for (const p of candidates) {
|
|
12824
|
+
try {
|
|
12825
|
+
const pkg = JSON.parse(readFileSync11(p, "utf8"));
|
|
12826
|
+
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
12827
|
+
} catch {
|
|
12828
|
+
}
|
|
12829
|
+
}
|
|
12830
|
+
return "0.0.0";
|
|
12831
|
+
}
|
|
12832
|
+
function isLikelyGlobalInstall() {
|
|
12833
|
+
const here = dirname10(fileURLToPath4(import.meta.url));
|
|
12834
|
+
return here.includes("/node_modules/sparkecoder/") || here.includes("\\node_modules\\sparkecoder\\");
|
|
12835
|
+
}
|
|
12836
|
+
function isEnabled() {
|
|
12837
|
+
if (process.env.SPARKECODER_AUTO_UPDATE === "false" || process.env.SPARKECODER_AUTO_UPDATE === "0") return false;
|
|
12838
|
+
try {
|
|
12839
|
+
const cfg = getConfig();
|
|
12840
|
+
if (cfg?.autoUpdate?.enabled === false) return false;
|
|
12841
|
+
} catch {
|
|
12842
|
+
}
|
|
12843
|
+
return true;
|
|
12844
|
+
}
|
|
12845
|
+
function remoteUrl() {
|
|
12846
|
+
try {
|
|
12847
|
+
const cfg = getConfig();
|
|
12848
|
+
const url = cfg?.remoteServer?.url;
|
|
12849
|
+
return typeof url === "string" && url.length > 0 ? url.replace(/\/+$/, "") : null;
|
|
12850
|
+
} catch {
|
|
12851
|
+
return null;
|
|
12852
|
+
}
|
|
12853
|
+
}
|
|
12854
|
+
function intervalMs() {
|
|
12855
|
+
try {
|
|
12856
|
+
const h = getConfig()?.autoUpdate?.intervalHours;
|
|
12857
|
+
if (typeof h === "number" && h > 0) return h * 60 * 6e4;
|
|
12858
|
+
} catch {
|
|
12859
|
+
}
|
|
12860
|
+
return DEFAULT_INTERVAL_HOURS * 60 * 6e4;
|
|
12861
|
+
}
|
|
12862
|
+
function semverGt(a, b) {
|
|
12863
|
+
const parse = (v) => v.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
|
|
12864
|
+
const pa = parse(a);
|
|
12865
|
+
const pb = parse(b);
|
|
12866
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
12867
|
+
const x = pa[i] ?? 0;
|
|
12868
|
+
const y = pb[i] ?? 0;
|
|
12869
|
+
if (x > y) return true;
|
|
12870
|
+
if (x < y) return false;
|
|
12871
|
+
}
|
|
12872
|
+
return false;
|
|
12873
|
+
}
|
|
12874
|
+
function statePath() {
|
|
12875
|
+
try {
|
|
12876
|
+
return join18(getAppDataDirectory(), "self-update-state.json");
|
|
12877
|
+
} catch {
|
|
12878
|
+
return null;
|
|
12879
|
+
}
|
|
12880
|
+
}
|
|
12881
|
+
function readState() {
|
|
12882
|
+
const p = statePath();
|
|
12883
|
+
if (!p) return {};
|
|
12884
|
+
try {
|
|
12885
|
+
return JSON.parse(readFileSync11(p, "utf8"));
|
|
12886
|
+
} catch {
|
|
12887
|
+
return {};
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
function writeState(s) {
|
|
12891
|
+
const p = statePath();
|
|
12892
|
+
if (!p) return;
|
|
12893
|
+
try {
|
|
12894
|
+
mkdirSync10(dirname10(p), { recursive: true });
|
|
12895
|
+
writeFileSync7(p, JSON.stringify(s));
|
|
12896
|
+
} catch {
|
|
12897
|
+
}
|
|
12898
|
+
}
|
|
12899
|
+
function attemptedRecently(target, now) {
|
|
12900
|
+
const s = readState();
|
|
12901
|
+
return s.lastTarget === target && typeof s.lastAttemptAt === "number" && now - s.lastAttemptAt < RETRY_COOLDOWN_MS;
|
|
12902
|
+
}
|
|
12903
|
+
function latestPublishedVersion() {
|
|
12904
|
+
return new Promise((resolve14) => {
|
|
12905
|
+
execFile("npm", ["view", "sparkecoder", "version"], { timeout: 3e4 }, (err, stdout) => {
|
|
12906
|
+
if (err) {
|
|
12907
|
+
resolve14(null);
|
|
12908
|
+
return;
|
|
12909
|
+
}
|
|
12910
|
+
const v = String(stdout).trim();
|
|
12911
|
+
resolve14(/^\d+\.\d+\.\d+/.test(v) ? v : null);
|
|
12912
|
+
});
|
|
12913
|
+
});
|
|
12914
|
+
}
|
|
12915
|
+
function runInstaller(url) {
|
|
12916
|
+
const secret = process.env.SPARKECODER_SETUP_SECRET || process.env.SPARKECODER_TUNNEL_SECRET || "";
|
|
12917
|
+
const query = secret ? `?secret=${encodeURIComponent(secret)}` : "";
|
|
12918
|
+
const oneLiner = `bash -c "$(curl -fsSL '${url}/install.sh${query}')" >/tmp/sparkecoder-selfupdate.log 2>&1`;
|
|
12919
|
+
const child = spawn2("bash", ["-lc", oneLiner], {
|
|
12920
|
+
detached: true,
|
|
12921
|
+
stdio: "ignore"
|
|
12922
|
+
});
|
|
12923
|
+
child.unref();
|
|
12924
|
+
}
|
|
12925
|
+
async function checkAndUpdate() {
|
|
12926
|
+
if (upgrading || !isEnabled()) return;
|
|
12927
|
+
const url = remoteUrl();
|
|
12928
|
+
if (!url) return;
|
|
12929
|
+
const latest = await latestPublishedVersion();
|
|
12930
|
+
if (!latest) return;
|
|
12931
|
+
const current = currentVersion2();
|
|
12932
|
+
if (!semverGt(latest, current)) return;
|
|
12933
|
+
const now = Date.now();
|
|
12934
|
+
if (attemptedRecently(latest, now)) {
|
|
12935
|
+
console.log(`[self-update] v${latest} already attempted recently; skipping until cooldown elapses`);
|
|
12936
|
+
return;
|
|
12937
|
+
}
|
|
12938
|
+
upgrading = true;
|
|
12939
|
+
const announced = await announceUpdate(latest);
|
|
12940
|
+
const delay = announced ? ANNOUNCE_GRACE_MS : 0;
|
|
12941
|
+
if (announced) {
|
|
12942
|
+
console.log(`[self-update] announced v${latest} in Slack; updating in ${Math.round(delay / 6e4)}m`);
|
|
12943
|
+
}
|
|
12944
|
+
const t = setTimeout(() => doInstall(latest, url, current), delay);
|
|
12945
|
+
if (typeof t.unref === "function") t.unref();
|
|
12946
|
+
}
|
|
12947
|
+
function doInstall(latest, url, current) {
|
|
12948
|
+
const prev = readState();
|
|
12949
|
+
writeState({
|
|
12950
|
+
lastTarget: latest,
|
|
12951
|
+
lastAttemptAt: Date.now(),
|
|
12952
|
+
attempts: prev.lastTarget === latest ? (prev.attempts ?? 0) + 1 : 1
|
|
12953
|
+
});
|
|
12954
|
+
console.log(`[self-update] newer version available: v${current} \u2192 v${latest}; re-running installer`);
|
|
12955
|
+
try {
|
|
12956
|
+
runInstaller(url);
|
|
12957
|
+
} catch (err) {
|
|
12958
|
+
upgrading = false;
|
|
12959
|
+
console.warn("[self-update] failed to launch installer:", err?.message || err);
|
|
12960
|
+
}
|
|
12961
|
+
}
|
|
12962
|
+
async function findOrchestratorId() {
|
|
12963
|
+
try {
|
|
12964
|
+
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
12965
|
+
const all = await sessionQueries2.list(500, 0);
|
|
12966
|
+
const orch = all.find((s) => s?.config?.role === "orchestrator");
|
|
12967
|
+
return orch?.id ?? null;
|
|
12968
|
+
} catch {
|
|
12969
|
+
return null;
|
|
12970
|
+
}
|
|
12971
|
+
}
|
|
12972
|
+
async function announceUpdate(target) {
|
|
12973
|
+
try {
|
|
12974
|
+
const { isSlackConfigured: isSlackConfigured2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
12975
|
+
if (!isSlackConfigured2()) return false;
|
|
12976
|
+
const orchId = await findOrchestratorId();
|
|
12977
|
+
if (!orchId) return false;
|
|
12978
|
+
const { pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports));
|
|
12979
|
+
pushToInbox2(orchId, {
|
|
12980
|
+
ref: { channel: "system", kind: "worker.completed", workerId: "self-update", workerName: "self-update" },
|
|
12981
|
+
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.`,
|
|
12982
|
+
wake: "now",
|
|
12983
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
12984
|
+
});
|
|
12985
|
+
return true;
|
|
12986
|
+
} catch {
|
|
12987
|
+
return false;
|
|
12988
|
+
}
|
|
12989
|
+
}
|
|
12990
|
+
function startSelfUpdater() {
|
|
12991
|
+
if (started) return;
|
|
12992
|
+
started = true;
|
|
12993
|
+
if (!isEnabled()) {
|
|
12994
|
+
console.log("[self-update] disabled");
|
|
12995
|
+
return;
|
|
12996
|
+
}
|
|
12997
|
+
if (!isLikelyGlobalInstall()) {
|
|
12998
|
+
console.log("[self-update] skipped (not a global install)");
|
|
12999
|
+
return;
|
|
13000
|
+
}
|
|
13001
|
+
const kickoff = setTimeout(() => {
|
|
13002
|
+
void checkAndUpdate();
|
|
13003
|
+
timer = setInterval(() => {
|
|
13004
|
+
void checkAndUpdate();
|
|
13005
|
+
}, intervalMs());
|
|
13006
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
13007
|
+
}, INITIAL_DELAY_MS);
|
|
13008
|
+
if (typeof kickoff.unref === "function") kickoff.unref();
|
|
13009
|
+
}
|
|
13010
|
+
function stopSelfUpdater() {
|
|
13011
|
+
if (timer) {
|
|
13012
|
+
clearInterval(timer);
|
|
13013
|
+
timer = null;
|
|
13014
|
+
}
|
|
13015
|
+
}
|
|
13016
|
+
var INITIAL_DELAY_MS, DEFAULT_INTERVAL_HOURS, ANNOUNCE_GRACE_MS, RETRY_COOLDOWN_MS, timer, started, upgrading, __test;
|
|
13017
|
+
var init_self_update = __esm({
|
|
13018
|
+
"src/orchestrator/self-update.ts"() {
|
|
13019
|
+
"use strict";
|
|
13020
|
+
init_config();
|
|
13021
|
+
INITIAL_DELAY_MS = 5 * 6e4;
|
|
13022
|
+
DEFAULT_INTERVAL_HOURS = 6;
|
|
13023
|
+
ANNOUNCE_GRACE_MS = 5 * 6e4;
|
|
13024
|
+
RETRY_COOLDOWN_MS = 24 * 60 * 6e4;
|
|
13025
|
+
timer = null;
|
|
13026
|
+
started = false;
|
|
13027
|
+
upgrading = false;
|
|
13028
|
+
__test = { currentVersion: currentVersion2, semverGt, isLikelyGlobalInstall };
|
|
13029
|
+
}
|
|
13030
|
+
});
|
|
13031
|
+
|
|
11924
13032
|
// src/tasks/scheduler.ts
|
|
11925
13033
|
var scheduler_exports = {};
|
|
11926
13034
|
__export(scheduler_exports, {
|
|
@@ -12223,8 +13331,8 @@ import chalk from "chalk";
|
|
|
12223
13331
|
import ora from "ora";
|
|
12224
13332
|
import "dotenv/config";
|
|
12225
13333
|
import { createInterface } from "readline";
|
|
12226
|
-
import { dirname as
|
|
12227
|
-
import { fileURLToPath as
|
|
13334
|
+
import { dirname as dirname12 } from "path";
|
|
13335
|
+
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
12228
13336
|
|
|
12229
13337
|
// src/server/index.ts
|
|
12230
13338
|
import "dotenv/config";
|
|
@@ -12232,11 +13340,11 @@ import { Hono as Hono10 } from "hono";
|
|
|
12232
13340
|
import { serve } from "@hono/node-server";
|
|
12233
13341
|
import { cors } from "hono/cors";
|
|
12234
13342
|
import { logger } from "hono/logger";
|
|
12235
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
12236
|
-
import { resolve as resolve12, dirname as
|
|
12237
|
-
import { spawn as
|
|
13343
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync11, writeFileSync as writeFileSync8 } from "fs";
|
|
13344
|
+
import { resolve as resolve12, dirname as dirname11, join as join19 } from "path";
|
|
13345
|
+
import { spawn as spawn3 } from "child_process";
|
|
12238
13346
|
import { createServer as createNetServer } from "net";
|
|
12239
|
-
import { fileURLToPath as
|
|
13347
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
12240
13348
|
|
|
12241
13349
|
// src/server/routes/sessions.ts
|
|
12242
13350
|
init_db();
|
|
@@ -12249,7 +13357,7 @@ import { zValidator } from "@hono/zod-validator";
|
|
|
12249
13357
|
import { z as z16 } from "zod";
|
|
12250
13358
|
import { existsSync as existsSync19, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync as statSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
12251
13359
|
import { readdir as readdir6 } from "fs/promises";
|
|
12252
|
-
import { join as
|
|
13360
|
+
import { join as join14, basename as basename5, extname as extname8, relative as relative9 } from "path";
|
|
12253
13361
|
import { nanoid as nanoid10 } from "nanoid";
|
|
12254
13362
|
|
|
12255
13363
|
// src/tasks/agent-status.ts
|
|
@@ -12890,7 +13998,7 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
12890
13998
|
});
|
|
12891
13999
|
function getAttachmentsDir(sessionId) {
|
|
12892
14000
|
const appDataDir = getAppDataDirectory();
|
|
12893
|
-
return
|
|
14001
|
+
return join14(appDataDir, "attachments", sessionId);
|
|
12894
14002
|
}
|
|
12895
14003
|
function ensureAttachmentsDir(sessionId) {
|
|
12896
14004
|
const dir = getAttachmentsDir(sessionId);
|
|
@@ -12911,7 +14019,7 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
12911
14019
|
}
|
|
12912
14020
|
const files = readdirSync3(dir);
|
|
12913
14021
|
const attachments = files.map((filename) => {
|
|
12914
|
-
const filePath =
|
|
14022
|
+
const filePath = join14(dir, filename);
|
|
12915
14023
|
const stats = statSync2(filePath);
|
|
12916
14024
|
return {
|
|
12917
14025
|
id: filename.split("_")[0],
|
|
@@ -12946,7 +14054,7 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12946
14054
|
const id = nanoid10(10);
|
|
12947
14055
|
const ext = extname8(file.name) || "";
|
|
12948
14056
|
const safeFilename = `${id}_${basename5(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12949
|
-
const filePath =
|
|
14057
|
+
const filePath = join14(dir, safeFilename);
|
|
12950
14058
|
const arrayBuffer = await file.arrayBuffer();
|
|
12951
14059
|
writeFileSync5(filePath, Buffer.from(arrayBuffer));
|
|
12952
14060
|
return c.json({
|
|
@@ -12972,7 +14080,7 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
12972
14080
|
const id = nanoid10(10);
|
|
12973
14081
|
const ext = extname8(body.filename) || "";
|
|
12974
14082
|
const safeFilename = `${id}_${basename5(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
12975
|
-
const filePath =
|
|
14083
|
+
const filePath = join14(dir, safeFilename);
|
|
12976
14084
|
let base64Data = body.data;
|
|
12977
14085
|
if (base64Data.includes(",")) {
|
|
12978
14086
|
base64Data = base64Data.split(",")[1];
|
|
@@ -13009,7 +14117,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
13009
14117
|
if (!file) {
|
|
13010
14118
|
return c.json({ error: "Attachment not found" }, 404);
|
|
13011
14119
|
}
|
|
13012
|
-
const filePath =
|
|
14120
|
+
const filePath = join14(dir, file);
|
|
13013
14121
|
unlinkSync2(filePath);
|
|
13014
14122
|
return c.json({ success: true, id: attachmentId });
|
|
13015
14123
|
});
|
|
@@ -13092,7 +14200,7 @@ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = [
|
|
|
13092
14200
|
const entries = await readdir6(currentDir, { withFileTypes: true });
|
|
13093
14201
|
for (const entry2 of entries) {
|
|
13094
14202
|
if (results.length >= limit * 2) break;
|
|
13095
|
-
const fullPath =
|
|
14203
|
+
const fullPath = join14(currentDir, entry2.name);
|
|
13096
14204
|
const relativePath = relative9(baseDir, fullPath);
|
|
13097
14205
|
if (entry2.isDirectory() && IGNORED_DIRECTORIES.has(entry2.name)) {
|
|
13098
14206
|
continue;
|
|
@@ -13252,7 +14360,7 @@ import { Hono as Hono2 } from "hono";
|
|
|
13252
14360
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
13253
14361
|
import { z as z17 } from "zod";
|
|
13254
14362
|
import { existsSync as existsSync20, mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
13255
|
-
import { join as
|
|
14363
|
+
import { join as join15 } from "path";
|
|
13256
14364
|
|
|
13257
14365
|
// src/agent/missing-tool-recovery.ts
|
|
13258
14366
|
init_db();
|
|
@@ -13519,6 +14627,7 @@ init_stream_proxy();
|
|
|
13519
14627
|
init_recorder();
|
|
13520
14628
|
init_remote();
|
|
13521
14629
|
init_resize_image();
|
|
14630
|
+
init_local_device_time();
|
|
13522
14631
|
var sessionRecorders = /* @__PURE__ */ new Map();
|
|
13523
14632
|
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
13524
14633
|
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
@@ -13634,7 +14743,7 @@ var rejectSchema = z17.object({
|
|
|
13634
14743
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
13635
14744
|
function getAttachmentsDirectory(sessionId) {
|
|
13636
14745
|
const appDataDir = getAppDataDirectory();
|
|
13637
|
-
return
|
|
14746
|
+
return join15(appDataDir, "attachments", sessionId);
|
|
13638
14747
|
}
|
|
13639
14748
|
async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
13640
14749
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
@@ -13657,7 +14766,7 @@ async function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
13657
14766
|
attachment.mediaType = resized.mediaType;
|
|
13658
14767
|
attachment.data = buffer.toString("base64");
|
|
13659
14768
|
}
|
|
13660
|
-
const filePath =
|
|
14769
|
+
const filePath = join15(attachmentsDir, filename);
|
|
13661
14770
|
writeFileSync6(filePath, buffer);
|
|
13662
14771
|
return filePath;
|
|
13663
14772
|
}
|
|
@@ -14034,9 +15143,12 @@ agents.post(
|
|
|
14034
15143
|
if (!session) {
|
|
14035
15144
|
return c.json({ error: "Session not found" }, 404);
|
|
14036
15145
|
}
|
|
14037
|
-
if (session.config?.role === "orchestrator" && !/^\[\
|
|
15146
|
+
if (session.config?.role === "orchestrator" && !/^\[(WEB|SLACK|SYSTEM|SCHEDULE|WEBHOOK)\b/.test(prompt)) {
|
|
14038
15147
|
prompt = `[WEB] ${prompt}`;
|
|
14039
15148
|
}
|
|
15149
|
+
if (session.config?.role === "orchestrator") {
|
|
15150
|
+
prompt = prependLocalDeviceTimeToUserMessage(prompt);
|
|
15151
|
+
}
|
|
14040
15152
|
const nextSequence = await messageQueries.getNextSequence(id);
|
|
14041
15153
|
await createCheckpoint(id, session.workingDirectory, nextSequence);
|
|
14042
15154
|
let userMessageContent;
|
|
@@ -14646,17 +15758,17 @@ import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
|
14646
15758
|
import { z as z18 } from "zod";
|
|
14647
15759
|
import { readFileSync as readFileSync10 } from "fs";
|
|
14648
15760
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14649
|
-
import { dirname as dirname8, join as
|
|
15761
|
+
import { dirname as dirname8, join as join16 } from "path";
|
|
14650
15762
|
var __filename = fileURLToPath3(import.meta.url);
|
|
14651
15763
|
var __dirname = dirname8(__filename);
|
|
14652
15764
|
var possiblePaths = [
|
|
14653
|
-
|
|
15765
|
+
join16(__dirname, "../package.json"),
|
|
14654
15766
|
// From dist/server -> dist/../package.json
|
|
14655
|
-
|
|
15767
|
+
join16(__dirname, "../../package.json"),
|
|
14656
15768
|
// From dist/server (if nested differently)
|
|
14657
|
-
|
|
15769
|
+
join16(__dirname, "../../../package.json"),
|
|
14658
15770
|
// From src/server/routes (development)
|
|
14659
|
-
|
|
15771
|
+
join16(process.cwd(), "package.json")
|
|
14660
15772
|
// From current working directory
|
|
14661
15773
|
];
|
|
14662
15774
|
var currentVersion = "0.0.0";
|
|
@@ -15117,6 +16229,25 @@ import { nanoid as nanoid12 } from "nanoid";
|
|
|
15117
16229
|
init_questions();
|
|
15118
16230
|
var tasks = new Hono5();
|
|
15119
16231
|
var taskAbortControllers = /* @__PURE__ */ new Map();
|
|
16232
|
+
var taskMcpServerSchema = z20.object({
|
|
16233
|
+
name: z20.string().min(1).describe("Tool prefix + display name."),
|
|
16234
|
+
transport: z20.enum(["http", "sse", "stdio"]),
|
|
16235
|
+
url: z20.string().url().optional().describe("http/sse transports."),
|
|
16236
|
+
headers: z20.record(z20.string(), z20.string()).optional().describe("Auth / custom headers for http/sse."),
|
|
16237
|
+
command: z20.string().optional().describe("stdio transport."),
|
|
16238
|
+
args: z20.array(z20.string()).optional(),
|
|
16239
|
+
env: z20.record(z20.string(), z20.string()).optional().describe("Env vars for stdio child process.")
|
|
16240
|
+
}).refine(
|
|
16241
|
+
(s) => s.transport === "stdio" ? !!s.command : !!s.url,
|
|
16242
|
+
{ message: 'http/sse require "url"; stdio requires "command".' }
|
|
16243
|
+
);
|
|
16244
|
+
var taskSkillSchema = z20.object({
|
|
16245
|
+
name: z20.string().min(1),
|
|
16246
|
+
description: z20.string().optional(),
|
|
16247
|
+
content: z20.string().min(1).describe("Full markdown body of the skill."),
|
|
16248
|
+
alwaysApply: z20.boolean().optional().describe("Inject into the system prompt up-front (vs load on demand)."),
|
|
16249
|
+
globs: z20.array(z20.string()).optional()
|
|
16250
|
+
});
|
|
15120
16251
|
var createTaskSchema = z20.object({
|
|
15121
16252
|
prompt: z20.string().min(1),
|
|
15122
16253
|
outputSchema: z20.record(z20.string(), z20.unknown()),
|
|
@@ -15128,8 +16259,30 @@ var createTaskSchema = z20.object({
|
|
|
15128
16259
|
parentTaskId: z20.string().optional(),
|
|
15129
16260
|
/** When set, the spawning orchestrator's session id. Stamped on the
|
|
15130
16261
|
* worker's config so terminal events can wake the orchestrator. */
|
|
15131
|
-
orchestratorSessionId: z20.string().optional()
|
|
16262
|
+
orchestratorSessionId: z20.string().optional(),
|
|
16263
|
+
/** Task-scoped MCP servers — auto-connected for this task only. */
|
|
16264
|
+
mcpServers: z20.array(taskMcpServerSchema).optional(),
|
|
16265
|
+
/** Task-scoped skills — available to this task only. */
|
|
16266
|
+
skills: z20.array(taskSkillSchema).optional()
|
|
15132
16267
|
});
|
|
16268
|
+
function redactMcpServers(servers2) {
|
|
16269
|
+
if (!servers2 || servers2.length === 0) return void 0;
|
|
16270
|
+
return servers2.map((s) => ({
|
|
16271
|
+
name: s.name,
|
|
16272
|
+
transport: s.transport,
|
|
16273
|
+
url: s.url,
|
|
16274
|
+
hasHeaders: !!(s.headers && Object.keys(s.headers).length > 0),
|
|
16275
|
+
command: s.command
|
|
16276
|
+
}));
|
|
16277
|
+
}
|
|
16278
|
+
function redactSkills(skills2) {
|
|
16279
|
+
if (!skills2 || skills2.length === 0) return void 0;
|
|
16280
|
+
return skills2.map((s) => ({
|
|
16281
|
+
name: s.name,
|
|
16282
|
+
description: s.description,
|
|
16283
|
+
alwaysApply: s.alwaysApply
|
|
16284
|
+
}));
|
|
16285
|
+
}
|
|
15133
16286
|
tasks.post(
|
|
15134
16287
|
"/",
|
|
15135
16288
|
zValidator5("json", createTaskSchema),
|
|
@@ -15142,7 +16295,9 @@ tasks.post(
|
|
|
15142
16295
|
webhookUrl: body.webhookUrl,
|
|
15143
16296
|
maxIterations: body.maxIterations ?? 50,
|
|
15144
16297
|
status: "running",
|
|
15145
|
-
parentTaskId: body.parentTaskId
|
|
16298
|
+
parentTaskId: body.parentTaskId,
|
|
16299
|
+
mcpServers: redactMcpServers(body.mcpServers),
|
|
16300
|
+
skills: redactSkills(body.skills)
|
|
15146
16301
|
};
|
|
15147
16302
|
let agent;
|
|
15148
16303
|
if (body.parentTaskId) {
|
|
@@ -15219,7 +16374,9 @@ tasks.post(
|
|
|
15219
16374
|
prompt: body.prompt,
|
|
15220
16375
|
taskConfig,
|
|
15221
16376
|
abortSignal: abortController.signal,
|
|
15222
|
-
writeSSE
|
|
16377
|
+
writeSSE,
|
|
16378
|
+
mcpServers: body.mcpServers,
|
|
16379
|
+
skills: body.skills
|
|
15223
16380
|
});
|
|
15224
16381
|
await writeSSE(JSON.stringify({ type: "finish" }));
|
|
15225
16382
|
} catch (err) {
|
|
@@ -15313,6 +16470,8 @@ tasks.get("/:id", async (c) => {
|
|
|
15313
16470
|
model: session.model,
|
|
15314
16471
|
name: session.name,
|
|
15315
16472
|
parentTaskId: task.parentTaskId,
|
|
16473
|
+
mcpServers: task.mcpServers,
|
|
16474
|
+
skills: task.skills,
|
|
15316
16475
|
createdAt: session.createdAt.toISOString(),
|
|
15317
16476
|
updatedAt: session.updatedAt.toISOString(),
|
|
15318
16477
|
browserRecordings: browserRecordings.length > 0 ? browserRecordings : void 0
|
|
@@ -15435,6 +16594,204 @@ function verifySlackSignature(opts) {
|
|
|
15435
16594
|
// src/server/routes/slack.ts
|
|
15436
16595
|
init_client3();
|
|
15437
16596
|
init_slack();
|
|
16597
|
+
|
|
16598
|
+
// src/integrations/slack/files.ts
|
|
16599
|
+
init_client3();
|
|
16600
|
+
var MAX_BYTES = 100 * 1024 * 1024;
|
|
16601
|
+
var INGEST_TIMEOUT_MS = 2500;
|
|
16602
|
+
function inferFileName(file) {
|
|
16603
|
+
return typeof file.name === "string" && file.name || typeof file.title === "string" && file.title || `slack-file-${file.id}`;
|
|
16604
|
+
}
|
|
16605
|
+
function inferContentType(file) {
|
|
16606
|
+
if (typeof file.mimetype === "string" && file.mimetype) return file.mimetype;
|
|
16607
|
+
return "application/octet-stream";
|
|
16608
|
+
}
|
|
16609
|
+
function formatBytes(n) {
|
|
16610
|
+
if (!Number.isFinite(n) || n <= 0) return "?";
|
|
16611
|
+
if (n < 1024) return `${n} B`;
|
|
16612
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
16613
|
+
return `${(n / 1024 / 1024).toFixed(2)} MB`;
|
|
16614
|
+
}
|
|
16615
|
+
function withTimeout(p, ms, label) {
|
|
16616
|
+
return new Promise((resolve14, reject) => {
|
|
16617
|
+
const t = setTimeout(() => reject(new Error(`${label}_timeout`)), ms);
|
|
16618
|
+
p.then(
|
|
16619
|
+
(v) => {
|
|
16620
|
+
clearTimeout(t);
|
|
16621
|
+
resolve14(v);
|
|
16622
|
+
},
|
|
16623
|
+
(e) => {
|
|
16624
|
+
clearTimeout(t);
|
|
16625
|
+
reject(e);
|
|
16626
|
+
}
|
|
16627
|
+
);
|
|
16628
|
+
});
|
|
16629
|
+
}
|
|
16630
|
+
async function ingestOne(file, sessionId, botToken) {
|
|
16631
|
+
const fileName = inferFileName(file);
|
|
16632
|
+
const contentType = inferContentType(file);
|
|
16633
|
+
const declaredSize = typeof file.size === "number" ? file.size : 0;
|
|
16634
|
+
const base = {
|
|
16635
|
+
slackFileId: file.id,
|
|
16636
|
+
fileName,
|
|
16637
|
+
contentType,
|
|
16638
|
+
sizeBytes: declaredSize
|
|
16639
|
+
};
|
|
16640
|
+
const sourceUrl = file.url_private_download || file.url_private;
|
|
16641
|
+
if (!sourceUrl || typeof sourceUrl !== "string") {
|
|
16642
|
+
return { ...base, shortUrl: null, error: "no_source_url" };
|
|
16643
|
+
}
|
|
16644
|
+
if (declaredSize > MAX_BYTES) {
|
|
16645
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
16646
|
+
}
|
|
16647
|
+
let bytes;
|
|
16648
|
+
try {
|
|
16649
|
+
const res = await fetch(sourceUrl, {
|
|
16650
|
+
headers: { Authorization: `Bearer ${botToken}` }
|
|
16651
|
+
});
|
|
16652
|
+
if (!res.ok) {
|
|
16653
|
+
return { ...base, shortUrl: null, error: `slack_fetch_${res.status}` };
|
|
16654
|
+
}
|
|
16655
|
+
const ab = await res.arrayBuffer();
|
|
16656
|
+
if (ab.byteLength > MAX_BYTES) {
|
|
16657
|
+
return { ...base, shortUrl: null, error: "size_exceeded" };
|
|
16658
|
+
}
|
|
16659
|
+
bytes = Buffer.from(ab);
|
|
16660
|
+
} catch (err) {
|
|
16661
|
+
return { ...base, shortUrl: null, error: `slack_fetch_error:${err?.message || "unknown"}` };
|
|
16662
|
+
}
|
|
16663
|
+
const { storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
16664
|
+
let upload;
|
|
16665
|
+
try {
|
|
16666
|
+
upload = await storageQueries2.getUploadUrl(sessionId, fileName, contentType, "slack");
|
|
16667
|
+
} catch (err) {
|
|
16668
|
+
return { ...base, sizeBytes: bytes.length, shortUrl: null, error: `presign_failed:${err?.message || "unknown"}` };
|
|
16669
|
+
}
|
|
16670
|
+
try {
|
|
16671
|
+
const putRes = await fetch(upload.uploadUrl, {
|
|
16672
|
+
method: "PUT",
|
|
16673
|
+
headers: { "Content-Type": contentType },
|
|
16674
|
+
body: bytes
|
|
16675
|
+
});
|
|
16676
|
+
if (!putRes.ok) {
|
|
16677
|
+
return {
|
|
16678
|
+
...base,
|
|
16679
|
+
sizeBytes: bytes.length,
|
|
16680
|
+
shortUrl: null,
|
|
16681
|
+
error: `gcs_put_${putRes.status}`
|
|
16682
|
+
};
|
|
16683
|
+
}
|
|
16684
|
+
} catch (err) {
|
|
16685
|
+
return {
|
|
16686
|
+
...base,
|
|
16687
|
+
sizeBytes: bytes.length,
|
|
16688
|
+
shortUrl: null,
|
|
16689
|
+
error: `gcs_put_error:${err?.message || "unknown"}`
|
|
16690
|
+
};
|
|
16691
|
+
}
|
|
16692
|
+
try {
|
|
16693
|
+
await storageQueries2.updateFile(upload.fileId, { sizeBytes: bytes.length });
|
|
16694
|
+
} catch (err) {
|
|
16695
|
+
console.warn(`[slack-files] sizeBytes patch failed for ${upload.fileId}:`, err?.message || err);
|
|
16696
|
+
}
|
|
16697
|
+
const shortUrl = upload.shortUrl || // Defensive fallback: build it from the upload URL's origin if the
|
|
16698
|
+
// server somehow forgot to return it (older remote-server versions).
|
|
16699
|
+
inferShortUrlFromUploadUrl(upload.uploadUrl, upload.fileId);
|
|
16700
|
+
return {
|
|
16701
|
+
...base,
|
|
16702
|
+
sizeBytes: bytes.length,
|
|
16703
|
+
shortUrl
|
|
16704
|
+
};
|
|
16705
|
+
}
|
|
16706
|
+
function inferShortUrlFromUploadUrl(uploadUrl, fileId) {
|
|
16707
|
+
try {
|
|
16708
|
+
const u = new URL(uploadUrl);
|
|
16709
|
+
if (u.hostname.endsWith(".googleapis.com")) return null;
|
|
16710
|
+
return `${u.origin}/f/${fileId}`;
|
|
16711
|
+
} catch {
|
|
16712
|
+
return null;
|
|
16713
|
+
}
|
|
16714
|
+
}
|
|
16715
|
+
async function ingestSlackFiles(files, sessionId, options = {}) {
|
|
16716
|
+
if (!Array.isArray(files) || files.length === 0) return [];
|
|
16717
|
+
const { isRemoteConfigured: isRemoteConfigured2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
16718
|
+
if (!isRemoteConfigured2()) {
|
|
16719
|
+
console.warn(`[slack-files] storage not configured \u2014 skipping ingestion for ${files.length} file(s)`);
|
|
16720
|
+
return files.map((f) => ({
|
|
16721
|
+
slackFileId: f.id,
|
|
16722
|
+
fileName: inferFileName(f),
|
|
16723
|
+
contentType: inferContentType(f),
|
|
16724
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
16725
|
+
shortUrl: null,
|
|
16726
|
+
error: "storage_unconfigured"
|
|
16727
|
+
}));
|
|
16728
|
+
}
|
|
16729
|
+
const botToken = getSlackBotToken();
|
|
16730
|
+
if (!botToken) {
|
|
16731
|
+
console.warn("[slack-files] no bot token \u2014 cannot download from Slack");
|
|
16732
|
+
return files.map((f) => ({
|
|
16733
|
+
slackFileId: f.id,
|
|
16734
|
+
fileName: inferFileName(f),
|
|
16735
|
+
contentType: inferContentType(f),
|
|
16736
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
16737
|
+
shortUrl: null,
|
|
16738
|
+
error: "no_bot_token"
|
|
16739
|
+
}));
|
|
16740
|
+
}
|
|
16741
|
+
const timeoutMs = options.timeoutMs ?? INGEST_TIMEOUT_MS;
|
|
16742
|
+
const startedAt = Date.now();
|
|
16743
|
+
const pipeline = Promise.allSettled(
|
|
16744
|
+
files.map((f) => ingestOne(f, sessionId, botToken))
|
|
16745
|
+
);
|
|
16746
|
+
let settled;
|
|
16747
|
+
try {
|
|
16748
|
+
settled = await withTimeout(pipeline, timeoutMs, "ingest");
|
|
16749
|
+
} catch (err) {
|
|
16750
|
+
console.warn(`[slack-files] pipeline timeout after ${Date.now() - startedAt}ms (${err?.message || "timeout"})`);
|
|
16751
|
+
return files.map((f) => ({
|
|
16752
|
+
slackFileId: f.id,
|
|
16753
|
+
fileName: inferFileName(f),
|
|
16754
|
+
contentType: inferContentType(f),
|
|
16755
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
16756
|
+
shortUrl: null,
|
|
16757
|
+
error: "timeout"
|
|
16758
|
+
}));
|
|
16759
|
+
}
|
|
16760
|
+
const results = settled.map((s, i) => {
|
|
16761
|
+
if (s.status === "fulfilled") return s.value;
|
|
16762
|
+
const f = files[i];
|
|
16763
|
+
return {
|
|
16764
|
+
slackFileId: f.id,
|
|
16765
|
+
fileName: inferFileName(f),
|
|
16766
|
+
contentType: inferContentType(f),
|
|
16767
|
+
sizeBytes: typeof f.size === "number" ? f.size : 0,
|
|
16768
|
+
shortUrl: null,
|
|
16769
|
+
error: `unexpected:${s.reason?.message || String(s.reason)}`
|
|
16770
|
+
};
|
|
16771
|
+
});
|
|
16772
|
+
const okCount = results.filter((r) => r.shortUrl).length;
|
|
16773
|
+
console.log(
|
|
16774
|
+
`[slack-files] ingested ${okCount}/${files.length} file(s) in ${Date.now() - startedAt}ms`
|
|
16775
|
+
);
|
|
16776
|
+
return results;
|
|
16777
|
+
}
|
|
16778
|
+
function formatFileBlock(files) {
|
|
16779
|
+
if (!files || files.length === 0) return "";
|
|
16780
|
+
const lines = ["[files]"];
|
|
16781
|
+
for (const f of files) {
|
|
16782
|
+
const sizeLabel = formatBytes(f.sizeBytes);
|
|
16783
|
+
if (f.shortUrl) {
|
|
16784
|
+
lines.push(` - ${f.fileName} (${f.contentType}, ${sizeLabel}): ${f.shortUrl}`);
|
|
16785
|
+
} else {
|
|
16786
|
+
lines.push(
|
|
16787
|
+
` - ${f.fileName} (${f.contentType}, ${sizeLabel}): [ingestion failed: ${f.error || "unknown"}]`
|
|
16788
|
+
);
|
|
16789
|
+
}
|
|
16790
|
+
}
|
|
16791
|
+
return lines.join("\n");
|
|
16792
|
+
}
|
|
16793
|
+
|
|
16794
|
+
// src/server/routes/slack.ts
|
|
15438
16795
|
init_webhook_events();
|
|
15439
16796
|
init_inbox();
|
|
15440
16797
|
var recentlyHandled = /* @__PURE__ */ new Map();
|
|
@@ -15521,9 +16878,43 @@ slack.post("/events", async (c) => {
|
|
|
15521
16878
|
inbound.content = inbound.content.replace(`user=${ev.user}`, () => enriched);
|
|
15522
16879
|
}
|
|
15523
16880
|
}
|
|
15524
|
-
|
|
16881
|
+
const slackFiles = Array.isArray(ev.files) ? ev.files : [];
|
|
15525
16882
|
markHandled(ev.channel, ev.ts);
|
|
15526
|
-
|
|
16883
|
+
if (ev.channel && ev.ts) {
|
|
16884
|
+
void addLoadingReaction(String(ev.channel), String(ev.ts));
|
|
16885
|
+
}
|
|
16886
|
+
let ingestedCount = 0;
|
|
16887
|
+
if (slackFiles.length > 0) {
|
|
16888
|
+
try {
|
|
16889
|
+
const ingested = await ingestSlackFiles(slackFiles, orchestratorId);
|
|
16890
|
+
const block = formatFileBlock(ingested);
|
|
16891
|
+
if (block) inbound.content = `${inbound.content}
|
|
16892
|
+
${block}`;
|
|
16893
|
+
ingestedCount = ingested.filter((f) => f.shortUrl).length;
|
|
16894
|
+
} catch (err) {
|
|
16895
|
+
console.warn("[slack-files] ingestion threw:", err?.message || err);
|
|
16896
|
+
inbound.content = `${inbound.content}
|
|
16897
|
+
[files] (ingestion failed: ${err?.message || "unknown"})`;
|
|
16898
|
+
}
|
|
16899
|
+
}
|
|
16900
|
+
pushToInbox(orchestratorId, inbound);
|
|
16901
|
+
updateEvent(auditId, {
|
|
16902
|
+
status: "routed",
|
|
16903
|
+
sessionId: orchestratorId,
|
|
16904
|
+
...slackFiles.length > 0 ? {
|
|
16905
|
+
// Preserve the original meta (ts, thread_ts, team,
|
|
16906
|
+
// event_subtype) from recordEvent above — updateEvent does a
|
|
16907
|
+
// shallow merge, so we have to re-include them.
|
|
16908
|
+
meta: {
|
|
16909
|
+
ts: ev.ts,
|
|
16910
|
+
thread_ts: ev.thread_ts,
|
|
16911
|
+
team: ev.team,
|
|
16912
|
+
event_subtype: ev.subtype,
|
|
16913
|
+
fileCount: slackFiles.length,
|
|
16914
|
+
ingestedCount
|
|
16915
|
+
}
|
|
16916
|
+
} : {}
|
|
16917
|
+
});
|
|
15527
16918
|
} else {
|
|
15528
16919
|
updateEvent(auditId, { status: "error", error: "no orchestrator session available" });
|
|
15529
16920
|
}
|
|
@@ -15743,10 +17134,35 @@ integrations.get("/", async (c) => {
|
|
|
15743
17134
|
cfAccess: {
|
|
15744
17135
|
enabled: !!cfg?.auth?.cfAccess?.enabled,
|
|
15745
17136
|
teamDomain: cfg?.auth?.cfAccess?.teamDomain || null,
|
|
17137
|
+
audTag: cfg?.auth?.cfAccess?.audTag || null,
|
|
15746
17138
|
allowedEmails: cfg?.auth?.allowedEmails || []
|
|
15747
17139
|
}
|
|
15748
17140
|
});
|
|
15749
17141
|
});
|
|
17142
|
+
var cfAccessSchema = z21.object({
|
|
17143
|
+
enabled: z21.boolean().optional(),
|
|
17144
|
+
teamDomain: z21.string().optional(),
|
|
17145
|
+
audTag: z21.string().optional(),
|
|
17146
|
+
// Email allowlist for the public (cloudflared) surface. Empty array = allow
|
|
17147
|
+
// any email that passes the Cloudflare Access policy (no extra filtering).
|
|
17148
|
+
allowedEmails: z21.array(z21.string().trim().toLowerCase()).optional()
|
|
17149
|
+
});
|
|
17150
|
+
integrations.post("/cf-access", zValidator6("json", cfAccessSchema), async (c) => {
|
|
17151
|
+
const body = c.req.valid("json");
|
|
17152
|
+
if (body.enabled) {
|
|
17153
|
+
const cfg = getConfig();
|
|
17154
|
+
const teamDomain = body.teamDomain ?? cfg?.auth?.cfAccess?.teamDomain;
|
|
17155
|
+
const audTag = body.audTag ?? cfg?.auth?.cfAccess?.audTag;
|
|
17156
|
+
if (!teamDomain || !audTag) {
|
|
17157
|
+
return c.json(
|
|
17158
|
+
{ error: "teamDomain and audTag are required to enable Cloudflare Access" },
|
|
17159
|
+
400
|
|
17160
|
+
);
|
|
17161
|
+
}
|
|
17162
|
+
}
|
|
17163
|
+
setCfAccessConfig(body);
|
|
17164
|
+
return c.json({ ok: true });
|
|
17165
|
+
});
|
|
15750
17166
|
var slackConfigSchema = z21.object({
|
|
15751
17167
|
botToken: z21.string().optional(),
|
|
15752
17168
|
signingSecret: z21.string().optional(),
|
|
@@ -15929,8 +17345,8 @@ import { Hono as Hono9 } from "hono";
|
|
|
15929
17345
|
import { zValidator as zValidator7 } from "@hono/zod-validator";
|
|
15930
17346
|
import { z as z22 } from "zod";
|
|
15931
17347
|
import { existsSync as existsSync21, statSync as statSync3 } from "fs";
|
|
15932
|
-
import { readFile as readFile12, writeFile as
|
|
15933
|
-
import { resolve as resolve11, join as
|
|
17348
|
+
import { readFile as readFile12, writeFile as writeFile7, unlink as unlink3, mkdir as mkdir5 } from "fs/promises";
|
|
17349
|
+
import { resolve as resolve11, join as join17, basename as basename6, dirname as dirname9, extname as extname9 } from "path";
|
|
15934
17350
|
var skills = new Hono9();
|
|
15935
17351
|
function encodeId(filePath) {
|
|
15936
17352
|
return Buffer.from(filePath, "utf-8").toString("base64url");
|
|
@@ -16078,13 +17494,13 @@ skills.post(
|
|
|
16078
17494
|
const safeName = basename6(fileName).replace(/[^A-Za-z0-9._-]/g, "-");
|
|
16079
17495
|
const ext = extname9(safeName).toLowerCase();
|
|
16080
17496
|
const finalName = ext === ".md" || ext === ".mdc" ? safeName : `${safeName}.md`;
|
|
16081
|
-
const filePath =
|
|
17497
|
+
const filePath = join17(targetDir, finalName);
|
|
16082
17498
|
if (existsSync21(filePath)) {
|
|
16083
17499
|
return c.json({ error: `file already exists: ${finalName}` }, 409);
|
|
16084
17500
|
}
|
|
16085
17501
|
try {
|
|
16086
17502
|
await mkdir5(targetDir, { recursive: true });
|
|
16087
|
-
await
|
|
17503
|
+
await writeFile7(filePath, content, "utf-8");
|
|
16088
17504
|
} catch (err) {
|
|
16089
17505
|
return c.json({ error: err?.message || "write failed" }, 500);
|
|
16090
17506
|
}
|
|
@@ -16100,7 +17516,7 @@ skills.put(
|
|
|
16100
17516
|
if (filePath.includes("/skills/default")) {
|
|
16101
17517
|
return c.json({ error: "built-in skills are read-only" }, 400);
|
|
16102
17518
|
}
|
|
16103
|
-
await
|
|
17519
|
+
await writeFile7(filePath, c.req.valid("json").content, "utf-8");
|
|
16104
17520
|
return c.json({ ok: true });
|
|
16105
17521
|
}
|
|
16106
17522
|
);
|
|
@@ -16144,6 +17560,14 @@ skills.delete("/directories", (c) => {
|
|
|
16144
17560
|
init_config();
|
|
16145
17561
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
16146
17562
|
var EXEMPT_PATH_PREFIXES = ["/health", "/api/slack/events", "/api/inbox/", "/w/"];
|
|
17563
|
+
function isExemptPath(path) {
|
|
17564
|
+
return EXEMPT_PATH_PREFIXES.some((p) => {
|
|
17565
|
+
if (p.endsWith("/")) {
|
|
17566
|
+
return path === p.slice(0, -1) || path === p || path.startsWith(p);
|
|
17567
|
+
}
|
|
17568
|
+
return path === p || path.startsWith(p + "/") || path.startsWith(p + "?");
|
|
17569
|
+
});
|
|
17570
|
+
}
|
|
16147
17571
|
var cachedJWKS = null;
|
|
16148
17572
|
var cachedJWKSUrl = null;
|
|
16149
17573
|
function getOrCreateJWKS(teamDomain) {
|
|
@@ -16173,12 +17597,13 @@ function cfAccessMiddleware() {
|
|
|
16173
17597
|
return next();
|
|
16174
17598
|
}
|
|
16175
17599
|
const path = c.req.path;
|
|
16176
|
-
if (
|
|
17600
|
+
if (isExemptPath(path)) {
|
|
16177
17601
|
return next();
|
|
16178
17602
|
}
|
|
16179
17603
|
const host = c.req.header("host");
|
|
16180
17604
|
const remote = c.req.raw?.socket?.remoteAddress;
|
|
16181
|
-
|
|
17605
|
+
const hasCfJwt = !!c.req.header("cf-access-jwt-assertion");
|
|
17606
|
+
if (!hasCfJwt && isLoopback(host, remote)) {
|
|
16182
17607
|
return next();
|
|
16183
17608
|
}
|
|
16184
17609
|
const teamDomain = cfg.teamDomain;
|
|
@@ -16197,8 +17622,10 @@ function cfAccessMiddleware() {
|
|
|
16197
17622
|
audience: aud
|
|
16198
17623
|
});
|
|
16199
17624
|
const email = String(payload.email || "").toLowerCase();
|
|
16200
|
-
const
|
|
16201
|
-
|
|
17625
|
+
const emailDomain = email.split("@")[1] || "";
|
|
17626
|
+
const allowed = (auth?.allowedEmails || []).map((e) => e.toLowerCase().trim().replace(/^@/, "")).filter(Boolean);
|
|
17627
|
+
const isAllowed = allowed.length === 0 || allowed.some((entry2) => entry2.includes("@") ? entry2 === email : entry2 === emailDomain);
|
|
17628
|
+
if (!isAllowed) {
|
|
16202
17629
|
console.warn(`[cf-access] rejected ${email}: not in allowlist`);
|
|
16203
17630
|
return c.json({ error: "Email not allowed" }, 403);
|
|
16204
17631
|
}
|
|
@@ -16396,13 +17823,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
16396
17823
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
16397
17824
|
function getWebDirectory() {
|
|
16398
17825
|
try {
|
|
16399
|
-
const currentDir =
|
|
17826
|
+
const currentDir = dirname11(fileURLToPath5(import.meta.url));
|
|
16400
17827
|
const webDir = resolve12(currentDir, "..", "web");
|
|
16401
|
-
if (existsSync22(webDir) && existsSync22(
|
|
17828
|
+
if (existsSync22(webDir) && existsSync22(join19(webDir, "package.json"))) {
|
|
16402
17829
|
return webDir;
|
|
16403
17830
|
}
|
|
16404
17831
|
const altWebDir = resolve12(currentDir, "..", "..", "web");
|
|
16405
|
-
if (existsSync22(altWebDir) && existsSync22(
|
|
17832
|
+
if (existsSync22(altWebDir) && existsSync22(join19(altWebDir, "package.json"))) {
|
|
16406
17833
|
return altWebDir;
|
|
16407
17834
|
}
|
|
16408
17835
|
return null;
|
|
@@ -16460,20 +17887,20 @@ async function findWebPort(preferredPort) {
|
|
|
16460
17887
|
return { port: preferredPort, alreadyRunning: false };
|
|
16461
17888
|
}
|
|
16462
17889
|
function hasProductionBuild(webDir) {
|
|
16463
|
-
const buildIdPath =
|
|
17890
|
+
const buildIdPath = join19(webDir, ".next", "BUILD_ID");
|
|
16464
17891
|
return existsSync22(buildIdPath);
|
|
16465
17892
|
}
|
|
16466
17893
|
function hasSourceFiles(webDir) {
|
|
16467
|
-
const appDir =
|
|
16468
|
-
const pagesDir =
|
|
16469
|
-
const rootAppDir =
|
|
16470
|
-
const rootPagesDir =
|
|
17894
|
+
const appDir = join19(webDir, "src", "app");
|
|
17895
|
+
const pagesDir = join19(webDir, "src", "pages");
|
|
17896
|
+
const rootAppDir = join19(webDir, "app");
|
|
17897
|
+
const rootPagesDir = join19(webDir, "pages");
|
|
16471
17898
|
return existsSync22(appDir) || existsSync22(pagesDir) || existsSync22(rootAppDir) || existsSync22(rootPagesDir);
|
|
16472
17899
|
}
|
|
16473
17900
|
function getStandaloneServerPath(webDir) {
|
|
16474
17901
|
const possiblePaths2 = [
|
|
16475
|
-
|
|
16476
|
-
|
|
17902
|
+
join19(webDir, ".next", "standalone", "server.js"),
|
|
17903
|
+
join19(webDir, ".next", "standalone", "web", "server.js")
|
|
16477
17904
|
];
|
|
16478
17905
|
for (const serverPath of possiblePaths2) {
|
|
16479
17906
|
if (existsSync22(serverPath)) {
|
|
@@ -16484,7 +17911,7 @@ function getStandaloneServerPath(webDir) {
|
|
|
16484
17911
|
}
|
|
16485
17912
|
function runCommand(command, args, cwd, env) {
|
|
16486
17913
|
return new Promise((resolve14) => {
|
|
16487
|
-
const child =
|
|
17914
|
+
const child = spawn3(command, args, {
|
|
16488
17915
|
cwd,
|
|
16489
17916
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16490
17917
|
env,
|
|
@@ -16516,15 +17943,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
16516
17943
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
16517
17944
|
return { process: null, port: actualPort };
|
|
16518
17945
|
}
|
|
16519
|
-
const usePnpm = existsSync22(
|
|
16520
|
-
const useNpm = !usePnpm && existsSync22(
|
|
17946
|
+
const usePnpm = existsSync22(join19(webDir, "pnpm-lock.yaml"));
|
|
17947
|
+
const useNpm = !usePnpm && existsSync22(join19(webDir, "package-lock.json"));
|
|
16521
17948
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
16522
17949
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
16523
17950
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
16524
|
-
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
16525
|
-
const runtimeConfigPath =
|
|
17951
|
+
const runtimeConfig = { apiBaseUrl: apiUrl, localApiBaseUrl: `http://127.0.0.1:${apiPort}` };
|
|
17952
|
+
const runtimeConfigPath = join19(webDir, "runtime-config.json");
|
|
16526
17953
|
try {
|
|
16527
|
-
|
|
17954
|
+
writeFileSync8(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
16528
17955
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
16529
17956
|
} catch (err) {
|
|
16530
17957
|
if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
|
|
@@ -16544,7 +17971,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
16544
17971
|
if (standaloneServerPath) {
|
|
16545
17972
|
command = "node";
|
|
16546
17973
|
args = ["server.js"];
|
|
16547
|
-
cwd =
|
|
17974
|
+
cwd = dirname11(standaloneServerPath);
|
|
16548
17975
|
webEnv.PORT = String(actualPort);
|
|
16549
17976
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
16550
17977
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -16574,7 +18001,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
16574
18001
|
}
|
|
16575
18002
|
return { process: null, port: actualPort };
|
|
16576
18003
|
}
|
|
16577
|
-
const child =
|
|
18004
|
+
const child = spawn3(command, args, {
|
|
16578
18005
|
cwd,
|
|
16579
18006
|
stdio: ["ignore", "pipe", "pipe"],
|
|
16580
18007
|
env: webEnv,
|
|
@@ -16582,12 +18009,12 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
16582
18009
|
shell: true
|
|
16583
18010
|
});
|
|
16584
18011
|
const startupTimeout = 3e4;
|
|
16585
|
-
let
|
|
18012
|
+
let started2 = false;
|
|
16586
18013
|
let exited = false;
|
|
16587
18014
|
let exitCode = null;
|
|
16588
18015
|
const startedPromise = new Promise((resolve14) => {
|
|
16589
18016
|
const timeout = setTimeout(() => {
|
|
16590
|
-
if (!
|
|
18017
|
+
if (!started2 && !exited) {
|
|
16591
18018
|
resolve14(false);
|
|
16592
18019
|
}
|
|
16593
18020
|
}, startupTimeout);
|
|
@@ -16599,8 +18026,8 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
16599
18026
|
console.log(` Web UI: ${line}`);
|
|
16600
18027
|
}
|
|
16601
18028
|
}
|
|
16602
|
-
if (!
|
|
16603
|
-
|
|
18029
|
+
if (!started2 && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
18030
|
+
started2 = true;
|
|
16604
18031
|
clearTimeout(timeout);
|
|
16605
18032
|
resolve14(true);
|
|
16606
18033
|
}
|
|
@@ -16619,7 +18046,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
16619
18046
|
child.on("exit", (code) => {
|
|
16620
18047
|
exited = true;
|
|
16621
18048
|
exitCode = code;
|
|
16622
|
-
if (!
|
|
18049
|
+
if (!started2) {
|
|
16623
18050
|
clearTimeout(timeout);
|
|
16624
18051
|
resolve14(false);
|
|
16625
18052
|
}
|
|
@@ -16739,7 +18166,7 @@ async function startServer(options = {}) {
|
|
|
16739
18166
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
16740
18167
|
}
|
|
16741
18168
|
if (!existsSync22(config.resolvedWorkingDirectory)) {
|
|
16742
|
-
|
|
18169
|
+
mkdirSync11(config.resolvedWorkingDirectory, { recursive: true });
|
|
16743
18170
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
16744
18171
|
}
|
|
16745
18172
|
if (!config.resolvedRemoteServer.url) {
|
|
@@ -16770,9 +18197,17 @@ async function startServer(options = {}) {
|
|
|
16770
18197
|
try {
|
|
16771
18198
|
const { startOrchestratorDaemon: startOrchestratorDaemon2 } = await Promise.resolve().then(() => (init_daemon(), daemon_exports));
|
|
16772
18199
|
startOrchestratorDaemon2();
|
|
18200
|
+
const { startReconciler: startReconciler2 } = await Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports));
|
|
18201
|
+
startReconciler2();
|
|
16773
18202
|
} catch (err) {
|
|
16774
18203
|
if (!options.quiet) console.warn(`[daemon] start skipped: ${err.message}`);
|
|
16775
18204
|
}
|
|
18205
|
+
try {
|
|
18206
|
+
const { startSelfUpdater: startSelfUpdater2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
|
|
18207
|
+
startSelfUpdater2();
|
|
18208
|
+
} catch (err) {
|
|
18209
|
+
if (!options.quiet) console.warn(`[self-update] start skipped: ${err.message}`);
|
|
18210
|
+
}
|
|
16776
18211
|
try {
|
|
16777
18212
|
const { startScheduler: startScheduler2 } = await Promise.resolve().then(() => (init_scheduler(), scheduler_exports));
|
|
16778
18213
|
startScheduler2({ quiet: options.quiet });
|
|
@@ -17297,18 +18732,18 @@ function generateOpenAPISpec() {
|
|
|
17297
18732
|
init_config();
|
|
17298
18733
|
init_semantic();
|
|
17299
18734
|
init_db();
|
|
17300
|
-
import { mkdirSync as
|
|
17301
|
-
import { resolve as resolve13, join as
|
|
18735
|
+
import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync9, readFileSync as readFileSync12, existsSync as existsSync23, statSync as statSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
18736
|
+
import { resolve as resolve13, join as join20 } from "path";
|
|
17302
18737
|
function getCliVersion() {
|
|
17303
|
-
const here =
|
|
18738
|
+
const here = dirname12(fileURLToPath6(import.meta.url));
|
|
17304
18739
|
const candidates = [
|
|
17305
|
-
|
|
17306
|
-
|
|
17307
|
-
|
|
18740
|
+
join20(here, "..", "package.json"),
|
|
18741
|
+
join20(here, "..", "..", "package.json"),
|
|
18742
|
+
join20(process.cwd(), "package.json")
|
|
17308
18743
|
];
|
|
17309
18744
|
for (const p of candidates) {
|
|
17310
18745
|
try {
|
|
17311
|
-
const pkg = JSON.parse(
|
|
18746
|
+
const pkg = JSON.parse(readFileSync12(p, "utf8"));
|
|
17312
18747
|
if (pkg.name === "sparkecoder" && pkg.version) return pkg.version;
|
|
17313
18748
|
} catch {
|
|
17314
18749
|
}
|
|
@@ -17926,7 +19361,7 @@ program.command("server").description("Start the SparkECoder server (API + Web U
|
|
|
17926
19361
|
program.command("chat").description("Start an interactive chat session").option("-s, --session <id>", "Resume an existing session").option("-n, --name <name>", "Name for the new session").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-c, --config <path>", "Path to config file").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("--no-auto-start", "Do not auto-start server if not running").option("--web-port <port>", "Web UI port", "6969").option("--no-web", "Do not start web UI when auto-starting server").option("--public-url <url>", "Public URL for web UI to connect to API (for Docker/remote access)").option("-v, --verbose", "Enable verbose logging for web server").option("--dangerously-skip-approvals", "Auto-approve all tool calls (no confirmation prompts)").action(async (options) => {
|
|
17927
19362
|
await runChat(options);
|
|
17928
19363
|
});
|
|
17929
|
-
program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--parent-task <id>", "Continue from a previous task (inherits its full conversation context)").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
|
|
19364
|
+
program.command("task").description("Run an autonomous task that completes without human interaction").requiredOption("--prompt <prompt>", "Task prompt describing what to do").requiredOption("--schema <schema>", "JSON Schema for the output (file path or inline JSON string)").option("--webhook <url>", "Webhook URL to receive task events").option("-m, --model <model>", "Model to use").option("-w, --working-dir <path>", "Working directory").option("-n, --name <name>", "Name for the task").option("--max-iterations <n>", "Maximum agent loop iterations", "50").option("-p, --port <port>", "Server port", "3141").option("-H, --host <host>", "Server host", "127.0.0.1").option("-c, --config <path>", "Path to config file").option("--no-auto-start", "Do not auto-start server if not running").option("--parent-task <id>", "Continue from a previous task (inherits its full conversation context)").option("--mcp <servers>", "Task-scoped MCP servers as a JSON array (file path or inline JSON). Each: {name, transport, url?, headers?, command?, args?, env?}").option("--skills <skills>", "Task-scoped skills as a JSON array (file path or inline JSON). Each: {name, description?, content, alwaysApply?, globs?}").option("--wait", "Block and poll until task completes").option("-v, --verbose", "Enable verbose logging").action(async (options) => {
|
|
17930
19365
|
await ensureDependencies({ quiet: true });
|
|
17931
19366
|
loadApiKeysIntoEnv();
|
|
17932
19367
|
const baseUrl = `http://${options.host}:${options.port}`;
|
|
@@ -17956,7 +19391,7 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17956
19391
|
try {
|
|
17957
19392
|
const schemaStr = options.schema;
|
|
17958
19393
|
if (existsSync23(schemaStr)) {
|
|
17959
|
-
outputSchema = JSON.parse(
|
|
19394
|
+
outputSchema = JSON.parse(readFileSync12(schemaStr, "utf-8"));
|
|
17960
19395
|
} else {
|
|
17961
19396
|
outputSchema = JSON.parse(schemaStr);
|
|
17962
19397
|
}
|
|
@@ -17975,6 +19410,30 @@ program.command("task").description("Run an autonomous task that completes witho
|
|
|
17975
19410
|
if (options.workingDir) body.workingDirectory = options.workingDir;
|
|
17976
19411
|
if (options.name) body.name = options.name;
|
|
17977
19412
|
if (options.parentTask) body.parentTaskId = options.parentTask;
|
|
19413
|
+
const parseJsonArrayOption = (raw, label) => {
|
|
19414
|
+
const text = existsSync23(raw) ? readFileSync12(raw, "utf-8") : raw;
|
|
19415
|
+
const parsed = JSON.parse(text);
|
|
19416
|
+
if (!Array.isArray(parsed)) {
|
|
19417
|
+
throw new Error(`${label} must be a JSON array`);
|
|
19418
|
+
}
|
|
19419
|
+
return parsed;
|
|
19420
|
+
};
|
|
19421
|
+
if (options.mcp) {
|
|
19422
|
+
try {
|
|
19423
|
+
body.mcpServers = parseJsonArrayOption(options.mcp, "--mcp");
|
|
19424
|
+
} catch (err) {
|
|
19425
|
+
console.error(chalk.red(`Invalid --mcp value: ${err.message}`));
|
|
19426
|
+
process.exit(1);
|
|
19427
|
+
}
|
|
19428
|
+
}
|
|
19429
|
+
if (options.skills) {
|
|
19430
|
+
try {
|
|
19431
|
+
body.skills = parseJsonArrayOption(options.skills, "--skills");
|
|
19432
|
+
} catch (err) {
|
|
19433
|
+
console.error(chalk.red(`Invalid --skills value: ${err.message}`));
|
|
19434
|
+
process.exit(1);
|
|
19435
|
+
}
|
|
19436
|
+
}
|
|
17978
19437
|
const spinner = ora("Creating task...").start();
|
|
17979
19438
|
const response = await apiRequest(baseUrl, "/tasks", {
|
|
17980
19439
|
method: "POST",
|
|
@@ -18023,7 +19482,7 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
18023
19482
|
let configLocation;
|
|
18024
19483
|
if (options.global) {
|
|
18025
19484
|
const appDataDir = ensureAppDataDirectory();
|
|
18026
|
-
configPath =
|
|
19485
|
+
configPath = join20(appDataDir, "sparkecoder.config.json");
|
|
18027
19486
|
configLocation = "global";
|
|
18028
19487
|
} else {
|
|
18029
19488
|
configPath = resolve13(process.cwd(), "sparkecoder.config.json");
|
|
@@ -18035,7 +19494,7 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
18035
19494
|
return;
|
|
18036
19495
|
}
|
|
18037
19496
|
const config = createDefaultConfig();
|
|
18038
|
-
|
|
19497
|
+
writeFileSync9(configPath, JSON.stringify(config, null, 2));
|
|
18039
19498
|
console.log(chalk.green(`\u2713 Created ${configLocation} config`));
|
|
18040
19499
|
console.log(chalk.dim(` ${configPath}`));
|
|
18041
19500
|
console.log(chalk.dim("Set AI_GATEWAY_API_KEY and run sparkecoder to start"));
|
|
@@ -18054,11 +19513,11 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
18054
19513
|
console.error(chalk.red("Both bot token and signing secret are required."));
|
|
18055
19514
|
process.exit(1);
|
|
18056
19515
|
}
|
|
18057
|
-
const configPath = options.global ?
|
|
19516
|
+
const configPath = options.global ? join20(ensureAppDataDirectory(), "sparkecoder.config.json") : resolve13(process.cwd(), "sparkecoder.config.json");
|
|
18058
19517
|
let existing = {};
|
|
18059
19518
|
if (existsSync23(configPath)) {
|
|
18060
19519
|
try {
|
|
18061
|
-
existing = JSON.parse(
|
|
19520
|
+
existing = JSON.parse(readFileSync12(configPath, "utf-8"));
|
|
18062
19521
|
} catch {
|
|
18063
19522
|
}
|
|
18064
19523
|
} else {
|
|
@@ -18070,7 +19529,7 @@ program.command("slack-setup").description("Interactively configure Slack integr
|
|
|
18070
19529
|
signingSecret,
|
|
18071
19530
|
defaultOrchestratorName: existing.slack?.defaultOrchestratorName ?? "orchestrator"
|
|
18072
19531
|
};
|
|
18073
|
-
|
|
19532
|
+
writeFileSync9(configPath, JSON.stringify(existing, null, 2));
|
|
18074
19533
|
console.log(chalk.green(`
|
|
18075
19534
|
\u2713 Slack configured`));
|
|
18076
19535
|
console.log(chalk.dim(` ${configPath}`));
|
|
@@ -18138,15 +19597,15 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18138
19597
|
if (options.remote) {
|
|
18139
19598
|
try {
|
|
18140
19599
|
let config = loadConfig(options.config, process.cwd());
|
|
18141
|
-
let
|
|
18142
|
-
if (!
|
|
19600
|
+
let remoteUrl2 = config.resolvedRemoteServer.url;
|
|
19601
|
+
if (!remoteUrl2) {
|
|
18143
19602
|
console.log(chalk.red("No remoteServer.url configured. Run `sparkecoder login` (or set remoteServer.url in your config) first."));
|
|
18144
19603
|
process.exitCode = 1;
|
|
18145
19604
|
return;
|
|
18146
19605
|
}
|
|
18147
19606
|
let authKey3 = config.resolvedRemoteServer.authKey;
|
|
18148
19607
|
if (!authKey3) {
|
|
18149
|
-
authKey3 = await ensureRemoteAuthKey(
|
|
19608
|
+
authKey3 = await ensureRemoteAuthKey(remoteUrl2);
|
|
18150
19609
|
}
|
|
18151
19610
|
const setupSecret = options.setupSecret || process.env.SPARKECODER_SETUP_SECRET || process.env.SPARKECODER_TUNNEL_SECRET;
|
|
18152
19611
|
const hostname = options.hostname;
|
|
@@ -18160,7 +19619,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18160
19619
|
name: tunnelName
|
|
18161
19620
|
};
|
|
18162
19621
|
if (hostname) reqBody.hostname = hostname;
|
|
18163
|
-
const res = await fetch(`${
|
|
19622
|
+
const res = await fetch(`${remoteUrl2.replace(/\/$/, "")}${path}`, {
|
|
18164
19623
|
method: "POST",
|
|
18165
19624
|
headers: {
|
|
18166
19625
|
"Content-Type": "application/json",
|
|
@@ -18175,7 +19634,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18175
19634
|
process.exitCode = 1;
|
|
18176
19635
|
return;
|
|
18177
19636
|
}
|
|
18178
|
-
const { tunnelToken, hostname: provisionedHost, apiHostname, reused, webhookToken, webhookBaseUrl } = await res.json();
|
|
19637
|
+
const { tunnelToken, hostname: provisionedHost, apiHostname, reused, webhookToken, webhookBaseUrl, cfAccess } = await res.json();
|
|
18179
19638
|
if (reused) {
|
|
18180
19639
|
console.log(chalk.dim("\u2713 Re-using existing tunnel \u2014 no new tunnel created on Cloudflare."));
|
|
18181
19640
|
}
|
|
@@ -18193,6 +19652,20 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18193
19652
|
console.warn(chalk.yellow(`Could not persist webhook token: ${err?.message ?? err}`));
|
|
18194
19653
|
}
|
|
18195
19654
|
}
|
|
19655
|
+
if (cfAccess?.teamDomain && cfAccess?.audTag) {
|
|
19656
|
+
try {
|
|
19657
|
+
setCfAccessConfig({
|
|
19658
|
+
enabled: true,
|
|
19659
|
+
teamDomain: cfAccess.teamDomain,
|
|
19660
|
+
audTag: cfAccess.audTag,
|
|
19661
|
+
allowedEmails: cfAccess.allowedEmails ?? []
|
|
19662
|
+
});
|
|
19663
|
+
const n = (cfAccess.allowedEmails ?? []).length;
|
|
19664
|
+
console.log(chalk.dim(`\u2713 Enabled Cloudflare Access locally (allowlist: ${n} email${n === 1 ? "" : "s"}).`));
|
|
19665
|
+
} catch (err) {
|
|
19666
|
+
console.warn(chalk.yellow(`Could not persist Cloudflare Access config: ${err?.message ?? err}`));
|
|
19667
|
+
}
|
|
19668
|
+
}
|
|
18196
19669
|
const cfPath = await installCloudflaredIfNeeded();
|
|
18197
19670
|
if (!cfPath) {
|
|
18198
19671
|
console.log(chalk.yellow("cloudflared not installed; run this once installed:"));
|
|
@@ -18322,8 +19795,8 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18322
19795
|
}
|
|
18323
19796
|
const verOut = run("cloudflared", ["--version"]).stdout?.toString().split("\n")[0] || "installed";
|
|
18324
19797
|
console.log(chalk.green("\u2713"), "cloudflared:", chalk.dim(verOut));
|
|
18325
|
-
const cfDir =
|
|
18326
|
-
const certPath =
|
|
19798
|
+
const cfDir = join20(homedir2(), ".cloudflared");
|
|
19799
|
+
const certPath = join20(cfDir, "cert.pem");
|
|
18327
19800
|
if (!existsSync23(certPath)) {
|
|
18328
19801
|
console.log(chalk.yellow("No Cloudflare login cert found."));
|
|
18329
19802
|
if (await confirm("Run `cloudflared tunnel login` now (opens a browser)?", true)) {
|
|
@@ -18368,7 +19841,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18368
19841
|
return;
|
|
18369
19842
|
}
|
|
18370
19843
|
}
|
|
18371
|
-
const credsFile = tunnel.credentials_file ||
|
|
19844
|
+
const credsFile = tunnel.credentials_file || join20(cfDir, `${tunnel.id}.json`);
|
|
18372
19845
|
if (!existsSync23(credsFile)) {
|
|
18373
19846
|
console.log(chalk.yellow(`Credentials file not found at ${credsFile}. The tunnel may still work via cert.pem.`));
|
|
18374
19847
|
}
|
|
@@ -18392,7 +19865,7 @@ program.command("cloudflared-setup").description("Auto-detect cloudflared + set
|
|
|
18392
19865
|
console.log(chalk.yellow("DNS route warning:"), err.trim().split("\n").slice(-2).join(" "));
|
|
18393
19866
|
}
|
|
18394
19867
|
}
|
|
18395
|
-
const configPath =
|
|
19868
|
+
const configPath = join20(cfDir, "config.yml");
|
|
18396
19869
|
const configBody = `tunnel: ${tunnel.id}
|
|
18397
19870
|
credentials-file: ${credsFile}
|
|
18398
19871
|
ingress:
|
|
@@ -18404,13 +19877,13 @@ ingress:
|
|
|
18404
19877
|
`;
|
|
18405
19878
|
let wroteConfig = false;
|
|
18406
19879
|
if (existsSync23(configPath)) {
|
|
18407
|
-
const existing =
|
|
19880
|
+
const existing = readFileSync12(configPath, "utf8");
|
|
18408
19881
|
if (existing.trim() === configBody.trim()) {
|
|
18409
19882
|
console.log(chalk.green("\u2713"), `config.yml already up to date: ${configPath}`);
|
|
18410
19883
|
wroteConfig = true;
|
|
18411
19884
|
} else if (await confirm(`A different ${configPath} exists. Overwrite (a backup will be saved)?`, false)) {
|
|
18412
19885
|
copyFileSync(configPath, `${configPath}.bak.${Date.now()}`);
|
|
18413
|
-
|
|
19886
|
+
writeFileSync9(configPath, configBody);
|
|
18414
19887
|
console.log(chalk.green("\u2713"), `wrote ${configPath} (previous saved as .bak.*)`);
|
|
18415
19888
|
wroteConfig = true;
|
|
18416
19889
|
} else {
|
|
@@ -18418,7 +19891,7 @@ ingress:
|
|
|
18418
19891
|
console.log(chalk.cyan(configBody));
|
|
18419
19892
|
}
|
|
18420
19893
|
} else {
|
|
18421
|
-
|
|
19894
|
+
writeFileSync9(configPath, configBody);
|
|
18422
19895
|
console.log(chalk.green("\u2713"), `wrote ${configPath}`);
|
|
18423
19896
|
wroteConfig = true;
|
|
18424
19897
|
}
|
|
@@ -18521,8 +19994,8 @@ program.command("index").description("Index repository for semantic search").opt
|
|
|
18521
19994
|
try {
|
|
18522
19995
|
const workingDir = options.workingDir ? resolve13(options.workingDir) : process.cwd();
|
|
18523
19996
|
let config = loadConfig(options.config, workingDir);
|
|
18524
|
-
const
|
|
18525
|
-
if (!
|
|
19997
|
+
const remoteUrl2 = config.resolvedRemoteServer.url;
|
|
19998
|
+
if (!remoteUrl2) {
|
|
18526
19999
|
console.error(chalk.red("Error: Remote server not configured"));
|
|
18527
20000
|
console.log(chalk.dim("Set REMOTE_SERVER_URL environment variable or remoteServer.url in config"));
|
|
18528
20001
|
process.exit(1);
|
|
@@ -18530,7 +20003,7 @@ program.command("index").description("Index repository for semantic search").opt
|
|
|
18530
20003
|
let authKey3 = config.resolvedRemoteServer.authKey;
|
|
18531
20004
|
if (!authKey3) {
|
|
18532
20005
|
console.log(chalk.dim("Registering with remote server..."));
|
|
18533
|
-
authKey3 = await ensureRemoteAuthKey(
|
|
20006
|
+
authKey3 = await ensureRemoteAuthKey(remoteUrl2);
|
|
18534
20007
|
config = loadConfig(options.config, workingDir);
|
|
18535
20008
|
authKey3 = config.resolvedRemoteServer.authKey || authKey3;
|
|
18536
20009
|
}
|
|
@@ -18539,7 +20012,7 @@ program.command("index").description("Index repository for semantic search").opt
|
|
|
18539
20012
|
console.log(chalk.dim("Set SPARKECODER_AUTH_KEY or run sparkecoder to register with the remote server"));
|
|
18540
20013
|
process.exit(1);
|
|
18541
20014
|
}
|
|
18542
|
-
initDatabase({ url:
|
|
20015
|
+
initDatabase({ url: remoteUrl2, authKey: authKey3 });
|
|
18543
20016
|
if (!isGitRepository(workingDir)) {
|
|
18544
20017
|
console.error(chalk.red("Error: Not a git repository"));
|
|
18545
20018
|
console.log(chalk.dim("Semantic indexing requires a git repository with a remote configured."));
|
|
@@ -18655,20 +20128,20 @@ program.command("search").description("Search indexed repository using semantic
|
|
|
18655
20128
|
try {
|
|
18656
20129
|
const workingDir = options.workingDir ? resolve13(options.workingDir) : process.cwd();
|
|
18657
20130
|
const config = loadConfig(options.config, workingDir);
|
|
18658
|
-
const
|
|
18659
|
-
if (!
|
|
20131
|
+
const remoteUrl2 = config.resolvedRemoteServer.url;
|
|
20132
|
+
if (!remoteUrl2) {
|
|
18660
20133
|
console.error(chalk.red("Error: Remote server not configured"));
|
|
18661
20134
|
process.exit(1);
|
|
18662
20135
|
}
|
|
18663
20136
|
let authKey3 = config.resolvedRemoteServer.authKey;
|
|
18664
20137
|
if (!authKey3) {
|
|
18665
|
-
authKey3 = await ensureRemoteAuthKey(
|
|
20138
|
+
authKey3 = await ensureRemoteAuthKey(remoteUrl2);
|
|
18666
20139
|
}
|
|
18667
20140
|
if (!authKey3) {
|
|
18668
20141
|
console.error(chalk.red("Error: Remote auth key not available"));
|
|
18669
20142
|
process.exit(1);
|
|
18670
20143
|
}
|
|
18671
|
-
initDatabase({ url:
|
|
20144
|
+
initDatabase({ url: remoteUrl2, authKey: authKey3 });
|
|
18672
20145
|
const namespace = await getRepoNamespace(workingDir, config.resolvedVectorGateway.namespace);
|
|
18673
20146
|
if (!namespace) {
|
|
18674
20147
|
console.error(chalk.red("Error: Could not determine repository namespace"));
|
|
@@ -18910,17 +20383,17 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18910
20383
|
let shellEscape2 = function(str) {
|
|
18911
20384
|
return `'${str.replace(/'/g, "'\\''")}'`;
|
|
18912
20385
|
}, stateFilePath = function() {
|
|
18913
|
-
return
|
|
18914
|
-
},
|
|
20386
|
+
return join20(ensureAppDataDirectory(), "recordings.json");
|
|
20387
|
+
}, readState2 = function() {
|
|
18915
20388
|
const p = stateFilePath();
|
|
18916
20389
|
if (!existsSync23(p)) return [];
|
|
18917
20390
|
try {
|
|
18918
|
-
return JSON.parse(
|
|
20391
|
+
return JSON.parse(readFileSync12(p, "utf-8"));
|
|
18919
20392
|
} catch {
|
|
18920
20393
|
return [];
|
|
18921
20394
|
}
|
|
18922
|
-
},
|
|
18923
|
-
|
|
20395
|
+
}, writeState2 = function(rows) {
|
|
20396
|
+
writeFileSync9(stateFilePath(), JSON.stringify(rows, null, 2), { mode: 384 });
|
|
18924
20397
|
}, isAlive = function(pid) {
|
|
18925
20398
|
try {
|
|
18926
20399
|
process.kill(pid, 0);
|
|
@@ -18931,7 +20404,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18931
20404
|
}, pruneDead = function(rows) {
|
|
18932
20405
|
return rows.filter((r) => isAlive(r.pid));
|
|
18933
20406
|
};
|
|
18934
|
-
shellEscape3 = shellEscape2, stateFilePath2 = stateFilePath,
|
|
20407
|
+
shellEscape3 = shellEscape2, stateFilePath2 = stateFilePath, readState3 = readState2, writeState3 = writeState2, isAlive2 = isAlive, pruneDead2 = pruneDead;
|
|
18935
20408
|
async function stopRecorderPid(pid) {
|
|
18936
20409
|
if (!isAlive(pid)) return;
|
|
18937
20410
|
try {
|
|
@@ -18993,12 +20466,12 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
18993
20466
|
const record = program.command("record").description("Start/stop screen recordings");
|
|
18994
20467
|
record.command("start").description("Start a screen recording (returns id, path, pid as JSON)").option("--name <slug>", "Optional human-readable label for the recording").option("--dir <path>", "Output directory (default: ~/recordings)").action(async (opts) => {
|
|
18995
20468
|
const { homedir: homedir2, platform: osPlatform } = await import("os");
|
|
18996
|
-
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) :
|
|
18997
|
-
|
|
20469
|
+
const outDir = opts.dir ? resolve13(opts.dir.replace(/^~/, homedir2())) : join20(homedir2(), "recordings");
|
|
20470
|
+
mkdirSync12(outDir, { recursive: true });
|
|
18998
20471
|
const id = `rec-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
|
|
18999
20472
|
const ext = "mp4";
|
|
19000
20473
|
const filename = `${id}${opts.name ? `-${String(opts.name).replace(/[^a-z0-9-]+/gi, "-").toLowerCase()}` : ""}.${ext}`;
|
|
19001
|
-
const path =
|
|
20474
|
+
const path = join20(outDir, filename);
|
|
19002
20475
|
let cmd;
|
|
19003
20476
|
let args;
|
|
19004
20477
|
let capturePath;
|
|
@@ -19080,13 +20553,13 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
19080
20553
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19081
20554
|
platform: osPlatform()
|
|
19082
20555
|
};
|
|
19083
|
-
const rows = pruneDead(
|
|
20556
|
+
const rows = pruneDead(readState2());
|
|
19084
20557
|
rows.push(row);
|
|
19085
|
-
|
|
20558
|
+
writeState2(rows);
|
|
19086
20559
|
console.log(JSON.stringify({ id, path, pid: child.pid, platform: osPlatform() }));
|
|
19087
20560
|
});
|
|
19088
20561
|
record.command("stop <id>").description("Stop a recording started by `sparkecoder record start`").action(async (id) => {
|
|
19089
|
-
const rows =
|
|
20562
|
+
const rows = readState2();
|
|
19090
20563
|
const row = rows.find((r) => r.id === id);
|
|
19091
20564
|
if (!row) {
|
|
19092
20565
|
console.error(JSON.stringify({ error: `No recording found with id ${id}` }));
|
|
@@ -19094,7 +20567,7 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
19094
20567
|
}
|
|
19095
20568
|
const startedAt = new Date(row.startedAt).getTime();
|
|
19096
20569
|
const { path: finalPath, ok, sizeMb, warning } = await finalizeRecording(row);
|
|
19097
|
-
|
|
20570
|
+
writeState2(rows.filter((r) => r.id !== id));
|
|
19098
20571
|
console.log(JSON.stringify({
|
|
19099
20572
|
id,
|
|
19100
20573
|
path: finalPath,
|
|
@@ -19105,25 +20578,25 @@ program.command("request-permissions").description("Open System Settings to the
|
|
|
19105
20578
|
}));
|
|
19106
20579
|
});
|
|
19107
20580
|
record.command("list").description("List active recordings").action(() => {
|
|
19108
|
-
const rows = pruneDead(
|
|
19109
|
-
|
|
20581
|
+
const rows = pruneDead(readState2());
|
|
20582
|
+
writeState2(rows);
|
|
19110
20583
|
console.log(JSON.stringify(rows, null, 2));
|
|
19111
20584
|
});
|
|
19112
20585
|
record.command("stop-all").description("Stop every active recording").action(async () => {
|
|
19113
|
-
const rows =
|
|
20586
|
+
const rows = readState2();
|
|
19114
20587
|
const stopped = [];
|
|
19115
20588
|
for (const r of rows) {
|
|
19116
20589
|
const { path: finalPath, ok, warning } = await finalizeRecording(r);
|
|
19117
20590
|
stopped.push({ id: r.id, path: finalPath, ok, ...warning ? { warning } : {} });
|
|
19118
20591
|
}
|
|
19119
|
-
|
|
20592
|
+
writeState2([]);
|
|
19120
20593
|
console.log(JSON.stringify({ stopped }));
|
|
19121
20594
|
});
|
|
19122
20595
|
}
|
|
19123
20596
|
var shellEscape3;
|
|
19124
20597
|
var stateFilePath2;
|
|
19125
|
-
var
|
|
19126
|
-
var
|
|
20598
|
+
var readState3;
|
|
20599
|
+
var writeState3;
|
|
19127
20600
|
var isAlive2;
|
|
19128
20601
|
var pruneDead2;
|
|
19129
20602
|
program.parse();
|