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/agent/index.js
CHANGED
|
@@ -145,8 +145,8 @@ var init_types = __esm({
|
|
|
145
145
|
authKey: z.string().optional()
|
|
146
146
|
}).optional();
|
|
147
147
|
SparkcoderConfigSchema = z.object({
|
|
148
|
-
// Default model to use (
|
|
149
|
-
defaultModel: z.string().default("
|
|
148
|
+
// Default model to use (LiteLLM model id)
|
|
149
|
+
defaultModel: z.string().default("gpt-5.5"),
|
|
150
150
|
// Working directory for file operations
|
|
151
151
|
workingDirectory: z.string().optional(),
|
|
152
152
|
// Tool approval settings
|
|
@@ -185,6 +185,14 @@ var init_types = __esm({
|
|
|
185
185
|
webhooks: z.object({
|
|
186
186
|
token: z.string().optional()
|
|
187
187
|
}).optional(),
|
|
188
|
+
// Self-update: when running as the managed service, periodically check
|
|
189
|
+
// npm for a newer published version and, if found, re-run the hosted
|
|
190
|
+
// installer (full upgrade + restart). Disabled automatically when not
|
|
191
|
+
// running from a global install (e.g. dev/source checkouts).
|
|
192
|
+
autoUpdate: z.object({
|
|
193
|
+
enabled: z.boolean().optional().default(true),
|
|
194
|
+
intervalHours: z.number().positive().optional().default(6)
|
|
195
|
+
}).optional().default({}),
|
|
188
196
|
// Database path (used for local SQLite - ignored if remoteServer is configured)
|
|
189
197
|
databasePath: z.string().optional().default("./sparkecoder.db"),
|
|
190
198
|
// Remote server configuration (for centralized storage)
|
|
@@ -334,7 +342,7 @@ function requiresApproval(toolName, sessionConfig) {
|
|
|
334
342
|
}
|
|
335
343
|
function createDefaultConfig() {
|
|
336
344
|
return {
|
|
337
|
-
defaultModel: "
|
|
345
|
+
defaultModel: "gpt-5.5",
|
|
338
346
|
// workingDirectory is intentionally not set - defaults to where CLI is run
|
|
339
347
|
toolApprovals: {
|
|
340
348
|
bash: true,
|
|
@@ -356,6 +364,10 @@ function createDefaultConfig() {
|
|
|
356
364
|
port: 3141,
|
|
357
365
|
host: "0.0.0.0"
|
|
358
366
|
},
|
|
367
|
+
autoUpdate: {
|
|
368
|
+
enabled: true,
|
|
369
|
+
intervalHours: 6
|
|
370
|
+
},
|
|
359
371
|
databasePath: "./sparkecoder.db"
|
|
360
372
|
};
|
|
361
373
|
}
|
|
@@ -371,6 +383,7 @@ var init_config = __esm({
|
|
|
371
383
|
openai: "OPENAI_API_KEY",
|
|
372
384
|
google: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
373
385
|
xai: "XAI_API_KEY",
|
|
386
|
+
litellm: "LITELLM_API_KEY",
|
|
374
387
|
"ai-gateway": "AI_GATEWAY_API_KEY"
|
|
375
388
|
};
|
|
376
389
|
SUPPORTED_PROVIDERS = Object.keys(PROVIDER_ENV_MAP);
|
|
@@ -1707,94 +1720,734 @@ var init_conversation_archive = __esm({
|
|
|
1707
1720
|
}
|
|
1708
1721
|
});
|
|
1709
1722
|
|
|
1710
|
-
// src/
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
return
|
|
1723
|
+
// src/integrations/slack/persistence.ts
|
|
1724
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
1725
|
+
import { join as join9, dirname as dirname6 } from "path";
|
|
1726
|
+
function cachePath() {
|
|
1727
|
+
return join9(ensureAppDataDirectory(), FILENAME);
|
|
1728
|
+
}
|
|
1729
|
+
function load() {
|
|
1730
|
+
if (loaded) return;
|
|
1731
|
+
loaded = true;
|
|
1732
|
+
const path = cachePath();
|
|
1733
|
+
if (!existsSync16(path)) return;
|
|
1734
|
+
try {
|
|
1735
|
+
const raw = readFileSync7(path, "utf-8");
|
|
1736
|
+
const parsed = JSON.parse(raw);
|
|
1737
|
+
if (parsed?.version !== FILE_VERSION) return;
|
|
1738
|
+
const now = Date.now();
|
|
1739
|
+
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
1740
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
1741
|
+
userMap.set(id, {
|
|
1742
|
+
name: e.name ?? null,
|
|
1743
|
+
realName: e.realName ?? null,
|
|
1744
|
+
email: e.email ?? null,
|
|
1745
|
+
expiresAt: e.expiresAt
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
1750
|
+
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
1751
|
+
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
} catch (err) {
|
|
1755
|
+
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
1725
1756
|
}
|
|
1726
|
-
console.log(`[BROWSER-WS] Creating new proxy for session ${sessionId} on port ${port} (active proxies: ${activeProxies.size})`);
|
|
1727
|
-
const proxy = new BrowserStreamProxy(port);
|
|
1728
|
-
activeProxies.set(sessionId, proxy);
|
|
1729
|
-
proxy.on("close", () => {
|
|
1730
|
-
console.log(`[BROWSER-WS] Proxy closed for session ${sessionId}, removing from registry`);
|
|
1731
|
-
activeProxies.delete(sessionId);
|
|
1732
|
-
});
|
|
1733
|
-
proxy.connect();
|
|
1734
|
-
return proxy;
|
|
1735
1757
|
}
|
|
1736
|
-
function
|
|
1737
|
-
|
|
1758
|
+
function evictIfOversized(map, max) {
|
|
1759
|
+
if (map.size <= max) return;
|
|
1760
|
+
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
1761
|
+
const toRemove = map.size - max;
|
|
1762
|
+
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
1738
1763
|
}
|
|
1739
|
-
function
|
|
1740
|
-
|
|
1741
|
-
if (
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1764
|
+
function scheduleSave() {
|
|
1765
|
+
dirty = true;
|
|
1766
|
+
if (saveTimer) return;
|
|
1767
|
+
saveTimer = setTimeout(() => {
|
|
1768
|
+
saveTimer = null;
|
|
1769
|
+
if (dirty) saveSync();
|
|
1770
|
+
}, SAVE_DEBOUNCE_MS);
|
|
1771
|
+
saveTimer.unref?.();
|
|
1772
|
+
}
|
|
1773
|
+
function saveSync() {
|
|
1774
|
+
dirty = false;
|
|
1775
|
+
try {
|
|
1776
|
+
const path = cachePath();
|
|
1777
|
+
const dir = dirname6(path);
|
|
1778
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
1779
|
+
const payload = {
|
|
1780
|
+
version: FILE_VERSION,
|
|
1781
|
+
users: Object.fromEntries(userMap),
|
|
1782
|
+
threads: Object.fromEntries(threadMap)
|
|
1783
|
+
};
|
|
1784
|
+
const tmp = `${path}.tmp`;
|
|
1785
|
+
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
1786
|
+
renameSync(tmp, path);
|
|
1787
|
+
} catch (err) {
|
|
1788
|
+
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
1747
1789
|
}
|
|
1748
1790
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1791
|
+
function hookExit() {
|
|
1792
|
+
if (exitHooked) return;
|
|
1793
|
+
exitHooked = true;
|
|
1794
|
+
const flush2 = () => {
|
|
1795
|
+
if (dirty) saveSync();
|
|
1796
|
+
};
|
|
1797
|
+
process.once("beforeExit", flush2);
|
|
1798
|
+
process.once("SIGINT", flush2);
|
|
1799
|
+
process.once("SIGTERM", flush2);
|
|
1800
|
+
}
|
|
1801
|
+
function setCachedThreadOwnership(key2, entry2) {
|
|
1802
|
+
load();
|
|
1803
|
+
hookExit();
|
|
1804
|
+
threadMap.set(key2, entry2);
|
|
1805
|
+
evictIfOversized(threadMap, MAX_THREADS);
|
|
1806
|
+
scheduleSave();
|
|
1807
|
+
}
|
|
1808
|
+
var FILENAME, FILE_VERSION, SAVE_DEBOUNCE_MS, MAX_THREADS, loaded, userMap, threadMap, dirty, saveTimer, exitHooked;
|
|
1809
|
+
var init_persistence = __esm({
|
|
1810
|
+
"src/integrations/slack/persistence.ts"() {
|
|
1752
1811
|
"use strict";
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1812
|
+
init_config();
|
|
1813
|
+
FILENAME = "slack-cache.json";
|
|
1814
|
+
FILE_VERSION = 1;
|
|
1815
|
+
SAVE_DEBOUNCE_MS = 500;
|
|
1816
|
+
MAX_THREADS = 5e3;
|
|
1817
|
+
loaded = false;
|
|
1818
|
+
userMap = /* @__PURE__ */ new Map();
|
|
1819
|
+
threadMap = /* @__PURE__ */ new Map();
|
|
1820
|
+
dirty = false;
|
|
1821
|
+
saveTimer = null;
|
|
1822
|
+
exitHooked = false;
|
|
1823
|
+
}
|
|
1824
|
+
});
|
|
1825
|
+
|
|
1826
|
+
// src/integrations/slack/client.ts
|
|
1827
|
+
function slackBackoffMs(attempt) {
|
|
1828
|
+
const expo = SLACK_BACKOFF_BASE_MS * 2 ** attempt;
|
|
1829
|
+
const jitter = Math.floor(Math.random() * SLACK_BACKOFF_BASE_MS);
|
|
1830
|
+
return Math.min(expo + jitter, SLACK_BACKOFF_CAP_MS);
|
|
1831
|
+
}
|
|
1832
|
+
async function slackFetchWithRetry(url, init, attempts = SLACK_FETCH_ATTEMPTS) {
|
|
1833
|
+
let lastErr;
|
|
1834
|
+
for (let i = 0; i < attempts; i++) {
|
|
1835
|
+
const isLast = i === attempts - 1;
|
|
1836
|
+
try {
|
|
1837
|
+
const res = await fetch(url, init);
|
|
1838
|
+
if ((res.status === 429 || res.status >= 500) && !isLast) {
|
|
1839
|
+
const ra = Number(res.headers.get("retry-after"));
|
|
1840
|
+
const waitMs = Number.isFinite(ra) && ra > 0 ? Math.min(ra * 1e3, SLACK_BACKOFF_CAP_MS) : slackBackoffMs(i);
|
|
1841
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
1842
|
+
continue;
|
|
1768
1843
|
}
|
|
1769
|
-
|
|
1770
|
-
|
|
1844
|
+
return res;
|
|
1845
|
+
} catch (err) {
|
|
1846
|
+
lastErr = err;
|
|
1847
|
+
if (isLast) throw err;
|
|
1848
|
+
await new Promise((r) => setTimeout(r, slackBackoffMs(i)));
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
throw lastErr ?? new Error("slack fetch failed");
|
|
1852
|
+
}
|
|
1853
|
+
async function addResultReaction(channel, timestamp, state2) {
|
|
1854
|
+
const name = RESULT_REACTIONS[state2];
|
|
1855
|
+
if (!name) return { ok: false, error: `no_reaction_for_state:${state2}` };
|
|
1856
|
+
const adapter = getSlackAdapter();
|
|
1857
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
1858
|
+
try {
|
|
1859
|
+
const res = await adapter.addReaction({ channel, timestamp, name });
|
|
1860
|
+
if (!res.ok && !REACTION_SOFT_ERRORS.has(res.error || "")) {
|
|
1861
|
+
console.warn(`[slack] addReaction ${name} failed on ${channel}/${timestamp}: ${res.error}`);
|
|
1862
|
+
}
|
|
1863
|
+
return res;
|
|
1864
|
+
} catch (err) {
|
|
1865
|
+
console.warn(`[slack] addResultReaction threw on ${channel}/${timestamp}:`, err?.message || err);
|
|
1866
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
async function postThreadMessage(channel, threadTs, text) {
|
|
1870
|
+
const adapter = getSlackAdapter();
|
|
1871
|
+
if (!adapter) return { ok: false, error: "slack_not_configured" };
|
|
1872
|
+
try {
|
|
1873
|
+
return await adapter.postMessage({ channel, text, threadTs });
|
|
1874
|
+
} catch (err) {
|
|
1875
|
+
console.warn(`[slack] postThreadMessage threw on ${channel}/${threadTs}:`, err?.message || err);
|
|
1876
|
+
return { ok: false, error: err?.message || "unknown" };
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
function readSlackConfig() {
|
|
1880
|
+
try {
|
|
1881
|
+
const cfg = getConfig();
|
|
1882
|
+
const slack = cfg?.slack;
|
|
1883
|
+
if (!slack?.botToken) return void 0;
|
|
1884
|
+
return {
|
|
1885
|
+
botToken: String(slack.botToken),
|
|
1886
|
+
signingSecret: slack.signingSecret ? String(slack.signingSecret) : void 0,
|
|
1887
|
+
defaultOrchestratorName: slack.defaultOrchestratorName ? String(slack.defaultOrchestratorName) : void 0
|
|
1888
|
+
};
|
|
1889
|
+
} catch {
|
|
1890
|
+
return void 0;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
function getSlackAdapter() {
|
|
1894
|
+
const cfg = readSlackConfig();
|
|
1895
|
+
if (!cfg) return void 0;
|
|
1896
|
+
const slackForm = async (endpoint, params) => {
|
|
1897
|
+
const body = new URLSearchParams(params).toString();
|
|
1898
|
+
const res = await fetch(`https://slack.com/api/${endpoint}`, {
|
|
1899
|
+
method: "POST",
|
|
1900
|
+
headers: {
|
|
1901
|
+
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
1902
|
+
Authorization: `Bearer ${cfg.botToken}`
|
|
1903
|
+
},
|
|
1904
|
+
body
|
|
1905
|
+
});
|
|
1906
|
+
const data = await res.json().catch(() => ({}));
|
|
1907
|
+
if (!res.ok || data?.ok === false) {
|
|
1908
|
+
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
1909
|
+
}
|
|
1910
|
+
return { ok: true };
|
|
1911
|
+
};
|
|
1912
|
+
return {
|
|
1913
|
+
async postMessage({ channel, text, threadTs }) {
|
|
1914
|
+
const res = await slackFetchWithRetry("https://slack.com/api/chat.postMessage", {
|
|
1915
|
+
method: "POST",
|
|
1916
|
+
headers: {
|
|
1917
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1918
|
+
"Authorization": `Bearer ${cfg.botToken}`
|
|
1919
|
+
},
|
|
1920
|
+
body: JSON.stringify({ channel, text, thread_ts: threadTs })
|
|
1921
|
+
});
|
|
1922
|
+
const data = await res.json().catch(() => ({}));
|
|
1923
|
+
if (!res.ok || data?.ok === false) {
|
|
1924
|
+
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
1771
1925
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1926
|
+
return { ok: true, ts: data?.ts };
|
|
1927
|
+
},
|
|
1928
|
+
addReaction({ channel, timestamp, name }) {
|
|
1929
|
+
return slackForm("reactions.add", { channel, timestamp, name });
|
|
1930
|
+
},
|
|
1931
|
+
removeReaction({ channel, timestamp, name }) {
|
|
1932
|
+
return slackForm("reactions.remove", { channel, timestamp, name });
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
function isSlackConfigured() {
|
|
1937
|
+
return readSlackConfig() !== void 0;
|
|
1938
|
+
}
|
|
1939
|
+
function threadCacheKey(channel, threadTs) {
|
|
1940
|
+
return `${channel}\u241F${threadTs}`;
|
|
1941
|
+
}
|
|
1942
|
+
function noteBotPostedInThread(channel, threadTs) {
|
|
1943
|
+
if (!channel || !threadTs) return;
|
|
1944
|
+
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
1945
|
+
owned: true,
|
|
1946
|
+
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
var RESULT_REACTIONS, SLACK_FETCH_ATTEMPTS, SLACK_BACKOFF_BASE_MS, SLACK_BACKOFF_CAP_MS, REACTION_SOFT_ERRORS, USER_TTL_MS, USER_FAIL_TTL_MS, THREAD_OWNED_TTL_MS, THREAD_NEG_TTL_MS;
|
|
1950
|
+
var init_client2 = __esm({
|
|
1951
|
+
"src/integrations/slack/client.ts"() {
|
|
1952
|
+
"use strict";
|
|
1953
|
+
init_config();
|
|
1954
|
+
init_persistence();
|
|
1955
|
+
RESULT_REACTIONS = {
|
|
1956
|
+
responded: "white_check_mark",
|
|
1957
|
+
skipped: "zzz",
|
|
1958
|
+
handed_off: "eyes",
|
|
1959
|
+
failed: "warning"
|
|
1960
|
+
};
|
|
1961
|
+
SLACK_FETCH_ATTEMPTS = 3;
|
|
1962
|
+
SLACK_BACKOFF_BASE_MS = 400;
|
|
1963
|
+
SLACK_BACKOFF_CAP_MS = 3e4;
|
|
1964
|
+
REACTION_SOFT_ERRORS = /* @__PURE__ */ new Set([
|
|
1965
|
+
"already_reacted",
|
|
1966
|
+
// add: someone (or we) already added this emoji
|
|
1967
|
+
"no_reaction",
|
|
1968
|
+
// remove: the emoji isn't on the message
|
|
1969
|
+
"message_not_found"
|
|
1970
|
+
// remove/add: original message deleted
|
|
1971
|
+
]);
|
|
1972
|
+
USER_TTL_MS = 60 * 60 * 1e3;
|
|
1973
|
+
USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
1974
|
+
THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
1975
|
+
THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
1976
|
+
}
|
|
1977
|
+
});
|
|
1978
|
+
|
|
1979
|
+
// src/agent/session-lock.ts
|
|
1980
|
+
function isSessionLocked(sessionId) {
|
|
1981
|
+
const s = locks.get(sessionId);
|
|
1982
|
+
return !!s && s.pending > 0;
|
|
1983
|
+
}
|
|
1984
|
+
var locks;
|
|
1985
|
+
var init_session_lock = __esm({
|
|
1986
|
+
"src/agent/session-lock.ts"() {
|
|
1987
|
+
"use strict";
|
|
1988
|
+
locks = /* @__PURE__ */ new Map();
|
|
1989
|
+
}
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
// src/orchestrator/webhook-events.ts
|
|
1993
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8, appendFileSync as appendFileSync3, writeFileSync as writeFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
1994
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
1995
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1996
|
+
function logFilePath() {
|
|
1997
|
+
return join10(getAppDataDirectory(), "webhook-events.jsonl");
|
|
1998
|
+
}
|
|
1999
|
+
function ensureLoaded() {
|
|
2000
|
+
if (cache !== null) return cache;
|
|
2001
|
+
cache = [];
|
|
2002
|
+
try {
|
|
2003
|
+
const p = logFilePath();
|
|
2004
|
+
if (!existsSync17(p)) return cache;
|
|
2005
|
+
const lines = readFileSync8(p, "utf-8").split("\n").filter(Boolean);
|
|
2006
|
+
for (const line of lines) {
|
|
2007
|
+
try {
|
|
2008
|
+
cache.push(JSON.parse(line));
|
|
2009
|
+
} catch {
|
|
1774
2010
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2011
|
+
}
|
|
2012
|
+
if (cache.length > MAX_EVENTS) {
|
|
2013
|
+
cache = cache.slice(-MAX_EVENTS);
|
|
2014
|
+
try {
|
|
2015
|
+
writeFileSync4(p, cache.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
2016
|
+
} catch {
|
|
1779
2017
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
2018
|
+
}
|
|
2019
|
+
} catch {
|
|
2020
|
+
}
|
|
2021
|
+
return cache;
|
|
2022
|
+
}
|
|
2023
|
+
function appendEvent(ev) {
|
|
2024
|
+
const list = ensureLoaded();
|
|
2025
|
+
list.push(ev);
|
|
2026
|
+
if (list.length > MAX_EVENTS) list.shift();
|
|
2027
|
+
try {
|
|
2028
|
+
const p = logFilePath();
|
|
2029
|
+
mkdirSync7(dirname7(p), { recursive: true });
|
|
2030
|
+
appendFileSync3(p, JSON.stringify(ev) + "\n");
|
|
2031
|
+
} catch {
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
function recordEvent(ev) {
|
|
2035
|
+
const full = {
|
|
2036
|
+
id: ev.id ?? nanoid4(),
|
|
2037
|
+
ts: ev.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2038
|
+
source: ev.source,
|
|
2039
|
+
status: ev.status,
|
|
2040
|
+
subtype: ev.subtype,
|
|
2041
|
+
channel: ev.channel,
|
|
2042
|
+
user: ev.user,
|
|
2043
|
+
textSnippet: ev.textSnippet?.slice(0, 200),
|
|
2044
|
+
dropReason: ev.dropReason,
|
|
2045
|
+
error: ev.error,
|
|
2046
|
+
sessionId: ev.sessionId,
|
|
2047
|
+
durationMs: ev.durationMs,
|
|
2048
|
+
meta: ev.meta
|
|
2049
|
+
};
|
|
2050
|
+
appendEvent(full);
|
|
2051
|
+
return full.id;
|
|
2052
|
+
}
|
|
2053
|
+
var MAX_EVENTS, cache;
|
|
2054
|
+
var init_webhook_events = __esm({
|
|
2055
|
+
"src/orchestrator/webhook-events.ts"() {
|
|
2056
|
+
"use strict";
|
|
2057
|
+
init_config();
|
|
2058
|
+
MAX_EVENTS = 1e3;
|
|
2059
|
+
cache = null;
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
// src/orchestrator/inbox.ts
|
|
2064
|
+
var inbox_exports = {};
|
|
2065
|
+
__export(inbox_exports, {
|
|
2066
|
+
clearInbox: () => clearInbox,
|
|
2067
|
+
flush: () => flush,
|
|
2068
|
+
peekInbox: () => peekInbox,
|
|
2069
|
+
pushToInbox: () => pushToInbox,
|
|
2070
|
+
setFlushHandler: () => setFlushHandler
|
|
2071
|
+
});
|
|
2072
|
+
function setFlushHandler(fn) {
|
|
2073
|
+
flushHandler = fn;
|
|
2074
|
+
}
|
|
2075
|
+
function entryFor(sessionId) {
|
|
2076
|
+
let e = inboxes.get(sessionId);
|
|
2077
|
+
if (!e) {
|
|
2078
|
+
e = { pending: [] };
|
|
2079
|
+
inboxes.set(sessionId, e);
|
|
2080
|
+
}
|
|
2081
|
+
return e;
|
|
2082
|
+
}
|
|
2083
|
+
function pushToInbox(orchestratorSessionId, event) {
|
|
2084
|
+
const e = entryFor(orchestratorSessionId);
|
|
2085
|
+
e.pending.push(event);
|
|
2086
|
+
try {
|
|
2087
|
+
trackInbound(orchestratorSessionId, event);
|
|
2088
|
+
} catch {
|
|
2089
|
+
}
|
|
2090
|
+
if (event.wake === "now") {
|
|
2091
|
+
scheduleFlush(orchestratorSessionId);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
function scheduleFlush(sessionId) {
|
|
2095
|
+
const e = inboxes.get(sessionId);
|
|
2096
|
+
if (!e) return;
|
|
2097
|
+
if (e.timer) clearTimeout(e.timer);
|
|
2098
|
+
e.timer = setTimeout(() => {
|
|
2099
|
+
void flush(sessionId);
|
|
2100
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
2101
|
+
}
|
|
2102
|
+
async function flush(sessionId) {
|
|
2103
|
+
const e = inboxes.get(sessionId);
|
|
2104
|
+
if (!e) return;
|
|
2105
|
+
if (e.timer) {
|
|
2106
|
+
clearTimeout(e.timer);
|
|
2107
|
+
e.timer = void 0;
|
|
2108
|
+
}
|
|
2109
|
+
const events = e.pending.splice(0);
|
|
2110
|
+
if (events.length === 0) return;
|
|
2111
|
+
if (!flushHandler) {
|
|
2112
|
+
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
try {
|
|
2116
|
+
await flushHandler(sessionId, events);
|
|
2117
|
+
} catch (err) {
|
|
2118
|
+
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
function peekInbox(sessionId) {
|
|
2122
|
+
return inboxes.get(sessionId)?.pending.slice() ?? [];
|
|
2123
|
+
}
|
|
2124
|
+
function clearInbox(sessionId) {
|
|
2125
|
+
const e = inboxes.get(sessionId);
|
|
2126
|
+
if (!e) return;
|
|
2127
|
+
if (e.timer) {
|
|
2128
|
+
clearTimeout(e.timer);
|
|
2129
|
+
e.timer = void 0;
|
|
2130
|
+
}
|
|
2131
|
+
e.pending.length = 0;
|
|
2132
|
+
}
|
|
2133
|
+
var inboxes, FLUSH_DEBOUNCE_MS, flushHandler;
|
|
2134
|
+
var init_inbox = __esm({
|
|
2135
|
+
"src/orchestrator/inbox.ts"() {
|
|
2136
|
+
"use strict";
|
|
2137
|
+
init_inbox_acks();
|
|
2138
|
+
inboxes = /* @__PURE__ */ new Map();
|
|
2139
|
+
FLUSH_DEBOUNCE_MS = 200;
|
|
2140
|
+
flushHandler = null;
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
// src/orchestrator/inbox-acks.ts
|
|
2145
|
+
var inbox_acks_exports = {};
|
|
2146
|
+
__export(inbox_acks_exports, {
|
|
2147
|
+
MAX_ATTEMPTS: () => MAX_ATTEMPTS,
|
|
2148
|
+
RECONCILE_EVERY_MS: () => RECONCILE_EVERY_MS,
|
|
2149
|
+
REPLAY_AFTER_MS: () => REPLAY_AFTER_MS,
|
|
2150
|
+
__getAck: () => __getAck,
|
|
2151
|
+
__listAcks: () => __listAcks,
|
|
2152
|
+
__resetAcks: () => __resetAcks,
|
|
2153
|
+
eventKey: () => eventKey,
|
|
2154
|
+
markRespondedForThread: () => markRespondedForThread,
|
|
2155
|
+
markState: () => markState,
|
|
2156
|
+
reconcileOnce: () => reconcileOnce,
|
|
2157
|
+
resolveBatchOnTurnEnd: () => resolveBatchOnTurnEnd,
|
|
2158
|
+
startReconciler: () => startReconciler,
|
|
2159
|
+
stopReconciler: () => stopReconciler,
|
|
2160
|
+
trackInbound: () => trackInbound
|
|
2161
|
+
});
|
|
2162
|
+
function eventKey(event) {
|
|
2163
|
+
const ref = event.ref;
|
|
2164
|
+
const ch = ref?.channel ?? "unknown";
|
|
2165
|
+
switch (ch) {
|
|
2166
|
+
case "slack":
|
|
2167
|
+
return `slack${SEP}${ref.slackChannel}${SEP}${ref.messageTs ?? ref.threadTs ?? ""}`;
|
|
2168
|
+
case "system":
|
|
2169
|
+
return `system${SEP}${ref.workerId}${SEP}${ref.kind}`;
|
|
2170
|
+
case "webhook":
|
|
2171
|
+
return `webhook${SEP}${ref.webhookId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
2172
|
+
case "schedule":
|
|
2173
|
+
return `schedule${SEP}${ref.scheduleId}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}`;
|
|
2174
|
+
default:
|
|
2175
|
+
return `${ch}${SEP}${event.enqueuedAt instanceof Date ? event.enqueuedAt.getTime() : Date.now()}${SEP}${event.content.slice(0, 40)}`;
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
function trackInbound(sessionId, event) {
|
|
2179
|
+
if (event.wake === "never") return null;
|
|
2180
|
+
const key2 = eventKey(event);
|
|
2181
|
+
const existing = ledger.get(key2);
|
|
2182
|
+
if (existing) return key2;
|
|
2183
|
+
const ref = event.ref;
|
|
2184
|
+
const now = Date.now();
|
|
2185
|
+
const entry2 = {
|
|
2186
|
+
key: key2,
|
|
2187
|
+
sessionId,
|
|
2188
|
+
event,
|
|
2189
|
+
channel: ref?.channel ?? "unknown",
|
|
2190
|
+
state: "working",
|
|
2191
|
+
attempts: 0,
|
|
2192
|
+
trackedAt: now,
|
|
2193
|
+
updatedAt: now
|
|
2194
|
+
};
|
|
2195
|
+
if (ref?.channel === "slack") {
|
|
2196
|
+
entry2.slackChannel = ref.slackChannel;
|
|
2197
|
+
entry2.threadTs = ref.threadTs;
|
|
2198
|
+
entry2.messageTs = ref.messageTs;
|
|
2199
|
+
}
|
|
2200
|
+
ledger.set(key2, entry2);
|
|
2201
|
+
if (ledger.size > MAX_ENTRIES) pruneOldest();
|
|
2202
|
+
return key2;
|
|
2203
|
+
}
|
|
2204
|
+
function markState(key2, state2) {
|
|
2205
|
+
const entry2 = ledger.get(key2);
|
|
2206
|
+
if (!entry2) return;
|
|
2207
|
+
if (TERMINAL.has(entry2.state)) return;
|
|
2208
|
+
entry2.state = state2;
|
|
2209
|
+
entry2.updatedAt = Date.now();
|
|
2210
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
2211
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, state2);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
function markRespondedForThread(slackChannel2, threadTs) {
|
|
2215
|
+
if (!slackChannel2 || !threadTs) return;
|
|
2216
|
+
for (const entry2 of ledger.values()) {
|
|
2217
|
+
if (entry2.channel === "slack" && entry2.state === "working" && entry2.slackChannel === slackChannel2 && entry2.threadTs === threadTs) {
|
|
2218
|
+
markState(entry2.key, "responded");
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
function resolveBatchOnTurnEnd(events, ok) {
|
|
2223
|
+
if (!ok) return;
|
|
2224
|
+
for (const ev of events) {
|
|
2225
|
+
const key2 = eventKey(ev);
|
|
2226
|
+
const entry2 = ledger.get(key2);
|
|
2227
|
+
if (!entry2 || entry2.state !== "working") continue;
|
|
2228
|
+
if (entry2.channel === "slack") continue;
|
|
2229
|
+
markState(key2, "responded");
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
async function reconcileOnce(now = Date.now()) {
|
|
2233
|
+
let pushToInbox2 = null;
|
|
2234
|
+
const toReplay = [];
|
|
2235
|
+
for (const entry2 of ledger.values()) {
|
|
2236
|
+
if (TERMINAL.has(entry2.state)) {
|
|
2237
|
+
if (now - entry2.updatedAt > PRUNE_AFTER_MS) ledger.delete(entry2.key);
|
|
2238
|
+
continue;
|
|
2239
|
+
}
|
|
2240
|
+
if (isSessionLocked(entry2.sessionId)) continue;
|
|
2241
|
+
if (now - entry2.updatedAt < REPLAY_AFTER_MS) continue;
|
|
2242
|
+
if (entry2.attempts >= MAX_ATTEMPTS) {
|
|
2243
|
+
failEntry(entry2);
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
toReplay.push(entry2);
|
|
2247
|
+
}
|
|
2248
|
+
if (toReplay.length === 0) return;
|
|
2249
|
+
try {
|
|
2250
|
+
({ pushToInbox: pushToInbox2 } = await Promise.resolve().then(() => (init_inbox(), inbox_exports)));
|
|
2251
|
+
} catch {
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
for (const entry2 of toReplay) {
|
|
2255
|
+
entry2.attempts += 1;
|
|
2256
|
+
entry2.updatedAt = Date.now();
|
|
2257
|
+
const nudged = {
|
|
2258
|
+
...entry2.event,
|
|
2259
|
+
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.]
|
|
2260
|
+
${entry2.event.content}`,
|
|
2261
|
+
wake: "now"
|
|
2262
|
+
};
|
|
2263
|
+
try {
|
|
2264
|
+
pushToInbox2(entry2.sessionId, nudged);
|
|
2265
|
+
} catch {
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
function failEntry(entry2) {
|
|
2270
|
+
entry2.state = "failed";
|
|
2271
|
+
entry2.updatedAt = Date.now();
|
|
2272
|
+
if (entry2.channel === "slack" && entry2.slackChannel && entry2.messageTs) {
|
|
2273
|
+
fireResultReaction(entry2.slackChannel, entry2.messageTs, "failed");
|
|
2274
|
+
if (entry2.threadTs) {
|
|
2275
|
+
fireFallback(
|
|
2276
|
+
entry2.slackChannel,
|
|
2277
|
+
entry2.threadTs,
|
|
2278
|
+
`: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.`
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
recordEvent({
|
|
2283
|
+
source: "daemon",
|
|
2284
|
+
status: "failed",
|
|
2285
|
+
channel: entry2.channel,
|
|
2286
|
+
sessionId: entry2.sessionId,
|
|
2287
|
+
error: `unacknowledged after ${entry2.attempts} replay attempt(s)`,
|
|
2288
|
+
textSnippet: entry2.event.content.slice(0, 200),
|
|
2289
|
+
meta: { ackKey: entry2.key, ackState: "failed" }
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
function pruneOldest() {
|
|
2293
|
+
const terminal = [];
|
|
2294
|
+
for (const e of ledger.values()) if (TERMINAL.has(e.state)) terminal.push(e);
|
|
2295
|
+
terminal.sort((a, b) => a.updatedAt - b.updatedAt);
|
|
2296
|
+
for (const e of terminal) {
|
|
2297
|
+
if (ledger.size <= MAX_ENTRIES) break;
|
|
2298
|
+
ledger.delete(e.key);
|
|
2299
|
+
}
|
|
2300
|
+
while (ledger.size > MAX_ENTRIES) {
|
|
2301
|
+
const oldest = ledger.keys().next().value;
|
|
2302
|
+
if (!oldest) break;
|
|
2303
|
+
ledger.delete(oldest);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
function fireResultReaction(channel, ts, state2) {
|
|
2307
|
+
if (typeof addResultReaction !== "function") return;
|
|
2308
|
+
try {
|
|
2309
|
+
void Promise.resolve(addResultReaction(channel, ts, state2)).catch(() => {
|
|
2310
|
+
});
|
|
2311
|
+
} catch {
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
function fireFallback(channel, threadTs, text) {
|
|
2315
|
+
if (typeof postThreadMessage !== "function") return;
|
|
2316
|
+
try {
|
|
2317
|
+
void Promise.resolve(postThreadMessage(channel, threadTs, text)).catch(() => {
|
|
2318
|
+
});
|
|
2319
|
+
} catch {
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
function startReconciler() {
|
|
2323
|
+
if (reconcileTimer) return;
|
|
2324
|
+
reconcileTimer = setInterval(() => {
|
|
2325
|
+
void reconcileOnce();
|
|
2326
|
+
}, RECONCILE_EVERY_MS);
|
|
2327
|
+
if (typeof reconcileTimer.unref === "function") reconcileTimer.unref();
|
|
2328
|
+
}
|
|
2329
|
+
function stopReconciler() {
|
|
2330
|
+
if (reconcileTimer) {
|
|
2331
|
+
clearInterval(reconcileTimer);
|
|
2332
|
+
reconcileTimer = null;
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
function __getAck(key2) {
|
|
2336
|
+
return ledger.get(key2);
|
|
2337
|
+
}
|
|
2338
|
+
function __listAcks() {
|
|
2339
|
+
return [...ledger.values()];
|
|
2340
|
+
}
|
|
2341
|
+
function __resetAcks() {
|
|
2342
|
+
ledger.clear();
|
|
2343
|
+
}
|
|
2344
|
+
var REPLAY_AFTER_MS, RECONCILE_EVERY_MS, MAX_ATTEMPTS, PRUNE_AFTER_MS, MAX_ENTRIES, TERMINAL, SEP, ledger, reconcileTimer;
|
|
2345
|
+
var init_inbox_acks = __esm({
|
|
2346
|
+
"src/orchestrator/inbox-acks.ts"() {
|
|
2347
|
+
"use strict";
|
|
2348
|
+
init_session_lock();
|
|
2349
|
+
init_webhook_events();
|
|
2350
|
+
init_client2();
|
|
2351
|
+
REPLAY_AFTER_MS = 3 * 6e4;
|
|
2352
|
+
RECONCILE_EVERY_MS = 6e4;
|
|
2353
|
+
MAX_ATTEMPTS = 2;
|
|
2354
|
+
PRUNE_AFTER_MS = 60 * 6e4;
|
|
2355
|
+
MAX_ENTRIES = 5e3;
|
|
2356
|
+
TERMINAL = /* @__PURE__ */ new Set(["responded", "skipped", "handed_off", "failed"]);
|
|
2357
|
+
SEP = "\u241F";
|
|
2358
|
+
ledger = /* @__PURE__ */ new Map();
|
|
2359
|
+
reconcileTimer = null;
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
|
|
2363
|
+
// src/browser/stream-proxy.ts
|
|
2364
|
+
var stream_proxy_exports = {};
|
|
2365
|
+
__export(stream_proxy_exports, {
|
|
2366
|
+
BrowserStreamProxy: () => BrowserStreamProxy,
|
|
2367
|
+
destroyProxy: () => destroyProxy,
|
|
2368
|
+
getOrCreateProxy: () => getOrCreateProxy,
|
|
2369
|
+
getProxy: () => getProxy
|
|
2370
|
+
});
|
|
2371
|
+
import WebSocket from "ws";
|
|
2372
|
+
import { EventEmitter } from "events";
|
|
2373
|
+
function getOrCreateProxy(sessionId, port) {
|
|
2374
|
+
const existing = activeProxies.get(sessionId);
|
|
2375
|
+
if (existing) {
|
|
2376
|
+
console.log(`[BROWSER-WS] Reusing existing proxy for session ${sessionId} (connected=${existing.connected})`);
|
|
2377
|
+
return existing;
|
|
2378
|
+
}
|
|
2379
|
+
console.log(`[BROWSER-WS] Creating new proxy for session ${sessionId} on port ${port} (active proxies: ${activeProxies.size})`);
|
|
2380
|
+
const proxy = new BrowserStreamProxy(port);
|
|
2381
|
+
activeProxies.set(sessionId, proxy);
|
|
2382
|
+
proxy.on("close", () => {
|
|
2383
|
+
console.log(`[BROWSER-WS] Proxy closed for session ${sessionId}, removing from registry`);
|
|
2384
|
+
activeProxies.delete(sessionId);
|
|
2385
|
+
});
|
|
2386
|
+
proxy.connect();
|
|
2387
|
+
return proxy;
|
|
2388
|
+
}
|
|
2389
|
+
function getProxy(sessionId) {
|
|
2390
|
+
return activeProxies.get(sessionId);
|
|
2391
|
+
}
|
|
2392
|
+
function destroyProxy(sessionId) {
|
|
2393
|
+
const proxy = activeProxies.get(sessionId);
|
|
2394
|
+
if (proxy) {
|
|
2395
|
+
console.log(`[BROWSER-WS] destroyProxy() called for session ${sessionId}`);
|
|
2396
|
+
proxy.destroy();
|
|
2397
|
+
activeProxies.delete(sessionId);
|
|
2398
|
+
} else {
|
|
2399
|
+
console.log(`[BROWSER-WS] destroyProxy() called but no proxy exists for session ${sessionId}`);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
var RECONNECT_DELAY_MS, MAX_RECONNECT_ATTEMPTS, FRAME_THROTTLE_MS, BrowserStreamProxy, activeProxies;
|
|
2403
|
+
var init_stream_proxy = __esm({
|
|
2404
|
+
"src/browser/stream-proxy.ts"() {
|
|
2405
|
+
"use strict";
|
|
2406
|
+
RECONNECT_DELAY_MS = 1e3;
|
|
2407
|
+
MAX_RECONNECT_ATTEMPTS = 20;
|
|
2408
|
+
FRAME_THROTTLE_MS = 100;
|
|
2409
|
+
BrowserStreamProxy = class extends EventEmitter {
|
|
2410
|
+
ws = null;
|
|
2411
|
+
port;
|
|
2412
|
+
reconnectAttempts = 0;
|
|
2413
|
+
reconnectTimer = null;
|
|
2414
|
+
destroyed = false;
|
|
2415
|
+
lastFrameTime = 0;
|
|
2416
|
+
_latestFrame = null;
|
|
2417
|
+
_connected = false;
|
|
2418
|
+
constructor(port) {
|
|
2419
|
+
super();
|
|
2420
|
+
this.port = port;
|
|
2421
|
+
}
|
|
2422
|
+
get connected() {
|
|
2423
|
+
return this._connected;
|
|
2424
|
+
}
|
|
2425
|
+
get latestFrame() {
|
|
2426
|
+
return this._latestFrame;
|
|
2427
|
+
}
|
|
2428
|
+
connect() {
|
|
2429
|
+
if (this.destroyed) return;
|
|
2430
|
+
console.log(`[BROWSER-WS] connect() called for port ${this.port}`);
|
|
2431
|
+
this.doConnect();
|
|
2432
|
+
}
|
|
2433
|
+
doConnect() {
|
|
2434
|
+
if (this.destroyed) return;
|
|
2435
|
+
const url = `ws://localhost:${this.port}`;
|
|
2436
|
+
console.log(`[BROWSER-WS] Attempting WebSocket connection to ${url} (attempt ${this.reconnectAttempts + 1}/${MAX_RECONNECT_ATTEMPTS})`);
|
|
2437
|
+
try {
|
|
2438
|
+
this.ws = new WebSocket(url);
|
|
2439
|
+
} catch (err) {
|
|
2440
|
+
console.warn(`[BROWSER-WS] WebSocket constructor threw for ${url}:`, err);
|
|
2441
|
+
this.scheduleReconnect();
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
this.ws.on("open", () => {
|
|
2445
|
+
console.log(`[BROWSER-WS] Connected to ${url} (after ${this.reconnectAttempts} retries)`);
|
|
2446
|
+
this.reconnectAttempts = 0;
|
|
2447
|
+
this._connected = true;
|
|
2448
|
+
});
|
|
2449
|
+
this.ws.on("message", (raw) => {
|
|
2450
|
+
try {
|
|
1798
2451
|
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString("utf8"));
|
|
1799
2452
|
this.handleMessage(msg);
|
|
1800
2453
|
} catch (err) {
|
|
@@ -1915,10 +2568,10 @@ __export(recorder_exports, {
|
|
|
1915
2568
|
});
|
|
1916
2569
|
import { exec as exec5 } from "child_process";
|
|
1917
2570
|
import { promisify as promisify5 } from "util";
|
|
1918
|
-
import { writeFile as
|
|
1919
|
-
import { join as
|
|
1920
|
-
import { tmpdir } from "os";
|
|
1921
|
-
import { nanoid as
|
|
2571
|
+
import { writeFile as writeFile6, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm as rm2 } from "fs/promises";
|
|
2572
|
+
import { join as join13 } from "path";
|
|
2573
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2574
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
1922
2575
|
async function checkFfmpeg() {
|
|
1923
2576
|
try {
|
|
1924
2577
|
await execAsync5("ffmpeg -version", { timeout: 5e3 });
|
|
@@ -1929,7 +2582,7 @@ async function checkFfmpeg() {
|
|
|
1929
2582
|
}
|
|
1930
2583
|
async function cleanup(dir) {
|
|
1931
2584
|
try {
|
|
1932
|
-
await
|
|
2585
|
+
await rm2(dir, { recursive: true, force: true });
|
|
1933
2586
|
} catch {
|
|
1934
2587
|
}
|
|
1935
2588
|
}
|
|
@@ -1973,21 +2626,21 @@ var init_recorder = __esm({
|
|
|
1973
2626
|
*/
|
|
1974
2627
|
async encode() {
|
|
1975
2628
|
if (this.frames.length === 0) return null;
|
|
1976
|
-
const workDir =
|
|
2629
|
+
const workDir = join13(tmpdir2(), `sparkecoder-recording-${nanoid8(8)}`);
|
|
1977
2630
|
await mkdir4(workDir, { recursive: true });
|
|
1978
2631
|
try {
|
|
1979
2632
|
for (let i = 0; i < this.frames.length; i++) {
|
|
1980
|
-
const framePath =
|
|
1981
|
-
await
|
|
2633
|
+
const framePath = join13(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
2634
|
+
await writeFile6(framePath, this.frames[i].data);
|
|
1982
2635
|
}
|
|
1983
2636
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
1984
2637
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
1985
2638
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
1986
|
-
const outputPath =
|
|
2639
|
+
const outputPath = join13(workDir, `recording_${this.sessionId}.mp4`);
|
|
1987
2640
|
const hasFfmpeg = await checkFfmpeg();
|
|
1988
2641
|
if (hasFfmpeg) {
|
|
1989
2642
|
await execAsync5(
|
|
1990
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
2643
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join13(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
1991
2644
|
{ timeout: 12e4 }
|
|
1992
2645
|
);
|
|
1993
2646
|
} else {
|
|
@@ -1999,7 +2652,7 @@ var init_recorder = __esm({
|
|
|
1999
2652
|
const files = await readdir5(workDir);
|
|
2000
2653
|
for (const f of files) {
|
|
2001
2654
|
if (f.startsWith("frame_")) {
|
|
2002
|
-
await unlink2(
|
|
2655
|
+
await unlink2(join13(workDir, f)).catch(() => {
|
|
2003
2656
|
});
|
|
2004
2657
|
}
|
|
2005
2658
|
}
|
|
@@ -2233,7 +2886,7 @@ var SUBAGENT_MODELS = {
|
|
|
2233
2886
|
init_db();
|
|
2234
2887
|
init_config();
|
|
2235
2888
|
import { z as z15 } from "zod";
|
|
2236
|
-
import { nanoid as
|
|
2889
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
2237
2890
|
|
|
2238
2891
|
// src/tools/bash.ts
|
|
2239
2892
|
import { tool } from "ai";
|
|
@@ -6011,7 +6664,8 @@ async function buildSystemPrompt(options) {
|
|
|
6011
6664
|
sessionId,
|
|
6012
6665
|
discoveredSkills,
|
|
6013
6666
|
activeFiles = [],
|
|
6014
|
-
customInstructions
|
|
6667
|
+
customInstructions,
|
|
6668
|
+
taskScopedSkills
|
|
6015
6669
|
} = options;
|
|
6016
6670
|
let alwaysLoadedContent = "";
|
|
6017
6671
|
let globMatchedContent = "";
|
|
@@ -6032,6 +6686,22 @@ async function buildSystemPrompt(options) {
|
|
|
6032
6686
|
const skills = await loadAllSkills2(skillsDirectories);
|
|
6033
6687
|
onDemandSkillsContext = formatSkillsForContext(skills);
|
|
6034
6688
|
}
|
|
6689
|
+
let taskScopedSkillsBlock = "";
|
|
6690
|
+
if (taskScopedSkills && (taskScopedSkills.always.length > 0 || taskScopedSkills.onDemand.length > 0)) {
|
|
6691
|
+
const parts = ["<task_provided_skills>"];
|
|
6692
|
+
parts.push("These skills were supplied with this task and are available for this run only.");
|
|
6693
|
+
if (taskScopedSkills.always.length > 0) {
|
|
6694
|
+
parts.push(formatAlwaysLoadedSkills(taskScopedSkills.always));
|
|
6695
|
+
}
|
|
6696
|
+
if (taskScopedSkills.onDemand.length > 0) {
|
|
6697
|
+
parts.push("Load any of these on demand with the load_skill tool:");
|
|
6698
|
+
for (const s of taskScopedSkills.onDemand) {
|
|
6699
|
+
parts.push(`- ${s.name}: ${s.description}`);
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
parts.push("</task_provided_skills>");
|
|
6703
|
+
taskScopedSkillsBlock = parts.join("\n");
|
|
6704
|
+
}
|
|
6035
6705
|
const todos = await todoQueries.getBySession(sessionId);
|
|
6036
6706
|
const todosContext = formatTodosForContext(todos);
|
|
6037
6707
|
const plans = await readSessionPlans(workingDirectory, sessionId);
|
|
@@ -6324,6 +6994,8 @@ ${globMatchedContent}
|
|
|
6324
6994
|
${onDemandSkillsContext}
|
|
6325
6995
|
</on_demand_skills>
|
|
6326
6996
|
|
|
6997
|
+
${taskScopedSkillsBlock}
|
|
6998
|
+
|
|
6327
6999
|
<current_task_list>
|
|
6328
7000
|
${todosContext}
|
|
6329
7001
|
</current_task_list>
|
|
@@ -6931,20 +7603,110 @@ function sanitizeModelMessages(messages) {
|
|
|
6931
7603
|
return result;
|
|
6932
7604
|
}
|
|
6933
7605
|
|
|
7606
|
+
// src/utils/cap-image-count.ts
|
|
7607
|
+
var MAX_IMAGES_IN_CONTEXT = 11;
|
|
7608
|
+
var IMAGE_TRUNCATED_PLACEHOLDER = "[image truncated due to length of conversation]";
|
|
7609
|
+
function isImagePart(part) {
|
|
7610
|
+
if (!part || typeof part !== "object") return false;
|
|
7611
|
+
const t = part.type;
|
|
7612
|
+
if (t === "image") return true;
|
|
7613
|
+
if (t === "image-data") return true;
|
|
7614
|
+
if (t === "media") {
|
|
7615
|
+
const data = part.data;
|
|
7616
|
+
const mt = part.mediaType;
|
|
7617
|
+
if (typeof data === "string" && typeof mt === "string" && mt.startsWith("image/")) {
|
|
7618
|
+
return true;
|
|
7619
|
+
}
|
|
7620
|
+
}
|
|
7621
|
+
return false;
|
|
7622
|
+
}
|
|
7623
|
+
function makePlaceholder() {
|
|
7624
|
+
return { type: "text", text: IMAGE_TRUNCATED_PLACEHOLDER };
|
|
7625
|
+
}
|
|
7626
|
+
function countImages(messages) {
|
|
7627
|
+
let n = 0;
|
|
7628
|
+
for (const msg of messages) {
|
|
7629
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7630
|
+
for (const part of msg.content) {
|
|
7631
|
+
if (isImagePart(part)) {
|
|
7632
|
+
n++;
|
|
7633
|
+
continue;
|
|
7634
|
+
}
|
|
7635
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7636
|
+
for (const sub of part.output.value) {
|
|
7637
|
+
if (isImagePart(sub)) n++;
|
|
7638
|
+
}
|
|
7639
|
+
}
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
return n;
|
|
7643
|
+
}
|
|
7644
|
+
function capImageCount(messages, max = MAX_IMAGES_IN_CONTEXT) {
|
|
7645
|
+
if (!Array.isArray(messages) || messages.length === 0) return messages;
|
|
7646
|
+
if (max < 0) throw new Error("capImageCount: max must be >= 0");
|
|
7647
|
+
const total = countImages(messages);
|
|
7648
|
+
if (total <= max) return messages;
|
|
7649
|
+
let toDrop = total - max;
|
|
7650
|
+
let mutated = false;
|
|
7651
|
+
const out = messages.slice();
|
|
7652
|
+
for (let i = 0; i < out.length && toDrop > 0; i++) {
|
|
7653
|
+
const msg = out[i];
|
|
7654
|
+
if (!Array.isArray(msg.content)) continue;
|
|
7655
|
+
let contentCloned = false;
|
|
7656
|
+
const ensureContentCloned = () => {
|
|
7657
|
+
if (contentCloned) return;
|
|
7658
|
+
out[i] = { ...msg, content: [...msg.content] };
|
|
7659
|
+
contentCloned = true;
|
|
7660
|
+
};
|
|
7661
|
+
const content = () => out[i].content;
|
|
7662
|
+
for (let j = 0; j < content().length && toDrop > 0; j++) {
|
|
7663
|
+
const part = content()[j];
|
|
7664
|
+
if (isImagePart(part)) {
|
|
7665
|
+
ensureContentCloned();
|
|
7666
|
+
out[i].content[j] = makePlaceholder();
|
|
7667
|
+
toDrop--;
|
|
7668
|
+
mutated = true;
|
|
7669
|
+
continue;
|
|
7670
|
+
}
|
|
7671
|
+
if (part && typeof part === "object" && part.type === "tool-result" && part.output && part.output.type === "content" && Array.isArray(part.output.value)) {
|
|
7672
|
+
const innerImages = [];
|
|
7673
|
+
const innerValue = part.output.value;
|
|
7674
|
+
for (let k = 0; k < innerValue.length; k++) {
|
|
7675
|
+
if (isImagePart(innerValue[k])) innerImages.push(k);
|
|
7676
|
+
}
|
|
7677
|
+
if (innerImages.length === 0) continue;
|
|
7678
|
+
const dropHere = Math.min(innerImages.length, toDrop);
|
|
7679
|
+
ensureContentCloned();
|
|
7680
|
+
const newOutputValue = [...innerValue];
|
|
7681
|
+
for (let d = 0; d < dropHere; d++) {
|
|
7682
|
+
newOutputValue[innerImages[d]] = makePlaceholder();
|
|
7683
|
+
}
|
|
7684
|
+
const newPart = {
|
|
7685
|
+
...part,
|
|
7686
|
+
output: {
|
|
7687
|
+
...part.output,
|
|
7688
|
+
value: newOutputValue
|
|
7689
|
+
}
|
|
7690
|
+
};
|
|
7691
|
+
out[i].content[j] = newPart;
|
|
7692
|
+
toDrop -= dropHere;
|
|
7693
|
+
mutated = true;
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
}
|
|
7697
|
+
if (mutated) {
|
|
7698
|
+
console.warn(
|
|
7699
|
+
`[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.`
|
|
7700
|
+
);
|
|
7701
|
+
}
|
|
7702
|
+
return mutated ? out : messages;
|
|
7703
|
+
}
|
|
7704
|
+
|
|
6934
7705
|
// src/agent/model-limits.ts
|
|
6935
7706
|
var MODEL_LIMITS = {
|
|
6936
|
-
"
|
|
6937
|
-
"
|
|
6938
|
-
"
|
|
6939
|
-
"anthropic/claude-3.5-sonnet": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6940
|
-
"anthropic/claude-3-haiku": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6941
|
-
"google/gemini-3-flash-preview": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6942
|
-
"google/gemini-2.5-pro": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6943
|
-
"google/gemini-2.5-flash": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6944
|
-
"openai/gpt-4o": { contextWindow: 128e3, rollingTarget: 78e3 },
|
|
6945
|
-
"openai/gpt-4.1": { contextWindow: 1e6, rollingTarget: 15e4 },
|
|
6946
|
-
"openai/o3": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
6947
|
-
"xai/grok-3": { contextWindow: 131072, rollingTarget: 8e4 }
|
|
7707
|
+
"claude-opus-4-8": { contextWindow: 2e5, rollingTarget: 15e4 },
|
|
7708
|
+
"gpt-5.5": { contextWindow: 35e4, rollingTarget: 15e4 },
|
|
7709
|
+
"claude-fable-5": { contextWindow: 2e5, rollingTarget: 15e4 }
|
|
6948
7710
|
};
|
|
6949
7711
|
var DEFAULT_LIMITS = { contextWindow: 2e5, rollingTarget: 15e4 };
|
|
6950
7712
|
var PREFIX_DEFAULTS = {
|
|
@@ -7015,6 +7777,7 @@ ${summaryContent}`
|
|
|
7015
7777
|
messages = repairToolPairing(messages);
|
|
7016
7778
|
messages = ensureToolResultsFollowCalls(messages);
|
|
7017
7779
|
messages = ensureEndsWithUserOrTool(messages);
|
|
7780
|
+
messages = capImageCount(messages);
|
|
7018
7781
|
return messages;
|
|
7019
7782
|
}
|
|
7020
7783
|
// ---------------------------------------------------------------------------
|
|
@@ -7132,7 +7895,7 @@ ${summaryContent}`
|
|
|
7132
7895
|
}
|
|
7133
7896
|
async summarizeChunk(chunk) {
|
|
7134
7897
|
const historyText = chunk.map((msg) => {
|
|
7135
|
-
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
7898
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(stripBinaryContentForSummary(msg.content));
|
|
7136
7899
|
return `[${msg.role}]: ${content}`;
|
|
7137
7900
|
}).join("\n\n");
|
|
7138
7901
|
try {
|
|
@@ -7243,9 +8006,35 @@ ${summaryContent}`
|
|
|
7243
8006
|
this.summaries = [];
|
|
7244
8007
|
}
|
|
7245
8008
|
};
|
|
7246
|
-
function
|
|
7247
|
-
if (
|
|
7248
|
-
|
|
8009
|
+
function stripBinaryContentForSummary(value) {
|
|
8010
|
+
if (Array.isArray(value)) return value.map(stripBinaryContentForSummary);
|
|
8011
|
+
if (!value || typeof value !== "object") return value;
|
|
8012
|
+
const record = value;
|
|
8013
|
+
const type = record.type;
|
|
8014
|
+
if ((type === "image-data" || type === "file-data" || type === "media") && typeof record.data === "string") {
|
|
8015
|
+
const mediaType = typeof record.mediaType === "string" ? record.mediaType : "unknown media type";
|
|
8016
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
8017
|
+
return {
|
|
8018
|
+
...record,
|
|
8019
|
+
data: `[${type}${filename}; ${mediaType}; ${record.data.length} base64 chars omitted for summary]`
|
|
8020
|
+
};
|
|
8021
|
+
}
|
|
8022
|
+
if (type === "image" && typeof record.image === "string") {
|
|
8023
|
+
const filename = typeof record.filename === "string" ? ` ${record.filename}` : "";
|
|
8024
|
+
return {
|
|
8025
|
+
...record,
|
|
8026
|
+
image: `[image${filename}; ${record.image.length} base64 chars omitted for summary]`
|
|
8027
|
+
};
|
|
8028
|
+
}
|
|
8029
|
+
const out = {};
|
|
8030
|
+
for (const [key2, nested] of Object.entries(record)) {
|
|
8031
|
+
out[key2] = stripBinaryContentForSummary(nested);
|
|
8032
|
+
}
|
|
8033
|
+
return out;
|
|
8034
|
+
}
|
|
8035
|
+
function stripOrphanedToolResults(msg, removedIds) {
|
|
8036
|
+
if (!Array.isArray(msg.content)) return msg;
|
|
8037
|
+
const parts = msg.content.filter((part) => {
|
|
7249
8038
|
if (part.type === "tool-result" && removedIds.has(part.toolCallId)) return false;
|
|
7250
8039
|
if (part.type === "tool-call" && removedIds.has(part.toolCallId)) return false;
|
|
7251
8040
|
return true;
|
|
@@ -7406,161 +8195,8 @@ var webChannel = {
|
|
|
7406
8195
|
displayLabel: () => "WEB"
|
|
7407
8196
|
};
|
|
7408
8197
|
|
|
7409
|
-
// src/integrations/slack/client.ts
|
|
7410
|
-
init_config();
|
|
7411
|
-
|
|
7412
|
-
// src/integrations/slack/persistence.ts
|
|
7413
|
-
init_config();
|
|
7414
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
7415
|
-
import { join as join9, dirname as dirname6 } from "path";
|
|
7416
|
-
var FILENAME = "slack-cache.json";
|
|
7417
|
-
var FILE_VERSION = 1;
|
|
7418
|
-
var SAVE_DEBOUNCE_MS = 500;
|
|
7419
|
-
var MAX_THREADS = 5e3;
|
|
7420
|
-
var loaded = false;
|
|
7421
|
-
var userMap = /* @__PURE__ */ new Map();
|
|
7422
|
-
var threadMap = /* @__PURE__ */ new Map();
|
|
7423
|
-
var dirty = false;
|
|
7424
|
-
var saveTimer = null;
|
|
7425
|
-
function cachePath() {
|
|
7426
|
-
return join9(ensureAppDataDirectory(), FILENAME);
|
|
7427
|
-
}
|
|
7428
|
-
function load() {
|
|
7429
|
-
if (loaded) return;
|
|
7430
|
-
loaded = true;
|
|
7431
|
-
const path = cachePath();
|
|
7432
|
-
if (!existsSync16(path)) return;
|
|
7433
|
-
try {
|
|
7434
|
-
const raw = readFileSync7(path, "utf-8");
|
|
7435
|
-
const parsed = JSON.parse(raw);
|
|
7436
|
-
if (parsed?.version !== FILE_VERSION) return;
|
|
7437
|
-
const now = Date.now();
|
|
7438
|
-
for (const [id, e] of Object.entries(parsed.users || {})) {
|
|
7439
|
-
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7440
|
-
userMap.set(id, {
|
|
7441
|
-
name: e.name ?? null,
|
|
7442
|
-
realName: e.realName ?? null,
|
|
7443
|
-
email: e.email ?? null,
|
|
7444
|
-
expiresAt: e.expiresAt
|
|
7445
|
-
});
|
|
7446
|
-
}
|
|
7447
|
-
}
|
|
7448
|
-
for (const [key2, e] of Object.entries(parsed.threads || {})) {
|
|
7449
|
-
if (e && typeof e.expiresAt === "number" && e.expiresAt > now) {
|
|
7450
|
-
threadMap.set(key2, { owned: !!e.owned, expiresAt: e.expiresAt });
|
|
7451
|
-
}
|
|
7452
|
-
}
|
|
7453
|
-
} catch (err) {
|
|
7454
|
-
console.warn(`[slack] could not load ${FILENAME}:`, err?.message || err);
|
|
7455
|
-
}
|
|
7456
|
-
}
|
|
7457
|
-
function evictIfOversized(map, max) {
|
|
7458
|
-
if (map.size <= max) return;
|
|
7459
|
-
const sorted = [...map.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
7460
|
-
const toRemove = map.size - max;
|
|
7461
|
-
for (let i = 0; i < toRemove; i++) map.delete(sorted[i][0]);
|
|
7462
|
-
}
|
|
7463
|
-
function scheduleSave() {
|
|
7464
|
-
dirty = true;
|
|
7465
|
-
if (saveTimer) return;
|
|
7466
|
-
saveTimer = setTimeout(() => {
|
|
7467
|
-
saveTimer = null;
|
|
7468
|
-
if (dirty) saveSync();
|
|
7469
|
-
}, SAVE_DEBOUNCE_MS);
|
|
7470
|
-
saveTimer.unref?.();
|
|
7471
|
-
}
|
|
7472
|
-
function saveSync() {
|
|
7473
|
-
dirty = false;
|
|
7474
|
-
try {
|
|
7475
|
-
const path = cachePath();
|
|
7476
|
-
const dir = dirname6(path);
|
|
7477
|
-
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
7478
|
-
const payload = {
|
|
7479
|
-
version: FILE_VERSION,
|
|
7480
|
-
users: Object.fromEntries(userMap),
|
|
7481
|
-
threads: Object.fromEntries(threadMap)
|
|
7482
|
-
};
|
|
7483
|
-
const tmp = `${path}.tmp`;
|
|
7484
|
-
writeFileSync3(tmp, JSON.stringify(payload), "utf-8");
|
|
7485
|
-
renameSync(tmp, path);
|
|
7486
|
-
} catch (err) {
|
|
7487
|
-
console.warn(`[slack] could not persist ${FILENAME}:`, err?.message || err);
|
|
7488
|
-
}
|
|
7489
|
-
}
|
|
7490
|
-
var exitHooked = false;
|
|
7491
|
-
function hookExit() {
|
|
7492
|
-
if (exitHooked) return;
|
|
7493
|
-
exitHooked = true;
|
|
7494
|
-
const flush2 = () => {
|
|
7495
|
-
if (dirty) saveSync();
|
|
7496
|
-
};
|
|
7497
|
-
process.once("beforeExit", flush2);
|
|
7498
|
-
process.once("SIGINT", flush2);
|
|
7499
|
-
process.once("SIGTERM", flush2);
|
|
7500
|
-
}
|
|
7501
|
-
function setCachedThreadOwnership(key2, entry2) {
|
|
7502
|
-
load();
|
|
7503
|
-
hookExit();
|
|
7504
|
-
threadMap.set(key2, entry2);
|
|
7505
|
-
evictIfOversized(threadMap, MAX_THREADS);
|
|
7506
|
-
scheduleSave();
|
|
7507
|
-
}
|
|
7508
|
-
|
|
7509
|
-
// src/integrations/slack/client.ts
|
|
7510
|
-
function readSlackConfig() {
|
|
7511
|
-
try {
|
|
7512
|
-
const cfg = getConfig();
|
|
7513
|
-
const slack = cfg?.slack;
|
|
7514
|
-
if (!slack?.botToken) return void 0;
|
|
7515
|
-
return {
|
|
7516
|
-
botToken: String(slack.botToken),
|
|
7517
|
-
signingSecret: slack.signingSecret ? String(slack.signingSecret) : void 0,
|
|
7518
|
-
defaultOrchestratorName: slack.defaultOrchestratorName ? String(slack.defaultOrchestratorName) : void 0
|
|
7519
|
-
};
|
|
7520
|
-
} catch {
|
|
7521
|
-
return void 0;
|
|
7522
|
-
}
|
|
7523
|
-
}
|
|
7524
|
-
function getSlackAdapter() {
|
|
7525
|
-
const cfg = readSlackConfig();
|
|
7526
|
-
if (!cfg) return void 0;
|
|
7527
|
-
return {
|
|
7528
|
-
async postMessage({ channel, text, threadTs }) {
|
|
7529
|
-
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
7530
|
-
method: "POST",
|
|
7531
|
-
headers: {
|
|
7532
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
7533
|
-
"Authorization": `Bearer ${cfg.botToken}`
|
|
7534
|
-
},
|
|
7535
|
-
body: JSON.stringify({ channel, text, thread_ts: threadTs })
|
|
7536
|
-
});
|
|
7537
|
-
const data = await res.json().catch(() => ({}));
|
|
7538
|
-
if (!res.ok || data?.ok === false) {
|
|
7539
|
-
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
7540
|
-
}
|
|
7541
|
-
return { ok: true, ts: data?.ts };
|
|
7542
|
-
}
|
|
7543
|
-
};
|
|
7544
|
-
}
|
|
7545
|
-
function isSlackConfigured() {
|
|
7546
|
-
return readSlackConfig() !== void 0;
|
|
7547
|
-
}
|
|
7548
|
-
var USER_TTL_MS = 60 * 60 * 1e3;
|
|
7549
|
-
var USER_FAIL_TTL_MS = 5 * 60 * 1e3;
|
|
7550
|
-
var THREAD_OWNED_TTL_MS = 60 * 60 * 1e3;
|
|
7551
|
-
var THREAD_NEG_TTL_MS = 5 * 60 * 1e3;
|
|
7552
|
-
function threadCacheKey(channel, threadTs) {
|
|
7553
|
-
return `${channel}\u241F${threadTs}`;
|
|
7554
|
-
}
|
|
7555
|
-
function noteBotPostedInThread(channel, threadTs) {
|
|
7556
|
-
if (!channel || !threadTs) return;
|
|
7557
|
-
setCachedThreadOwnership(threadCacheKey(channel, threadTs), {
|
|
7558
|
-
owned: true,
|
|
7559
|
-
expiresAt: Date.now() + THREAD_OWNED_TTL_MS
|
|
7560
|
-
});
|
|
7561
|
-
}
|
|
7562
|
-
|
|
7563
8198
|
// src/integrations/channels/slack.ts
|
|
8199
|
+
init_client2();
|
|
7564
8200
|
var ownedThreads = /* @__PURE__ */ new Set();
|
|
7565
8201
|
function threadKey(channel, threadTs) {
|
|
7566
8202
|
return `${channel}\u241F${threadTs}`;
|
|
@@ -7584,6 +8220,8 @@ var slackChannel = {
|
|
|
7584
8220
|
if (r.slackChannel && r.threadTs) {
|
|
7585
8221
|
markThreadOwned(r.slackChannel, r.threadTs);
|
|
7586
8222
|
noteBotPostedInThread(r.slackChannel, r.threadTs);
|
|
8223
|
+
void Promise.resolve().then(() => (init_inbox_acks(), inbox_acks_exports)).then((m) => m.markRespondedForThread(r.slackChannel, r.threadTs)).catch(() => {
|
|
8224
|
+
});
|
|
7587
8225
|
}
|
|
7588
8226
|
},
|
|
7589
8227
|
displayLabel(ref) {
|
|
@@ -7710,7 +8348,7 @@ function describeConfiguredChannels() {
|
|
|
7710
8348
|
|
|
7711
8349
|
// src/orchestrator/schedules-store.ts
|
|
7712
8350
|
init_db();
|
|
7713
|
-
import { nanoid as
|
|
8351
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
7714
8352
|
async function readOrch(orchestratorSessionId) {
|
|
7715
8353
|
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
7716
8354
|
if (!s) return null;
|
|
@@ -7725,7 +8363,7 @@ async function createSchedule(orchestratorSessionId, input) {
|
|
|
7725
8363
|
const data = await readOrch(orchestratorSessionId);
|
|
7726
8364
|
if (!data) throw new Error("orchestrator session not found");
|
|
7727
8365
|
const row = {
|
|
7728
|
-
id: `sch_${
|
|
8366
|
+
id: `sch_${nanoid5(10)}`,
|
|
7729
8367
|
name: input.name,
|
|
7730
8368
|
cron: input.cron,
|
|
7731
8369
|
prompt: input.prompt,
|
|
@@ -7760,7 +8398,7 @@ init_config();
|
|
|
7760
8398
|
// src/orchestrator/webhooks-store.ts
|
|
7761
8399
|
init_db();
|
|
7762
8400
|
import { randomBytes } from "crypto";
|
|
7763
|
-
import { nanoid as
|
|
8401
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
7764
8402
|
function newToken() {
|
|
7765
8403
|
return randomBytes(24).toString("base64url");
|
|
7766
8404
|
}
|
|
@@ -7777,7 +8415,7 @@ async function createWebhook(orchestratorSessionId, input) {
|
|
|
7777
8415
|
const data = await readOrch2(orchestratorSessionId);
|
|
7778
8416
|
if (!data) throw new Error("orchestrator session not found");
|
|
7779
8417
|
const row = {
|
|
7780
|
-
id: `whk_${
|
|
8418
|
+
id: `whk_${nanoid6(10)}`,
|
|
7781
8419
|
name: input.name,
|
|
7782
8420
|
token: newToken(),
|
|
7783
8421
|
wake: input.wake ?? "now",
|
|
@@ -7853,6 +8491,26 @@ var agentInputSchema = z14.object({
|
|
|
7853
8491
|
model: z14.string().optional().describe("spawn only: model override."),
|
|
7854
8492
|
workingDirectory: z14.string().optional().describe("spawn only: working directory override."),
|
|
7855
8493
|
maxIterations: z14.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
8494
|
+
mcpServers: z14.array(
|
|
8495
|
+
z14.object({
|
|
8496
|
+
name: z14.string(),
|
|
8497
|
+
transport: z14.enum(["http", "sse", "stdio"]),
|
|
8498
|
+
url: z14.string().optional(),
|
|
8499
|
+
headers: z14.record(z14.string(), z14.string()).optional(),
|
|
8500
|
+
command: z14.string().optional(),
|
|
8501
|
+
args: z14.array(z14.string()).optional(),
|
|
8502
|
+
env: z14.record(z14.string(), z14.string()).optional()
|
|
8503
|
+
})
|
|
8504
|
+
).optional().describe("spawn only: task-scoped MCP servers (with auth headers) connected for this worker only, tools exposed as mcp_<name>_<tool>."),
|
|
8505
|
+
skills: z14.array(
|
|
8506
|
+
z14.object({
|
|
8507
|
+
name: z14.string(),
|
|
8508
|
+
description: z14.string().optional(),
|
|
8509
|
+
content: z14.string(),
|
|
8510
|
+
alwaysApply: z14.boolean().optional(),
|
|
8511
|
+
globs: z14.array(z14.string()).optional()
|
|
8512
|
+
})
|
|
8513
|
+
).optional().describe("spawn only: task-scoped skills (inline markdown) available to this worker only."),
|
|
7856
8514
|
// message
|
|
7857
8515
|
text: z14.string().optional().describe("message only: the text to deliver to the worker."),
|
|
7858
8516
|
force: z14.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
@@ -7923,7 +8581,9 @@ function buildAgentTool(opts) {
|
|
|
7923
8581
|
workingDirectory: input.workingDirectory ?? opts.defaultWorkingDirectory,
|
|
7924
8582
|
name: input.name,
|
|
7925
8583
|
maxIterations: input.maxIterations ?? 100,
|
|
7926
|
-
orchestratorSessionId: opts.orchestratorSessionId
|
|
8584
|
+
orchestratorSessionId: opts.orchestratorSessionId,
|
|
8585
|
+
...input.mcpServers ? { mcpServers: input.mcpServers } : {},
|
|
8586
|
+
...input.skills ? { skills: input.skills } : {}
|
|
7927
8587
|
}
|
|
7928
8588
|
});
|
|
7929
8589
|
return {
|
|
@@ -8104,9 +8764,9 @@ import { createMCPClient } from "@ai-sdk/mcp";
|
|
|
8104
8764
|
|
|
8105
8765
|
// src/integrations/mcp/store.ts
|
|
8106
8766
|
init_config();
|
|
8107
|
-
import { nanoid as
|
|
8108
|
-
import { existsSync as
|
|
8109
|
-
import { resolve as resolve10, join as
|
|
8767
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
8768
|
+
import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
|
|
8769
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
8110
8770
|
function readServers() {
|
|
8111
8771
|
try {
|
|
8112
8772
|
const cfg = getConfig();
|
|
@@ -8118,12 +8778,12 @@ function readServers() {
|
|
|
8118
8778
|
function refreshMcpServersFromDisk() {
|
|
8119
8779
|
const candidates = [
|
|
8120
8780
|
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8121
|
-
|
|
8781
|
+
join11(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8122
8782
|
];
|
|
8123
8783
|
for (const path of candidates) {
|
|
8124
|
-
if (!
|
|
8784
|
+
if (!existsSync18(path)) continue;
|
|
8125
8785
|
try {
|
|
8126
|
-
const raw = JSON.parse(
|
|
8786
|
+
const raw = JSON.parse(readFileSync9(path, "utf-8"));
|
|
8127
8787
|
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8128
8788
|
setMcpServers(servers2);
|
|
8129
8789
|
return servers2;
|
|
@@ -8207,6 +8867,149 @@ async function loadAllMcpTools(opts = {}) {
|
|
|
8207
8867
|
return tools;
|
|
8208
8868
|
}
|
|
8209
8869
|
|
|
8870
|
+
// src/integrations/mcp/task-scoped.ts
|
|
8871
|
+
import { createMCPClient as createMCPClient2 } from "@ai-sdk/mcp";
|
|
8872
|
+
function sanitizeName(raw) {
|
|
8873
|
+
return raw.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/_+/g, "_");
|
|
8874
|
+
}
|
|
8875
|
+
function buildHttpLikeTransport(server) {
|
|
8876
|
+
if (!server.url) {
|
|
8877
|
+
throw new Error(`${server.transport} transport requires a url`);
|
|
8878
|
+
}
|
|
8879
|
+
return {
|
|
8880
|
+
type: server.transport,
|
|
8881
|
+
url: server.url,
|
|
8882
|
+
headers: server.headers
|
|
8883
|
+
};
|
|
8884
|
+
}
|
|
8885
|
+
async function buildStdioTransport2(server) {
|
|
8886
|
+
if (!server.command) {
|
|
8887
|
+
throw new Error("stdio transport requires a command");
|
|
8888
|
+
}
|
|
8889
|
+
const mod = await import("@ai-sdk/mcp/mcp-stdio");
|
|
8890
|
+
const Cls = mod.Experimental_StdioMCPTransport || mod.StdioClientTransport;
|
|
8891
|
+
if (!Cls) throw new Error("@ai-sdk/mcp/mcp-stdio is missing the stdio transport class");
|
|
8892
|
+
return new Cls({
|
|
8893
|
+
command: server.command,
|
|
8894
|
+
args: server.args ?? [],
|
|
8895
|
+
env: server.env
|
|
8896
|
+
});
|
|
8897
|
+
}
|
|
8898
|
+
async function buildTransport(server) {
|
|
8899
|
+
return server.transport === "stdio" ? await buildStdioTransport2(server) : buildHttpLikeTransport(server);
|
|
8900
|
+
}
|
|
8901
|
+
async function connectTaskMcpServers(servers2, opts = {}) {
|
|
8902
|
+
const tools = {};
|
|
8903
|
+
const connected = [];
|
|
8904
|
+
const errors = [];
|
|
8905
|
+
const clients2 = [];
|
|
8906
|
+
for (const raw of servers2 ?? []) {
|
|
8907
|
+
const name = sanitizeName(raw.name || "");
|
|
8908
|
+
if (!name) {
|
|
8909
|
+
errors.push({ name: raw.name || "(unnamed)", error: "server name is required" });
|
|
8910
|
+
continue;
|
|
8911
|
+
}
|
|
8912
|
+
let client = null;
|
|
8913
|
+
try {
|
|
8914
|
+
const transport = await buildTransport(raw);
|
|
8915
|
+
client = await createMCPClient2({ transport });
|
|
8916
|
+
clients2.push(client);
|
|
8917
|
+
const serverTools = await client.tools();
|
|
8918
|
+
for (const [toolName, t] of Object.entries(serverTools)) {
|
|
8919
|
+
tools[`mcp_${name}_${toolName}`] = t;
|
|
8920
|
+
}
|
|
8921
|
+
connected.push(name);
|
|
8922
|
+
} catch (err) {
|
|
8923
|
+
const message = err?.message || String(err);
|
|
8924
|
+
errors.push({ name, error: message });
|
|
8925
|
+
if (!opts.quiet) {
|
|
8926
|
+
console.warn(`[mcp:task] connecting "${name}" failed: ${message}`);
|
|
8927
|
+
}
|
|
8928
|
+
if (client) {
|
|
8929
|
+
try {
|
|
8930
|
+
await client.close();
|
|
8931
|
+
} catch {
|
|
8932
|
+
}
|
|
8933
|
+
const idx = clients2.indexOf(client);
|
|
8934
|
+
if (idx >= 0) clients2.splice(idx, 1);
|
|
8935
|
+
}
|
|
8936
|
+
}
|
|
8937
|
+
}
|
|
8938
|
+
let closed = false;
|
|
8939
|
+
const close = async () => {
|
|
8940
|
+
if (closed) return;
|
|
8941
|
+
closed = true;
|
|
8942
|
+
await Promise.all(
|
|
8943
|
+
clients2.map(async (c) => {
|
|
8944
|
+
try {
|
|
8945
|
+
await c.close();
|
|
8946
|
+
} catch {
|
|
8947
|
+
}
|
|
8948
|
+
})
|
|
8949
|
+
);
|
|
8950
|
+
};
|
|
8951
|
+
return { tools, connected, errors, close };
|
|
8952
|
+
}
|
|
8953
|
+
|
|
8954
|
+
// src/skills/task-scoped.ts
|
|
8955
|
+
init_skills();
|
|
8956
|
+
import { mkdtemp, writeFile as writeFile5, rm } from "fs/promises";
|
|
8957
|
+
import { tmpdir } from "os";
|
|
8958
|
+
import { join as join12 } from "path";
|
|
8959
|
+
function safeFileName(name, index) {
|
|
8960
|
+
const base = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
8961
|
+
return `${base || `skill-${index + 1}`}.md`;
|
|
8962
|
+
}
|
|
8963
|
+
function escapeFrontmatterValue(value) {
|
|
8964
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
8965
|
+
}
|
|
8966
|
+
function buildSkillFile(skill) {
|
|
8967
|
+
const lines = ["---"];
|
|
8968
|
+
lines.push(`name: ${escapeFrontmatterValue(skill.name)}`);
|
|
8969
|
+
lines.push(`description: ${escapeFrontmatterValue(skill.description || skill.name)}`);
|
|
8970
|
+
if (skill.alwaysApply) lines.push("alwaysApply: true");
|
|
8971
|
+
if (skill.globs && skill.globs.length > 0) {
|
|
8972
|
+
lines.push(`globs: [${skill.globs.map((g) => escapeFrontmatterValue(g)).join(", ")}]`);
|
|
8973
|
+
}
|
|
8974
|
+
lines.push("---");
|
|
8975
|
+
lines.push("");
|
|
8976
|
+
lines.push(skill.content);
|
|
8977
|
+
return lines.join("\n");
|
|
8978
|
+
}
|
|
8979
|
+
async function materializeTaskSkills(skills, taskId) {
|
|
8980
|
+
if (!skills || skills.length === 0) return null;
|
|
8981
|
+
const safeTaskId = taskId.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
8982
|
+
const dir = await mkdtemp(join12(tmpdir(), `sparkecoder-task-skills-${safeTaskId}-`));
|
|
8983
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8984
|
+
await Promise.all(
|
|
8985
|
+
skills.map(async (skill, i) => {
|
|
8986
|
+
let fileName = safeFileName(skill.name, i);
|
|
8987
|
+
while (seen.has(fileName)) fileName = `dup-${i}-${fileName}`;
|
|
8988
|
+
seen.add(fileName);
|
|
8989
|
+
await writeFile5(join12(dir, fileName), buildSkillFile(skill), "utf-8");
|
|
8990
|
+
})
|
|
8991
|
+
);
|
|
8992
|
+
const loaded2 = await loadSkillsFromDirectory(dir, { priority: 1, defaultLoadType: "on_demand" });
|
|
8993
|
+
const alwaysSkills = loaded2.filter((s) => s.alwaysApply || s.loadType === "always");
|
|
8994
|
+
const onDemand = loaded2.filter((s) => !(s.alwaysApply || s.loadType === "always"));
|
|
8995
|
+
const always = (await Promise.all(
|
|
8996
|
+
alwaysSkills.map(async (s) => {
|
|
8997
|
+
const withContent = await loadSkillContent(s.name, [dir]);
|
|
8998
|
+
return withContent ? { ...s, content: withContent.content } : null;
|
|
8999
|
+
})
|
|
9000
|
+
)).filter((s) => s !== null);
|
|
9001
|
+
let cleaned = false;
|
|
9002
|
+
const cleanup2 = async () => {
|
|
9003
|
+
if (cleaned) return;
|
|
9004
|
+
cleaned = true;
|
|
9005
|
+
try {
|
|
9006
|
+
await rm(dir, { recursive: true, force: true });
|
|
9007
|
+
} catch {
|
|
9008
|
+
}
|
|
9009
|
+
};
|
|
9010
|
+
return { dir, always, onDemand, cleanup: cleanup2 };
|
|
9011
|
+
}
|
|
9012
|
+
|
|
8210
9013
|
// src/utils/webhook.ts
|
|
8211
9014
|
var TERMINAL_EVENTS = /* @__PURE__ */ new Set([
|
|
8212
9015
|
"task.started",
|
|
@@ -8295,51 +9098,55 @@ function clearInterruptController(sessionId) {
|
|
|
8295
9098
|
if (e) e.interruptController = void 0;
|
|
8296
9099
|
}
|
|
8297
9100
|
|
|
8298
|
-
// src/
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
9101
|
+
// src/agent/index.ts
|
|
9102
|
+
init_inbox();
|
|
9103
|
+
|
|
9104
|
+
// src/utils/local-device-time.ts
|
|
9105
|
+
var LOCAL_TIME_MARKER = "[Local device time:";
|
|
9106
|
+
function formatLocalDeviceTimeLine(now = /* @__PURE__ */ new Date()) {
|
|
9107
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
9108
|
+
const formatted = now.toLocaleString("en-US", {
|
|
9109
|
+
weekday: "long",
|
|
9110
|
+
year: "numeric",
|
|
9111
|
+
month: "long",
|
|
9112
|
+
day: "numeric",
|
|
9113
|
+
hour: "numeric",
|
|
9114
|
+
minute: "2-digit",
|
|
9115
|
+
second: "2-digit",
|
|
9116
|
+
timeZoneName: "short"
|
|
9117
|
+
});
|
|
9118
|
+
return `${LOCAL_TIME_MARKER} ${formatted} (${timeZone})]`;
|
|
8309
9119
|
}
|
|
8310
|
-
function
|
|
8311
|
-
|
|
8312
|
-
e.pending.push(event);
|
|
8313
|
-
if (event.wake === "now") {
|
|
8314
|
-
scheduleFlush(orchestratorSessionId);
|
|
8315
|
-
}
|
|
9120
|
+
function hasLocalDeviceTimeLine(text) {
|
|
9121
|
+
return text.includes(LOCAL_TIME_MARKER);
|
|
8316
9122
|
}
|
|
8317
|
-
function
|
|
8318
|
-
const
|
|
8319
|
-
if (!
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
void flush(sessionId);
|
|
8323
|
-
}, FLUSH_DEBOUNCE_MS);
|
|
9123
|
+
function prependLocalDeviceTimeToUserMessage(text, now) {
|
|
9124
|
+
const trimmed = text.trim();
|
|
9125
|
+
if (!trimmed || hasLocalDeviceTimeLine(text)) return text;
|
|
9126
|
+
return `${formatLocalDeviceTimeLine(now)}
|
|
9127
|
+
${text}`;
|
|
8324
9128
|
}
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
if (e.timer) {
|
|
8329
|
-
clearTimeout(e.timer);
|
|
8330
|
-
e.timer = void 0;
|
|
9129
|
+
function prependLocalDeviceTimeToUserContent(content, now) {
|
|
9130
|
+
if (typeof content === "string") {
|
|
9131
|
+
return prependLocalDeviceTimeToUserMessage(content, now);
|
|
8331
9132
|
}
|
|
8332
|
-
const
|
|
8333
|
-
if (
|
|
8334
|
-
|
|
8335
|
-
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
8336
|
-
return;
|
|
9133
|
+
const line = formatLocalDeviceTimeLine(now);
|
|
9134
|
+
if (content.some((p) => p.type === "text" && p.text && hasLocalDeviceTimeLine(p.text))) {
|
|
9135
|
+
return content;
|
|
8337
9136
|
}
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
9137
|
+
const userIdx = content.findIndex(
|
|
9138
|
+
(p) => p.type === "text" && p.text?.includes("[USER MESSAGE]")
|
|
9139
|
+
);
|
|
9140
|
+
if (userIdx >= 0 && content[userIdx].text) {
|
|
9141
|
+
const copy = content.map((p) => ({ ...p }));
|
|
9142
|
+
copy[userIdx] = {
|
|
9143
|
+
...copy[userIdx],
|
|
9144
|
+
text: `${line}
|
|
9145
|
+
${copy[userIdx].text}`
|
|
9146
|
+
};
|
|
9147
|
+
return copy;
|
|
8342
9148
|
}
|
|
9149
|
+
return [{ type: "text", text: line }, ...content];
|
|
8343
9150
|
}
|
|
8344
9151
|
|
|
8345
9152
|
// src/agent/index.ts
|
|
@@ -8533,9 +9340,11 @@ ${prompt}` });
|
|
|
8533
9340
|
*/
|
|
8534
9341
|
async stream(options) {
|
|
8535
9342
|
const config = getConfig();
|
|
8536
|
-
const
|
|
9343
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
9344
|
+
const userContent = this.buildUserMessageContent(prompt, options.attachments);
|
|
9345
|
+
const persistedUserContent = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserContent(userContent) : userContent;
|
|
8537
9346
|
if (!options.skipSaveUserMessage) {
|
|
8538
|
-
await this.context.addUserMessage(
|
|
9347
|
+
await this.context.addUserMessage(persistedUserContent);
|
|
8539
9348
|
}
|
|
8540
9349
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
8541
9350
|
let systemPrompt = await buildSystemPrompt({
|
|
@@ -8613,7 +9422,8 @@ ${personality.trim()}
|
|
|
8613
9422
|
*/
|
|
8614
9423
|
async run(options) {
|
|
8615
9424
|
const config = getConfig();
|
|
8616
|
-
|
|
9425
|
+
const prompt = this.session.config?.role === "orchestrator" ? prependLocalDeviceTimeToUserMessage(options.prompt) : options.prompt;
|
|
9426
|
+
await this.context.addUserMessage(prompt);
|
|
8617
9427
|
const systemPrompt = await buildSystemPrompt({
|
|
8618
9428
|
workingDirectory: this.session.workingDirectory,
|
|
8619
9429
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -8657,355 +9467,387 @@ ${personality.trim()}
|
|
|
8657
9467
|
*/
|
|
8658
9468
|
async runTask(options) {
|
|
8659
9469
|
const config = getConfig();
|
|
8660
|
-
const
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
const completion = { signal: null };
|
|
8675
|
-
const onComplete = (signal) => {
|
|
8676
|
-
completion.signal = signal;
|
|
8677
|
-
};
|
|
8678
|
-
let taskRecorder = null;
|
|
8679
|
-
const sessionId = this.session.id;
|
|
8680
|
-
const emit = options.writeSSE;
|
|
8681
|
-
const bashProgressHandler = (progress) => {
|
|
8682
|
-
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
8683
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
8684
|
-
});
|
|
8685
|
-
const port = progress.browserStreamPort;
|
|
8686
|
-
if (port && progress.status === "started") {
|
|
8687
|
-
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
8688
|
-
const proxy = getOrCreateProxy2(sessionId, port);
|
|
8689
|
-
if (!taskRecorder) {
|
|
8690
|
-
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
8691
|
-
taskRecorder = new FrameRecorder2(sessionId);
|
|
8692
|
-
taskRecorder.start();
|
|
8693
|
-
});
|
|
8694
|
-
}
|
|
8695
|
-
if (proxy.listenerCount("frame") === 0) {
|
|
8696
|
-
proxy.on("frame", (frame) => {
|
|
8697
|
-
taskRecorder?.addFrame(frame);
|
|
8698
|
-
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
8699
|
-
});
|
|
8700
|
-
});
|
|
8701
|
-
proxy.on("status", (s) => {
|
|
8702
|
-
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
8703
|
-
});
|
|
8704
|
-
});
|
|
8705
|
-
}
|
|
9470
|
+
const taskScopedCleanups = [];
|
|
9471
|
+
try {
|
|
9472
|
+
const maxIterations = options.taskConfig.maxIterations ?? 50;
|
|
9473
|
+
const webhookUrl = options.taskConfig.webhookUrl;
|
|
9474
|
+
const parentTaskId = options.taskConfig.parentTaskId;
|
|
9475
|
+
const fireWebhook = (type, data) => {
|
|
9476
|
+
if (!webhookUrl) return;
|
|
9477
|
+
sendWebhook(webhookUrl, {
|
|
9478
|
+
type,
|
|
9479
|
+
taskId: this.session.id,
|
|
9480
|
+
sessionId: this.session.id,
|
|
9481
|
+
...parentTaskId ? { parentTaskId } : {},
|
|
9482
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9483
|
+
data
|
|
8706
9484
|
});
|
|
9485
|
+
};
|
|
9486
|
+
const completion = { signal: null };
|
|
9487
|
+
const onComplete = (signal) => {
|
|
9488
|
+
completion.signal = signal;
|
|
9489
|
+
};
|
|
9490
|
+
let taskMcpTools = {};
|
|
9491
|
+
if (options.mcpServers && options.mcpServers.length > 0) {
|
|
9492
|
+
const mcpConnection = await connectTaskMcpServers(options.mcpServers, { quiet: true });
|
|
9493
|
+
taskScopedCleanups.push(mcpConnection.close);
|
|
9494
|
+
taskMcpTools = mcpConnection.tools;
|
|
9495
|
+
if (mcpConnection.connected.length > 0) {
|
|
9496
|
+
console.log(`[TASK] connected ${mcpConnection.connected.length} task-scoped MCP server(s): ${mcpConnection.connected.join(", ")}`);
|
|
9497
|
+
}
|
|
9498
|
+
for (const e of mcpConnection.errors) {
|
|
9499
|
+
console.warn(`[TASK] task-scoped MCP server "${e.name}" failed to connect: ${e.error}`);
|
|
9500
|
+
if (options.writeSSE) await options.writeSSE(JSON.stringify({ type: "task-mcp-error", data: { name: e.name, error: e.error } }));
|
|
9501
|
+
}
|
|
8707
9502
|
}
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
|
|
8715
|
-
options.onToolProgress?.({ toolName: "
|
|
8716
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "
|
|
8717
|
-
});
|
|
8718
|
-
},
|
|
8719
|
-
onSearchProgress: (progress) => {
|
|
8720
|
-
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
8721
|
-
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
9503
|
+
const materializedSkills = await materializeTaskSkills(options.skills, this.session.id);
|
|
9504
|
+
if (materializedSkills) taskScopedCleanups.push(materializedSkills.cleanup);
|
|
9505
|
+
const taskSkillsDir = materializedSkills?.dir;
|
|
9506
|
+
let taskRecorder = null;
|
|
9507
|
+
const sessionId = this.session.id;
|
|
9508
|
+
const emit = options.writeSSE;
|
|
9509
|
+
const bashProgressHandler = (progress) => {
|
|
9510
|
+
options.onToolProgress?.({ toolName: "bash", data: progress });
|
|
9511
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "bash", data: progress })).catch(() => {
|
|
8722
9512
|
});
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
9513
|
+
const port = progress.browserStreamPort;
|
|
9514
|
+
if (port && progress.status === "started") {
|
|
9515
|
+
Promise.resolve().then(() => (init_stream_proxy(), stream_proxy_exports)).then(({ getOrCreateProxy: getOrCreateProxy2 }) => {
|
|
9516
|
+
const proxy = getOrCreateProxy2(sessionId, port);
|
|
9517
|
+
if (!taskRecorder) {
|
|
9518
|
+
Promise.resolve().then(() => (init_recorder(), recorder_exports)).then(({ FrameRecorder: FrameRecorder2 }) => {
|
|
9519
|
+
taskRecorder = new FrameRecorder2(sessionId);
|
|
9520
|
+
taskRecorder.start();
|
|
9521
|
+
});
|
|
9522
|
+
}
|
|
9523
|
+
if (proxy.listenerCount("frame") === 0) {
|
|
9524
|
+
proxy.on("frame", (frame) => {
|
|
9525
|
+
taskRecorder?.addFrame(frame);
|
|
9526
|
+
if (emit) emit(JSON.stringify({ type: "browser-frame", data: frame.data, metadata: frame.metadata })).catch(() => {
|
|
9527
|
+
});
|
|
9528
|
+
});
|
|
9529
|
+
proxy.on("status", (s) => {
|
|
9530
|
+
if (emit) emit(JSON.stringify({ type: "browser-status", ...s })).catch(() => {
|
|
9531
|
+
});
|
|
9532
|
+
});
|
|
9533
|
+
}
|
|
8741
9534
|
});
|
|
8742
|
-
|
|
8743
|
-
|
|
8744
|
-
|
|
8745
|
-
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
}
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
9535
|
+
}
|
|
9536
|
+
};
|
|
9537
|
+
const taskTools = await createTools({
|
|
9538
|
+
sessionId: this.session.id,
|
|
9539
|
+
workingDirectory: this.session.workingDirectory,
|
|
9540
|
+
onBashProgress: bashProgressHandler,
|
|
9541
|
+
onWriteFileProgress: (progress) => {
|
|
9542
|
+
options.onToolProgress?.({ toolName: "write_file", data: progress });
|
|
9543
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "write_file", data: progress })).catch(() => {
|
|
9544
|
+
});
|
|
9545
|
+
},
|
|
9546
|
+
onSearchProgress: (progress) => {
|
|
9547
|
+
options.onToolProgress?.({ toolName: "explore_agent", data: progress });
|
|
9548
|
+
if (emit) emit(JSON.stringify({ type: "tool-progress", toolName: "explore_agent", data: progress })).catch(() => {
|
|
9549
|
+
});
|
|
9550
|
+
},
|
|
9551
|
+
// Add the task-scoped skills temp dir (if any) so load_skill can list
|
|
9552
|
+
// and load the inline skills supplied with this task.
|
|
9553
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
9554
|
+
taskTools: {
|
|
9555
|
+
outputSchema: options.taskConfig.outputSchema,
|
|
9556
|
+
onComplete,
|
|
9557
|
+
onQuestion: async (question) => {
|
|
9558
|
+
const payload = {
|
|
9559
|
+
questionId: question.questionId,
|
|
9560
|
+
question: question.question,
|
|
9561
|
+
context: question.context,
|
|
9562
|
+
choices: question.choices,
|
|
9563
|
+
status: "pending"
|
|
9564
|
+
};
|
|
9565
|
+
const answerPromise = waitForTaskQuestionAnswer({
|
|
9566
|
+
taskId: this.session.id,
|
|
9567
|
+
questionId: question.questionId,
|
|
9568
|
+
question: question.question,
|
|
9569
|
+
context: question.context,
|
|
9570
|
+
choices: question.choices
|
|
9571
|
+
});
|
|
9572
|
+
fireWebhook("task.question", payload);
|
|
9573
|
+
if (emit) {
|
|
9574
|
+
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
9575
|
+
}
|
|
9576
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
9577
|
+
if (orchId) {
|
|
9578
|
+
pushToInbox(orchId, workerQuestionEvent(
|
|
9579
|
+
this.session.id,
|
|
9580
|
+
this.session.name || "worker",
|
|
9581
|
+
question.question,
|
|
9582
|
+
question.questionId
|
|
9583
|
+
));
|
|
9584
|
+
}
|
|
9585
|
+
const answer = await answerPromise;
|
|
9586
|
+
const answeredPayload = {
|
|
9587
|
+
questionId: question.questionId,
|
|
9588
|
+
answer: answer.answer,
|
|
9589
|
+
answeredBy: answer.answeredBy
|
|
9590
|
+
};
|
|
9591
|
+
fireWebhook("task.question_answered", answeredPayload);
|
|
9592
|
+
if (emit) {
|
|
9593
|
+
await emit(JSON.stringify({ type: "task-question-answered", data: answeredPayload }));
|
|
9594
|
+
}
|
|
9595
|
+
return answer;
|
|
8764
9596
|
}
|
|
8765
|
-
return answer;
|
|
8766
9597
|
}
|
|
9598
|
+
});
|
|
9599
|
+
for (const [name, t] of Object.entries(taskMcpTools)) {
|
|
9600
|
+
taskTools[name] = t;
|
|
8767
9601
|
}
|
|
8768
|
-
|
|
8769
|
-
|
|
8770
|
-
|
|
8771
|
-
|
|
8772
|
-
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
9602
|
+
const baseSystemPrompt = await buildSystemPrompt({
|
|
9603
|
+
workingDirectory: this.session.workingDirectory,
|
|
9604
|
+
skillsDirectories: taskSkillsDir ? [...config.resolvedSkillsDirectories, taskSkillsDir] : config.resolvedSkillsDirectories,
|
|
9605
|
+
sessionId: this.session.id,
|
|
9606
|
+
discoveredSkills: config.discoveredSkills,
|
|
9607
|
+
activeFiles: [],
|
|
9608
|
+
taskScopedSkills: materializedSkills ? { always: materializedSkills.always, onDemand: materializedSkills.onDemand } : void 0
|
|
9609
|
+
});
|
|
9610
|
+
const taskAddendum = buildTaskPromptAddendum(options.taskConfig.outputSchema);
|
|
9611
|
+
const systemPrompt = `${baseSystemPrompt}
|
|
8778
9612
|
|
|
8779
9613
|
${taskAddendum}`;
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
9614
|
+
fireWebhook("task.started", { prompt: options.prompt });
|
|
9615
|
+
if (emit) {
|
|
9616
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: options.prompt } }));
|
|
9617
|
+
}
|
|
9618
|
+
await this.context.addUserMessage(options.prompt);
|
|
9619
|
+
let iteration = 0;
|
|
9620
|
+
while (iteration < maxIterations) {
|
|
9621
|
+
iteration++;
|
|
9622
|
+
if (options.abortSignal?.aborted) {
|
|
9623
|
+
const cancelError = "Task was cancelled";
|
|
9624
|
+
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
9625
|
+
clearInterruptController(this.session.id);
|
|
9626
|
+
return { status: "failed", error: cancelError, iterations: iteration };
|
|
9627
|
+
}
|
|
9628
|
+
const pending = drainInputs(this.session.id);
|
|
9629
|
+
for (const p of pending) {
|
|
9630
|
+
const labelled = p.source === "orchestrator" ? `[message from orchestrator]
|
|
8797
9631
|
${p.text}` : p.source === "system" ? `[system note]
|
|
8798
9632
|
${p.text}` : p.text;
|
|
8799
|
-
if (emit) {
|
|
8800
|
-
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
8801
|
-
}
|
|
8802
|
-
await this.context.addUserMessage(labelled);
|
|
8803
|
-
}
|
|
8804
|
-
const interruptController = new AbortController();
|
|
8805
|
-
registerInterruptController(this.session.id, interruptController);
|
|
8806
|
-
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
8807
|
-
const messages = await this.context.getMessages();
|
|
8808
|
-
const useAnthropic = isAnthropicModel(this.session.model);
|
|
8809
|
-
if (emit) {
|
|
8810
|
-
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
8811
|
-
}
|
|
8812
|
-
let textStarted = false;
|
|
8813
|
-
let textId = `text_${Date.now()}`;
|
|
8814
|
-
let reasoningId = `reasoning_${Date.now()}`;
|
|
8815
|
-
let reasoningStarted = false;
|
|
8816
|
-
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
8817
|
-
const iterStream = streamText2({
|
|
8818
|
-
model: resolveModel(this.session.model),
|
|
8819
|
-
system: systemPrompt,
|
|
8820
|
-
messages,
|
|
8821
|
-
tools: wrapToolsNeverThrow(taskTools),
|
|
8822
|
-
stopWhen: stepCountIs2(500),
|
|
8823
|
-
abortSignal: combinedAbort,
|
|
8824
|
-
providerOptions: useAnthropic ? {
|
|
8825
|
-
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
8826
|
-
} : void 0,
|
|
8827
|
-
// See the matching note in `stream()` — repair tool pairing before
|
|
8828
|
-
// every step so we never feed the model an orphan tool-call.
|
|
8829
|
-
prepareStep: async ({ messages: stepMessages }) => {
|
|
8830
|
-
const paired = repairToolPairing(stepMessages);
|
|
8831
|
-
const ordered = ensureToolResultsFollowCalls(paired);
|
|
8832
|
-
if (ordered === stepMessages) return {};
|
|
8833
|
-
return { messages: ordered };
|
|
8834
|
-
},
|
|
8835
|
-
onStepFinish: async (step) => {
|
|
8836
|
-
options.onStepFinish?.(step);
|
|
8837
|
-
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
8838
9633
|
if (emit) {
|
|
8839
|
-
|
|
8840
|
-
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
8841
|
-
textStarted = false;
|
|
8842
|
-
textId = `text_${Date.now()}`;
|
|
8843
|
-
}
|
|
8844
|
-
await emit(JSON.stringify({ type: "finish-step" }));
|
|
9634
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
8845
9635
|
}
|
|
9636
|
+
await this.context.addUserMessage(labelled);
|
|
8846
9637
|
}
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
9638
|
+
const interruptController = new AbortController();
|
|
9639
|
+
registerInterruptController(this.session.id, interruptController);
|
|
9640
|
+
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
9641
|
+
const messages = await this.context.getMessages();
|
|
9642
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
9643
|
+
if (emit) {
|
|
9644
|
+
await emit(JSON.stringify({ type: "start", messageId: `msg_${Date.now()}` }));
|
|
9645
|
+
}
|
|
9646
|
+
let textStarted = false;
|
|
9647
|
+
let textId = `text_${Date.now()}`;
|
|
9648
|
+
let reasoningId = `reasoning_${Date.now()}`;
|
|
9649
|
+
let reasoningStarted = false;
|
|
9650
|
+
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
9651
|
+
const iterStream = streamText2({
|
|
9652
|
+
model: resolveModel(this.session.model),
|
|
9653
|
+
system: systemPrompt,
|
|
9654
|
+
messages,
|
|
9655
|
+
tools: wrapToolsNeverThrow(taskTools),
|
|
9656
|
+
stopWhen: stepCountIs2(500),
|
|
9657
|
+
abortSignal: combinedAbort,
|
|
9658
|
+
providerOptions: useAnthropic ? {
|
|
9659
|
+
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
9660
|
+
} : void 0,
|
|
9661
|
+
// See the matching note in `stream()` — repair tool pairing before
|
|
9662
|
+
// every step so we never feed the model an orphan tool-call.
|
|
9663
|
+
prepareStep: async ({ messages: stepMessages }) => {
|
|
9664
|
+
const paired = repairToolPairing(stepMessages);
|
|
9665
|
+
const ordered = ensureToolResultsFollowCalls(paired);
|
|
9666
|
+
if (ordered === stepMessages) return {};
|
|
9667
|
+
return { messages: ordered };
|
|
9668
|
+
},
|
|
9669
|
+
onStepFinish: async (step) => {
|
|
9670
|
+
options.onStepFinish?.(step);
|
|
9671
|
+
fireWebhook("task.step_finished", { iteration, text: step.text });
|
|
9672
|
+
if (emit) {
|
|
9673
|
+
if (textStarted) {
|
|
9674
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
9675
|
+
textStarted = false;
|
|
9676
|
+
textId = `text_${Date.now()}`;
|
|
9677
|
+
}
|
|
9678
|
+
await emit(JSON.stringify({ type: "finish-step" }));
|
|
8854
9679
|
}
|
|
8855
|
-
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
8856
9680
|
}
|
|
8857
|
-
}
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
if (
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
9681
|
+
});
|
|
9682
|
+
for await (const part of iterStream.fullStream) {
|
|
9683
|
+
if (part.type === "text-delta") {
|
|
9684
|
+
if (emit) {
|
|
9685
|
+
if (!textStarted) {
|
|
9686
|
+
await emit(JSON.stringify({ type: "text-start", id: textId }));
|
|
9687
|
+
textStarted = true;
|
|
9688
|
+
}
|
|
9689
|
+
await emit(JSON.stringify({ type: "text-delta", id: textId, delta: part.text }));
|
|
9690
|
+
}
|
|
9691
|
+
} else if (part.type === "reasoning-start") {
|
|
9692
|
+
if (emit) {
|
|
9693
|
+
await emit(JSON.stringify({ type: "reasoning-start", id: reasoningId }));
|
|
9694
|
+
reasoningStarted = true;
|
|
9695
|
+
}
|
|
9696
|
+
} else if (part.type === "reasoning-delta") {
|
|
9697
|
+
if (emit) {
|
|
9698
|
+
await emit(JSON.stringify({ type: "reasoning-delta", id: reasoningId, delta: part.text }));
|
|
9699
|
+
}
|
|
9700
|
+
} else if (part.type === "reasoning-end") {
|
|
9701
|
+
if (emit && reasoningStarted) {
|
|
9702
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
9703
|
+
reasoningStarted = false;
|
|
9704
|
+
reasoningId = `reasoning_${Date.now()}`;
|
|
9705
|
+
}
|
|
9706
|
+
} else if (part.type === "tool-call-streaming-start") {
|
|
9707
|
+
if (emit) {
|
|
9708
|
+
const p = part;
|
|
9709
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: p.toolCallId, toolName: p.toolName }));
|
|
9710
|
+
toolCallStarts.add(p.toolCallId);
|
|
9711
|
+
}
|
|
9712
|
+
} else if (part.type === "tool-call-delta") {
|
|
9713
|
+
if (emit) {
|
|
9714
|
+
const p = part;
|
|
9715
|
+
await emit(JSON.stringify({ type: "tool-input-delta", toolCallId: p.toolCallId, argsTextDelta: p.argsTextDelta }));
|
|
9716
|
+
}
|
|
9717
|
+
} else if (part.type === "tool-call") {
|
|
9718
|
+
if (emit) {
|
|
9719
|
+
if (!toolCallStarts.has(part.toolCallId)) {
|
|
9720
|
+
await emit(JSON.stringify({ type: "tool-input-start", toolCallId: part.toolCallId, toolName: part.toolName }));
|
|
9721
|
+
toolCallStarts.add(part.toolCallId);
|
|
9722
|
+
}
|
|
9723
|
+
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
9724
|
+
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
9725
|
+
}
|
|
9726
|
+
} else if (part.type === "tool-result") {
|
|
9727
|
+
if (emit) {
|
|
9728
|
+
await emit(JSON.stringify({ type: "tool-output-available", toolCallId: part.toolCallId, output: part.output }));
|
|
9729
|
+
}
|
|
9730
|
+
} else if (part.type === "error") {
|
|
9731
|
+
console.error("Task stream error:", part.error);
|
|
9732
|
+
if (emit) {
|
|
9733
|
+
await emit(JSON.stringify({ type: "error", errorText: String(part.error) }));
|
|
9734
|
+
}
|
|
8871
9735
|
}
|
|
8872
|
-
}
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
9736
|
+
}
|
|
9737
|
+
if (emit && textStarted) {
|
|
9738
|
+
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
9739
|
+
}
|
|
9740
|
+
if (emit && reasoningStarted) {
|
|
9741
|
+
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
9742
|
+
}
|
|
9743
|
+
const interrupted = interruptController.signal.aborted;
|
|
9744
|
+
clearInterruptController(this.session.id);
|
|
9745
|
+
const iterResponse = await iterStream.response;
|
|
9746
|
+
const responseMessages = iterResponse.messages;
|
|
9747
|
+
await this.context.addResponseMessages(responseMessages);
|
|
9748
|
+
const resultText = await iterStream.text;
|
|
9749
|
+
const resultSteps = await iterStream.steps;
|
|
9750
|
+
if (resultText) {
|
|
9751
|
+
options.onText?.(resultText);
|
|
9752
|
+
fireWebhook("task.message", { iteration, text: resultText });
|
|
9753
|
+
}
|
|
9754
|
+
for (const step of resultSteps) {
|
|
9755
|
+
if (step.toolCalls) {
|
|
9756
|
+
for (const tc of step.toolCalls) {
|
|
9757
|
+
options.onToolCall?.({ toolCallId: tc.toolCallId, toolName: tc.toolName, input: tc.input });
|
|
9758
|
+
fireWebhook("task.tool_call", { iteration, toolName: tc.toolName, toolCallId: tc.toolCallId, input: tc.input });
|
|
9759
|
+
}
|
|
8877
9760
|
}
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
9761
|
+
if (step.toolResults) {
|
|
9762
|
+
for (const tr of step.toolResults) {
|
|
9763
|
+
options.onToolResult?.({ toolCallId: tr.toolCallId, toolName: tr.toolName, output: tr.output });
|
|
9764
|
+
fireWebhook("task.tool_result", { iteration, toolName: tr.toolName, toolCallId: tr.toolCallId, output: tr.output });
|
|
9765
|
+
}
|
|
8882
9766
|
}
|
|
8883
|
-
}
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
9767
|
+
}
|
|
9768
|
+
if (completion.signal) {
|
|
9769
|
+
const sig = completion.signal;
|
|
9770
|
+
const finalStatus = sig.status;
|
|
9771
|
+
let fileUrls;
|
|
9772
|
+
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
9773
|
+
const resultObj = sig.result;
|
|
9774
|
+
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
9775
|
+
if (filePaths.length > 0) {
|
|
9776
|
+
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
8888
9777
|
}
|
|
8889
|
-
const safeInput = part.toolName === "write_file" && part.input && typeof part.input === "object" ? truncateWriteFileInput(part.input) : part.input;
|
|
8890
|
-
await emit(JSON.stringify({ type: "tool-input-available", toolCallId: part.toolCallId, toolName: part.toolName, input: safeInput }));
|
|
8891
9778
|
}
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
9779
|
+
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
9780
|
+
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
9781
|
+
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
9782
|
+
fireWebhook(eventType, {
|
|
9783
|
+
status: finalStatus,
|
|
9784
|
+
result: sig.result,
|
|
9785
|
+
error: sig.error,
|
|
9786
|
+
iterations: iteration,
|
|
9787
|
+
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
9788
|
+
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
9789
|
+
});
|
|
9790
|
+
const updatedTask2 = {
|
|
9791
|
+
...options.taskConfig,
|
|
9792
|
+
status: finalStatus,
|
|
9793
|
+
result: sig.result,
|
|
9794
|
+
error: sig.error,
|
|
9795
|
+
iterations: iteration
|
|
9796
|
+
};
|
|
9797
|
+
await sessionQueries.update(this.session.id, {
|
|
9798
|
+
config: { ...this.session.config, task: updatedTask2 }
|
|
9799
|
+
});
|
|
9800
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
9801
|
+
if (orchId) {
|
|
9802
|
+
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
9803
|
+
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
8895
9804
|
}
|
|
8896
|
-
|
|
8897
|
-
|
|
9805
|
+
return {
|
|
9806
|
+
status: finalStatus,
|
|
9807
|
+
result: sig.result,
|
|
9808
|
+
error: sig.error,
|
|
9809
|
+
iterations: iteration
|
|
9810
|
+
};
|
|
9811
|
+
}
|
|
9812
|
+
if (!interrupted) {
|
|
9813
|
+
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.";
|
|
8898
9814
|
if (emit) {
|
|
8899
|
-
await emit(JSON.stringify({ type: "
|
|
9815
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: continuationPrompt } }));
|
|
8900
9816
|
}
|
|
9817
|
+
await this.context.addUserMessage(continuationPrompt);
|
|
8901
9818
|
}
|
|
8902
9819
|
}
|
|
8903
|
-
if (emit && textStarted) {
|
|
8904
|
-
await emit(JSON.stringify({ type: "text-end", id: textId }));
|
|
8905
|
-
}
|
|
8906
|
-
if (emit && reasoningStarted) {
|
|
8907
|
-
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
8908
|
-
}
|
|
8909
|
-
const interrupted = interruptController.signal.aborted;
|
|
8910
9820
|
clearInterruptController(this.session.id);
|
|
8911
|
-
const
|
|
8912
|
-
const
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
}
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
}
|
|
8932
|
-
}
|
|
8933
|
-
}
|
|
8934
|
-
if (completion.signal) {
|
|
8935
|
-
const sig = completion.signal;
|
|
8936
|
-
const finalStatus = sig.status;
|
|
8937
|
-
let fileUrls;
|
|
8938
|
-
if (finalStatus === "completed" && sig.result && typeof sig.result === "object") {
|
|
8939
|
-
const resultObj = sig.result;
|
|
8940
|
-
const filePaths = Array.isArray(resultObj.files) ? resultObj.files : [];
|
|
8941
|
-
if (filePaths.length > 0) {
|
|
8942
|
-
fileUrls = await this.uploadTaskFiles(filePaths);
|
|
8943
|
-
}
|
|
8944
|
-
}
|
|
8945
|
-
const recordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
8946
|
-
const allFileUrls = [...fileUrls || [], ...recordingUrls];
|
|
8947
|
-
const eventType = finalStatus === "completed" ? "task.completed" : "task.failed";
|
|
8948
|
-
fireWebhook(eventType, {
|
|
8949
|
-
status: finalStatus,
|
|
8950
|
-
result: sig.result,
|
|
8951
|
-
error: sig.error,
|
|
8952
|
-
iterations: iteration,
|
|
8953
|
-
fileUrls: allFileUrls.length > 0 ? allFileUrls : void 0,
|
|
8954
|
-
browserRecordingUrls: recordingUrls.length > 0 ? recordingUrls : void 0
|
|
8955
|
-
});
|
|
8956
|
-
const updatedTask2 = {
|
|
8957
|
-
...options.taskConfig,
|
|
8958
|
-
status: finalStatus,
|
|
8959
|
-
result: sig.result,
|
|
8960
|
-
error: sig.error,
|
|
8961
|
-
iterations: iteration
|
|
8962
|
-
};
|
|
8963
|
-
await sessionQueries.update(this.session.id, {
|
|
8964
|
-
config: { ...this.session.config, task: updatedTask2 }
|
|
8965
|
-
});
|
|
8966
|
-
const orchId = this.session.config?.orchestratorSessionId;
|
|
8967
|
-
if (orchId) {
|
|
8968
|
-
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
8969
|
-
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
8970
|
-
}
|
|
8971
|
-
return {
|
|
8972
|
-
status: finalStatus,
|
|
8973
|
-
result: sig.result,
|
|
8974
|
-
error: sig.error,
|
|
8975
|
-
iterations: iteration
|
|
8976
|
-
};
|
|
9821
|
+
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
9822
|
+
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
9823
|
+
fireWebhook("task.failed", {
|
|
9824
|
+
status: "failed",
|
|
9825
|
+
error: timeoutError,
|
|
9826
|
+
iterations: iteration,
|
|
9827
|
+
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
9828
|
+
});
|
|
9829
|
+
const updatedTask = {
|
|
9830
|
+
...options.taskConfig,
|
|
9831
|
+
status: "failed",
|
|
9832
|
+
error: timeoutError,
|
|
9833
|
+
iterations: iteration
|
|
9834
|
+
};
|
|
9835
|
+
await sessionQueries.update(this.session.id, {
|
|
9836
|
+
config: { ...this.session.config, task: updatedTask }
|
|
9837
|
+
});
|
|
9838
|
+
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
9839
|
+
if (orchIdTimeout) {
|
|
9840
|
+
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
8977
9841
|
}
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
9842
|
+
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
9843
|
+
} finally {
|
|
9844
|
+
for (const cleanup2 of taskScopedCleanups) {
|
|
9845
|
+
try {
|
|
9846
|
+
await cleanup2();
|
|
9847
|
+
} catch {
|
|
8982
9848
|
}
|
|
8983
|
-
await this.context.addUserMessage(continuationPrompt);
|
|
8984
9849
|
}
|
|
8985
9850
|
}
|
|
8986
|
-
clearInterruptController(this.session.id);
|
|
8987
|
-
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
8988
|
-
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
8989
|
-
fireWebhook("task.failed", {
|
|
8990
|
-
status: "failed",
|
|
8991
|
-
error: timeoutError,
|
|
8992
|
-
iterations: iteration,
|
|
8993
|
-
browserRecordingUrls: timeoutRecordingUrls.length > 0 ? timeoutRecordingUrls : void 0
|
|
8994
|
-
});
|
|
8995
|
-
const updatedTask = {
|
|
8996
|
-
...options.taskConfig,
|
|
8997
|
-
status: "failed",
|
|
8998
|
-
error: timeoutError,
|
|
8999
|
-
iterations: iteration
|
|
9000
|
-
};
|
|
9001
|
-
await sessionQueries.update(this.session.id, {
|
|
9002
|
-
config: { ...this.session.config, task: updatedTask }
|
|
9003
|
-
});
|
|
9004
|
-
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
9005
|
-
if (orchIdTimeout) {
|
|
9006
|
-
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
9007
|
-
}
|
|
9008
|
-
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
9009
9851
|
}
|
|
9010
9852
|
/**
|
|
9011
9853
|
* Stop a task-mode browser recording, encode to MP4, upload to GCS.
|
|
@@ -9065,11 +9907,11 @@ ${p.text}` : p.text;
|
|
|
9065
9907
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
9066
9908
|
if (!isRemoteConfigured2()) return [];
|
|
9067
9909
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
9068
|
-
const { join:
|
|
9910
|
+
const { join: join14, basename: basename5 } = await import("path");
|
|
9069
9911
|
const urls = [];
|
|
9070
9912
|
for (const filePath of filePaths) {
|
|
9071
9913
|
try {
|
|
9072
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
9914
|
+
const fullPath = filePath.startsWith("/") ? filePath : join14(this.session.workingDirectory, filePath);
|
|
9073
9915
|
const fileName = basename5(fullPath);
|
|
9074
9916
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
9075
9917
|
const mimeMap = {
|
|
@@ -9131,7 +9973,7 @@ ${p.text}` : p.text;
|
|
|
9131
9973
|
description: originalTool.description || "",
|
|
9132
9974
|
inputSchema: originalTool.inputSchema || z15.object({}),
|
|
9133
9975
|
execute: async (input, toolOptions) => {
|
|
9134
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
9976
|
+
const toolCallId = toolOptions.toolCallId || nanoid9();
|
|
9135
9977
|
const execution = toolExecutionQueries.create({
|
|
9136
9978
|
sessionId: this.session.id,
|
|
9137
9979
|
toolName: name,
|