arkitek-relay-skill 1.0.0
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/LICENSE +21 -0
- package/README.md +236 -0
- package/dist/council.d.ts +3 -0
- package/dist/council.d.ts.map +1 -0
- package/dist/council.js +57 -0
- package/dist/council.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/relay.d.ts +37 -0
- package/dist/relay.d.ts.map +1 -0
- package/dist/relay.js +429 -0
- package/dist/relay.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +35 -0
- package/dist/validation.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ArkiTek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# arkitek-relay-skill
|
|
2
|
+
|
|
3
|
+
Connect your self-hosted [OpenClaw](https://github.com/openclaw) agent to [ArkiTek](https://arkitek.dev) — a modern web UI for interacting with AI agents. This skill opens a persistent, outbound-only SSE connection from your agent to ArkiTek's cloud relay, so users can chat with your agent through ArkiTek's interface. No tunnels, public URLs, or open ports required.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ArkiTek Web UI ←→ ArkiTek Cloud ←——SSE—— Your Agent (this skill)
|
|
9
|
+
(user) (relay) ——POST→
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
1. Your agent connects **outbound** to ArkiTek over HTTPS
|
|
13
|
+
2. Messages from the ArkiTek UI are delivered to your agent via SSE (Server-Sent Events)
|
|
14
|
+
3. Your agent processes each message and sends the response back via HTTPS POST
|
|
15
|
+
4. ArkiTek delivers the response to the user's browser in real time
|
|
16
|
+
|
|
17
|
+
The agent initiates all connections. Nothing is exposed on your network.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- **Node.js 18+** (uses native `fetch` and `ReadableStream`)
|
|
22
|
+
- An **OpenClaw agent** (or any agent framework — the handler interface is generic)
|
|
23
|
+
- An **ArkiTek account** at [arkitek.dev](https://arkitek.dev)
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install arkitek-relay-skill
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Or clone and build from source:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/raleighgardner16-source/arkitek-relay-skill.git
|
|
35
|
+
cd arkitek-relay-skill
|
|
36
|
+
npm install
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Getting Your API Key
|
|
41
|
+
|
|
42
|
+
1. Log in to [arkitek.dev](https://arkitek.dev)
|
|
43
|
+
2. Navigate to **Agents** → **Add Agent**
|
|
44
|
+
3. Give your agent a name and description
|
|
45
|
+
4. Click **Create** — you'll receive a private API key (starts with `ak_`)
|
|
46
|
+
5. Copy the key immediately — it won't be shown again
|
|
47
|
+
|
|
48
|
+
> **Security**: Your API key is a secret. Never commit it to version control, share it publicly, or log it. Use environment variables.
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Set your API key as an environment variable:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export ARKITEK_API_KEY=ak_your_api_key_here
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Or use a `.env` file (see `.env.example`):
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
ARKITEK_API_KEY=ak_your_api_key_here
|
|
62
|
+
ARKITEK_AUTO_RECONNECT=true
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Configuration Options
|
|
66
|
+
|
|
67
|
+
| Variable | Required | Default | Description |
|
|
68
|
+
|----------|----------|---------|-------------|
|
|
69
|
+
| `ARKITEK_API_KEY` | Yes | — | Your agent's private API key from ArkiTek |
|
|
70
|
+
| `ARKITEK_AUTO_RECONNECT` | No | `true` | Auto-reconnect on network errors |
|
|
71
|
+
|
|
72
|
+
## Usage
|
|
73
|
+
|
|
74
|
+
### As an imported module
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { createArkitekRelay } from "arkitek-relay-skill";
|
|
78
|
+
import type { IncomingMessage } from "arkitek-relay-skill";
|
|
79
|
+
|
|
80
|
+
// Define your message handler
|
|
81
|
+
async function handleMessage(message: IncomingMessage): Promise<string> {
|
|
82
|
+
console.log(`User said: ${message.content}`);
|
|
83
|
+
|
|
84
|
+
// Pass to your OpenClaw agent, LLM, or any processing logic
|
|
85
|
+
const response = await yourAgent.process(message.content, message.images);
|
|
86
|
+
|
|
87
|
+
return response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create and connect the relay
|
|
91
|
+
const relay = createArkitekRelay(
|
|
92
|
+
{
|
|
93
|
+
apiKey: process.env.ARKITEK_API_KEY!,
|
|
94
|
+
autoReconnect: true,
|
|
95
|
+
},
|
|
96
|
+
handleMessage,
|
|
97
|
+
{
|
|
98
|
+
onConnect: (agentId) => console.log(`Connected as agent ${agentId}`),
|
|
99
|
+
onDisconnect: (reason) => console.log(`Disconnected: ${reason}`),
|
|
100
|
+
onError: (err) => console.error(`Error: ${err.message}`),
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
await relay.connect();
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### As a standalone script (echo mode)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
ARKITEK_API_KEY=ak_your_key node dist/index.js
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This starts the skill with a built-in echo handler — useful for testing that your connection works.
|
|
114
|
+
|
|
115
|
+
### With the RelayClient class directly
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { RelayClient } from "arkitek-relay-skill";
|
|
119
|
+
|
|
120
|
+
const client = new RelayClient(
|
|
121
|
+
{ apiKey: process.env.ARKITEK_API_KEY! },
|
|
122
|
+
async (message) => {
|
|
123
|
+
// Your logic here
|
|
124
|
+
return `Processed: ${message.content}`;
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await client.connect();
|
|
129
|
+
|
|
130
|
+
// Later...
|
|
131
|
+
client.disconnect();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Council of LLMs (Optional)
|
|
135
|
+
|
|
136
|
+
ArkiTek's Council feature lets you query multiple LLMs simultaneously and get aggregated responses. This requires an active ArkiTek subscription.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { queryCouncil } from "arkitek-relay-skill";
|
|
140
|
+
|
|
141
|
+
const result = await queryCouncil(
|
|
142
|
+
{ apiKey: process.env.ARKITEK_API_KEY! },
|
|
143
|
+
"What are the security implications of SSE vs WebSockets?",
|
|
144
|
+
["gpt-4", "claude-3"] // optional — omit to use defaults
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
for (const r of result.responses) {
|
|
148
|
+
console.log(`${r.modelId}: ${r.response}`);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Limits**: 10 requests per minute, prompt max 100KB, max 8 models per request.
|
|
153
|
+
|
|
154
|
+
## Message Format
|
|
155
|
+
|
|
156
|
+
### Incoming messages (from ArkiTek)
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
interface IncomingMessage {
|
|
160
|
+
messageId: string; // Unique ID — must be included in your response
|
|
161
|
+
content: string; // The user's message text
|
|
162
|
+
images?: string[]; // Optional base64-encoded images
|
|
163
|
+
userId: string; // The ArkiTek user's ID
|
|
164
|
+
timestamp: string; // ISO 8601 timestamp
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Your handler's return value
|
|
169
|
+
|
|
170
|
+
Return a `string` — the agent's response text. The skill handles sending it back to ArkiTek with the correct `messageId`.
|
|
171
|
+
|
|
172
|
+
## Security
|
|
173
|
+
|
|
174
|
+
This skill is designed with security as a top priority:
|
|
175
|
+
|
|
176
|
+
- **Outbound-only connections** — your agent never exposes any ports or URLs
|
|
177
|
+
- **TLS enforced** — the skill refuses to start if `NODE_TLS_REJECT_UNAUTHORIZED=0` is detected
|
|
178
|
+
- **API key validation** — keys are validated against the expected format before any network request
|
|
179
|
+
- **Key masking** — API keys are never logged; only `ak_****...last4` appears in output
|
|
180
|
+
- **No tunnels** — no ngrok, no Cloudflare tunnels, no port forwarding
|
|
181
|
+
- **Auth on every request** — both SSE and POST endpoints require the Bearer token
|
|
182
|
+
- **Message integrity** — responses must include the matching `messageId` or they are rejected
|
|
183
|
+
|
|
184
|
+
## Troubleshooting
|
|
185
|
+
|
|
186
|
+
### "API key invalid or revoked"
|
|
187
|
+
|
|
188
|
+
- Double-check your `ARKITEK_API_KEY` value
|
|
189
|
+
- Verify the key in ArkiTek's dashboard — it may have been revoked
|
|
190
|
+
- Keys start with `ak_` and are exactly 67 characters
|
|
191
|
+
- If you rotated your key, the old key has a 1-hour grace period
|
|
192
|
+
|
|
193
|
+
### "NODE_TLS_REJECT_UNAUTHORIZED=0 detected"
|
|
194
|
+
|
|
195
|
+
- Remove `NODE_TLS_REJECT_UNAUTHORIZED=0` from your environment
|
|
196
|
+
- This setting disables TLS certificate verification, which would allow man-in-the-middle attacks
|
|
197
|
+
- If you're behind a corporate proxy, configure `NODE_EXTRA_CA_CERTS` instead
|
|
198
|
+
|
|
199
|
+
### Connection keeps dropping
|
|
200
|
+
|
|
201
|
+
- Check your network connection and firewall rules
|
|
202
|
+
- Ensure outbound HTTPS (port 443) to `arkitek.dev` is allowed
|
|
203
|
+
- The skill auto-reconnects with exponential backoff (1s → 30s max)
|
|
204
|
+
- Messages are queued server-side for up to 5 minutes during disconnections
|
|
205
|
+
|
|
206
|
+
### "Response not delivered"
|
|
207
|
+
|
|
208
|
+
- Ensure you're returning from your handler within 1 hour (server timeout)
|
|
209
|
+
- The `messageId` in your response must exactly match the incoming message
|
|
210
|
+
- Response content must be under 500KB
|
|
211
|
+
|
|
212
|
+
### No messages arriving
|
|
213
|
+
|
|
214
|
+
- Confirm your agent shows as "Connected" in the ArkiTek dashboard
|
|
215
|
+
- Try sending a test message from the ArkiTek web UI
|
|
216
|
+
- Check that your handler isn't throwing errors (they're caught and logged)
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# Install dependencies
|
|
222
|
+
npm install
|
|
223
|
+
|
|
224
|
+
# Build
|
|
225
|
+
npm run build
|
|
226
|
+
|
|
227
|
+
# Run tests
|
|
228
|
+
npm test
|
|
229
|
+
|
|
230
|
+
# Watch mode (rebuilds on changes)
|
|
231
|
+
npm run dev
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## License
|
|
235
|
+
|
|
236
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"council.d.ts","sourceRoot":"","sources":["../src/council.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAElB,KAAK,eAAe,EAMrB,MAAM,YAAY,CAAC;AAGpB,wBAAsB,YAAY,CAChC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,eAAe,CAAC,CA0E1B"}
|
package/dist/council.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { DEFAULT_BASE_URL, LOG_PREFIX, MAX_COUNCIL_MODELS, MAX_COUNCIL_PROMPT_SIZE, COUNCIL_TIMEOUT_MS, } from "./types.js";
|
|
2
|
+
import { validateApiKey, checkTlsSafety, warnIfNotHttps } from "./validation.js";
|
|
3
|
+
export async function queryCouncil(config, prompt, models) {
|
|
4
|
+
validateApiKey(config.apiKey);
|
|
5
|
+
checkTlsSafety();
|
|
6
|
+
const rawBaseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
7
|
+
try {
|
|
8
|
+
const parsed = new URL(rawBaseUrl);
|
|
9
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
10
|
+
throw new Error("Only http:// and https:// protocols are supported");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (err instanceof TypeError) {
|
|
15
|
+
throw new Error(`Invalid baseUrl "${rawBaseUrl}": not a valid URL`);
|
|
16
|
+
}
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
const baseUrl = rawBaseUrl.replace(/\/+$/, "");
|
|
20
|
+
warnIfNotHttps(baseUrl);
|
|
21
|
+
const url = `${baseUrl}/council`;
|
|
22
|
+
const promptBytes = new TextEncoder().encode(prompt).length;
|
|
23
|
+
if (promptBytes > MAX_COUNCIL_PROMPT_SIZE) {
|
|
24
|
+
throw new Error(`Council prompt exceeds 100KB limit (${promptBytes} bytes)`);
|
|
25
|
+
}
|
|
26
|
+
if (models && models.length > MAX_COUNCIL_MODELS) {
|
|
27
|
+
throw new Error(`Council request exceeds ${MAX_COUNCIL_MODELS} model limit (got ${models.length})`);
|
|
28
|
+
}
|
|
29
|
+
const body = { prompt };
|
|
30
|
+
if (models && models.length > 0) {
|
|
31
|
+
body.models = models;
|
|
32
|
+
}
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify(body),
|
|
40
|
+
signal: AbortSignal.timeout(COUNCIL_TIMEOUT_MS),
|
|
41
|
+
});
|
|
42
|
+
if (response.status === 401 || response.status === 403) {
|
|
43
|
+
throw new Error(`${LOG_PREFIX} Council request authentication failed (HTTP ${response.status})`);
|
|
44
|
+
}
|
|
45
|
+
if (response.status === 429) {
|
|
46
|
+
throw new Error(`${LOG_PREFIX} Council rate limit exceeded (10 requests per minute). Try again later.`);
|
|
47
|
+
}
|
|
48
|
+
if (response.status === 402) {
|
|
49
|
+
throw new Error(`${LOG_PREFIX} Council requires an active ArkiTek subscription.`);
|
|
50
|
+
}
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const errorText = await response.text().catch(() => "");
|
|
53
|
+
throw new Error(`${LOG_PREFIX} Council request failed: HTTP ${response.status} — ${errorText}`);
|
|
54
|
+
}
|
|
55
|
+
return (await response.json());
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=council.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"council.js","sourceRoot":"","sources":["../src/council.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { type ArkitekConfig, type ArkitekRelayEvents, type IncomingMessage, type MessageHandler, type ConnectionState, type ConnectedEvent, type PingEvent, type RespondPayload, type RespondResponse, type CouncilRequest, type CouncilResponse, type CouncilModelResponse, } from "./types.js";
|
|
2
|
+
export { RelayClient } from "./relay.js";
|
|
3
|
+
export { maskKey, validateApiKey, checkTlsSafety, warnIfNotHttps } from "./validation.js";
|
|
4
|
+
export { queryCouncil } from "./council.js";
|
|
5
|
+
import { type ArkitekConfig, type ArkitekRelayEvents, type MessageHandler } from "./types.js";
|
|
6
|
+
import { RelayClient } from "./relay.js";
|
|
7
|
+
export declare function createArkitekRelay(config: ArkitekConfig, handler: MessageHandler, events?: ArkitekRelayEvents): RelayClient;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GAC1B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,kBAAkB,EAAE,KAAK,cAAc,EAAc,MAAM,YAAY,CAAC;AAC1G,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIzC,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,EACvB,MAAM,CAAC,EAAE,kBAAkB,GAC1B,WAAW,CAEb"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { RelayClient } from "./relay.js";
|
|
2
|
+
export { maskKey, validateApiKey, checkTlsSafety, warnIfNotHttps } from "./validation.js";
|
|
3
|
+
export { queryCouncil } from "./council.js";
|
|
4
|
+
import { LOG_PREFIX } from "./types.js";
|
|
5
|
+
import { RelayClient } from "./relay.js";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
export function createArkitekRelay(config, handler, events) {
|
|
9
|
+
return new RelayClient(config, handler, events);
|
|
10
|
+
}
|
|
11
|
+
const isMainModule = typeof process !== "undefined" &&
|
|
12
|
+
process.argv[1] &&
|
|
13
|
+
fileURLToPath(import.meta.url) === resolve(process.argv[1]);
|
|
14
|
+
if (isMainModule) {
|
|
15
|
+
const apiKey = process.env.ARKITEK_API_KEY;
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
console.error(`${LOG_PREFIX} ARKITEK_API_KEY environment variable is required`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const autoReconnect = process.env.ARKITEK_AUTO_RECONNECT !== "false";
|
|
21
|
+
const echoHandler = async (message) => {
|
|
22
|
+
console.log(`${LOG_PREFIX} [Echo] Received: ${message.content.slice(0, 100)}`);
|
|
23
|
+
return `Echo: ${message.content}`;
|
|
24
|
+
};
|
|
25
|
+
const relay = createArkitekRelay({ apiKey, autoReconnect }, echoHandler, {
|
|
26
|
+
onConnect: (agentId) => console.log(`${LOG_PREFIX} Agent ${agentId} connected`),
|
|
27
|
+
onDisconnect: (reason) => console.log(`${LOG_PREFIX} Disconnected: ${reason}`),
|
|
28
|
+
onError: (err) => console.error(`${LOG_PREFIX} Error: ${err.message}`),
|
|
29
|
+
});
|
|
30
|
+
relay.connect().catch((err) => {
|
|
31
|
+
console.error(`${LOG_PREFIX} Fatal:`, err);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EAAoE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1G,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,UAAU,kBAAkB,CAChC,MAAqB,EACrB,OAAuB,EACvB,MAA2B;IAE3B,OAAO,IAAI,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,YAAY,GAChB,OAAO,OAAO,KAAK,WAAW;IAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9D,IAAI,YAAY,EAAE,CAAC;IACjB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC3C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,GAAG,UAAU,mDAAmD,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,KAAK,OAAO,CAAC;IAErE,MAAM,WAAW,GAAmB,KAAK,EAAE,OAAO,EAAE,EAAE;QACpD,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,qBAAqB,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/E,OAAO,SAAS,OAAO,CAAC,OAAO,EAAE,CAAC;IACpC,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,kBAAkB,CAC9B,EAAE,MAAM,EAAE,aAAa,EAAE,EACzB,WAAW,EACX;QACE,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,UAAU,OAAO,YAAY,CAAC;QAC/E,YAAY,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,UAAU,kBAAkB,MAAM,EAAE,CAAC;QAC9E,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,UAAU,WAAW,GAAG,CAAC,OAAO,EAAE,CAAC;KACvE,CACF,CAAC;IAEF,KAAK,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;QACrC,OAAO,CAAC,KAAK,CAAC,GAAG,UAAU,SAAS,EAAE,GAAG,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/relay.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ArkitekConfig, type ArkitekRelayEvents, type ConnectionState, type MessageHandler } from "./types.js";
|
|
2
|
+
import { maskKey, validateApiKey, checkTlsSafety, warnIfNotHttps } from "./validation.js";
|
|
3
|
+
export { maskKey, validateApiKey, checkTlsSafety, warnIfNotHttps };
|
|
4
|
+
export declare class RelayClient {
|
|
5
|
+
private readonly config;
|
|
6
|
+
private readonly handler;
|
|
7
|
+
private readonly events;
|
|
8
|
+
private state;
|
|
9
|
+
private abortController;
|
|
10
|
+
private heartbeatTimer;
|
|
11
|
+
private reconnectTimer;
|
|
12
|
+
private reconnectAttempt;
|
|
13
|
+
private activeHandlers;
|
|
14
|
+
private agentId;
|
|
15
|
+
private shutdownRequested;
|
|
16
|
+
private connectResolve;
|
|
17
|
+
private connectReject;
|
|
18
|
+
private readonly boundShutdown;
|
|
19
|
+
constructor(config: ArkitekConfig, handler: MessageHandler, events?: ArkitekRelayEvents);
|
|
20
|
+
getState(): ConnectionState;
|
|
21
|
+
isConnected(): boolean;
|
|
22
|
+
connect(): Promise<void>;
|
|
23
|
+
disconnect(reason?: string): void;
|
|
24
|
+
private registerShutdownHandlers;
|
|
25
|
+
private unregisterShutdownHandlers;
|
|
26
|
+
private cleanup;
|
|
27
|
+
private openStream;
|
|
28
|
+
private consumeStream;
|
|
29
|
+
private handleSSEEvent;
|
|
30
|
+
private processMessage;
|
|
31
|
+
private sendResponse;
|
|
32
|
+
private startHeartbeatMonitor;
|
|
33
|
+
private resetHeartbeatMonitor;
|
|
34
|
+
private scheduleReconnect;
|
|
35
|
+
private emitSafe;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=relay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAEvB,KAAK,eAAe,EAEpB,KAAK,cAAc,EAkBpB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE1F,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAoBnE,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CACgB;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAE5C,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,iBAAiB,CAAS;IAElC,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,aAAa,CAAuC;IAE5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;gBAGzC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,cAAc,EACvB,MAAM,GAAE,kBAAuB;IAgCjC,QAAQ,IAAI,eAAe;IAI3B,WAAW,IAAI,OAAO;IAIhB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB9B,UAAU,CAAC,MAAM,SAAW,GAAG,IAAI;IAenC,OAAO,CAAC,wBAAwB;IAKhC,OAAO,CAAC,0BAA0B;IAKlC,OAAO,CAAC,OAAO;YAcD,UAAU;YAgEV,aAAa;IAsD3B,OAAO,CAAC,cAAc;IA6CtB,OAAO,CAAC,cAAc;YAyCR,YAAY;IA4D1B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,iBAAiB;IA4CzB,OAAO,CAAC,QAAQ;CAcjB"}
|
package/dist/relay.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { DEFAULT_BASE_URL, HEARTBEAT_TIMEOUT_MS, LOG_PREFIX, MAX_RESPONSE_SIZE, MAX_RECONNECT_ATTEMPTS, MAX_CONCURRENT_HANDLERS, RESPOND_TIMEOUT_MS, RESPOND_MAX_RETRIES, RESPOND_RETRY_DELAY_MS, RECONNECT_BASE_MS, RECONNECT_MAX_MS, MAX_SSE_BUFFER_SIZE, MAX_IMAGES_PER_MESSAGE, MAX_IMAGE_SIZE, } from "./types.js";
|
|
2
|
+
import { maskKey, validateApiKey, checkTlsSafety, warnIfNotHttps } from "./validation.js";
|
|
3
|
+
export { maskKey, validateApiKey, checkTlsSafety, warnIfNotHttps };
|
|
4
|
+
function log(...args) {
|
|
5
|
+
console.log(LOG_PREFIX, ...args);
|
|
6
|
+
}
|
|
7
|
+
function logError(...args) {
|
|
8
|
+
console.error(LOG_PREFIX, ...args);
|
|
9
|
+
}
|
|
10
|
+
function backoffDelay(attempt) {
|
|
11
|
+
const base = Math.min(RECONNECT_BASE_MS * Math.pow(2, attempt), RECONNECT_MAX_MS);
|
|
12
|
+
const jitter = Math.random() * base * 0.3;
|
|
13
|
+
return base + jitter;
|
|
14
|
+
}
|
|
15
|
+
function sleep(ms) {
|
|
16
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
17
|
+
}
|
|
18
|
+
export class RelayClient {
|
|
19
|
+
config;
|
|
20
|
+
handler;
|
|
21
|
+
events;
|
|
22
|
+
state = "disconnected";
|
|
23
|
+
abortController = null;
|
|
24
|
+
heartbeatTimer = null;
|
|
25
|
+
reconnectTimer = null;
|
|
26
|
+
reconnectAttempt = 0;
|
|
27
|
+
activeHandlers = 0;
|
|
28
|
+
agentId = null;
|
|
29
|
+
shutdownRequested = false;
|
|
30
|
+
connectResolve = null;
|
|
31
|
+
connectReject = null;
|
|
32
|
+
boundShutdown;
|
|
33
|
+
constructor(config, handler, events = {}) {
|
|
34
|
+
validateApiKey(config.apiKey);
|
|
35
|
+
checkTlsSafety();
|
|
36
|
+
const resolvedUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = new URL(resolvedUrl);
|
|
39
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
40
|
+
throw new Error("Only http:// and https:// protocols are supported");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err instanceof TypeError) {
|
|
45
|
+
throw new Error(`Invalid baseUrl "${resolvedUrl}": not a valid URL`);
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
this.config = {
|
|
50
|
+
apiKey: config.apiKey,
|
|
51
|
+
baseUrl: resolvedUrl.replace(/\/+$/, ""),
|
|
52
|
+
autoReconnect: config.autoReconnect ?? true,
|
|
53
|
+
};
|
|
54
|
+
warnIfNotHttps(this.config.baseUrl);
|
|
55
|
+
this.handler = handler;
|
|
56
|
+
this.events = events;
|
|
57
|
+
this.boundShutdown = () => {
|
|
58
|
+
this.disconnect("process signal");
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
getState() {
|
|
62
|
+
return this.state;
|
|
63
|
+
}
|
|
64
|
+
isConnected() {
|
|
65
|
+
return this.state === "connected";
|
|
66
|
+
}
|
|
67
|
+
async connect() {
|
|
68
|
+
if (this.state === "connected" || this.state === "connecting") {
|
|
69
|
+
log("Already connected or connecting");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (this.state === "auth_failed") {
|
|
73
|
+
logError("Cannot connect — API key was rejected. Please check your key.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.shutdownRequested = false;
|
|
77
|
+
this.registerShutdownHandlers();
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
this.connectResolve = resolve;
|
|
80
|
+
this.connectReject = reject;
|
|
81
|
+
this.openStream().catch((err) => {
|
|
82
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
83
|
+
this.connectReject?.(error);
|
|
84
|
+
this.connectResolve = null;
|
|
85
|
+
this.connectReject = null;
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
disconnect(reason = "manual") {
|
|
90
|
+
this.shutdownRequested = true;
|
|
91
|
+
const hadPendingConnect = this.connectReject !== null;
|
|
92
|
+
this.cleanup();
|
|
93
|
+
this.state = "disconnected";
|
|
94
|
+
this.unregisterShutdownHandlers();
|
|
95
|
+
log(`Disconnected (reason: ${reason})`);
|
|
96
|
+
this.emitSafe("onDisconnect", reason);
|
|
97
|
+
if (hadPendingConnect) {
|
|
98
|
+
this.connectReject?.(new Error(`Disconnected before connection was established (reason: ${reason})`));
|
|
99
|
+
}
|
|
100
|
+
this.connectResolve = null;
|
|
101
|
+
this.connectReject = null;
|
|
102
|
+
}
|
|
103
|
+
registerShutdownHandlers() {
|
|
104
|
+
process.on("SIGINT", this.boundShutdown);
|
|
105
|
+
process.on("SIGTERM", this.boundShutdown);
|
|
106
|
+
}
|
|
107
|
+
unregisterShutdownHandlers() {
|
|
108
|
+
process.removeListener("SIGINT", this.boundShutdown);
|
|
109
|
+
process.removeListener("SIGTERM", this.boundShutdown);
|
|
110
|
+
}
|
|
111
|
+
cleanup() {
|
|
112
|
+
this.abortController?.abort();
|
|
113
|
+
this.abortController = null;
|
|
114
|
+
if (this.heartbeatTimer) {
|
|
115
|
+
clearTimeout(this.heartbeatTimer);
|
|
116
|
+
this.heartbeatTimer = null;
|
|
117
|
+
}
|
|
118
|
+
if (this.reconnectTimer) {
|
|
119
|
+
clearTimeout(this.reconnectTimer);
|
|
120
|
+
this.reconnectTimer = null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async openStream() {
|
|
124
|
+
this.state = "connecting";
|
|
125
|
+
this.abortController = new AbortController();
|
|
126
|
+
const url = `${this.config.baseUrl}/stream`;
|
|
127
|
+
log(`Connecting to ${url}...`);
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(url, {
|
|
130
|
+
method: "GET",
|
|
131
|
+
headers: {
|
|
132
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
133
|
+
Accept: "text/event-stream",
|
|
134
|
+
"Cache-Control": "no-cache",
|
|
135
|
+
},
|
|
136
|
+
signal: this.abortController.signal,
|
|
137
|
+
});
|
|
138
|
+
if (response.status === 401 || response.status === 403) {
|
|
139
|
+
this.state = "auth_failed";
|
|
140
|
+
const authErr = new Error(`API key invalid or revoked (HTTP ${response.status}). Key: ${maskKey(this.config.apiKey)}. ` +
|
|
141
|
+
"Will NOT retry. Please check your API key in ArkiTek.");
|
|
142
|
+
logError(authErr.message);
|
|
143
|
+
this.cleanup();
|
|
144
|
+
this.unregisterShutdownHandlers();
|
|
145
|
+
this.emitSafe("onError", authErr);
|
|
146
|
+
this.connectReject?.(authErr);
|
|
147
|
+
this.connectResolve = null;
|
|
148
|
+
this.connectReject = null;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (!response.ok) {
|
|
152
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
153
|
+
}
|
|
154
|
+
if (!response.body) {
|
|
155
|
+
throw new Error("Response body is null — SSE stream not available");
|
|
156
|
+
}
|
|
157
|
+
this.reconnectAttempt = 0;
|
|
158
|
+
this.startHeartbeatMonitor();
|
|
159
|
+
await this.consumeStream(response.body);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (this.shutdownRequested)
|
|
163
|
+
return;
|
|
164
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
165
|
+
return;
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
logError(`Connection error: ${message}`);
|
|
168
|
+
const error = err instanceof Error ? err : new Error(message);
|
|
169
|
+
this.emitSafe("onError", error);
|
|
170
|
+
if (this.connectReject) {
|
|
171
|
+
this.connectReject(error);
|
|
172
|
+
this.connectResolve = null;
|
|
173
|
+
this.connectReject = null;
|
|
174
|
+
}
|
|
175
|
+
this.scheduleReconnect();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async consumeStream(body) {
|
|
179
|
+
const reader = body.getReader();
|
|
180
|
+
const decoder = new TextDecoder();
|
|
181
|
+
let buffer = "";
|
|
182
|
+
let currentEvent = "";
|
|
183
|
+
let currentData = "";
|
|
184
|
+
try {
|
|
185
|
+
while (true) {
|
|
186
|
+
const { done, value } = await reader.read();
|
|
187
|
+
if (done)
|
|
188
|
+
break;
|
|
189
|
+
buffer += decoder.decode(value, { stream: true });
|
|
190
|
+
if (buffer.length > MAX_SSE_BUFFER_SIZE) {
|
|
191
|
+
logError("SSE buffer exceeded 1MB limit — disconnecting to prevent memory exhaustion");
|
|
192
|
+
reader.cancel();
|
|
193
|
+
this.scheduleReconnect();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const lines = buffer.split("\n");
|
|
197
|
+
buffer = lines.pop() ?? "";
|
|
198
|
+
for (const rawLine of lines) {
|
|
199
|
+
const line = rawLine.replace(/\r$/, "");
|
|
200
|
+
if (line.startsWith("event:")) {
|
|
201
|
+
currentEvent = line.slice(6).trim();
|
|
202
|
+
}
|
|
203
|
+
else if (line.startsWith("data:")) {
|
|
204
|
+
if (currentData)
|
|
205
|
+
currentData += "\n";
|
|
206
|
+
currentData += line.slice(5).trim();
|
|
207
|
+
}
|
|
208
|
+
else if (line === "") {
|
|
209
|
+
if (currentEvent && currentData) {
|
|
210
|
+
this.handleSSEEvent(currentEvent, currentData);
|
|
211
|
+
}
|
|
212
|
+
currentEvent = "";
|
|
213
|
+
currentData = "";
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (this.shutdownRequested)
|
|
220
|
+
return;
|
|
221
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
222
|
+
return;
|
|
223
|
+
throw err;
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
reader.releaseLock();
|
|
227
|
+
}
|
|
228
|
+
if (!this.shutdownRequested) {
|
|
229
|
+
log("SSE stream ended");
|
|
230
|
+
this.scheduleReconnect();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
handleSSEEvent(event, rawData) {
|
|
234
|
+
try {
|
|
235
|
+
switch (event) {
|
|
236
|
+
case "connected": {
|
|
237
|
+
const data = JSON.parse(rawData);
|
|
238
|
+
if (typeof data.agentId !== "string" || !data.agentId) {
|
|
239
|
+
logError("Received malformed connected event — missing or invalid agentId");
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
this.state = "connected";
|
|
243
|
+
this.agentId = data.agentId;
|
|
244
|
+
log(`Connected — agent ID: ${data.agentId}`);
|
|
245
|
+
this.emitSafe("onConnect", data.agentId);
|
|
246
|
+
this.connectResolve?.();
|
|
247
|
+
this.connectResolve = null;
|
|
248
|
+
this.connectReject = null;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
case "ping": {
|
|
252
|
+
const data = JSON.parse(rawData);
|
|
253
|
+
if (typeof data.t !== "number" || !Number.isFinite(data.t)) {
|
|
254
|
+
logError("Received malformed ping event — missing or invalid timestamp");
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
this.resetHeartbeatMonitor();
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
case "new_message": {
|
|
261
|
+
const data = JSON.parse(rawData);
|
|
262
|
+
if (!data.messageId || !data.content) {
|
|
263
|
+
logError("Received malformed new_message — missing messageId or content");
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
this.processMessage(data);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
default:
|
|
270
|
+
log(`Unknown event type: ${event}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
275
|
+
logError(`Failed to parse SSE event "${event}": ${message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
processMessage(message) {
|
|
279
|
+
if (this.activeHandlers >= MAX_CONCURRENT_HANDLERS) {
|
|
280
|
+
logError(`Dropping message ${message.messageId} — concurrency limit reached (${MAX_CONCURRENT_HANDLERS} active handlers)`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (message.images) {
|
|
284
|
+
if (!Array.isArray(message.images)) {
|
|
285
|
+
logError(`Dropping message ${message.messageId} — images field is not an array`);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (message.images.length > MAX_IMAGES_PER_MESSAGE) {
|
|
289
|
+
logError(`Dropping message ${message.messageId} — too many images (${message.images.length}, max ${MAX_IMAGES_PER_MESSAGE})`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
for (const img of message.images) {
|
|
293
|
+
if (typeof img !== "string" || img.length > MAX_IMAGE_SIZE) {
|
|
294
|
+
logError(`Dropping message ${message.messageId} — invalid or oversized image data`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
this.activeHandlers++;
|
|
300
|
+
log(`Received message ${message.messageId} from user ${message.userId}`);
|
|
301
|
+
this.handler(message)
|
|
302
|
+
.then((responseContent) => this.sendResponse(message.messageId, responseContent))
|
|
303
|
+
.catch((err) => {
|
|
304
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
305
|
+
logError(`Handler error for message ${message.messageId}: ${msg}`);
|
|
306
|
+
})
|
|
307
|
+
.finally(() => {
|
|
308
|
+
this.activeHandlers--;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
async sendResponse(messageId, content) {
|
|
312
|
+
const encoder = new TextEncoder();
|
|
313
|
+
const encoded = encoder.encode(content);
|
|
314
|
+
if (encoded.length > MAX_RESPONSE_SIZE) {
|
|
315
|
+
logError(`Response for ${messageId} exceeds 500KB limit (${encoded.length} bytes). Truncating.`);
|
|
316
|
+
const truncatedBytes = encoded.slice(0, MAX_RESPONSE_SIZE);
|
|
317
|
+
content = new TextDecoder().decode(truncatedBytes);
|
|
318
|
+
if (content.endsWith("\uFFFD")) {
|
|
319
|
+
content = content.slice(0, -1);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const url = `${this.config.baseUrl}/respond`;
|
|
323
|
+
const payload = { messageId, content };
|
|
324
|
+
for (let attempt = 0; attempt <= RESPOND_MAX_RETRIES; attempt++) {
|
|
325
|
+
try {
|
|
326
|
+
const response = await fetch(url, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: {
|
|
329
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
330
|
+
"Content-Type": "application/json",
|
|
331
|
+
},
|
|
332
|
+
body: JSON.stringify(payload),
|
|
333
|
+
signal: AbortSignal.timeout(RESPOND_TIMEOUT_MS),
|
|
334
|
+
});
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
const body = await response.text().catch(() => "");
|
|
337
|
+
logError(`Failed to send response for ${messageId}: HTTP ${response.status} — ${body}`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const result = (await response.json());
|
|
341
|
+
if (result.success && result.delivered) {
|
|
342
|
+
log(`Response delivered for message ${messageId}`);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
logError(`Response sent but not confirmed for ${messageId}:`, result);
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
351
|
+
if (attempt < RESPOND_MAX_RETRIES) {
|
|
352
|
+
logError(`Network error sending response for ${messageId} (attempt ${attempt + 1}/${RESPOND_MAX_RETRIES + 1}): ${msg}. Retrying...`);
|
|
353
|
+
await sleep(RESPOND_RETRY_DELAY_MS * (attempt + 1));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
logError(`Network error sending response for ${messageId} after ${RESPOND_MAX_RETRIES + 1} attempts: ${msg}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
startHeartbeatMonitor() {
|
|
362
|
+
this.resetHeartbeatMonitor();
|
|
363
|
+
}
|
|
364
|
+
resetHeartbeatMonitor() {
|
|
365
|
+
if (this.heartbeatTimer) {
|
|
366
|
+
clearTimeout(this.heartbeatTimer);
|
|
367
|
+
}
|
|
368
|
+
this.heartbeatTimer = setTimeout(() => {
|
|
369
|
+
if (this.state !== "connected")
|
|
370
|
+
return;
|
|
371
|
+
log("Heartbeat timeout — no ping received for 60s. Reconnecting...");
|
|
372
|
+
this.cleanup();
|
|
373
|
+
this.scheduleReconnect();
|
|
374
|
+
}, HEARTBEAT_TIMEOUT_MS);
|
|
375
|
+
}
|
|
376
|
+
scheduleReconnect() {
|
|
377
|
+
if (this.shutdownRequested)
|
|
378
|
+
return;
|
|
379
|
+
if (this.state === "auth_failed")
|
|
380
|
+
return;
|
|
381
|
+
if (!this.config.autoReconnect) {
|
|
382
|
+
log("Auto-reconnect disabled. Staying disconnected.");
|
|
383
|
+
this.state = "disconnected";
|
|
384
|
+
this.emitSafe("onDisconnect", "connection_lost");
|
|
385
|
+
if (this.connectReject) {
|
|
386
|
+
this.connectReject(new Error("Connection lost and auto-reconnect is disabled"));
|
|
387
|
+
this.connectResolve = null;
|
|
388
|
+
this.connectReject = null;
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (this.reconnectAttempt >= MAX_RECONNECT_ATTEMPTS) {
|
|
393
|
+
const err = new Error(`Max reconnect attempts reached (${MAX_RECONNECT_ATTEMPTS}). Giving up.`);
|
|
394
|
+
logError(err.message);
|
|
395
|
+
this.state = "disconnected";
|
|
396
|
+
this.emitSafe("onError", err);
|
|
397
|
+
this.emitSafe("onDisconnect", "max_reconnect_attempts");
|
|
398
|
+
if (this.connectReject) {
|
|
399
|
+
this.connectReject(err);
|
|
400
|
+
this.connectResolve = null;
|
|
401
|
+
this.connectReject = null;
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
this.state = "reconnecting";
|
|
406
|
+
const delay = backoffDelay(this.reconnectAttempt);
|
|
407
|
+
this.reconnectAttempt++;
|
|
408
|
+
log(`Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempt}/${MAX_RECONNECT_ATTEMPTS})...`);
|
|
409
|
+
this.reconnectTimer = setTimeout(() => {
|
|
410
|
+
this.openStream().catch((err) => {
|
|
411
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
412
|
+
logError(`Reconnect stream error: ${msg}`);
|
|
413
|
+
});
|
|
414
|
+
}, delay);
|
|
415
|
+
}
|
|
416
|
+
emitSafe(event, ...args) {
|
|
417
|
+
try {
|
|
418
|
+
const handler = this.events[event];
|
|
419
|
+
if (handler) {
|
|
420
|
+
handler(...args);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
425
|
+
logError(`User callback "${event}" threw: ${msg}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
//# sourceMappingURL=relay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relay.js","sourceRoot":"","sources":["../src/relay.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface ArkitekConfig {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
autoReconnect?: boolean;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface IncomingMessage {
|
|
7
|
+
messageId: string;
|
|
8
|
+
content: string;
|
|
9
|
+
images?: string[];
|
|
10
|
+
userId: string;
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ConnectedEvent {
|
|
14
|
+
agentId: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
export interface PingEvent {
|
|
18
|
+
t: number;
|
|
19
|
+
}
|
|
20
|
+
export interface RespondPayload {
|
|
21
|
+
messageId: string;
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
export interface RespondResponse {
|
|
25
|
+
success: boolean;
|
|
26
|
+
delivered: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface CouncilRequest {
|
|
29
|
+
prompt: string;
|
|
30
|
+
models?: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface CouncilResponse {
|
|
33
|
+
success: boolean;
|
|
34
|
+
responses: CouncilModelResponse[];
|
|
35
|
+
}
|
|
36
|
+
export interface CouncilModelResponse {
|
|
37
|
+
modelId: string;
|
|
38
|
+
response: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
export type MessageHandler = (message: IncomingMessage) => Promise<string>;
|
|
42
|
+
export interface ArkitekRelayEvents {
|
|
43
|
+
onConnect?: (agentId: string) => void;
|
|
44
|
+
onDisconnect?: (reason: string) => void;
|
|
45
|
+
onError?: (error: Error) => void;
|
|
46
|
+
}
|
|
47
|
+
export type ConnectionState = "disconnected" | "connecting" | "connected" | "auth_failed" | "reconnecting";
|
|
48
|
+
export declare const DEFAULT_BASE_URL = "https://arkitek.dev/v1/agents/relay";
|
|
49
|
+
export declare const API_KEY_PATTERN: RegExp;
|
|
50
|
+
export declare const MAX_RESPONSE_SIZE: number;
|
|
51
|
+
export declare const MAX_COUNCIL_PROMPT_SIZE: number;
|
|
52
|
+
export declare const MAX_COUNCIL_MODELS = 8;
|
|
53
|
+
export declare const HEARTBEAT_TIMEOUT_MS = 60000;
|
|
54
|
+
export declare const RECONNECT_BASE_MS = 1000;
|
|
55
|
+
export declare const RECONNECT_MAX_MS = 30000;
|
|
56
|
+
export declare const MAX_RECONNECT_ATTEMPTS = 50;
|
|
57
|
+
export declare const RESPOND_TIMEOUT_MS = 15000;
|
|
58
|
+
export declare const COUNCIL_TIMEOUT_MS = 60000;
|
|
59
|
+
export declare const MAX_CONCURRENT_HANDLERS = 10;
|
|
60
|
+
export declare const RESPOND_MAX_RETRIES = 2;
|
|
61
|
+
export declare const RESPOND_RETRY_DELAY_MS = 1000;
|
|
62
|
+
export declare const MAX_SSE_BUFFER_SIZE: number;
|
|
63
|
+
export declare const MAX_IMAGES_PER_MESSAGE = 20;
|
|
64
|
+
export declare const MAX_IMAGE_SIZE: number;
|
|
65
|
+
export declare const LOG_PREFIX = "[ArkiTek Relay]";
|
|
66
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAE3E,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,YAAY,GACZ,WAAW,GACX,aAAa,GACb,cAAc,CAAC;AAEnB,eAAO,MAAM,gBAAgB,wCAAwC,CAAC;AACtE,eAAO,MAAM,eAAe,QAAyB,CAAC;AACtD,eAAO,MAAM,iBAAiB,QAAa,CAAC;AAC5C,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAClD,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAC3C,eAAO,MAAM,iBAAiB,OAAQ,CAAC;AACvC,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,sBAAsB,OAAQ,CAAC;AAC5C,eAAO,MAAM,mBAAmB,QAAgB,CAAC;AACjD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,cAAc,QAAqB,CAAC;AACjD,eAAO,MAAM,UAAU,oBAAoB,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const DEFAULT_BASE_URL = "https://arkitek.dev/v1/agents/relay";
|
|
2
|
+
export const API_KEY_PATTERN = /^ak_[a-zA-Z0-9]{64}$/;
|
|
3
|
+
export const MAX_RESPONSE_SIZE = 500 * 1024; // 500KB
|
|
4
|
+
export const MAX_COUNCIL_PROMPT_SIZE = 100 * 1024; // 100KB
|
|
5
|
+
export const MAX_COUNCIL_MODELS = 8;
|
|
6
|
+
export const HEARTBEAT_TIMEOUT_MS = 60_000;
|
|
7
|
+
export const RECONNECT_BASE_MS = 1_000;
|
|
8
|
+
export const RECONNECT_MAX_MS = 30_000;
|
|
9
|
+
export const MAX_RECONNECT_ATTEMPTS = 50;
|
|
10
|
+
export const RESPOND_TIMEOUT_MS = 15_000;
|
|
11
|
+
export const COUNCIL_TIMEOUT_MS = 60_000;
|
|
12
|
+
export const MAX_CONCURRENT_HANDLERS = 10;
|
|
13
|
+
export const RESPOND_MAX_RETRIES = 2;
|
|
14
|
+
export const RESPOND_RETRY_DELAY_MS = 1_000;
|
|
15
|
+
export const MAX_SSE_BUFFER_SIZE = 1_024 * 1_024; // 1MB
|
|
16
|
+
export const MAX_IMAGES_PER_MESSAGE = 20;
|
|
17
|
+
export const MAX_IMAGE_SIZE = 10 * 1_024 * 1_024; // 10MB per image string
|
|
18
|
+
export const LOG_PREFIX = "[ArkiTek Relay]";
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAgEA,MAAM,CAAC,MAAM,gBAAgB,GAAG,qCAAqC,CAAC;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG,sBAAsB,CAAC;AACtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,QAAQ;AACrD,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,QAAQ;AAC3D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC;AACpC,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC;AAC3C,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AACvC,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AACvC,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC;AACrC,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,CAAC;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM;AACxD,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC,wBAAwB;AAC1E,MAAM,CAAC,MAAM,UAAU,GAAG,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAEA,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAShD;AAED,wBAAgB,cAAc,IAAI,IAAI,CAMrC;AAKD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAahD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { API_KEY_PATTERN, LOG_PREFIX } from "./types.js";
|
|
2
|
+
export function maskKey(key) {
|
|
3
|
+
if (!key || key.length < 8)
|
|
4
|
+
return "ak_****";
|
|
5
|
+
return `ak_****...${key.slice(-4)}`;
|
|
6
|
+
}
|
|
7
|
+
export function validateApiKey(key) {
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw new Error("ARKITEK_API_KEY is required");
|
|
10
|
+
}
|
|
11
|
+
if (!API_KEY_PATTERN.test(key)) {
|
|
12
|
+
throw new Error(`Invalid API key format. Expected ak_ prefix followed by 64 alphanumeric characters (67 total). Got ${key.length} characters.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function checkTlsSafety() {
|
|
16
|
+
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0") {
|
|
17
|
+
throw new Error("NODE_TLS_REJECT_UNAUTHORIZED=0 detected. Refusing to connect — TLS verification must be enabled for secure communication with ArkiTek.");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const MAX_WARNED_URLS = 50;
|
|
21
|
+
const warnedUrls = new Set();
|
|
22
|
+
export function warnIfNotHttps(url) {
|
|
23
|
+
if (warnedUrls.has(url))
|
|
24
|
+
return;
|
|
25
|
+
if (!url.startsWith("https://") && !url.startsWith("http://localhost") && !url.startsWith("http://127.0.0.1")) {
|
|
26
|
+
console.warn(`${LOG_PREFIX} WARNING: baseUrl "${url}" is not using HTTPS. ` +
|
|
27
|
+
"Your API key may be transmitted in plain text. " +
|
|
28
|
+
"Use HTTPS in production to prevent credential exposure.");
|
|
29
|
+
if (warnedUrls.size >= MAX_WARNED_URLS) {
|
|
30
|
+
warnedUrls.clear();
|
|
31
|
+
}
|
|
32
|
+
warnedUrls.add(url);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7C,OAAO,aAAa,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CACb,sGAAsG,GAAG,CAAC,MAAM,cAAc,CAC/H,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,CAAC,GAAG,CAAC,4BAA4B,KAAK,GAAG,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CACb,wIAAwI,CACzI,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;AAErC,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAChC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QAC9G,OAAO,CAAC,IAAI,CACV,GAAG,UAAU,sBAAsB,GAAG,wBAAwB;YAC5D,iDAAiD;YACjD,yDAAyD,CAC5D,CAAC;QACF,IAAI,UAAU,CAAC,IAAI,IAAI,eAAe,EAAE,CAAC;YACvC,UAAU,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arkitek-relay-skill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Connect your OpenClaw agent to ArkiTek via secure outbound SSE relay",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"clean": "rm -rf dist"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"openclaw",
|
|
27
|
+
"arkitek",
|
|
28
|
+
"agent",
|
|
29
|
+
"relay",
|
|
30
|
+
"sse",
|
|
31
|
+
"skill"
|
|
32
|
+
],
|
|
33
|
+
"author": "",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/raleighgardner16-source/arkitek-relay-skill.git"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^20.11.0",
|
|
46
|
+
"typescript": "^5.3.0",
|
|
47
|
+
"vitest": "^1.2.0"
|
|
48
|
+
}
|
|
49
|
+
}
|