agorai 0.5.0 → 0.6.1

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 CHANGED
@@ -75,12 +75,16 @@ Your PC / VPS
75
75
  │ │
76
76
  │ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
77
77
  │ │ Projects │ │ Convos │ │ Shared Memory │ │
78
- │ │ │ │ @mentions │ │ per-project │ │
78
+ │ │ + Tasks │ │ + Whisper │ │ + Agent Memory │ │
79
79
  │ └──────────┘ └───────────┘ └──────────────────┘ │
80
80
  │ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
81
81
  │ │ Auth │ │ Rate │ │ 4-level │ │
82
82
  │ │ (salted) │ │ limiting │ │ visibility │ │
83
83
  │ └──────────┘ └───────────┘ └──────────────────┘ │
84
+ │ ┌──────────┐ ┌───────────┐ ┌──────────────────┐ │
85
+ │ │ Capabil. │ │ Skills │ │ 35 MCP tools │ │
86
+ │ │ catalog │ │ system │ │ + SSE push │ │
87
+ │ └──────────┘ └───────────┘ └──────────────────┘ │
84
88
  │ SQLite │
85
89
  └────────────────────┬─────────────────────────────┘
86
90
  │ HTTP (MCP protocol)
@@ -94,7 +98,7 @@ Your PC / VPS
94
98
 
95
99
  Two npm packages:
96
100
 
97
- - **`agorai`** — The bridge server. Hosts projects, conversations, shared memory, auth, and 26 MCP tools over HTTP. SQLite storage, zero external services. Can also run internal agents in the same process via `--with-agent`.
101
+ - **`agorai`** — The bridge server. Hosts projects, conversations, shared memory, auth, and 35 MCP tools over HTTP. SQLite storage, zero external services. Can also run internal agents in the same process via `--with-agent`.
98
102
  - **`agorai-connect`** — Connects any agent to the bridge. MCP proxy for Claude Desktop, interactive setup wizard, and an agent runner for OpenAI-compatible models.
99
103
 
100
104
  ## Key features
@@ -122,6 +126,16 @@ npx agorai debate "Redis vs Memcached for session storage?"
122
126
 
123
127
  **Task claiming** — Create tasks with required capabilities, claim them atomically (no race conditions), complete with results. Stale claims auto-release when agents go offline. Pull model — agents discover and claim work, not push.
124
128
 
129
+ **Directed messages (whisper)** — Send private messages to specific agents with `recipients`. Only listed agents and the sender can see the message. Store-enforced — non-recipients never know the message exists. Additive to visibility: both filters apply.
130
+
131
+ **Capability discovery** — Agents register capabilities on connect. `discover_capabilities` lets agents find each other by skill (`code-review`, `analysis`, `code-execution`). The foundation for intelligent task routing.
132
+
133
+ **Skills system** — Progressive disclosure skills replace instructions. Skills have title, summary, instructions hint, full content, and supporting files. Agents receive only metadata (tier 1) on subscribe — load full content (tier 2) and files (tier 3) on demand. Target skills to specific agents by name or by type/capability selector. Tags for filtering. ~80-90% context savings.
134
+
135
+ **Agent memory** — Private per-agent scratchpad with 3 scopes: global, per-project, and per-conversation. Each agent manages its own memory — invisible to other agents. Conversation memory auto-cleans on unsubscribe.
136
+
137
+ **Message tags** — Tag messages with metadata (`review`, `urgent`, `decision`) and filter by tags or sender in `get_messages`. Structured message types (`proposal`, `decision`) enable formal conversation protocols.
138
+
125
139
  **Structured metadata** — Every message carries trusted `bridgeMetadata` (visibility, capping info, confidentiality instructions) and private `agentMetadata` (only visible to the sender). Agents can't forge bridge data.
126
140
 
127
141
  **Security** — Salted HMAC-SHA-256 API key hashing, per-agent rate limiting, input size limits on all fields, visibility-capped writes. Everything localhost by default.
@@ -142,15 +156,16 @@ docker run -v ./agorai.config.json:/app/agorai.config.json -p 3100:3100 agorai/b
142
156
 
