@locusai/locus-gateway 0.23.3 → 0.23.4
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 +290 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# @locusai/locus-gateway
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@locusai/locus-gateway)
|
|
4
|
+
[](https://github.com/asgarovf/locusai/blob/master/LICENSE)
|
|
5
|
+
|
|
6
|
+
Channel-agnostic message gateway for [Locus](https://github.com/asgarovf/locusai) platform adapters. Routes inbound messages from any platform (Telegram, Discord, WhatsApp, etc.) through a unified command pipeline — parsing, tracking, executing, and formatting output per platform capabilities.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @locusai/locus-gateway
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { Gateway } from "@locusai/locus-gateway";
|
|
18
|
+
|
|
19
|
+
// Create the gateway
|
|
20
|
+
const gateway = new Gateway({
|
|
21
|
+
onEvent: (event) => console.log(`[${event.type}]`, event),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Register a platform adapter
|
|
25
|
+
gateway.register(myAdapter);
|
|
26
|
+
|
|
27
|
+
// Start all adapters
|
|
28
|
+
await gateway.start();
|
|
29
|
+
|
|
30
|
+
// Handle an inbound message (adapters call this internally)
|
|
31
|
+
await gateway.handleMessage({
|
|
32
|
+
platform: "telegram",
|
|
33
|
+
sessionId: "chat-123",
|
|
34
|
+
userId: "user-456",
|
|
35
|
+
text: "/run 42",
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
Messages flow through a five-stage pipeline:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
Adapter → Gateway → Router → Tracker → Executor → Adapter
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
1. **Adapter** receives a platform message and passes it to the gateway
|
|
48
|
+
2. **Router** (`CommandRouter`) parses text into a structured `ParsedCommand` or `FreeText`
|
|
49
|
+
3. **Tracker** (`CommandTracker`) checks concurrency — blocks commands that conflict with running ones in the same exclusivity group
|
|
50
|
+
4. **Executor** (`CommandExecutor`) spawns the Locus CLI via `invokeLocusStream()`, supports streaming and buffered output modes
|
|
51
|
+
5. **Gateway** sends the formatted result back through the originating adapter
|
|
52
|
+
|
|
53
|
+
Free-text messages (non-commands) are automatically routed to `locus exec` as prompts.
|
|
54
|
+
|
|
55
|
+
## API Reference
|
|
56
|
+
|
|
57
|
+
### `Gateway`
|
|
58
|
+
|
|
59
|
+
Central orchestrator connecting platform adapters to the command pipeline.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { Gateway } from "@locusai/locus-gateway";
|
|
63
|
+
|
|
64
|
+
const gateway = new Gateway(options?: GatewayOptions);
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**`GatewayOptions`**
|
|
68
|
+
|
|
69
|
+
| Property | Type | Description |
|
|
70
|
+
|----------|------|-------------|
|
|
71
|
+
| `onEvent` | `(event: GatewayEvent) => void` | Optional handler for gateway lifecycle events |
|
|
72
|
+
|
|
73
|
+
**Methods**
|
|
74
|
+
|
|
75
|
+
| Method | Description |
|
|
76
|
+
|--------|-------------|
|
|
77
|
+
| `register(adapter)` | Register a `PlatformAdapter` (throws if platform already registered) |
|
|
78
|
+
| `getAdapter(platform)` | Get a registered adapter by platform name |
|
|
79
|
+
| `handleMessage(message)` | Main entry point — parse, route, and execute an `InboundMessage` |
|
|
80
|
+
| `start()` | Start all registered adapters |
|
|
81
|
+
| `stop()` | Stop all adapters gracefully |
|
|
82
|
+
| `getRouter()` | Access the `CommandRouter` instance |
|
|
83
|
+
| `getExecutor()` | Access the `CommandExecutor` instance |
|
|
84
|
+
| `getTracker()` | Access the `CommandTracker` instance |
|
|
85
|
+
|
|
86
|
+
### `CommandRouter`
|
|
87
|
+
|
|
88
|
+
Parses inbound text into structured commands or free-text.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { CommandRouter } from "@locusai/locus-gateway";
|
|
92
|
+
|
|
93
|
+
const router = new CommandRouter(prefix?: string); // default: "/"
|
|
94
|
+
const parsed = router.parse("/run 42 43");
|
|
95
|
+
// → { type: "command", command: "run", args: ["42", "43"], raw: "/run 42 43" }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Returns `ParsedCommand` for slash commands, `FreeText` for everything else. Handles `@botname` suffixes (e.g., `/run@MyBot` → command `"run"`).
|
|
99
|
+
|
|
100
|
+
### `CommandExecutor`
|
|
101
|
+
|
|
102
|
+
Executes Locus CLI commands via subprocess, with streaming or buffered output.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { CommandExecutor, CommandTracker } from "@locusai/locus-gateway";
|
|
106
|
+
|
|
107
|
+
const tracker = new CommandTracker();
|
|
108
|
+
const executor = new CommandExecutor(tracker);
|
|
109
|
+
|
|
110
|
+
// Buffered execution
|
|
111
|
+
const result = await executor.executeLocusCommand(sessionId, "status", []);
|
|
112
|
+
|
|
113
|
+
// Streaming execution with callbacks
|
|
114
|
+
const result = await executor.executeLocusCommand(sessionId, "run", ["42"], {
|
|
115
|
+
onStart: async (text) => { /* send initial message, return messageId */ },
|
|
116
|
+
onUpdate: async (messageId, text) => { /* edit message with progress */ },
|
|
117
|
+
onComplete: async (messageId, text, exitCode) => { /* final update */ },
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**`StreamCallbacks`**
|
|
122
|
+
|
|
123
|
+
| Callback | Description |
|
|
124
|
+
|----------|-------------|
|
|
125
|
+
| `onStart(text)` | Called when execution begins. Return a message ID for subsequent edits |
|
|
126
|
+
| `onUpdate(messageId, text)` | Called periodically with updated output (every ~2s) |
|
|
127
|
+
| `onComplete(messageId, text, exitCode)` | Called when execution finishes |
|
|
128
|
+
|
|
129
|
+
### `CommandTracker`
|
|
130
|
+
|
|
131
|
+
Tracks active commands per session and enforces exclusivity groups.
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { CommandTracker } from "@locusai/locus-gateway";
|
|
135
|
+
|
|
136
|
+
const tracker = new CommandTracker();
|
|
137
|
+
|
|
138
|
+
// Check for conflicts before executing
|
|
139
|
+
const conflict = tracker.checkExclusiveConflict(sessionId, "run");
|
|
140
|
+
if (conflict) {
|
|
141
|
+
console.log(`Blocked by: ${conflict.runningCommand.command}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Track a running command
|
|
145
|
+
const id = tracker.track(sessionId, "run", ["42"], childProcess);
|
|
146
|
+
|
|
147
|
+
// List active commands
|
|
148
|
+
const active = tracker.getActive(sessionId);
|
|
149
|
+
|
|
150
|
+
// Kill a specific command or all commands in a session
|
|
151
|
+
tracker.kill(sessionId, id);
|
|
152
|
+
tracker.killAll(sessionId);
|
|
153
|
+
|
|
154
|
+
// Untrack when done
|
|
155
|
+
tracker.untrack(sessionId, id);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Exclusivity Groups**
|
|
159
|
+
|
|
160
|
+
| Group | Commands | Behavior |
|
|
161
|
+
|-------|----------|----------|
|
|
162
|
+
| `workspace` | `run`, `plan`, `iterate`, `exec` | Only one per session |
|
|
163
|
+
| `git` | `stage`, `commit`, `checkout`, `stash`, `pr` | Only one per session |
|
|
164
|
+
|
|
165
|
+
Commands outside these groups can run concurrently.
|
|
166
|
+
|
|
167
|
+
### Formatting Utilities
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { bestFormat, splitMessage, truncate } from "@locusai/locus-gateway";
|
|
171
|
+
|
|
172
|
+
// Pick the richest format a platform supports (HTML > Markdown > plain)
|
|
173
|
+
const format = bestFormat(adapter.capabilities);
|
|
174
|
+
|
|
175
|
+
// Split long text respecting platform message limits
|
|
176
|
+
const chunks = splitMessage(longText, 4096);
|
|
177
|
+
|
|
178
|
+
// Truncate text with an indicator
|
|
179
|
+
const short = truncate(longText, 200);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Command Registry
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
import {
|
|
186
|
+
COMMAND_REGISTRY,
|
|
187
|
+
STREAMING_COMMANDS,
|
|
188
|
+
getCommandDefinition,
|
|
189
|
+
} from "@locusai/locus-gateway";
|
|
190
|
+
|
|
191
|
+
// Look up a command definition
|
|
192
|
+
const def = getCommandDefinition("run");
|
|
193
|
+
// → { cliArgs: ["run"], streaming: true }
|
|
194
|
+
|
|
195
|
+
// Check if a command streams
|
|
196
|
+
STREAMING_COMMANDS.has("run"); // true
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Platform Adapter Interface
|
|
200
|
+
|
|
201
|
+
To build a custom adapter, implement the `PlatformAdapter` interface:
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import type {
|
|
205
|
+
PlatformAdapter,
|
|
206
|
+
PlatformCapabilities,
|
|
207
|
+
OutboundMessage,
|
|
208
|
+
} from "@locusai/locus-gateway";
|
|
209
|
+
|
|
210
|
+
class MyAdapter implements PlatformAdapter {
|
|
211
|
+
readonly platform = "my-platform";
|
|
212
|
+
|
|
213
|
+
capabilities: PlatformCapabilities = {
|
|
214
|
+
supportsEditing: true, // Can edit previously sent messages
|
|
215
|
+
supportsInlineButtons: true, // Supports inline action buttons
|
|
216
|
+
supportsMarkdown: true, // Supports Markdown formatting
|
|
217
|
+
supportsHTML: false, // Supports HTML formatting
|
|
218
|
+
supportsFileUpload: false, // Supports file attachments
|
|
219
|
+
maxMessageLength: 4096, // Max characters before message splitting
|
|
220
|
+
supportsStreaming: true, // Enable streaming via message edits
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
async start(): Promise<void> {
|
|
224
|
+
// Initialize platform connection (e.g., connect to API, start polling)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async stop(): Promise<void> {
|
|
228
|
+
// Clean up resources, disconnect
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async send(sessionId: string, message: OutboundMessage): Promise<void> {
|
|
232
|
+
// Send a message to the platform
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Optional — required if supportsEditing is true
|
|
236
|
+
async edit?(sessionId: string, messageId: string, message: OutboundMessage): Promise<void> {
|
|
237
|
+
// Edit a previously sent message
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**`PlatformCapabilities`**
|
|
243
|
+
|
|
244
|
+
| Property | Type | Description |
|
|
245
|
+
|----------|------|-------------|
|
|
246
|
+
| `supportsEditing` | `boolean` | Can edit sent messages (enables streaming via edits) |
|
|
247
|
+
| `supportsInlineButtons` | `boolean` | Supports inline action buttons |
|
|
248
|
+
| `supportsMarkdown` | `boolean` | Supports Markdown formatting |
|
|
249
|
+
| `supportsHTML` | `boolean` | Supports HTML formatting |
|
|
250
|
+
| `supportsFileUpload` | `boolean` | Supports file uploads/attachments |
|
|
251
|
+
| `maxMessageLength` | `number` | Max characters per message before splitting |
|
|
252
|
+
| `supportsStreaming` | `boolean` | Enable real-time streaming output via message edits |
|
|
253
|
+
|
|
254
|
+
**`OutboundMessage`**
|
|
255
|
+
|
|
256
|
+
| Property | Type | Description |
|
|
257
|
+
|----------|------|-------------|
|
|
258
|
+
| `text` | `string` | Message content |
|
|
259
|
+
| `format` | `"plain" \| "markdown" \| "html"` | Text format |
|
|
260
|
+
| `actions?` | `Action[]` | Optional inline buttons/links |
|
|
261
|
+
| `attachments?` | `Attachment[]` | Optional file attachments |
|
|
262
|
+
|
|
263
|
+
## Gateway Events
|
|
264
|
+
|
|
265
|
+
Subscribe to lifecycle events via the `onEvent` option:
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
const gateway = new Gateway({
|
|
269
|
+
onEvent: (event) => {
|
|
270
|
+
switch (event.type) {
|
|
271
|
+
case "message_received": // Inbound message from a platform
|
|
272
|
+
case "command_started": // Command execution began
|
|
273
|
+
case "command_completed": // Command finished (includes exitCode)
|
|
274
|
+
case "error": // Error with optional context string
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Related Packages
|
|
281
|
+
|
|
282
|
+
| Package | Description |
|
|
283
|
+
|---------|-------------|
|
|
284
|
+
| [`@locusai/locus-pm2`](../pm2) | Process management for long-running adapter daemons |
|
|
285
|
+
| [`@locusai/locus-telegram`](../telegram) | Telegram adapter built on this gateway |
|
|
286
|
+
| [`@locusai/sdk`](../sdk) | SDK for building community packages |
|
|
287
|
+
|
|
288
|
+
## License
|
|
289
|
+
|
|
290
|
+
[MIT](https://github.com/asgarovf/locusai/blob/master/LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@locusai/locus-gateway",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.4",
|
|
4
4
|
"description": "Channel-agnostic message gateway for Locus platform adapters",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"clean": "rm -rf dist node_modules"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@locusai/sdk": "^0.23.
|
|
32
|
+
"@locusai/sdk": "^0.23.4"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "^5.8.3"
|