nodal-agents 0.1.5 → 0.1.6

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 (75) hide show
  1. package/README.md +211 -81
  2. package/cli.js +6 -5
  3. package/migrations/0018_mcp_servers_bearer_auth.sql +12 -0
  4. package/migrations/meta/_journal.json +7 -0
  5. package/package.json +1 -1
  6. package/runner.js +126 -14
  7. package/web/.next/BUILD_ID +1 -1
  8. package/web/.next/app-path-routes-manifest.json +6 -6
  9. package/web/.next/build-manifest.json +2 -2
  10. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  11. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  12. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  13. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  14. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  15. package/web/.next/server/app/(dashboard)/billing/page_client-reference-manifest.js +1 -1
  16. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  17. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  18. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  19. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  20. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  21. package/web/.next/server/app/(dashboard)/mcp/page.js +1 -1
  22. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  23. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  24. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  25. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  26. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  27. package/web/.next/server/app/(dashboard)/stats/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  29. package/web/.next/server/app/_global-error.html +1 -1
  30. package/web/.next/server/app/_global-error.rsc +1 -1
  31. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  33. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  34. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  35. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  36. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  37. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  38. package/web/.next/server/app/_not-found.html +1 -1
  39. package/web/.next/server/app/_not-found.rsc +1 -1
  40. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  41. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  43. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  46. package/web/.next/server/app/index.html +1 -1
  47. package/web/.next/server/app/index.rsc +1 -1
  48. package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  49. package/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  50. package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  51. package/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  52. package/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  53. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  54. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/onboarding.html +1 -1
  56. package/web/.next/server/app/onboarding.rsc +1 -1
  57. package/web/.next/server/app/onboarding.segments/_full.segment.rsc +1 -1
  58. package/web/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  59. package/web/.next/server/app/onboarding.segments/_index.segment.rsc +1 -1
  60. package/web/.next/server/app/onboarding.segments/_tree.segment.rsc +1 -1
  61. package/web/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
  62. package/web/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  63. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app-paths-manifest.json +6 -6
  65. package/web/.next/server/chunks/574.js +1 -1
  66. package/web/.next/server/chunks/631.js +2 -2
  67. package/web/.next/server/middleware-build-manifest.js +1 -1
  68. package/web/.next/server/pages/404.html +1 -1
  69. package/web/.next/server/pages/500.html +1 -1
  70. package/web/.next/server/server-reference-manifest.js +1 -1
  71. package/web/.next/server/server-reference-manifest.json +1 -1
  72. package/web/.next/static/chunks/app/(dashboard)/mcp/page-69f5cfd2e4f57677.js +1 -0
  73. package/web/.next/static/chunks/app/(dashboard)/mcp/page-77dd7810003d6437.js +0 -1
  74. /package/web/.next/static/{GnQaz2FhfFL1-PYQfExPD → 1HpaEqSrARPvyPllAmQtF}/_buildManifest.js +0 -0
  75. /package/web/.next/static/{GnQaz2FhfFL1-PYQfExPD → 1HpaEqSrARPvyPllAmQtF}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -1,81 +1,211 @@