143
157
  | Version | Focus |
144
158
  |---------|-------|
145
- | **v0.2** | **Bridge — shared workspace, visibility, auth, 26 MCP tools** |
159
+ | **v0.2** | **Bridge — shared workspace, visibility, auth, MCP tools** |
146
160
  | v0.2.x | Security hardening, Docker, npm publish, session recovery, internal agents |
147
161
  | **v0.3** | **SSE push notifications — real-time message delivery, 3-layer EventBus→Dispatcher→Client** |
148
- | **v0.4** | **Metadata overhaul — bridgeMetadata/agentMetadata, confidentiality modes, high-water marks** |
149
- | v0.4.x | Strict mode enforcement, discovery rules, access control |
150
- | v0.5 | Discover, Decide, Deliver capability catalog, task claiming, structured conversations, directed messages |
151
- | v0.6 | Full-text search, archive conversations, Sentinel AI (auto-classification, redaction) |
152
- | v0.7 | Web dashboard, human participants, A2A protocol support |
153
- | v0.8+ | Enterprise — OAuth/JWT, RBAC, audit trail |
162
+ | **v0.4** | **Metadata overhaul — bridgeMetadata/agentMetadata, confidentiality modes, access requests** |
163
+ | **v0.5** | **Discover, Decide, Deliver — 32 tools: capability catalog, task claiming, whispers, message tags, agent memory, instruction matrix, structured protocol** |
164
+ | **v0.6** | **Skills systemprogressive disclosure (3-tier), agent targeting, skill files, replaces instruction matrix. 35 tools** |
165
+ | v0.7 | Task dependencies, explicit project access control, full-text search, conversation templates |
166
+ | v0.8 | Orchestrator agent, Sentinel AI, debate engine via bridge |
167
+ | v0.9 | Web dashboard, human participants, A2A protocol support |
168
+ | v1.0+ | Enterprise — OAuth/JWT, RBAC, audit trail, SaaS |
154
169
 
155
170
  ## Positioning
156
171
 
@@ -162,6 +177,9 @@ Agorai is **not** another agent framework. It's infrastructure — the collabora
162
177
  | Protocol | MCP (open standard) | Custom | Custom | Custom |
163
178
  | Models | Any (BYOM) | OpenAI-focused | OpenAI-focused | LangChain |
164
179
  | Visibility | 4-level, store-enforced | None | None | None |
180
+ | Task claiming | Atomic, capability-based | Role assignment | None | DAG nodes |
181
+ | Agent memory | Private per-agent, 3 scopes | Shared only | Shared only | None |
182
+ | Directed messages | Whisper (recipients) | None | None | None |
165
183
  | Debate/consensus | Built-in | None | Basic | None |
166
184
  | Local-first | Yes | Cloud-centric | Cloud-centric | Cloud-centric |
167
185
 
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAC,CAAS;gBAEV,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAe3D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CA8BvD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,WAAW,UAAU;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAK7D;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IACtD,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAC,CAAS;gBAEV,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;IAiB3D,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;CA8BvD"}
@@ -38,7 +38,10 @@ export class ApiKeyAuthProvider {
38
38
  log.warn("No bridge.salt configured — API key hashes are unsalted. Set bridge.salt in agorai.config.json for better security.");
39
39
  }
40
40
  for (const entry of apiKeys) {
41
- const hash = hashApiKey(entry.key, salt);
41
+ const resolvedKey = entry.keyEnv ? process.env[entry.keyEnv] : entry.key;
42
+ if (!resolvedKey)
43
+ continue; // skip entries with missing env var
44
+ const hash = hashApiKey(resolvedKey, salt);
42
45
  this.keyMap.set(hash, entry);
43
46
  }
