@locusai/locus-gateway 0.23.2 → 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.
Files changed (2) hide show
  1. package/README.md +290 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,290 @@
1
+ # @locusai/locus-gateway
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@locusai/locus-gateway?color=blue)](https://www.npmjs.com/package/@locusai/locus-gateway)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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.2",
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.2"
32
+ "@locusai/sdk": "^0.23.4"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.8.3"