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.
Files changed (190) hide show
  1. package/README.md +42 -18
  2. package/cli.js +84 -3
  3. package/migrations/0024_agent_jobs_dashboard_channel.sql +11 -0
  4. package/migrations/0025_agent_jobs_conversational.sql +9 -0
  5. package/migrations/0026_chat_messages.sql +19 -0
  6. package/migrations/0027_drop_agent_jobs_conversational.sql +6 -0
  7. package/migrations/0028_conversations.sql +27 -0
  8. package/migrations/0029_backfill_root_agent.sql +33 -0
  9. package/migrations/0030_agent_fallback_llm_keys.sql +6 -0
  10. package/migrations/0031_llm_key_capabilities.sql +7 -0
  11. package/migrations/0032_drop_llm_key_model_caps.sql +9 -0
  12. package/migrations/0033_agent_fallback_chain.sql +21 -0
  13. package/migrations/meta/_journal.json +244 -174
  14. package/package.json +1 -1
  15. package/runner.js +2164 -1226
  16. package/web/.next/BUILD_ID +1 -1
  17. package/web/.next/app-path-routes-manifest.json +1 -0
  18. package/web/.next/build-manifest.json +2 -2
  19. package/web/.next/prerender-manifest.json +3 -3
  20. package/web/.next/routes-manifest.json +6 -0
  21. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
  22. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
  23. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +2 -2
  25. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
  26. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
  28. package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
  29. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  30. package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
  31. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
  32. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  33. package/web/.next/server/app/(dashboard)/automations/page.js +2 -2
  34. package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
  35. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  36. package/web/.next/server/app/(dashboard)/billing/page.js +2 -2
  37. package/web/.next/server/app/(dashboard)/billing/page.js.nft.json +1 -1
  38. package/web/.next/server/app/(dashboard)/billing/page_client-reference-manifest.js +1 -1
  39. package/web/.next/server/app/(dashboard)/chat/page.js +2 -0
  40. package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -0
  41. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -0
  42. package/web/.next/server/app/(dashboard)/connectors/page.js +2 -2
  43. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
  44. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  45. package/web/.next/server/app/(dashboard)/credentials/page.js +2 -2
  46. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
  47. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  48. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
  49. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
  50. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  51. package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
  52. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
  53. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  54. package/web/.next/server/app/(dashboard)/llm-providers/page.js +2 -2
  55. package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
  56. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  57. package/web/.next/server/app/(dashboard)/logs/page.js +2 -2
  58. package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
  59. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  60. package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
  61. package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
  62. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  63. package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
  64. package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
  65. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  66. package/web/.next/server/app/(dashboard)/page.js +3 -3
  67. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  68. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  69. package/web/.next/server/app/(dashboard)/settings/page.js +2 -63
  70. package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
  71. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  72. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +2 -2
  73. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
  74. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  75. package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
  76. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
  77. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  78. package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
  79. package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
  80. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  81. package/web/.next/server/app/_global-error/page.js +2 -2
  82. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/_global-error.html +1 -1
  84. package/web/.next/server/app/_global-error.rsc +2 -2
  85. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  86. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  87. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  88. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  89. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
  90. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  91. package/web/.next/server/app/_not-found/page.js +2 -2
  92. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  93. package/web/.next/server/app/_not-found.html +1 -1
  94. package/web/.next/server/app/_not-found.rsc +3 -3
  95. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  96. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  97. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  98. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  99. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  100. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  101. package/web/.next/server/app/api/auth/[...all]/route.js +1 -1
  102. package/web/.next/server/app/api/health/route.js +1 -1
  103. package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
  104. package/web/.next/server/app/api/oauth/[provider]/start/route.js +1 -1
  105. package/web/.next/server/app/auth/callback/route.js +1 -1
  106. package/web/.next/server/app/login/page.js +2 -2
  107. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  108. package/web/.next/server/app/onboarding/page.js +2 -2
  109. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  110. package/web/.next/server/app/onboarding.html +1 -1
  111. package/web/.next/server/app/onboarding.rsc +3 -3
  112. package/web/.next/server/app/onboarding.segments/_full.segment.rsc +3 -3
  113. package/web/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  114. package/web/.next/server/app/onboarding.segments/_index.segment.rsc +3 -3
  115. package/web/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
  116. package/web/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
  117. package/web/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  118. package/web/.next/server/app-paths-manifest.json +1 -0
  119. package/web/.next/server/chunks/1945.js +1 -0
  120. package/web/.next/server/chunks/3233.js +1 -0
  121. package/web/.next/server/chunks/4574.js +1 -1
  122. package/web/.next/server/chunks/4808.js +1 -0
  123. package/web/.next/server/chunks/4839.js +1 -0
  124. package/web/.next/server/chunks/{6937.js → 7466.js} +1 -1
  125. package/web/.next/server/chunks/7741.js +3 -3
  126. package/web/.next/server/chunks/8206.js +1 -0
  127. package/web/.next/server/chunks/8398.js +1 -0
  128. package/web/.next/server/chunks/9606.js +62 -0
  129. package/web/.next/server/middleware-build-manifest.js +1 -1
  130. package/web/.next/server/pages/404.html +1 -1
  131. package/web/.next/server/pages/500.html +1 -1
  132. package/web/.next/server/server-reference-manifest.js +1 -1
  133. package/web/.next/server/server-reference-manifest.json +1 -1
  134. package/web/.next/static/ZuUX-HBTQOhLf0tFI6JQI/_buildManifest.js +1 -0
  135. package/web/.next/static/chunks/{9060-df7c0c4c6fa27737.js → 2575-e660568bd1a9bcb6.js} +2 -2
  136. package/web/.next/static/chunks/3233-e6efb7fb1fa24591.js +1 -0
  137. package/web/.next/static/chunks/5436-c1006a40e59853ed.js +1 -0
  138. package/web/.next/static/chunks/7025-7afa82fda10bddc4.js +62 -0
  139. package/web/.next/static/chunks/8396-f3502b9af3172006.js +1 -0
  140. package/web/.next/static/chunks/9098-2bfef80a73c706b3.js +1 -0
  141. package/web/.next/static/chunks/9123-20653d928e33410a.js +1 -0
  142. package/web/.next/static/chunks/{6679-7c76034b83edeb06.js → 9582-fbf7c8d9b2a39101.js} +1 -1
  143. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-a8e293c54c818084.js +2 -0
  144. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/{page-e6b35d5f361044a9.js → page-7a94ae67b2c3c9c3.js} +1 -1
  145. package/web/.next/static/chunks/app/(dashboard)/agents/page-b258b8975ac6450b.js +1 -0
  146. package/web/.next/static/chunks/app/(dashboard)/approvals/page-79dea6e91956eeba.js +1 -0
  147. package/web/.next/static/chunks/app/(dashboard)/automations/page-3b863b7af8e2c1a3.js +1 -0
  148. package/web/.next/static/chunks/app/(dashboard)/chat/page-4d965bb7ee3732db.js +1 -0
  149. package/web/.next/static/chunks/app/(dashboard)/connectors/page-4a437ba82f4086da.js +1 -0
  150. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-be20dcbf25c8f3ce.js +1 -0
  151. package/web/.next/static/chunks/app/(dashboard)/jobs/page-94a311f688a255d8.js +1 -0
  152. package/web/.next/static/chunks/app/(dashboard)/layout-e1b0d4fad2926646.js +1 -0
  153. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-e5e2c4e2b783d37f.js +1 -0
  154. package/web/.next/static/chunks/app/(dashboard)/mcp/page-c071c54f76273ac4.js +1 -0
  155. package/web/.next/static/chunks/app/(dashboard)/memories/page-8ca0b34ad35eb1fa.js +1 -0
  156. package/web/.next/static/chunks/app/(dashboard)/{page-fc49d7ed8e472118.js → page-fb50e1576a3ab2e4.js} +1 -1
  157. package/web/.next/static/chunks/app/(dashboard)/settings/page-7b256e9c462e97f8.js +1 -0
  158. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-12930816795e8b20.js +1 -0
  159. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-e3a19abaf7468db9.js +1 -0
  160. package/web/.next/static/chunks/app/(dashboard)/skills/page-43f1475a0bc9c45f.js +1 -0
  161. package/web/.next/static/css/78ead23854ab041e.css +3 -0
  162. package/web/.next/server/chunks/211.js +0 -1
  163. package/web/.next/server/chunks/5163.js +0 -1
  164. package/web/.next/server/chunks/6680.js +0 -1
  165. package/web/.next/server/chunks/8013.js +0 -1
  166. package/web/.next/server/chunks/9140.js +0 -1
  167. package/web/.next/static/chunks/1165-ec573be2aa63710b.js +0 -1
  168. package/web/.next/static/chunks/374-60415230f01c844a.js +0 -1
  169. package/web/.next/static/chunks/5070-4385fb454f6ec84b.js +0 -62
  170. package/web/.next/static/chunks/639-79c3c2ac769ef007.js +0 -1
  171. package/web/.next/static/chunks/6522-3f865de55adb618d.js +0 -1
  172. package/web/.next/static/chunks/921-f437093debcddbb3.js +0 -1
  173. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-d3724fbf38b71806.js +0 -2
  174. package/web/.next/static/chunks/app/(dashboard)/agents/page-b58294bf588f4581.js +0 -1
  175. package/web/.next/static/chunks/app/(dashboard)/approvals/page-b9e504918d043b6d.js +0 -1
  176. package/web/.next/static/chunks/app/(dashboard)/automations/page-4807e81e2af3030e.js +0 -1
  177. package/web/.next/static/chunks/app/(dashboard)/connectors/page-e12174534c2c2acf.js +0 -1
  178. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-40172a14d0b1368f.js +0 -1
  179. package/web/.next/static/chunks/app/(dashboard)/jobs/page-d4a3a16745e02fd1.js +0 -1
  180. package/web/.next/static/chunks/app/(dashboard)/layout-d01f5919f54fb37a.js +0 -1
  181. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-90fb785e2ab32759.js +0 -1
  182. package/web/.next/static/chunks/app/(dashboard)/mcp/page-3fa9d4448a31b696.js +0 -1
  183. package/web/.next/static/chunks/app/(dashboard)/memories/page-aa46f5f7efbfa262.js +0 -1
  184. package/web/.next/static/chunks/app/(dashboard)/settings/page-8bc69f353f8d48a3.js +0 -1
  185. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-0b61f21847f4c7a0.js +0 -1
  186. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-9de96e643c361732.js +0 -1
  187. package/web/.next/static/chunks/app/(dashboard)/skills/page-54c6adeb65475a1a.js +0 -1
  188. package/web/.next/static/css/90e1bf719df42081.css +0 -3
  189. package/web/.next/static/oCvSEboPKulEdUGcfE5Px/_buildManifest.js +0 -1
  190. /package/web/.next/static/{oCvSEboPKulEdUGcfE5Px → ZuUX-HBTQOhLf0tFI6JQI}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -7,9 +7,20 @@