44
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;AAcjC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAa;IACnD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,kBAAkB;IACrB,MAAM,CAA4B;IAClC,KAAK,CAAS;IACd,IAAI,CAAU;IAEtB,YAAY,OAAuB,EAAE,KAAa,EAAE,IAAa;QAC/D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QAExB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAC;QAClI,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC5D,CAAC;QAED,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAExC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC5D,CAAC;QAED,yCAAyC;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;YAC3C,IAAI,EAAE,SAAS,CAAC,KAAK;YACrB,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,cAAc,EAAE,SAAS,CAAC,cAAc;YACxC,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAE/C,OAAO;YACL,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,SAAS,EAAE,KAAK,CAAC,IAAI;YACrB,cAAc,EAAE,KAAK,CAAC,cAAc;SACrC,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/bridge/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,MAAM,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;AAcjC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAa;IACnD,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,kBAAkB;IACrB,MAAM,CAA4B;IAClC,KAAK,CAAS;IACd,IAAI,CAAU;IAEtB,YAAY,OAAuB,EAAE,KAAa,EAAE,IAAa;QAC/D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QAExB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,qHAAqH,CAAC,CAAC;QAClI,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;YACzE,IAAI,CAAC,WAAW;gBAAE,SAAS,CAAC,oCAAoC;YAChE,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC5D,CAAC;QAED,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAExC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC5D,CAAC;QAED,yCAAyC;QACzC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;YAC3C,IAAI,EAAE,SAAS,CAAC,KAAK;YACrB,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,cAAc,EAAE,SAAS,CAAC,cAAc;YACxC,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAE/C,OAAO;YACL,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,SAAS,EAAE,KAAK,CAAC,IAAI;YACrB,cAAc,EAAE,KAAK,CAAC,cAAc;SACrC,CAAC;IACJ,CAAC;CACF"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bridge HTTP server — Streamable HTTP transport for the MCP bridge.
3
3
  *
4
- * Exposes 32 bridge tools + debate tools over HTTP.
4
+ * Exposes 35 bridge tools + debate tools over HTTP.
5
5
  * Auth is handled via API key in Authorization header.
6
6
  * Each request is authenticated before being passed to the MCP handler.
7
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAwG3C,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AA6wBD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC;IAC1E,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAqLD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,WAAW,CAAC;AAC3D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AA2G3C,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,aAAa,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AA+6BD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC;IAC1E,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAqLD"}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Bridge HTTP server — Streamable HTTP transport for the MCP bridge.
3
3
  *
4
- * Exposes 32 bridge tools + debate tools over HTTP.
4
+ * Exposes 35 bridge tools + debate tools over HTTP.
5
5
  * Auth is handled via API key in Authorization header.
6
6
  * Each request is authenticated before being passed to the MCP handler.
7
7
  */
@@ -16,7 +16,7 @@ import { fileURLToPath } from "node:url";
16
16
  import { resolve, dirname } from "node:path";
17
17
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
18
  const PKG_VERSION = JSON.parse(readFileSync(resolve(__dirname, "../../package.json"), "utf-8")).version;
19
- import { RegisterAgentSchema, ListBridgeAgentsSchema, DiscoverCapabilitiesSchema, CreateProjectSchema, ListProjectsSchema, SetMemorySchema, GetMemorySchema, DeleteMemorySchema, CreateConversationSchema, ListConversationsSchema, SubscribeSchema, UnsubscribeSchema, SendMessageSchema, GetMessagesSchema, GetStatusSchema, MarkReadSchema, ListSubscribersSchema, ListAccessRequestsSchema, RespondToAccessRequestSchema, GetMyAccessRequestsSchema, CreateTaskSchema, ListTasksSchema, ClaimTaskSchema, CompleteTaskSchema, ReleaseTaskSchema, UpdateTaskSchema, SetInstructionsSchema, ListInstructionsSchema, DeleteInstructionsSchema, SetAgentMemorySchema, GetAgentMemorySchema, DeleteAgentMemorySchema, } from "./tools.js";
19
+ import { RegisterAgentSchema, ListBridgeAgentsSchema, DiscoverCapabilitiesSchema, CreateProjectSchema, ListProjectsSchema, SetMemorySchema, GetMemorySchema, DeleteMemorySchema, CreateConversationSchema, ListConversationsSchema, SubscribeSchema, UnsubscribeSchema, SendMessageSchema, GetMessagesSchema, GetStatusSchema, MarkReadSchema, ListSubscribersSchema, ListAccessRequestsSchema, RespondToAccessRequestSchema, GetMyAccessRequestsSchema, CreateTaskSchema, ListTasksSchema, ClaimTaskSchema, CompleteTaskSchema, ReleaseTaskSchema, UpdateTaskSchema, SetSkillSchema, ListSkillsSchema, GetSkillSchema, DeleteSkillSchema, SetSkillFileSchema, GetSkillFileSchema, SetAgentMemorySchema, GetAgentMemorySchema, DeleteAgentMemorySchema, } from "./tools.js";
20
20
  const log = createLogger("bridge");