1
- # Nodal-Agents
2
-
3
- Local-first AI agent platform. Self-host on Mac, PC, Linux, VPS, or NAS in 3 commands.
4
-
5
- ## Install in 30 seconds (Docker)
6
-
7
- Works on any host with Docker — Mac, PC, Linux, VPS, Synology / Unraid / TrueNAS NAS, Raspberry Pi.
8
-
9
- ```bash
10
- curl -O https://raw.githubusercontent.com/Kwintspiracy/nodal-agents/main/docker-compose.yml
11
- docker compose up -d
12
- ```
13
-
14
- Open <http://localhost:3000> — Nodal-Agents is running. Configure your LLM provider from **Settings → LLM keys** in the dashboard.
15
-
16
- Data (config, postgres, logs) lives in the `nodal-data` Docker volume. To wipe everything: `docker compose down -v`.
17
-
18
- The image is published to `ghcr.io/kwintspiracy/nodal-agents:latest` (multi-arch: amd64 + arm64). To pin a version, replace `latest` with a release tag (e.g. `v0.1.0`).
19
-
20
- ### Build the image locally
21
-
22
- If you'd rather build from source instead of pulling the published image:
23
-
24
- ```bash
25
- git clone https://github.com/Kwintspiracy/nodal-agents.git
26
- cd nodal-agents
27
- docker compose build
28
- docker compose up -d
29
- ```
30
-
31
- (Comment the `image:` line and uncomment `build: .` in `docker-compose.yml`.)
32
-
33
- ## Developer setup (monorepo)
34
-
35
- ```bash
36
- pnpm install
37
- pnpm typecheck
38
- pnpm lint
39
- pnpm test
40
- pnpm --filter nodal-agents dev # CLI in tsx watch mode
41
- ```
42
-
43
- ## Monorepo structure
44
-
45
- - `apps/web` — Next.js dashboard (UI)
46
- - `apps/runner` Agent runtime (HTTP API, job execution, cron ticker)
47
- - `apps/cli` — `nodal-agents` install + ops command
48
- - `packages/db` Drizzle schema + migrations + client (only place that imports postgres)
49
- - `packages/shared` — Zod types and constants shared across web + runner
50
- - `packages/llm` — Vercel AI SDK wrapper, multi-provider abstraction
51
- - `packages/tools` — Tool registration + execution + approval gates
52
- - `packages/memory` — Agent memory CRUD + embeddings
53
- - `packages/orchestration` — Router + Planner patterns (delegation, task board)
54
- - `packages/runner-adapters` Connectors: notion, drive, gmail, sheets, etc.
55
- - `packages/delivery` — Telegram, email, future Slack/Discord
56
- - `packages/auth` — Pluggable auth provider (local-trust default, better-auth opt-in, bearer-token for LAN)
57
-
58
- ## Architecture rules (enforced by `dependency-cruiser`)
59
-
60
- - `apps/*` may import `packages/*`, never the reverse
61
- - `apps/web` and `apps/runner` cannot import each other (DB or HTTP only)
62
- - Only `packages/db` may import postgres clients (`pg`, `postgres`, `drizzle-orm`)
63
- - `packages/runner-adapters/*` may only import from `packages/tools` and `packages/shared`
64
- - No circular dependencies
65
-
66
- Run `pnpm deps:check` locally before pushing.
67
-
68
- ## Stack
69
-
70
- - **Runtime:** Node 22+, TypeScript strict
71
- - **Monorepo:** pnpm workspaces + Turborepo
72
- - **DB:** embedded-postgres (local + Docker), Drizzle ORM, pgvector
73
- - **Validation:** Zod
74
- - **HTTP server:** Hono (runner)
75
- - **LLM:** Vercel AI SDK
76
- - **Auth:** local-trust (single-user loopback) / local-auth (better-auth, multi-user LAN) / bearer-token
77
- - **Tests:** Vitest (unit), Playwright (e2e), dependency-cruiser (architecture)
78
-
79
- ## License
80
-
81
- TBD.
1
+ # Nodal-Agents
2
+
3
+ > **Your AI agents. Your data. Your machine.**
4
+ > Self-hosted, local-first AI agent platform — install in two commands.
5
+
6
+ [![npm](https://img.shields.io/npm/v/nodal-agents?color=cb3837&label=npm&logo=npm)](https://www.npmjs.com/package/nodal-agents)
7
+ [![Node](https://img.shields.io/badge/node-22%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org)
8
+ [![TypeScript](https://img.shields.io/badge/typescript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
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.
13
+
14
+ ---
15
+
16
+ ## Why Nodal-Agents
17
+
18
+ | | |
19
+ | --- | --- |
20
+ | 🏠 &nbsp;**Local-first** | Single binary, embedded Postgres, zero cloud dependency. Your conversations, memory, and credentials stay on your machine. |
21
+ | 🔌 &nbsp;**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. |
22
+ | 🧠 &nbsp;**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
+ | 🤝 &nbsp;**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. |
24
+ | 🔧 &nbsp;**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
+ | 📡 &nbsp;**MCP support** | Connect Streamable HTTP MCP servers — per-job tool discovery, tool whitelisting, multi-instance. |
26
+ | 💬 &nbsp;**Telegram out of the box** | Long-polling, multi-agent routing (`/ask <slug>`), group-chat filters, conversation continuity, delegation gracefulness on Telegram. |
27
+ | ⚙️ &nbsp;**Real engineering** | TypeScript strict, dependency-cruiser-enforced architecture, full unit + integration suite, Playwright e2e, idempotent migrations, encryption at rest for keys. |
28
+
29
+ ---
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install -g nodal-agents
35
+ nodal-agents up
36
+ ```
37
+
38
+ Open <http://localhost:3000>. The CLI spawns an embedded Postgres on a
39
+ free port, applies migrations, seeds the system skills, and starts the
40
+ runner (`:3001`) and dashboard (`:3000`). Configure your LLM provider
41
+ from **Settings → LLM Keys** in the dashboard.
42
+
43
+ > Requires Node 22+. No external Postgres, no Redis, no cloud config.
44
+ > Data lives at `~/.nodalai/` — wipe with `rm -rf ~/.nodalai`.
45
+
46
+ To stop the stack: `nodal-agents down`.
47
+
48
+ ### Build from source
49
+
50
+ ```bash
51
+ git clone https://github.com/Kwintspiracy/nodal-agents.git
52
+ cd nodal-agents
53
+ pnpm install
54
+ pnpm --filter nodal-agents exec tsx src/index.ts --dev
55
+ ```
56
+
57
+ Dev mode runs `next dev` so the dashboard hot-reloads on file changes.
58
+
59
+ ---
60
+
61
+ ## How it works
62
+
63
+ ```
64
+ ┌─────────────┐ Telegram / ┌────────────────────────────────┐
65
+ │ Channel │ Dashboard ───▶ │ Runner (Hono) │
66
+ │ (telegram, │ POST /api ◀── │ • Job queue + executor │
67
+ │ web …) │ │ • Anti-runaway guards │
68
+ └─────────────┘ │ • Per-agent tool whitelist │
69
+ │ • Memory auto-injection │
70
+ │ • Session-thread continuity │
71
+ └─────────┬──────────┬────────────┘
72
+ │ │
73
+ ┌───────▼───┐ ┌───▼─────────────┐
74
+ │ LLM │ │ Connectors / │
75
+ │ client │ │ MCP servers │
76
+ (multi- │ │ (Gmail, Drive,
77
+ provider) │ │ Notion, Cogni
78
+ └───────────┘ │ Cortex …) │
79
+ └─────────────────┘
80
+ ```
81
+
82
+ Every agent is a row in Postgres — personality, skills, connectors,
83
+ memory budget, team assignments live in the database. The runtime is
84
+ generic: **zero hardcoded agent metadata.** Adding capabilities means
85
+ inserting rows, not editing code.
86
+
87
+ A user message via Telegram becomes an `agent_jobs` row. The runner
88
+ loads the agent's prior chat-thread history, injects relevant
89
+ persistent memories, dispatches to the LLM, executes any tool calls
90
+ emitted, and finalizes via `telegram_send_message` + `return_result`.
91
+ Delegations create child jobs that resume the parent on completion.
92
+
93
+ ---
94
+
95
+ ## Dashboard
96
+
97
+ | Route | Purpose |
98
+ | --- | --- |
99
+ | `/agents` | Create, edit, assign skills + connectors + MCP servers to agents. |
100
+ | `/jobs` | Live job stream — task, agent, status, full transcript, tool I/O. |
101
+ | `/connectors` | Active connector instances + Marketplace (multi-instance, OAuth or API-key). |
102
+ | `/mcp` | Active MCP servers + Marketplace (HTTP transport). |
103
+ | `/memories` | Persistent facts per entity — search, edit, archive. |
104
+ | `/skills` | Procedural memory blocks — shipped with the product, user-overridable. |
105
+ | `/logs` | Tool-call audit — input/output JSON per call, filterable by tool name. |
106
+ | `/approvals` | Human-in-the-loop gates for risky tools. |
107
+ | `/automations` | Cron-scheduled agent triggers. |
108
+ | `/settings` | LLM keys, auth mode, network (loopback / LAN), bot tokens. |
109
+
110
+ ---
111
+
112
+ ## Stack
113
+
114
+ | Layer | Tech |
115
+ | --- | --- |
116
+ | Runtime | Node 22+, TypeScript strict (no `any`, no `@ts-ignore` without comment) |
117
+ | Monorepo | pnpm workspaces + Turborepo |
118
+ | Database | embedded-postgres (Win / Mac / Linux), Drizzle ORM, idempotent migrations |
119
+ | Validation | Zod everywhere |
120
+ | HTTP server | Hono (runner), Next.js 16 (dashboard) |
121
+ | LLM | Vercel AI SDK — multi-provider with retry + timeout + tolerant parsing |
122
+ | Auth | local-trust (single-user loopback) / better-auth (multi-user LAN) / bearer-token |
123
+ | Encryption | AES-256-GCM at rest for API keys, master key in `~/.nodalai/secrets.key` |
124
+ | Tests | Vitest (unit + integration), Playwright (e2e), dependency-cruiser (architecture) |
125
+
126
+ ---
127
+
128
+ ## Monorepo
129
+
130
+ ```
131
+ apps/
132
+ ├── cli nodal-agents CLI: install, up, down, ops
133
+ ├── runner Hono server: job execution, cron, channel pollers
134
+ └── web Next.js dashboard
135
+
136
+ packages/
137
+ ├── db Drizzle schema + migrations + client (only postgres importer)
138
+ ├── shared Zod types + constants shared across web + runner
139
+ ├── llm Vercel AI SDK wrapper, retry, timeout, native tool-call parsers
140
+ ├── tools Tool registration + execution + approval gates
141
+ ├── memory Persistent memory CRUD + sanitation + dedup + auto-injection
142
+ ├── orchestration Router / Planner patterns, delegation, chain counters
143
+ ├── adapters Connector packages (gmail, drive, sheets, docs, notion,
144
+ │ airtable, apify, firecrawl, tavily, MCP)
145
+ ├── runner-adapters Adapter registry, agent ↔ tool wiring
146
+ ├── delivery Telegram, email
147
+ ├── auth Pluggable auth provider
148
+ ├── catalog Shipped system skills (telegram-responder, obsidian,
149
+ │ research-scope-discipline, claude-html-design)
150
+ └── secrets AES-256-GCM key vault
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Architecture rules (enforced by `dependency-cruiser`)
156
+
157
+ - `apps/*` may import `packages/*` — never the reverse.
158
+ - `apps/web` and `apps/runner` cannot import each other (DB or HTTP only).
159
+ - Only `packages/db` may import `postgres` / `drizzle-orm` / `pg`.
160
+ - `packages/runner-adapters/*` may only import from `packages/tools` and
161
+ `packages/shared`.
162
+ - No circular dependencies.
163
+
164
+ ```bash
165
+ pnpm deps:check # runs locally and in CI before every release
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Status
171
+
172
+ **Current release:** `0.1.5` on npm `latest`. Used daily by the
173
+ maintainer, stable enough for personal production. Pre-1.0 — breaking
174
+ changes are still possible between minors.
175
+
176
+ ### Shipped and working
177
+
178
+ - Multi-LLM provider routing with per-agent keys
179
+ - Persistent memory (sanitation, dedup, importance ranking, auto-injection,
180
+ feedback loop)
181
+ - Session-thread continuity on chat channels (Telegram today)
182
+ - Orchestrator (router + planner) with delegation chains and anti-runaway
183
+ caps
184
+ - Multi-instance connectors with OAuth (Gmail, Drive, Sheets, Docs, Notion,
185
+ Airtable) and API-key (Notion, Airtable, Apify, Firecrawl, Tavily)
186
+ - MCP Streamable HTTP catalog with API-key auth
187
+ - Telegram delivery (long-poll, group filters, multi-agent routing,
188
+ delegation gracefulness)
189
+ - Approval gates for risky tools
190
+ - Cron scheduling
191
+ - Encryption at rest for LLM keys + MCP keys
192
+ - Embedded Postgres distribution via npm (no external DB to install)
193
+
194
+ ### On the roadmap (genuine, not vaporware)
195
+
196
+ - **MCP OAuth flow** → unlocks Linear, Notion remote, GitHub remote,
197
+ Atlassian, Sentry, and the rest of the SaaS-as-MCP ecosystem.
198
+ - **MCP stdio transport** → unlocks the Anthropic reference servers
199
+ (filesystem, sqlite, brave-search, GitHub stdio…) and the community
200
+ catalog.
201
+ - **pgvector binaries bundled in the npm pack** → semantic memory search
202
+ active out-of-the-box. Today, installs without pgvector fall back to
203
+ keyword search (which works, just less smart for cross-vocabulary
204
+ recall).
205
+ - Data migration tools from the legacy Python stack.
206
+
207
+ ---
208
+
209
+ ## License
210
+
211
+ TBD.
package/cli.js CHANGED
@@ -12990,7 +12990,7 @@ var init_mcp = __esm({
12990
12990
  check("mcp_servers_transport_check", sql`${table.transport} IN ('http','stdio')`),
12991
12991
  check(
12992
12992
  "mcp_servers_auth_scheme_check",
12993
- sql`${table.authScheme} IN ('header','query') OR ${table.authScheme} IS NULL`
12993
+ sql`${table.authScheme} IN ('header','query','bearer') OR ${table.authScheme} IS NULL`
12994
12994
  )
12995
12995
  // Multi-instance brique (migration 0017): the (entity_id, slug) UNIQUE
12996
12996
  // index was dropped to allow multiple instances of the same MCP server
@@ -13464,10 +13464,11 @@ function buildEnvForWeb(config, databaseUrl) {
13464
13464
  // readConfig() guarantees this is set (auto-mints on first read).
13465
13465
  NEXT_SERVER_ACTIONS_ENCRYPTION_KEY: config.serverActionsKey ?? "",
13466
13466
  // The Next.js standalone server reads HOSTNAME for its listener bind
13467
- // (defaults to '0.0.0.0' when unset). Docker sets HOSTNAME to the
13468
- // container ID, which Next interprets as the hostname to bind on —
13469
- // resolving via /etc/hosts to 127.0.0.1 only, breaking port forwarding
13470
- // and the `localhost:3000` healthcheck. Pin to the loopback/lan choice.
13467
+ // (defaults to '0.0.0.0' when unset). Some host environments set
13468
+ // HOSTNAME to an arbitrary identifier (e.g. a runner ID), which Next
13469
+ // would interpret as the hostname to bind on usually resolving via
13470
+ // /etc/hosts to 127.0.0.1 only, breaking port forwarding and the
13471
+ // `localhost:3000` healthcheck. Pin to the loopback/lan choice.
13471
13472
  HOSTNAME: bind
13472
13473
  };
13473
13474
  if (config.llm) {
@@ -0,0 +1,12 @@
1
+ -- Migration 0018 — relax mcp_servers.auth_scheme to allow 'bearer'.
2
+ --
3
+ -- Brique A v3 (MCP catalog enrichment): adds the 'bearer' auth scheme so
4
+ -- catalog entries for servers expecting `Authorization: Bearer <token>`
5
+ -- (Stripe, OpenAI, etc.) can be represented and connected without a
6
+ -- bespoke per-server hack. The adapter injects the literal "Bearer "
7
+ -- prefix when this scheme is selected; authParamName is ignored.
8
+
9
+ ALTER TABLE "mcp_servers" DROP CONSTRAINT IF EXISTS "mcp_servers_auth_scheme_check";
10
+ --> statement-breakpoint
11
+ ALTER TABLE "mcp_servers" ADD CONSTRAINT "mcp_servers_auth_scheme_check"
12
+ CHECK ("auth_scheme" IN ('header','query','bearer') OR "auth_scheme" IS NULL);
@@ -127,6 +127,13 @@
127
127
  "when": 1779700000000,
128
128
  "tag": "0017_drop_mcp_server_entity_slug_unique",
129
129
  "breakpoints": true
130
+ },
131
+ {
132
+ "idx": 18,
133
+ "version": "7",
134
+ "when": 1779800000000,
135
+ "tag": "0018_mcp_servers_bearer_auth",
136
+ "breakpoints": true
130
137
  }
131
138
  ]
132
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodal-agents",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Local-first AI agent platform — self-host on Mac, PC, Linux, VPS, or NAS.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/runner.js CHANGED
@@ -1858,7 +1858,7 @@ var init_mcp = __esm({
1858
1858
  check11("mcp_servers_transport_check", sql13`${table.transport} IN ('http','stdio')`),
1859
1859
  check11(
1860
1860
  "mcp_servers_auth_scheme_check",
1861
- sql13`${table.authScheme} IN ('header','query') OR ${table.authScheme} IS NULL`
1861
+ sql13`${table.authScheme} IN ('header','query','bearer') OR ${table.authScheme} IS NULL`
1862
1862
  )
1863
1863
  // Multi-instance brique (migration 0017): the (entity_id, slug) UNIQUE
1864
1864
  // index was dropped to allow multiple instances of the same MCP server
@@ -81026,15 +81026,22 @@ function buildMcpRequest(opts) {
81026
81026
  const url2 = new URL(opts.url);
81027
81027
  const headers = {};
81028
81028
  if (opts.apiKey) {
81029
- if (!opts.authScheme || !opts.authParamName) {
81030
- throw new Error(
81031
- "buildMcpRequest: authScheme and authParamName are required when apiKey is set"
81032
- );
81029
+ if (!opts.authScheme) {
81030
+ throw new Error("buildMcpRequest: authScheme is required when apiKey is set");
81033
81031
  }
81034
- if (opts.authScheme === "query") {
81035
- url2.searchParams.set(opts.authParamName, opts.apiKey);
81032
+ if (opts.authScheme === "bearer") {
81033
+ headers["Authorization"] = `Bearer ${opts.apiKey}`;
81036
81034
  } else {
81037
- headers[opts.authParamName] = opts.apiKey;
81035
+ if (!opts.authParamName) {
81036
+ throw new Error(
81037
+ `buildMcpRequest: authParamName is required when authScheme is '${opts.authScheme}'`
81038
+ );
81039
+ }
81040
+ if (opts.authScheme === "query") {
81041
+ url2.searchParams.set(opts.authParamName, opts.apiKey);
81042
+ } else {
81043
+ headers[opts.authParamName] = opts.apiKey;
81044
+ }
81038
81045
  }
81039
81046
  }
81040
81047
  return { url: url2, headers };
@@ -81373,6 +81380,64 @@ async function generateAssignTools(parentAgentId, db) {
81373
81380
  skillMap.set(r.agentId, existing);
81374
81381
  }
81375
81382
  }
81383
+ const connectorRows = await Promise.all(
81384
+ childIds.map(
81385
+ (id) => db.select({
81386
+ agentId: agentConnectorAssignments.agentId,
81387
+ slug: connectors.slug,
81388
+ enabledOperations: agentConnectorAssignments.enabledOperations
81389
+ }).from(agentConnectorAssignments).innerJoin(connectors, eq2(connectors.id, agentConnectorAssignments.connectorId)).where(eq2(agentConnectorAssignments.agentId, id))
81390
+ )
81391
+ );
81392
+ const connectorMap = /* @__PURE__ */ new Map();
81393
+ for (const batch of connectorRows) {
81394
+ for (const r of batch) {
81395
+ const entry = ADAPTER_REGISTRY[r.slug];
81396
+ if (!entry) continue;
81397
+ const allToolNames = entry.operations.map((o) => o.slug);
81398
+ const toolNames = r.enabledOperations === null ? allToolNames : allToolNames.filter((n) => r.enabledOperations.includes(n));
81399
+ if (toolNames.length === 0) continue;
81400
+ const existing = connectorMap.get(r.agentId) ?? [];
81401
+ existing.push({ slug: r.slug, toolNames });
81402
+ connectorMap.set(r.agentId, existing);
81403
+ }
81404
+ }
81405
+ const mcpRows = await Promise.all(
81406
+ childIds.map(
81407
+ (id) => db.select({
81408
+ agentId: agentMcpServers.agentId,
81409
+ serverSlug: mcpServers.slug,
81410
+ enabledTools: agentMcpServers.enabledTools,
81411
+ availableTools: mcpServers.availableTools,
81412
+ serverActive: mcpServers.active
81413
+ }).from(agentMcpServers).innerJoin(mcpServers, eq2(mcpServers.id, agentMcpServers.mcpServerId)).where(eq2(agentMcpServers.agentId, id))
81414
+ )
81415
+ );
81416
+ const mcpMap = /* @__PURE__ */ new Map();
81417
+ for (const batch of mcpRows) {
81418
+ for (const r of batch) {
81419
+ if (r.serverActive === false) continue;
81420
+ const prefix = r.serverSlug.replace(/-/g, "_");
81421
+ const available = Array.isArray(r.availableTools) ? r.availableTools.map((t) => t && typeof t.name === "string" ? t.name : null).filter((n) => n !== null) : [];
81422
+ if (available.length === 0) continue;
81423
+ const enabled = Array.isArray(r.enabledTools) ? new Set(r.enabledTools.filter((n) => typeof n === "string")) : null;
81424
+ const kept = enabled === null ? available : available.filter((n) => enabled.has(n));
81425
+ if (kept.length === 0) continue;
81426
+ const toolNames = kept.map((n) => `${prefix}__${n}`);
81427
+ const existing = mcpMap.get(r.agentId) ?? [];
81428
+ existing.push({ slug: r.serverSlug, toolNames });
81429
+ mcpMap.set(r.agentId, existing);
81430
+ }
81431
+ }
81432
+ function formatToolsTag(subAgentId) {
81433
+ const conn = connectorMap.get(subAgentId);
81434
+ const mcp = mcpMap.get(subAgentId);
81435
+ const parts = [];
81436
+ if (conn) parts.push(...conn.map((c) => `${c.slug} (${c.toolNames.join(", ")})`));
81437
+ if (mcp) parts.push(...mcp.map((c) => `${c.slug} (${c.toolNames.join(", ")})`));
81438
+ if (parts.length === 0) return "";
81439
+ return ` Tools: ${parts.join("; ")}.`;
81440
+ }
81376
81441
  const tools = [];
81377
81442
  for (const row of rows) {
81378
81443
  const { subAgentId, instructions, agentName, agentSlug, agentRole } = row;
@@ -81380,9 +81445,10 @@ async function generateAssignTools(parentAgentId, db) {
81380
81445
  const toolName = `assign_${toolSlug}`;
81381
81446
  const skills = skillMap.get(subAgentId) ?? [];
81382
81447
  const skillsDesc = skills.length > 0 ? ` Skills: ${skills.join(", ")}.` : "";
81448
+ const toolsDesc = formatToolsTag(subAgentId);
81383
81449
  const roleNote = agentRole === "orchestrator" ? " (orchestrator \u2014 manages their own team)" : "";
81384
81450
  const instrNote = instructions ? ` Instructions: ${instructions}` : "";
81385
- const description = `Assign a task to ${agentName}${roleNote}.${skillsDesc}${instrNote}`.trim();
81451
+ const description = `Assign a task to ${agentName}${roleNote}.${skillsDesc}${toolsDesc}${instrNote}`.trim();
81386
81452
  const capturedSlug = agentSlug;
81387
81453
  const tool = {
81388
81454
  name: toolName,
@@ -81760,10 +81826,40 @@ async function buildTeamBlock(parentAgentId, db) {
81760
81826
  connectorMap.set(r.agentId, existing);
81761
81827
  }
81762
81828
  }
81829
+ const mcpRows = await Promise.all(
81830
+ childIds.map(
81831
+ (id) => db.select({
81832
+ agentId: agentMcpServers.agentId,
81833
+ serverSlug: mcpServers.slug,
81834
+ enabledTools: agentMcpServers.enabledTools,
81835
+ availableTools: mcpServers.availableTools,
81836
+ serverActive: mcpServers.active
81837
+ }).from(agentMcpServers).innerJoin(mcpServers, eq2(mcpServers.id, agentMcpServers.mcpServerId)).where(eq2(agentMcpServers.agentId, id))
81838
+ )
81839
+ );
81840
+ const mcpMap = /* @__PURE__ */ new Map();
81841
+ for (const batch of mcpRows) {
81842
+ for (const r of batch) {
81843
+ if (r.serverActive === false) continue;
81844
+ const prefix = r.serverSlug.replace(/-/g, "_");
81845
+ const available = Array.isArray(r.availableTools) ? r.availableTools.map((t) => t && typeof t.name === "string" ? t.name : null).filter((n) => n !== null) : [];
81846
+ if (available.length === 0) continue;
81847
+ const enabled = Array.isArray(r.enabledTools) ? new Set(r.enabledTools.filter((n) => typeof n === "string")) : null;
81848
+ const kept = enabled === null ? available : available.filter((n) => enabled.has(n));
81849
+ if (kept.length === 0) continue;
81850
+ const toolNames = kept.map((n) => `${prefix}__${n}`);
81851
+ const existing = mcpMap.get(r.agentId) ?? [];
81852
+ existing.push({ slug: r.serverSlug, toolNames });
81853
+ mcpMap.set(r.agentId, existing);
81854
+ }
81855
+ }
81763
81856
  function formatConnectorsTag(subAgentId) {
81764
- const list = connectorMap.get(subAgentId);
81765
- if (!list || list.length === 0) return "";
81766
- const parts = list.map((c) => `${c.slug} (${c.toolNames.join(", ")})`);
81857
+ const conn = connectorMap.get(subAgentId);
81858
+ const mcp = mcpMap.get(subAgentId);
81859
+ const parts = [];
81860
+ if (conn) parts.push(...conn.map((c) => `${c.slug} (${c.toolNames.join(", ")})`));
81861
+ if (mcp) parts.push(...mcp.map((c) => `${c.slug} (${c.toolNames.join(", ")})`));
81862
+ if (parts.length === 0) return "";
81767
81863
  return `
81768
81864
  Tools: ${parts.join("; ")}`;
81769
81865
  }
@@ -81960,7 +82056,12 @@ async function loadThreadHistory(opts) {
81960
82056
  let nextSynthId = 0;
81961
82057
  const blocks = [];
81962
82058
  for (const row of chronological) {
81963
- const assistant = extractAssistantReply(row);
82059
+ const assistant = extractAssistantReply({
82060
+ task: row.task,
82061
+ result: row.result,
82062
+ messages: row.messages,
82063
+ channel: row.channel
82064
+ });
81964
82065
  if (assistant === null) continue;
81965
82066
  const sendTool = CHANNEL_SEND_TOOL[row.channel];
81966
82067
  if (sendTool) {
@@ -82009,7 +82110,18 @@ function extractAssistantReply(row) {
82009
82110
  if (row.result !== null && row.result !== void 0 && row.result.trim() !== "") {
82010
82111
  return row.result;
82011
82112
  }
82012
- const messages = Array.isArray(row.messages) ? row.messages : [];
82113
+ const allMessages = Array.isArray(row.messages) ? row.messages : [];
82114
+ let currentStart = 0;
82115
+ for (let i = allMessages.length - 1; i >= 0; i--) {
82116
+ const m = allMessages[i];
82117
+ if (!m || m.role !== "user") continue;
82118
+ const c = m.content;
82119
+ if (typeof c === "string" && c === row.task) {
82120
+ currentStart = i;
82121
+ break;
82122
+ }
82123
+ }
82124
+ const messages = allMessages.slice(currentStart);
82013
82125
  const sendTool = CHANNEL_SEND_TOOL[row.channel];
82014
82126
  if (sendTool) {
82015
82127
  const parts = [];
@@ -1 +1 @@
1
- GnQaz2FhfFL1-PYQfExPD
1
+ 1HpaEqSrARPvyPllAmQtF
@@ -11,19 +11,19 @@
11
11
  "/page": "/",
12
12
  "/(dashboard)/billing/page": "/billing",
13
13
  "/(dashboard)/agents/[id]/edit/page": "/agents/[id]/edit",
14
- "/(dashboard)/agents/page": "/agents",
15
14
  "/(dashboard)/agents/[id]/telegram/page": "/agents/[id]/telegram",
16
- "/(dashboard)/automations/page": "/automations",
15
+ "/(dashboard)/agents/page": "/agents",
17
16
  "/(dashboard)/approvals/page": "/approvals",
17
+ "/(dashboard)/automations/page": "/automations",
18
+ "/(dashboard)/connectors/page": "/connectors",
18
19
  "/(dashboard)/credentials/page": "/credentials",
20
+ "/(dashboard)/jobs/[id]/page": "/jobs/[id]",
19
21
  "/(dashboard)/jobs/page": "/jobs",
20
- "/(dashboard)/mcp/page": "/mcp",
21
22
  "/(dashboard)/logs/page": "/logs",
23
+ "/(dashboard)/mcp/page": "/mcp",
22
24
  "/(dashboard)/memories/page": "/memories",
25
+ "/(dashboard)/settings/page": "/settings",
23
26
  "/(dashboard)/skills/[id]/edit/page": "/skills/[id]/edit",
24
27
  "/(dashboard)/skills/page": "/skills",
25
- "/(dashboard)/jobs/[id]/page": "/jobs/[id]",
26
- "/(dashboard)/settings/page": "/settings",
27
- "/(dashboard)/connectors/page": "/connectors",
28
28
  "/(dashboard)/stats/page": "/stats"
29
29
  }
@@ -4,8 +4,8 @@
4
4
  ],
5
5
  "devFiles": [],
6
6
  "lowPriorityFiles": [
7
- "static/GnQaz2FhfFL1-PYQfExPD/_buildManifest.js",
8
- "static/GnQaz2FhfFL1-PYQfExPD/_ssgManifest.js"
7
+ "static/1HpaEqSrARPvyPllAmQtF/_buildManifest.js",
8
+ "static/1HpaEqSrARPvyPllAmQtF/_ssgManifest.js"
9
9
  ],
10
10
  "rootMainFiles": [
11
11
  "static/chunks/webpack-821bd0359b891822.js",