7
7
  [![Node](https://img.shields.io/badge/node-22%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org)
8
8
  [![TypeScript](https://img.shields.io/badge/typescript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
9
9
 
10
- Build and orchestrate AI agents on your own hardware. Multi-LLM,
11
- multi-channel, multi-connector **no SaaS lock-in, no per-token markup,
12
- no cloud roundtrip.** Runs on any machine with Node 22+ — Mac, PC, Linux.
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
- | 🔌  **LLM-agnostic** | Anthropic, OpenAI, Google, Groq, Mistral, OpenRouter, or any OpenAI-compatible local model (LM Studio, Ollama). Per-agent key, swap providers without code changes. |
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. Hard guards against runaway loops — turn caps, chain caps, per-slug delegation budgets, smart retry on side-effect-free failures. |
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`). Configure your LLM provider
52
- from **Settings → LLM Keys** in the dashboard.
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.0 available — run `nodal-agents update`
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` | LLM keys, auth mode, network (loopback / LAN), bot tokens, workspace management, ROOT-agent designation + grants/autonomy. |
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.3.10` on npm `latest`. Used daily by the
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 routing with per-agent keys
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 and anti-runaway
218
- caps
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 agentdesignate an orchestrator that can create *and update* skills, create agents and assign them, gated by per-grant toggles + an autonomy/approval level; skill authoring is grounded in the workspace's real tools (a linter rejects skills referencing tools the agent doesn't have)
247
+ - In-app ROOT chattalk 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
- - **Dashboard chat for the ROOT companion** → talk to your ROOT agent in
241
- the app, multi-turn, instead of via Telegram; plus a dry-run mode and a
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";