21
21
  /** Per-session context: maps transport sessionId → agent auth. */
22
22
  const sessionAuth = new Map();
@@ -98,6 +98,13 @@ function createBridgeMcpServer(store, agentId) {
98
98
  "If you try to subscribe to a conversation you don't have access to, an access request is created automatically.",
99
99
  "Subscribers of that conversation can approve or deny your request via list_access_requests + respond_to_access_request.",
100
100
  "Check your request status with get_my_access_requests.",
101
+ "",
102
+ "IMPORTANT — Skills system (progressive disclosure):",
103
+ "Skills provide behavioral instructions and context. They use 3-tier progressive disclosure to save context:",
104
+ "- Tier 1 (metadata): When you subscribe, you receive skill metadata (title, summary, instructions, tags) — NOT the full content.",
105
+ "- Tier 2 (content): Call get_skill(skill_id) to load the full content of a skill you need.",
106
+ "- Tier 3 (files): Call get_skill_file(skill_id, filename) to load supporting files attached to a skill.",
107
+ "Only load tier 2/3 when you actually need the detail. The summary and instructions fields give you enough to decide.",
101
108
  ].join("\n"),
102
109
  });
103
110
  // --- Agent tools ---
@@ -214,9 +221,20 @@ function createBridgeMcpServer(store, agentId) {
214
221
  defaultVisibility: args.default_visibility,
215
222
  createdBy: agentId,
216
223
  });
217
- // Auto-subscribe the creator
224
+ // Auto-subscribe the creator and include skills metadata
218
225
  await store.subscribe(conv.id, agentId);
219
- return { content: [{ type: "text", text: JSON.stringify(conv, null, 2) }] };
226
+ const agent = await store.getAgent(agentId);
227
+ const matchingSkills = agent
228
+ ? await store.getMatchingSkills({ name: agent.name, type: agent.type, capabilities: agent.capabilities }, conv.id)
229
+ : [];
230
+ return { content: [{ type: "text", text: JSON.stringify({
231
+ ...conv,
232
+ skills: matchingSkills.map((s) => ({
233
+ id: s.id, title: s.title, summary: s.summary,
234
+ instructions: s.instructions, tags: s.tags,
235
+ scope: s.scope, agents: s.agents, files: s.files,
236
+ })),
237
+ }, null, 2) }] };
220
238
  });
