nodal-agents 0.3.10 → 0.4.2
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 +42 -18
- package/cli.js +84 -3
- package/migrations/0024_agent_jobs_dashboard_channel.sql +11 -0
- package/migrations/0025_agent_jobs_conversational.sql +9 -0
- package/migrations/0026_chat_messages.sql +19 -0
- package/migrations/0027_drop_agent_jobs_conversational.sql +6 -0
- package/migrations/0028_conversations.sql +27 -0
- package/migrations/0029_backfill_root_agent.sql +33 -0
- package/migrations/0030_agent_fallback_llm_keys.sql +6 -0
- package/migrations/0031_llm_key_capabilities.sql +7 -0
- package/migrations/0032_drop_llm_key_model_caps.sql +9 -0
- package/migrations/0033_agent_fallback_chain.sql +21 -0
- package/migrations/meta/_journal.json +244 -174
- package/package.json +1 -1
- package/runner.js +2164 -1226
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/app-path-routes-manifest.json +1 -0
- package/web/.next/build-manifest.json +2 -2
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/routes-manifest.json +6 -0
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +2 -2
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
- package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
- package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
- package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/billing/page.js +2 -2
- package/web/.next/server/app/(dashboard)/billing/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/billing/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/chat/page.js +2 -0
- package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -0
- package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -0
- package/web/.next/server/app/(dashboard)/connectors/page.js +2 -2
- package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page.js +2 -2
- package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
- package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
- package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page.js +2 -2
- package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/logs/page.js +2 -2
- package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
- package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
- package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page.js +3 -3
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/settings/page.js +2 -63
- package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
- package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error/page.js +2 -2
- package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +1 -1
- package/web/.next/server/app/_global-error.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page.js +2 -2
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/server/app/_not-found.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/api/auth/[...all]/route.js +1 -1
- package/web/.next/server/app/api/health/route.js +1 -1
- package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
- package/web/.next/server/app/api/oauth/[provider]/start/route.js +1 -1
- package/web/.next/server/app/auth/callback/route.js +1 -1
- package/web/.next/server/app/login/page.js +2 -2
- package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/onboarding/page.js +2 -2
- package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/onboarding.html +1 -1
- package/web/.next/server/app/onboarding.rsc +3 -3
- package/web/.next/server/app/onboarding.segments/_full.segment.rsc +3 -3
- package/web/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/onboarding.segments/_index.segment.rsc +3 -3
- package/web/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
- package/web/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
- package/web/.next/server/app-paths-manifest.json +1 -0
- package/web/.next/server/chunks/1945.js +1 -0
- package/web/.next/server/chunks/3233.js +1 -0
- package/web/.next/server/chunks/4574.js +1 -1
- package/web/.next/server/chunks/4808.js +1 -0
- package/web/.next/server/chunks/4839.js +1 -0
- package/web/.next/server/chunks/{6937.js → 7466.js} +1 -1
- package/web/.next/server/chunks/7741.js +3 -3
- package/web/.next/server/chunks/8206.js +1 -0
- package/web/.next/server/chunks/8398.js +1 -0
- package/web/.next/server/chunks/9606.js +62 -0
- package/web/.next/server/middleware-build-manifest.js +1 -1
- package/web/.next/server/pages/404.html +1 -1
- package/web/.next/server/pages/500.html +1 -1
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/static/ZuUX-HBTQOhLf0tFI6JQI/_buildManifest.js +1 -0
- package/web/.next/static/chunks/{9060-df7c0c4c6fa27737.js → 2575-e660568bd1a9bcb6.js} +2 -2
- package/web/.next/static/chunks/3233-e6efb7fb1fa24591.js +1 -0
- package/web/.next/static/chunks/5436-c1006a40e59853ed.js +1 -0
- package/web/.next/static/chunks/7025-7afa82fda10bddc4.js +62 -0
- package/web/.next/static/chunks/8396-f3502b9af3172006.js +1 -0
- package/web/.next/static/chunks/9098-2bfef80a73c706b3.js +1 -0
- package/web/.next/static/chunks/9123-20653d928e33410a.js +1 -0
- package/web/.next/static/chunks/{6679-7c76034b83edeb06.js → 9582-fbf7c8d9b2a39101.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-a8e293c54c818084.js +2 -0
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/{page-e6b35d5f361044a9.js → page-7a94ae67b2c3c9c3.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/page-b258b8975ac6450b.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/approvals/page-79dea6e91956eeba.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/automations/page-3b863b7af8e2c1a3.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/chat/page-4d965bb7ee3732db.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/connectors/page-4a437ba82f4086da.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-be20dcbf25c8f3ce.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/jobs/page-94a311f688a255d8.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/layout-e1b0d4fad2926646.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-e5e2c4e2b783d37f.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/mcp/page-c071c54f76273ac4.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/memories/page-8ca0b34ad35eb1fa.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/{page-fc49d7ed8e472118.js → page-fb50e1576a3ab2e4.js} +1 -1
- package/web/.next/static/chunks/app/(dashboard)/settings/page-7b256e9c462e97f8.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-12930816795e8b20.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/new/page-e3a19abaf7468db9.js +1 -0
- package/web/.next/static/chunks/app/(dashboard)/skills/page-43f1475a0bc9c45f.js +1 -0
- package/web/.next/static/css/78ead23854ab041e.css +3 -0
- package/web/.next/server/chunks/211.js +0 -1
- package/web/.next/server/chunks/5163.js +0 -1
- package/web/.next/server/chunks/6680.js +0 -1
- package/web/.next/server/chunks/8013.js +0 -1
- package/web/.next/server/chunks/9140.js +0 -1
- package/web/.next/static/chunks/1165-ec573be2aa63710b.js +0 -1
- package/web/.next/static/chunks/374-60415230f01c844a.js +0 -1
- package/web/.next/static/chunks/5070-4385fb454f6ec84b.js +0 -62
- package/web/.next/static/chunks/639-79c3c2ac769ef007.js +0 -1
- package/web/.next/static/chunks/6522-3f865de55adb618d.js +0 -1
- package/web/.next/static/chunks/921-f437093debcddbb3.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-d3724fbf38b71806.js +0 -2
- package/web/.next/static/chunks/app/(dashboard)/agents/page-b58294bf588f4581.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/approvals/page-b9e504918d043b6d.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/automations/page-4807e81e2af3030e.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/connectors/page-e12174534c2c2acf.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-40172a14d0b1368f.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/jobs/page-d4a3a16745e02fd1.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/layout-d01f5919f54fb37a.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-90fb785e2ab32759.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/mcp/page-3fa9d4448a31b696.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/memories/page-aa46f5f7efbfa262.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/settings/page-8bc69f353f8d48a3.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-0b61f21847f4c7a0.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/new/page-9de96e643c361732.js +0 -1
- package/web/.next/static/chunks/app/(dashboard)/skills/page-54c6adeb65475a1a.js +0 -1
- package/web/.next/static/css/90e1bf719df42081.css +0 -3
- package/web/.next/static/oCvSEboPKulEdUGcfE5Px/_buildManifest.js +0 -1
- /package/web/.next/static/{oCvSEboPKulEdUGcfE5Px → ZuUX-HBTQOhLf0tFI6JQI}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -7,9 +7,20 @@
|
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://www.typescriptlang.org)
|
|
9
9
|
|
|
10
|
-
Build and orchestrate AI agents on your own hardware
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Build and orchestrate a **team of AI agents** on your own hardware —
|
|
11
|
+
each with its own personality, tools, memory, and model. Talk to them in
|
|
12
|
+
the dashboard or on Telegram; they research, write files, call your
|
|
13
|
+
connectors, and delegate to each other to get the job done.
|
|
14
|
+
|
|
15
|
+
**No SaaS lock-in. No per-token markup. No cloud roundtrip.** Two commands
|
|
16
|
+
to install, runs on any machine with Node 22+ (Mac, PC, Linux), and works
|
|
17
|
+
with **any LLM** — frontier or local, paid or free.
|
|
18
|
+
|
|
19
|
+
> **Run the models others choke on.** DeepSeek V4 works out of the box —
|
|
20
|
+
> including the cheap, fast OpenRouter routes — because the runtime quietly
|
|
21
|
+
> handles its quirks (non-standard tool-call payloads) instead of failing.
|
|
22
|
+
> Mix the right model per agent: a frontier model where judgment matters, a
|
|
23
|
+
> near-free one where it doesn't.
|
|
13
24
|
|
|
14
25
|
---
|
|
15
26
|
|
|
@@ -18,9 +29,11 @@ no cloud roundtrip.** Runs on any machine with Node 22+ — Mac, PC, Linux.
|
|
|
18
29
|
| | |
|
|
19
30
|
| --- | --- |
|
|
20
31
|
| 🏠 **Local-first** | Single binary, embedded Postgres, zero cloud dependency. Your conversations, memory, and credentials stay on your machine. |
|
|
21
|
-
| 🔌 **
|
|
32
|
+
| 🔌 **Any model, per agent** | Anthropic, OpenAI, Google, Groq, Mistral, OpenRouter, or any local model (LM Studio, Ollama). Setup is just an API key per provider — **each agent picks its own model**, so you can run Claude for the orchestrator and **DeepSeek V4** for the workers. The quirks of OSS frontier models (DeepSeek's non-spec tool args, Kimi/Qwen/GLM XML tool formats) are handled automatically. |
|
|
33
|
+
| 🛟 **Provider failover** | Give an agent a backup key chain — if its provider 5xx's, times out, or hits quota mid-job, the runner fails over to the next one and keeps going (and fails loud only when the whole chain is down). |
|
|
22
34
|
| 🧠 **Memory that compounds** | Persistent facts (entity-scoped, auto-injected into every job) and chat-thread continuity (your agent remembers what it said 30 seconds ago — and what it said yesterday). |
|
|
23
|
-
| 🤝 **Orchestrators that finish** | Router and planner orchestrators delegate to specialist sub-agents
|
|
35
|
+
| 🤝 **Orchestrators that finish** | Router and planner orchestrators delegate to specialist sub-agents, then resume on completion to wrap up the answer. |
|
|
36
|
+
| 🛡️ **Agents that don't lie, loop, or die** | Generic runtime guards: a per-job token budget and a no-progress detector kill runaway loops; a no-false-success guard refuses to report "done" when an action actually failed (fail loud, never fake it); turn/chain/delegation caps bound everything. |
|
|
24
37
|
| 🔧 **Multi-instance connectors** | Gmail perso *and* Gmail boulot on the same install. OAuth *and* API-key supported. Active list + Marketplace UI in the dashboard. |
|
|
25
38
|
| 🗂️ **Workspaces** | Multiple isolated workspaces on one install (personal vs work) — each with its own agents, skills, connectors, jobs and memory. Switch from the sidebar. |
|
|
26
39
|
| 🤖 **Self-extending (ROOT agent)** | Designate an orchestrator as ROOT and let it create skills/agents and assign them on your behalf — gated by per-grant toggles and an autonomy level (propose-confirm → fully-autonomous). |
|
|
@@ -48,8 +61,8 @@ nodal-agents up
|
|
|
48
61
|
|
|
49
62
|
Open <http://localhost:3000>. The CLI spawns an embedded Postgres on a
|
|
50
63
|
free port, applies migrations, seeds the system skills, and starts the
|
|
51
|
-
runner (`:3001`) and dashboard (`:3000`).
|
|
52
|
-
|
|
64
|
+
runner (`:3001`) and dashboard (`:3000`). Connect an LLM provider from
|
|
65
|
+
**LLM Providers** in the dashboard — paste an API key and you're running.
|
|
53
66
|
|
|
54
67
|
> Requires Node 22+. No external Postgres, no Redis, no cloud config.
|
|
55
68
|
> Data lives at `~/.nodalai/` — wipe with `rm -rf ~/.nodalai`.
|
|
@@ -77,7 +90,7 @@ When a newer version is available, `nodal-agents up` also prints a one-line
|
|
|
77
90
|
notice:
|
|
78
91
|
|
|
79
92
|
```
|
|
80
|
-
ℹ v0.4.
|
|
93
|
+
ℹ v0.4.2 available — run `nodal-agents update`
|
|
81
94
|
```
|
|
82
95
|
|
|
83
96
|
### Build from source
|
|
@@ -131,7 +144,8 @@ Delegations create child jobs that resume the parent on completion.
|
|
|
131
144
|
|
|
132
145
|
| Route | Purpose |
|
|
133
146
|
| --- | --- |
|
|
134
|
-
| `/agents` | Create, edit, assign skills + connectors + MCP servers to agents. |
|
|
147
|
+
| `/agents` | Create, edit, assign skills + connectors + MCP servers to agents — and pick each agent's provider + model (with an optional failover chain). |
|
|
148
|
+
| `/llm-providers` | Connect each LLM provider with a single API key — enable/disable inline. Models are chosen per-agent, not here. |
|
|
135
149
|
| `/jobs` | Live job stream — task, agent, status, full transcript, tool I/O. |
|
|
136
150
|
| `/connectors` | Active connector instances + Marketplace (multi-instance, OAuth or API-key). |
|
|
137
151
|
| `/mcp` | Active MCP servers + Marketplace — HTTP & stdio, a growing catalogue, plus add/edit your own custom servers. |
|
|
@@ -140,7 +154,7 @@ Delegations create child jobs that resume the parent on completion.
|
|
|
140
154
|
| `/logs` | Tool-call audit — input/output JSON per call, filterable by tool name. |
|
|
141
155
|
| `/approvals` | Human-in-the-loop gates for risky tools (and the ROOT agent's meta-tools under propose-confirm). |
|
|
142
156
|
| `/automations` | Cron-scheduled agent triggers. |
|
|
143
|
-
| `/settings` |
|
|
157
|
+
| `/settings` | Auth mode, network (loopback / LAN), bot tokens, workspace management, ROOT-agent grants + autonomy. |
|
|
144
158
|
|
|
145
159
|
---
|
|
146
160
|
|
|
@@ -204,23 +218,34 @@ pnpm deps:check # runs locally and in CI before every release
|
|
|
204
218
|
|
|
205
219
|
## Status
|
|
206
220
|
|
|
207
|
-
**Current release:** `0.
|
|
221
|
+
**Current release:** `0.4.2` on npm `latest`. Used daily by the
|
|
208
222
|
maintainer, stable enough for personal production. Pre-1.0 — breaking
|
|
209
223
|
changes are still possible between minors.
|
|
210
224
|
|
|
211
225
|
### Shipped and working
|
|
212
226
|
|
|
213
|
-
- Multi-LLM provider
|
|
227
|
+
- Multi-LLM, **per-agent model selection** — provider setup is just an API key
|
|
228
|
+
(one per provider); each agent chooses its own model on top. Frontier and
|
|
229
|
+
local providers, including **DeepSeek V4** (non-spec tool-call args normalized
|
|
230
|
+
automatically), plus native tool-call parsing for Kimi K2 / Qwen3-Coder / GLM
|
|
231
|
+
- **Provider failover** — an opt-in ordered key chain per agent; on a 5xx /
|
|
232
|
+
timeout / quota mid-job the runner fails over to the next key, and fails loud
|
|
233
|
+
(`all_providers_failed`) only when the whole chain is exhausted
|
|
234
|
+
- **Reliability guards (generic, model-agnostic)** — per-job token budget +
|
|
235
|
+
no-progress detector (kill runaway loops), and a no-false-success guard that
|
|
236
|
+
refuses to complete a job as "success" when a tool action failed and was never
|
|
237
|
+
resolved (fail loud, never fake it)
|
|
214
238
|
- Persistent memory (sanitation, dedup, importance ranking, auto-injection,
|
|
215
239
|
feedback loop)
|
|
216
240
|
- Session-thread continuity on chat channels (Telegram today)
|
|
217
|
-
- Orchestrator (router + planner) with delegation chains
|
|
218
|
-
|
|
241
|
+
- Orchestrator (router + planner) with delegation chains that resume the
|
|
242
|
+
parent on completion
|
|
219
243
|
- Multi-instance connectors with OAuth (Gmail, Drive, Sheets, Docs, Notion,
|
|
220
244
|
Airtable) and API-key (Notion, Airtable, Apify, Firecrawl, Tavily)
|
|
221
245
|
- MCP catalog — Streamable HTTP *and* stdio (local subprocess) servers, API-key auth; a growing catalogue (Stripe, n8n, Supabase, Airtable, Notion…) with a "test pending" badge on entries not yet verified live, plus add *and edit* your own custom HTTP/stdio servers from the dashboard
|
|
222
246
|
- Top-level workspaces — multiple isolated entities (agents/skills/connectors/jobs/memory per workspace), switch in the sidebar
|
|
223
|
-
- ROOT
|
|
247
|
+
- In-app ROOT chat — talk to your workspace ROOT right in the dashboard: conversation-first (pure chat never creates a job — recall is free, the agent's memory is auto-loaded), multiple conversations with searchable history, and inline "dispatched to N agents" cards when it escalates a real action into a tracked job
|
|
248
|
+
- ROOT agent — your first orchestrator automatically becomes the workspace ROOT (the single top-level agent; later orchestrators slot under it). It can create *and update* skills, create agents and assign them, and create MCP servers + API connectors — each gated by per-grant toggles + an autonomy/approval level (powers start off, opt in per grant). Provisioning verifies before it writes (an MCP server is connected and its tools listed first); skill authoring is grounded in the workspace's real tools (a linter rejects skills referencing tools the agent doesn't have)
|
|
224
249
|
- Office file editing — Excel in-place edit, Word/PowerPoint create, in the agent workspace (office-editing skill)
|
|
225
250
|
- Multiple filesystem folders per agent (sandboxed `file_*` tools)
|
|
226
251
|
- Telegram delivery (long-poll, group filters, multi-agent routing,
|
|
@@ -237,9 +262,8 @@ changes are still possible between minors.
|
|
|
237
262
|
|
|
238
263
|
- **MCP OAuth flow** → unlocks Linear, Notion remote, GitHub remote,
|
|
239
264
|
Atlassian, Sentry, and the rest of the SaaS-as-MCP ecosystem.
|
|
240
|
-
- **
|
|
241
|
-
|
|
242
|
-
test-workflow meta-tool.
|
|
265
|
+
- **Dry-run mode + a test-workflow meta-tool** → preview what the ROOT would
|
|
266
|
+
do before it runs, and let it validate an automation end-to-end.
|
|
243
267
|
- **pgvector binaries bundled in the npm pack** → semantic memory search
|
|
244
268
|
active out-of-the-box. Today, installs without pgvector fall back to
|
|
245
269
|
keyword search (which works, just less smart for cross-vocabulary
|
package/cli.js
CHANGED
|
@@ -11272,7 +11272,8 @@ var init_enums = __esm({
|
|
|
11272
11272
|
"cron",
|
|
11273
11273
|
"task-board",
|
|
11274
11274
|
"slack",
|
|
11275
|
-
"discord"
|
|
11275
|
+
"discord",
|
|
11276
|
+
"dashboard"
|
|
11276
11277
|
];
|
|
11277
11278
|
JobChannelSchema = z2.enum(JOB_CHANNELS);
|
|
11278
11279
|
JOB_STATUSES = [
|
|
@@ -12170,11 +12171,27 @@ var init_root_agent = __esm({
|
|
|
12170
12171
|
createSkill: z19.boolean(),
|
|
12171
12172
|
updateSkill: z19.boolean(),
|
|
12172
12173
|
assignSkill: z19.boolean(),
|
|
12174
|
+
createMcp: z19.boolean(),
|
|
12175
|
+
createConnector: z19.boolean(),
|
|
12173
12176
|
autonomy: AutonomyLevelSchema
|
|
12174
12177
|
});
|
|
12175
12178
|
}
|
|
12176
12179
|
});
|
|
12177
12180
|
|
|
12181
|
+
// ../../packages/shared/src/connector-catalog.ts
|
|
12182
|
+
var init_connector_catalog = __esm({
|
|
12183
|
+
"../../packages/shared/src/connector-catalog.ts"() {
|
|
12184
|
+
"use strict";
|
|
12185
|
+
}
|
|
12186
|
+
});
|
|
12187
|
+
|
|
12188
|
+
// ../../packages/shared/src/model-catalog.ts
|
|
12189
|
+
var init_model_catalog = __esm({
|
|
12190
|
+
"../../packages/shared/src/model-catalog.ts"() {
|
|
12191
|
+
"use strict";
|
|
12192
|
+
}
|
|
12193
|
+
});
|
|
12194
|
+
|
|
12178
12195
|
// ../../packages/shared/src/index.ts
|
|
12179
12196
|
var init_src2 = __esm({
|
|
12180
12197
|
"../../packages/shared/src/index.ts"() {
|
|
@@ -12199,6 +12216,8 @@ var init_src2 = __esm({
|
|
|
12199
12216
|
init_operation();
|
|
12200
12217
|
init_providers();
|
|
12201
12218
|
init_root_agent();
|
|
12219
|
+
init_connector_catalog();
|
|
12220
|
+
init_model_catalog();
|
|
12202
12221
|
}
|
|
12203
12222
|
});
|
|
12204
12223
|
|
|
@@ -12353,7 +12372,6 @@ var init_llm_keys = __esm({
|
|
|
12353
12372
|
apiKeyLast4: text("api_key_last4").notNull().default(""),
|
|
12354
12373
|
baseUrl: text("base_url"),
|
|
12355
12374
|
nickname: text("nickname"),
|
|
12356
|
-
defaultModel: text("default_model"),
|
|
12357
12375
|
isActive: boolean("is_active").notNull().default(true),
|
|
12358
12376
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
12359
12377
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -12382,6 +12400,14 @@ var init_agents = __esm({
|
|
|
12382
12400
|
personality: text("personality").notNull(),
|
|
12383
12401
|
model: text("model").default("claude-sonnet-4-6-20260217"),
|
|
12384
12402
|
llmKeyId: uuid("llm_key_id").references(() => entityLlmKeys.id, { onDelete: "set null" }),
|
|
12403
|
+
// Ordered LLM-key fallback chain (Guard 2). When the primary key
|
|
12404
|
+
// (llmKeyId) exhausts retries / times out / hits quota mid-job, the runner
|
|
12405
|
+
// fails over to these in order; all-down fails loud (`all_providers_failed`).
|
|
12406
|
+
// Empty = no failover (default). Each link is a (keyId, model) pair so a
|
|
12407
|
+
// fallback runs on a CHOSEN model (empty model ⇒ that provider's catalog
|
|
12408
|
+
// default). FK integrity is enforced in the app layer; a deleted key is
|
|
12409
|
+
// skipped at resolution time.
|
|
12410
|
+
fallbackChain: jsonb("fallback_chain").$type().default(sql`'[]'::jsonb`),
|
|
12385
12411
|
active: boolean("active").default(true),
|
|
12386
12412
|
isDefault: boolean("is_default").default(false),
|
|
12387
12413
|
role: text("role").default("agent"),
|
|
@@ -12531,7 +12557,7 @@ var init_jobs = __esm({
|
|
|
12531
12557
|
),
|
|
12532
12558
|
check(
|
|
12533
12559
|
"agent_jobs_channel_check",
|
|
12534
|
-
sql`${table.channel} IN ('telegram','api','whatsapp','internal','cron','task-board','slack','discord')`
|
|
12560
|
+
sql`${table.channel} IN ('telegram','api','whatsapp','internal','cron','task-board','slack','discord','dashboard')`
|
|
12535
12561
|
)
|
|
12536
12562
|
]
|
|
12537
12563
|
);
|
|
@@ -13290,6 +13316,56 @@ var init_agent_workspaces = __esm({
|
|
|
13290
13316
|
}
|
|
13291
13317
|
});
|
|
13292
13318
|
|
|
13319
|
+
// ../../packages/db/src/schema/chat-messages.ts
|
|
13320
|
+
var conversations, chatMessages;
|
|
13321
|
+
var init_chat_messages = __esm({
|
|
13322
|
+
"../../packages/db/src/schema/chat-messages.ts"() {
|
|
13323
|
+
"use strict";
|
|
13324
|
+
init_pg_core();
|
|
13325
|
+
init_drizzle_orm();
|
|
13326
|
+
init_entities();
|
|
13327
|
+
init_agents();
|
|
13328
|
+
init_jobs();
|
|
13329
|
+
conversations = pgTable(
|
|
13330
|
+
"conversations",
|
|
13331
|
+
{
|
|
13332
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
13333
|
+
entityId: uuid("entity_id").references(() => entities.id, { onDelete: "cascade" }),
|
|
13334
|
+
// The agent this conversation is with (the ROOT, today).
|
|
13335
|
+
agentId: uuid("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
13336
|
+
title: text("title").notNull().default(""),
|
|
13337
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(),
|
|
13338
|
+
// Bumped on each new turn — drives the recency sort in the sidebar.
|
|
13339
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow()
|
|
13340
|
+
},
|
|
13341
|
+
(table) => [
|
|
13342
|
+
index("idx_conversations_entity_agent").on(table.entityId, table.agentId, table.updatedAt)
|
|
13343
|
+
]
|
|
13344
|
+
);
|
|
13345
|
+
chatMessages = pgTable(
|
|
13346
|
+
"chat_messages",
|
|
13347
|
+
{
|
|
13348
|
+
id: uuid("id").primaryKey().defaultRandom(),
|
|
13349
|
+
entityId: uuid("entity_id").references(() => entities.id, { onDelete: "cascade" }),
|
|
13350
|
+
agentId: uuid("agent_id").notNull().references(() => agents.id, { onDelete: "cascade" }),
|
|
13351
|
+
conversationId: uuid("conversation_id").references(() => conversations.id, {
|
|
13352
|
+
onDelete: "cascade"
|
|
13353
|
+
}),
|
|
13354
|
+
role: text("role").notNull(),
|
|
13355
|
+
content: text("content").notNull(),
|
|
13356
|
+
// Set when this (assistant) turn escalated into a real action job — lets the
|
|
13357
|
+
// UI render the job's dispatch/progress inline. NULL for pure conversation.
|
|
13358
|
+
jobId: uuid("job_id").references(() => agentJobs.id, { onDelete: "set null" }),
|
|
13359
|
+
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow()
|
|
13360
|
+
},
|
|
13361
|
+
(table) => [
|
|
13362
|
+
index("idx_chat_messages_conversation").on(table.conversationId, table.createdAt),
|
|
13363
|
+
check("chat_messages_role_check", sql`${table.role} IN ('user','assistant')`)
|
|
13364
|
+
]
|
|
13365
|
+
);
|
|
13366
|
+
}
|
|
13367
|
+
});
|
|
13368
|
+
|
|
13293
13369
|
// ../../packages/db/src/schema/index.ts
|
|
13294
13370
|
var schema_exports = {};
|
|
13295
13371
|
__export(schema_exports, {
|
|
@@ -13331,8 +13407,10 @@ __export(schema_exports, {
|
|
|
13331
13407
|
agents: () => agents,
|
|
13332
13408
|
approvalRequests: () => approvalRequests,
|
|
13333
13409
|
approvalRules: () => approvalRules,
|
|
13410
|
+
chatMessages: () => chatMessages,
|
|
13334
13411
|
configuratorSessions: () => configuratorSessions,
|
|
13335
13412
|
connectors: () => connectors,
|
|
13413
|
+
conversations: () => conversations,
|
|
13336
13414
|
credentials: () => credentials,
|
|
13337
13415
|
entities: () => entities,
|
|
13338
13416
|
entityLlmKeys: () => entityLlmKeys,
|
|
@@ -13373,6 +13451,7 @@ var init_schema2 = __esm({
|
|
|
13373
13451
|
init_auth();
|
|
13374
13452
|
init_agent_connector_assignments();
|
|
13375
13453
|
init_agent_workspaces();
|
|
13454
|
+
init_chat_messages();
|
|
13376
13455
|
}
|
|
13377
13456
|
});
|
|
13378
13457
|
|
|
@@ -13433,7 +13512,9 @@ var init_credentials2 = __esm({
|
|
|
13433
13512
|
var init_agents2 = __esm({
|
|
13434
13513
|
"../../packages/db/src/repos/agents.ts"() {
|
|
13435
13514
|
"use strict";
|
|
13515
|
+
init_src2();
|
|
13436
13516
|
init_agents();
|
|
13517
|
+
init_entities();
|
|
13437
13518
|
}
|
|
13438
13519
|
});
|
|
13439
13520
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- 0024_agent_jobs_dashboard_channel.sql
|
|
2
|
+
-- V4 — in-app ROOT chat (2026-05-31):
|
|
3
|
+
-- Allow 'dashboard' as an agent_jobs.channel value so the dashboard chat with
|
|
4
|
+
-- the ROOT agent reuses the same job lifecycle + thread continuity as Telegram.
|
|
5
|
+
-- Rebuild the CHECK constraint to include 'dashboard'. Idempotent.
|
|
6
|
+
|
|
7
|
+
ALTER TABLE agent_jobs DROP CONSTRAINT IF EXISTS agent_jobs_channel_check;
|
|
8
|
+
|
|
9
|
+
ALTER TABLE agent_jobs
|
|
10
|
+
ADD CONSTRAINT agent_jobs_channel_check
|
|
11
|
+
CHECK (channel IN ('telegram','api','whatsapp','internal','cron','task-board','slack','discord','dashboard'));
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- 0025_agent_jobs_conversational.sql
|
|
2
|
+
-- V4 — in-app ROOT chat, "jobless" conversation model (2026-05-31):
|
|
3
|
+
-- A chat turn that only used conversational tools (text + memory + delivery)
|
|
4
|
+
-- is conversation, not work — it belongs in /chat, never in /jobs. The runner
|
|
5
|
+
-- flags it at completion; the dashboard filters Runs/stats on this column.
|
|
6
|
+
-- Default false → all existing + non-chat jobs remain visible as Runs.
|
|
7
|
+
|
|
8
|
+
ALTER TABLE agent_jobs
|
|
9
|
+
ADD COLUMN IF NOT EXISTS conversational boolean NOT NULL DEFAULT false;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- 0026_chat_messages.sql
|
|
2
|
+
-- V4 — conversation-first in-app chat (2026-05-31):
|
|
3
|
+
-- A conversation is NOT a job. Pure chat turns (text + memory) are stored here
|
|
4
|
+
-- and never create an agent_jobs row. Only an ACTION turn escalates to a job,
|
|
5
|
+
-- whose id is linked via job_id so the UI shows progress inline.
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
8
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
9
|
+
entity_id uuid REFERENCES entities(id) ON DELETE CASCADE,
|
|
10
|
+
agent_id uuid NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
11
|
+
thread_id text NOT NULL DEFAULT 'main',
|
|
12
|
+
role text NOT NULL CHECK (role IN ('user','assistant')),
|
|
13
|
+
content text NOT NULL,
|
|
14
|
+
job_id uuid REFERENCES agent_jobs(id) ON DELETE SET NULL,
|
|
15
|
+
created_at timestamptz DEFAULT now()
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_thread
|
|
19
|
+
ON chat_messages (entity_id, agent_id, thread_id, created_at);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- 0027_drop_agent_jobs_conversational.sql
|
|
2
|
+
-- Revert the short-lived "conversational" flag (migration 0025). The in-app chat
|
|
3
|
+
-- is now conversation-first: pure chat lives in chat_messages and never creates
|
|
4
|
+
-- an agent_jobs row at all, so flagging/hiding jobs is no longer needed.
|
|
5
|
+
|
|
6
|
+
ALTER TABLE agent_jobs DROP COLUMN IF EXISTS conversational;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- 0028_conversations.sql
|
|
2
|
+
-- V4 chat — multi-conversation backbone (2026-06-01):
|
|
3
|
+
-- Group chat_messages under named `conversations` (the sidebar entries), so
|
|
4
|
+
-- the chat gets multiple threads, "+ New", recency sort, and a clean history
|
|
5
|
+
-- model. Replaces the single hardcoded thread_id on chat_messages.
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
8
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
9
|
+
entity_id uuid REFERENCES entities(id) ON DELETE CASCADE,
|
|
10
|
+
agent_id uuid NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
|
11
|
+
title text NOT NULL DEFAULT '',
|
|
12
|
+
created_at timestamptz DEFAULT now(),
|
|
13
|
+
updated_at timestamptz DEFAULT now()
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_entity_agent
|
|
17
|
+
ON conversations (entity_id, agent_id, updated_at);
|
|
18
|
+
|
|
19
|
+
ALTER TABLE chat_messages
|
|
20
|
+
ADD COLUMN IF NOT EXISTS conversation_id uuid REFERENCES conversations(id) ON DELETE CASCADE;
|
|
21
|
+
|
|
22
|
+
-- thread_id is superseded by conversation_id. Existing dev rows (if any) had
|
|
23
|
+
-- thread_id='main' and get a NULL conversation_id (orphaned, not shown).
|
|
24
|
+
ALTER TABLE chat_messages DROP COLUMN IF EXISTS thread_id;
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_conversation
|
|
27
|
+
ON chat_messages (conversation_id, created_at);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- 0029_backfill_root_agent.sql
|
|
2
|
+
-- Brique F — "ROOT = origin orchestrator" (2026-06-01).
|
|
3
|
+
--
|
|
4
|
+
-- Designate a ROOT for any entity that has at least one orchestrator but no
|
|
5
|
+
-- root_agent_id yet: the earliest-created orchestrator wins. Additive only —
|
|
6
|
+
-- entities that already have a ROOT (and its configured grants) are left
|
|
7
|
+
-- untouched, so a manually-set ROOT and its powers are preserved.
|
|
8
|
+
--
|
|
9
|
+
-- Going forward, createAgentRepo auto-designates the first orchestrator of an
|
|
10
|
+
-- entity as ROOT and forces subsequent ones under it. Grants start all-off
|
|
11
|
+
-- (opt-in powers in Settings → ROOT agent); writing them explicitly matters
|
|
12
|
+
-- because a null root_grants parses to all-on (un-gated meta-tools).
|
|
13
|
+
--
|
|
14
|
+
-- Idempotent: the `root_agent_id IS NULL` guard makes re-runs a no-op.
|
|
15
|
+
|
|
16
|
+
UPDATE entities e
|
|
17
|
+
SET
|
|
18
|
+
root_agent_id = sub.first_orch,
|
|
19
|
+
root_grants = '{"createAgent":false,"createSkill":false,"updateSkill":false,"assignSkill":false,"createMcp":false,"autonomy":"propose_confirm"}'::jsonb,
|
|
20
|
+
updated_at = now()
|
|
21
|
+
FROM (
|
|
22
|
+
SELECT a.entity_id, a.id AS first_orch
|
|
23
|
+
FROM agents a
|
|
24
|
+
WHERE a.role = 'orchestrator'
|
|
25
|
+
AND NOT EXISTS (
|
|
26
|
+
SELECT 1 FROM agents a2
|
|
27
|
+
WHERE a2.entity_id = a.entity_id
|
|
28
|
+
AND a2.role = 'orchestrator'
|
|
29
|
+
AND (a2.created_at < a.created_at OR (a2.created_at = a.created_at AND a2.id < a.id))
|
|
30
|
+
)
|
|
31
|
+
) AS sub
|
|
32
|
+
WHERE e.id = sub.entity_id
|
|
33
|
+
AND e.root_agent_id IS NULL;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Guard 2 (provider failover): ordered LLM-key fallback chain per agent.
|
|
2
|
+
-- When the agent's primary key (llm_key_id) exhausts retries / times out /
|
|
3
|
+
-- hits quota mid-job, the runner fails over to these keys in order; all-down
|
|
4
|
+
-- fails loud (`all_providers_failed`). Empty default = no failover.
|
|
5
|
+
ALTER TABLE "agents"
|
|
6
|
+
ADD COLUMN IF NOT EXISTS "fallback_llm_key_ids" uuid[] DEFAULT '{}'::uuid[];
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- T2 (per-model LLM capabilities): cache a key's tool-calling capability so the
|
|
2
|
+
-- runtime can pick the right tool_choice WITHOUT a failed first call. NULL =
|
|
3
|
+
-- unknown (runtime assumes supported; the tool_choice floor catches a wrong
|
|
4
|
+
-- guess). Set from the model catalog or the live "Test" capability probe.
|
|
5
|
+
ALTER TABLE "entity_llm_keys"
|
|
6
|
+
ADD COLUMN IF NOT EXISTS "supports_tools" boolean,
|
|
7
|
+
ADD COLUMN IF NOT EXISTS "supports_forced_tool_choice" boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Simplify LLM providers: a key is now JUST credentials (provider + API key +
|
|
2
|
+
-- base URL + active). The model is chosen per-AGENT, and tool capability is
|
|
3
|
+
-- read from the code model catalog at runtime — so these per-key columns are
|
|
4
|
+
-- dead weight. Drop them. (0030 fallback_llm_key_ids stays — failover is still a
|
|
5
|
+
-- per-key concern.)
|
|
6
|
+
ALTER TABLE "entity_llm_keys"
|
|
7
|
+
DROP COLUMN IF EXISTS "default_model",
|
|
8
|
+
DROP COLUMN IF EXISTS "supports_tools",
|
|
9
|
+
DROP COLUMN IF EXISTS "supports_forced_tool_choice";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-- Per-fallback model selection (Guard 2). The failover chain now stores
|
|
2
|
+
-- (keyId, model) pairs instead of bare key ids, so each fallback runs on a
|
|
3
|
+
-- CHOSEN model rather than its provider's catalog default. Backfill existing
|
|
4
|
+
-- id arrays with an empty model ('' ⇒ runtime uses the catalog default), then
|
|
5
|
+
-- drop the old column.
|
|
6
|
+
ALTER TABLE "agents"
|
|
7
|
+
ADD COLUMN IF NOT EXISTS "fallback_chain" jsonb DEFAULT '[]'::jsonb;
|
|
8
|
+
|
|
9
|
+
UPDATE "agents"
|
|
10
|
+
SET "fallback_chain" = COALESCE(
|
|
11
|
+
(
|
|
12
|
+
SELECT jsonb_agg(jsonb_build_object('keyId', kid::text, 'model', ''))
|
|
13
|
+
FROM unnest("fallback_llm_key_ids") AS kid
|
|
14
|
+
),
|
|
15
|
+
'[]'::jsonb
|
|
16
|
+
)
|
|
17
|
+
WHERE "fallback_llm_key_ids" IS NOT NULL
|
|
18
|
+
AND array_length("fallback_llm_key_ids", 1) > 0;
|
|
19
|
+
|
|
20
|
+
ALTER TABLE "agents"
|
|
21
|
+
DROP COLUMN IF EXISTS "fallback_llm_key_ids";
|