221
239
  server.tool("list_conversations", "List conversations in a project", ListConversationsSchema.shape, async (args) => {
222
240
  let conversations = await store.listConversations(args.project_id, agentId);
@@ -226,10 +244,23 @@ function createBridgeMcpServer(store, agentId) {
226
244
  return { content: [{ type: "text", text: JSON.stringify(conversations, null, 2) }] };
227
245
  });
228
246
  server.tool("subscribe", "Subscribe to a conversation. If you don't have access, an access request is created automatically — existing subscribers can approve it.", SubscribeSchema.shape, async (args) => {
229
- // Check if already subscribed
247
+ // If already subscribed, return current skills metadata (not an error)
230
248
  const alreadySubscribed = await store.isSubscribed(args.conversation_id, agentId);
231
249
  if (alreadySubscribed) {
232
- return { content: [{ type: "text", text: JSON.stringify({ error: "Already subscribed to this conversation" }) }] };
250
+ const agent = await store.getAgent(agentId);
251
+ const matchingSkills = agent
252
+ ? await store.getMatchingSkills({ name: agent.name, type: agent.type, capabilities: agent.capabilities }, args.conversation_id)
253
+ : [];
254
+ return { content: [{ type: "text", text: JSON.stringify({
255
+ subscribed: true,
256
+ already_subscribed: true,
257
+ conversation_id: args.conversation_id,
258
+ skills: matchingSkills.map((s) => ({
259
+ id: s.id, title: s.title, summary: s.summary,
260
+ instructions: s.instructions, tags: s.tags,
261
+ scope: s.scope, agents: s.agents, files: s.files,
262
+ })),
263
+ }) }] };
233
264
  }
234
265
  // Verify conversation exists and caller can access its project
235
266
  const conv = await store.getConversation(args.conversation_id);
@@ -241,18 +272,23 @@ function createBridgeMcpServer(store, agentId) {
241
272
  await store.subscribe(args.conversation_id, agentId, {
242
273
  historyAccess: args.history_access,
243
274
  });
244
- // Include matching instructions in subscribe response
275
+ // Include matching skill metadata in subscribe response (progressive disclosure: tier 1 only)
245
276
  const agent = await store.getAgent(agentId);
246
- const matchingInstructions = agent
247
- ? await store.getMatchingInstructions({ type: agent.type, capabilities: agent.capabilities }, args.conversation_id)
277
+ const matchingSkills = agent
278
+ ? await store.getMatchingSkills({ name: agent.name, type: agent.type, capabilities: agent.capabilities }, args.conversation_id)
248
279
  : [];
249
280
  return { content: [{ type: "text", text: JSON.stringify({
250
281
  subscribed: true,
251
282
  conversation_id: args.conversation_id,
252
- instructions: matchingInstructions.map((i) => ({
253
- scope: i.scope,
254
- selector: i.selector,
255
- content: i.content,
283
+ skills: matchingSkills.map((s) => ({
284
+ id: s.id,
285
+ title: s.title,
286
+ summary: s.summary,
287
+ instructions: s.instructions,
288
+ tags: s.tags,
289
+ scope: s.scope,
290
+ agents: s.agents,
291
+ files: s.files,
256
292
  })),
257
293
  }) }] };
258
294
  }
@@ -411,48 +447,71 @@ function createBridgeMcpServer(store, agentId) {
411
447
  }
412
448
  return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
413
449
  });
414
- // --- Instruction tools ---
415
- server.tool("set_instructions", "Set instructions for agents in a scope. Only the project/conversation creator can set instructions for their scope. Use selector to target specific agent types or capabilities. Omit selector for instructions that apply to all agents.", SetInstructionsSchema.shape, async (args) => {
450
+ // --- Skill tools ---
451
+ server.tool("set_skill", "Create or update a skill in a scope. Creator can always create/edit. Listed agents (in agents[]) can edit existing skills. Use selector and agents[] to target specific agents.", SetSkillSchema.shape, async (args) => {
416
452
  let scope = "bridge";
417
453
  let scopeId;
418
454
  if (args.conversation_id) {
419
455
  scope = "conversation";
420
456
  scopeId = args.conversation_id;
421
- // Verify caller created the conversation
422
457
  const conv = await store.getConversation(args.conversation_id);
423
- if (!conv || conv.createdBy !== agentId) {
424
- return { content: [{ type: "text", text: JSON.stringify({ error: "Only the conversation creator can set instructions" }) }] };
458
+ if (!conv)
459
+ return ACCESS_DENIED;
460
+ // Creator can always set. Listed agents can edit existing.
461
+ if (conv.createdBy !== agentId) {
462
+ // Check if caller is a listed agent on an existing skill with this title
463
+ const existing = (await store.listSkills("conversation", args.conversation_id)).find((s) => s.title === args.title);
464
+ if (!existing) {
465
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Only the conversation creator can create new skills" }) }] };
466
+ }
467
+ const agent = await store.getAgent(agentId);
468
+ const agentName = agent?.name ?? "";
469
+ if (!existing.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
470
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can edit this skill" }) }] };
471
+ }
425
472
  }
426
473
  }
427
474
  else if (args.project_id) {
428
475
  scope = "project";
429
476
  scopeId = args.project_id;
430
- // Verify caller created the project
431
477
  const project = await store.getProject(args.project_id, agentId);
432
- if (!project || project.createdBy !== agentId) {
433
- return { content: [{ type: "text", text: JSON.stringify({ error: "Only the project creator can set instructions" }) }] };
478
+ if (!project)
479
+ return ACCESS_DENIED;
480
+ if (project.createdBy !== agentId) {
481
+ const existing = (await store.listSkills("project", args.project_id)).find((s) => s.title === args.title);
482
+ if (!existing) {
483
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Only the project creator can create new skills" }) }] };
484
+ }
485
+ const agent = await store.getAgent(agentId);
486
+ const agentName = agent?.name ?? "";
487
+ if (!existing.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
488
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can edit this skill" }) }] };
489
+ }
434
490
  }
435
491
  }
436
492
  else {
437
- // Bridge scope not settable via MCP (future admin dashboard)
438
- return { content: [{ type: "text", text: JSON.stringify({ error: "Bridge-level instructions cannot be set via MCP. Provide project_id or conversation_id." }) }] };
493
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Bridge-level skills cannot be set via MCP. Provide project_id or conversation_id." }) }] };
439
494
  }
440
- const instr = await store.setInstruction({
495
+ const skill = await store.setSkill({
441
496
  scope,
442
497
  scopeId,
498
+ title: args.title,
499
+ summary: args.summary,
500
+ instructions: args.instructions,
443
501
  selector: args.selector,
502
+ agents: args.agents,
503
+ tags: args.tags,
444
504
  content: args.content,
445
505
  createdBy: agentId,
446
506
  });
447
- return { content: [{ type: "text", text: JSON.stringify(instr, null, 2) }] };
507
+ return { content: [{ type: "text", text: JSON.stringify(skill, null, 2) }] };
448
508
  });
449
- server.tool("list_instructions", "List instructions for a scope. No params = bridge-level. With project_id = project-level. With conversation_id = conversation-level.", ListInstructionsSchema.shape, async (args) => {
509
+ server.tool("list_skills", "List skills for a scope (returns metadata only — no content). No params = bridge-level. With project_id = project-level. With conversation_id = conversation-level.", ListSkillsSchema.shape, async (args) => {
450
510
  let scope = "bridge";
451
511
  let scopeId;
452
512
  if (args.conversation_id) {
453
513
  scope = "conversation";
454
514
  scopeId = args.conversation_id;
455
- // Verify subscribed
456
515
  const subscribed = await store.isSubscribed(args.conversation_id, agentId);
457
516
  if (!subscribed)
458
517
  return ACCESS_DENIED;
@@ -464,13 +523,86 @@ function createBridgeMcpServer(store, agentId) {
464
523
  if (!project)
465
524
  return ACCESS_DENIED;
466
525
  }
467
- const instructions = await store.listInstructions(scope, scopeId);
468
- return { content: [{ type: "text", text: JSON.stringify(instructions, null, 2) }] };
526
+ const skills = await store.listSkills(scope, scopeId, { tags: args.tags });
527
+ // Filter by agent visibility
528
+ const agent = await store.getAgent(agentId);
529
+ const agentName = agent?.name ?? "";
530
+ const visible = skills.filter((s) => {
531
+ if (s.agents.length === 0)
532
+ return true;
533
+ return s.agents.some((a) => a.toLowerCase() === agentName.toLowerCase());
534
+ });
535
+ // Return metadata only (progressive disclosure tier 1)
536
+ const metadata = visible.map(({ content: _, ...rest }) => rest);
537
+ return { content: [{ type: "text", text: JSON.stringify(metadata, null, 2) }] };
469
538
  });
470
- server.tool("delete_instructions", "Delete an instruction by ID. Only the creator can delete.", DeleteInstructionsSchema.shape, async (args) => {
471
- const deleted = await store.deleteInstruction(args.instruction_id, agentId);
539
+ server.tool("get_skill", "Get a skill's full content by ID (progressive disclosure tier 2). Returns content + file list.", GetSkillSchema.shape, async (args) => {
540
+ const skill = await store.getSkill(args.skill_id);
541
+ if (!skill)
542
+ return ACCESS_DENIED;
543
+ // Check agent visibility (agents[] + selector)
544
+ const agent = await store.getAgent(agentId);
545
+ if (agent && skill.agents.length > 0) {
546
+ if (!skill.agents.some((a) => a.toLowerCase() === agent.name.toLowerCase())) {
547
+ return ACCESS_DENIED;
548
+ }
549
+ }
550
+ if (agent && skill.selector) {
551
+ if (skill.selector.type && skill.selector.type.toLowerCase() !== agent.type.toLowerCase())
552
+ return ACCESS_DENIED;
553
+ if (skill.selector.capability && !agent.capabilities.some((c) => c.toLowerCase() === skill.selector.capability.toLowerCase()))
554
+ return ACCESS_DENIED;
555
+ }
556
+ return { content: [{ type: "text", text: JSON.stringify(skill, null, 2) }] };
557
+ });
558
+ server.tool("delete_skill", "Delete a skill by ID. Creator or listed agents can delete.", DeleteSkillSchema.shape, async (args) => {
559
+ const skill = await store.getSkill(args.skill_id);
560
+ if (!skill)
561
+ return ACCESS_DENIED;
562
+ // Authorization: creator OR listed agent
563
+ if (skill.createdBy !== agentId) {
564
+ const agent = await store.getAgent(agentId);
565
+ const agentName = agent?.name ?? "";
566
+ if (!skill.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
567
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can delete this skill" }) }] };
568
+ }
569
+ }
570
+ const deleted = await store.deleteSkill(args.skill_id);
472
571
  return { content: [{ type: "text", text: JSON.stringify({ deleted }) }] };
473
572
  });
573
+ // --- Skill File tools ---
574
+ server.tool("set_skill_file", "Add or update a file attached to a skill. Creator or listed agents can manage files.", SetSkillFileSchema.shape, async (args) => {
575
+ const skill = await store.getSkill(args.skill_id);
576
+ if (!skill)
577
+ return ACCESS_DENIED;
578
+ // Authorization: creator OR listed agent
579
+ if (skill.createdBy !== agentId) {
580
+ const agent = await store.getAgent(agentId);
581
+ const agentName = agent?.name ?? "";
582
+ if (!skill.agents.some((a) => a.toLowerCase() === agentName.toLowerCase())) {
583
+ return { content: [{ type: "text", text: JSON.stringify({ error: "Only the creator or listed agents can manage skill files" }) }] };
584
+ }
585
+ }
586
+ const file = await store.setSkillFile(args.skill_id, args.filename, args.content);
587
+ return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
588
+ });
589
+ server.tool("get_skill_file", "Get a file attached to a skill (progressive disclosure tier 3).", GetSkillFileSchema.shape, async (args) => {
590
+ // Check parent skill visibility first
591
+ const skill = await store.getSkill(args.skill_id);
592
+ if (!skill)
593
+ return ACCESS_DENIED;
594
+ const agent = await store.getAgent(agentId);
595
+ if (agent && skill.agents.length > 0) {
596
+ if (!skill.agents.some((a) => a.toLowerCase() === agent.name.toLowerCase())) {
597
+ return ACCESS_DENIED;
598
+ }
599
+ }
600
+ const file = await store.getSkillFile(args.skill_id, args.filename);
601
+ if (!file) {
602
+ return { content: [{ type: "text", text: JSON.stringify({ error: "File not found" }) }] };
603
+ }
604
+ return { content: [{ type: "text", text: JSON.stringify(file, null, 2) }] };
605
+ });
474
606
  // --- Agent Memory tools ---
475
607
  server.tool("set_agent_memory", "Save private memory for yourself. No scope = global. With project_id = per-project. With conversation_id = per-conversation. Content overwrites previous.", SetAgentMemorySchema.shape, async (args) => {
476
608
  let scope = "global";