@openagents-org/agent-launcher 0.2.143 → 0.2.145
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/package.json +1 -1
- package/registry.json +274 -66
- package/src/adapters/aider.js +634 -0
- package/src/adapters/amp.js +477 -0
- package/src/adapters/base.js +108 -7
- package/src/adapters/claude.js +36 -34
- package/src/adapters/cline-stream.js +632 -0
- package/src/adapters/cline.js +914 -0
- package/src/adapters/codex.js +14 -16
- package/src/adapters/copilot-stream-parser.js +380 -0
- package/src/adapters/copilot.js +796 -0
- package/src/adapters/cursor.js +9 -15
- package/src/adapters/gemini.js +10 -12
- package/src/adapters/goose-stream.js +268 -0
- package/src/adapters/goose.js +649 -0
- package/src/adapters/health-status.js +166 -0
- package/src/adapters/hermes.js +8 -17
- package/src/adapters/index.js +16 -1
- package/src/adapters/nanoclaw-bridge.js +264 -0
- package/src/adapters/nanoclaw-channel-install.js +411 -0
- package/src/adapters/nanoclaw-control.js +491 -0
- package/src/adapters/nanoclaw-delivery-store.js +0 -0
- package/src/adapters/nanoclaw-protocol.js +349 -0
- package/src/adapters/nanoclaw.js +661 -9
- package/src/adapters/opencode.js +541 -75
- package/src/agent-rows.js +13 -1
- package/src/auth-guidance.js +98 -0
- package/src/cli.js +24 -7
- package/src/config.js +8 -2
- package/src/daemon.js +86 -5
- package/src/installer.js +509 -10
- package/src/paths.js +125 -2
- package/src/tui.js +29 -2
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NanoClaw bridge protocol — pure, dependency-free helpers.
|
|
3
|
+
*
|
|
4
|
+
* NanoClaw is a CONTAINERIZED agent runtime, not an stdin/stdout CLI and not a
|
|
5
|
+
* direct LLM API. The OpenAgents bridge talks to a thin native NanoClaw
|
|
6
|
+
* `openagents` *channel* (the official extension point) over a local Unix
|
|
7
|
+
* socket (`<nanoclaw>/data/openagents.sock`). This module holds the wire
|
|
8
|
+
* format, id/addressing rules, secret redaction, and error classification —
|
|
9
|
+
* everything that can be unit-tested without any IO.
|
|
10
|
+
*
|
|
11
|
+
* Wire format: one JSON object per line ("\n"-delimited), both directions.
|
|
12
|
+
*
|
|
13
|
+
* OpenAgents adapter (client) → channel (server):
|
|
14
|
+
* { op: "hello", workspace, agent, protocol, secret } // secret REQUIRED
|
|
15
|
+
* { op: "inbound", platformId, threadId, msgId, text, sender, senderId, ts }
|
|
16
|
+
* { op: "ack", outId, platformId } // confirms an outbound was delivered
|
|
17
|
+
* { op: "cancel", platformId, msgId }
|
|
18
|
+
* { op: "ping" }
|
|
19
|
+
*
|
|
20
|
+
* channel (server) → OpenAgents adapter (client):
|
|
21
|
+
* { op: "ready", channelType, protocol } // ONLY after a valid hello
|
|
22
|
+
* { op: "outbound", platformId, threadId, outId, inReplyTo, kind, text, ts }
|
|
23
|
+
* { op: "status", platformId, state: "working"|"idle", ts }
|
|
24
|
+
* { op: "error", platformId?, code?, message } // sanitized by the channel
|
|
25
|
+
* { op: "pong" }
|
|
26
|
+
*
|
|
27
|
+
* Local trust boundary: `data/openagents/bridge.sock` (0600) lives in a 0700
|
|
28
|
+
* dir; the client must present a random per-host `secret` (read from the 0600
|
|
29
|
+
* `data/openagents/secret` file written by the channel) in `hello`. No frame
|
|
30
|
+
* other than `hello`/`ping` is honored before the handshake succeeds, and the
|
|
31
|
+
* server re-authenticates on every (re)connect. The secret never enters the
|
|
32
|
+
* Workspace, the container, or logs.
|
|
33
|
+
*
|
|
34
|
+
* Delivery is AT-LEAST-ONCE with dedup: the channel holds each outbound until
|
|
35
|
+
* the bridge ACKs it; the bridge ACKs only after the message is handed to the
|
|
36
|
+
* Workspace, and persists processed `outId`s so a replay is re-ACKed but not
|
|
37
|
+
* re-displayed. This is NOT unconditional exactly-once (see docs).
|
|
38
|
+
*
|
|
39
|
+
* The workspace TOKEN is deliberately NOT part of the protocol — the adapter
|
|
40
|
+
* does all workspace IO, so no OpenAgents credential is ever duplicated into
|
|
41
|
+
* the NanoClaw process. See [[nanoclaw-facts-and-arch]].
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
'use strict';
|
|
45
|
+
|
|
46
|
+
const crypto = require('crypto');
|
|
47
|
+
|
|
48
|
+
const PROTOCOL_VERSION = 1;
|
|
49
|
+
|
|
50
|
+
// Channel identity on the NanoClaw side. Each OpenAgents channel maps to a
|
|
51
|
+
// distinct messaging group (channel_type='openagents', platform_id=<below>),
|
|
52
|
+
// which — with session_mode 'shared' — yields one isolated NanoClaw session
|
|
53
|
+
// per OpenAgents channel.
|
|
54
|
+
const CHANNEL_TYPE = 'openagents';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Stable, collision-resistant platform id for an OpenAgents channel.
|
|
58
|
+
* Distinct OpenAgents channels → distinct platform ids → distinct NanoClaw
|
|
59
|
+
* sessions (isolation). Distinct workspaces never collide on one NanoClaw host.
|
|
60
|
+
* @param {string} workspaceId
|
|
61
|
+
* @param {string} channel OpenAgents channel/session name
|
|
62
|
+
* @returns {string}
|
|
63
|
+
*/
|
|
64
|
+
function platformIdFor(workspaceId, channel) {
|
|
65
|
+
const ws = String(workspaceId || 'ws').trim();
|
|
66
|
+
const ch = String(channel || 'general').trim() || 'general';
|
|
67
|
+
return `oa:${ws}:${ch}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Recover the OpenAgents channel from a platform id produced by platformIdFor.
|
|
72
|
+
* Returns null if the platform id isn't ours (defensive — ignore foreign
|
|
73
|
+
* platforms so we never cross-deliver another channel's traffic).
|
|
74
|
+
* @param {string} platformId
|
|
75
|
+
* @param {string} workspaceId
|
|
76
|
+
* @returns {string|null}
|
|
77
|
+
*/
|
|
78
|
+
function channelFromPlatformId(platformId, workspaceId) {
|
|
79
|
+
const prefix = `oa:${String(workspaceId || 'ws').trim()}:`;
|
|
80
|
+
if (typeof platformId !== 'string' || !platformId.startsWith(prefix)) return null;
|
|
81
|
+
const ch = platformId.slice(prefix.length);
|
|
82
|
+
return ch || null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Deterministic, unique message id for an inbound workspace message. Stable for
|
|
87
|
+
* the same source message (so a reconnect/redelivery does NOT create a second
|
|
88
|
+
* NanoClaw message — idempotency), and unique across messages (dedup). Prefer
|
|
89
|
+
* the workspace-issued id; fall back to a content+timestamp hash.
|
|
90
|
+
* @param {object} msg workspace message ({id, content, sessionId, ...})
|
|
91
|
+
* @param {string} workspaceId
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
function makeMessageId(msg, workspaceId) {
|
|
95
|
+
const ws = String(workspaceId || 'ws').trim();
|
|
96
|
+
const raw = msg && (msg.id || msg.messageId || msg.eventId);
|
|
97
|
+
if (raw) return `oa:${ws}:${raw}`;
|
|
98
|
+
const basis = JSON.stringify({
|
|
99
|
+
c: (msg && msg.content) || '',
|
|
100
|
+
s: (msg && (msg.sessionId || msg.channel)) || '',
|
|
101
|
+
t: (msg && (msg.timestamp || msg.ts)) || '',
|
|
102
|
+
});
|
|
103
|
+
const h = crypto.createHash('sha1').update(basis).digest('hex').slice(0, 20);
|
|
104
|
+
return `oa:${ws}:h:${h}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Should this inbound workspace message be forwarded to NanoClaw at all?
|
|
109
|
+
* Loop/echo guard: never forward the agent's OWN output back into NanoClaw
|
|
110
|
+
* (which would create an infinite agent→channel→agent loop), and never forward
|
|
111
|
+
* non-user control/status chatter.
|
|
112
|
+
* @param {object} msg
|
|
113
|
+
* @param {string} agentName this adapter's agent name
|
|
114
|
+
* @returns {boolean}
|
|
115
|
+
*/
|
|
116
|
+
function shouldForwardInbound(msg, agentName) {
|
|
117
|
+
if (!msg) return false;
|
|
118
|
+
const type = msg.messageType || msg.type;
|
|
119
|
+
if (type && type !== 'chat' && type !== 'message' && type !== 'text') return false;
|
|
120
|
+
const senderType = msg.senderType || msg.sender_type;
|
|
121
|
+
if (senderType === 'agent') return false; // our own / another agent's output
|
|
122
|
+
const sender = msg.senderName || msg.sender_name || msg.sender;
|
|
123
|
+
if (sender && agentName && String(sender) === String(agentName)) return false;
|
|
124
|
+
const content = (msg.content || msg.text || '').trim();
|
|
125
|
+
return content.length > 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Frame build / parse
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function encodeFrame(obj) {
|
|
133
|
+
return JSON.stringify(obj) + '\n';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildHello(workspace, agent, secret) {
|
|
137
|
+
return encodeFrame({ op: 'hello', workspace, agent, protocol: PROTOCOL_VERSION, secret: secret || '' });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function buildAck(outId, platformId) {
|
|
141
|
+
return encodeFrame({ op: 'ack', outId, platformId: platformId || null });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildInbound({ platformId, threadId = null, msgId, turnId, text, sender = 'user', senderId, ts }) {
|
|
145
|
+
return encodeFrame({
|
|
146
|
+
op: 'inbound',
|
|
147
|
+
platformId,
|
|
148
|
+
threadId: threadId == null ? null : threadId,
|
|
149
|
+
msgId,
|
|
150
|
+
// turnId identifies the OpenAgents delivery turn; the channel echoes it on
|
|
151
|
+
// outbound (best-effort: the last inbound turn on that platform, since
|
|
152
|
+
// NanoClaw does not expose per-message reply correlation to channels).
|
|
153
|
+
turnId: turnId || msgId,
|
|
154
|
+
text,
|
|
155
|
+
sender,
|
|
156
|
+
senderId: senderId || `oa:${sender}`,
|
|
157
|
+
ts: ts || new Date().toISOString(),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildCancel(platformId, msgId) {
|
|
162
|
+
return encodeFrame({ op: 'cancel', platformId, msgId: msgId || null });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function buildPing() {
|
|
166
|
+
return encodeFrame({ op: 'ping' });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Incremental line-delimited JSON parser. Feed it raw chunks; it returns the
|
|
171
|
+
* parsed frames found so far plus the unconsumed remainder to carry forward.
|
|
172
|
+
* Malformed lines are skipped (forward-compatibility), never thrown.
|
|
173
|
+
* @param {string} buffer accumulated text (prev remainder + new chunk)
|
|
174
|
+
* @returns {{frames: object[], rest: string}}
|
|
175
|
+
*/
|
|
176
|
+
function parseFrames(buffer) {
|
|
177
|
+
const frames = [];
|
|
178
|
+
let rest = buffer;
|
|
179
|
+
let idx;
|
|
180
|
+
while ((idx = rest.indexOf('\n')) >= 0) {
|
|
181
|
+
const line = rest.slice(0, idx).trim();
|
|
182
|
+
rest = rest.slice(idx + 1);
|
|
183
|
+
if (!line) continue;
|
|
184
|
+
try {
|
|
185
|
+
const obj = JSON.parse(line);
|
|
186
|
+
if (obj && typeof obj === 'object' && typeof obj.op === 'string') frames.push(obj);
|
|
187
|
+
} catch {
|
|
188
|
+
// ignore non-JSON / partial garbage
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { frames, rest };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Secret redaction — never let tokens / keys / cookies / message bodies leak
|
|
196
|
+
// into logs.
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
const _SECRET_PATTERNS = [
|
|
200
|
+
[/(Bearer\s+)[A-Za-z0-9._\-]+/gi, '$1[REDACTED]'],
|
|
201
|
+
[/(Authorization"?\s*[:=]\s*"?)[^"\s,&]+/gi, '$1[REDACTED]'],
|
|
202
|
+
[/(Cookie"?\s*[:=]\s*"?)[^"\n]+/gi, '$1[REDACTED]'],
|
|
203
|
+
[/([?&](?:token|api[_-]?key|apikey|key|secret|password|auth[_-]?token)=)[^&\s"]+/gi, '$1[REDACTED]'],
|
|
204
|
+
[/((?:api[_-]?key|apikey|secret|password|token|auth[_-]?token)"?\s*[:=]\s*"?)[^"\s,}]+/gi, '$1[REDACTED]'],
|
|
205
|
+
[/\bsk-[A-Za-z0-9_\-]{12,}\b/g, 'sk-[REDACTED]'],
|
|
206
|
+
[/\bsk-ant-[A-Za-z0-9_\-]{12,}\b/g, 'sk-ant-[REDACTED]'],
|
|
207
|
+
[/\bgh[oprsu]_[A-Za-z0-9]{20,}\b/g, 'gh_[REDACTED]'],
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Redact secrets from an arbitrary string. Optionally scrub a list of exact
|
|
212
|
+
* secret values (e.g. the live workspace token) before pattern-based passes.
|
|
213
|
+
* @param {string} str
|
|
214
|
+
* @param {string[]} [extraSecrets]
|
|
215
|
+
* @returns {string}
|
|
216
|
+
*/
|
|
217
|
+
function redactSecrets(str, extraSecrets = []) {
|
|
218
|
+
if (str == null) return '';
|
|
219
|
+
let s = String(str);
|
|
220
|
+
for (const secret of extraSecrets) {
|
|
221
|
+
if (secret && String(secret).length >= 6) {
|
|
222
|
+
s = s.split(String(secret)).join('[REDACTED]');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
for (const [re, repl] of _SECRET_PATTERNS) s = s.replace(re, repl);
|
|
226
|
+
return s;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Error classification — distinct, user-facing categories. Detailed cause is
|
|
231
|
+
// returned separately (already redactable) for logs; userMessage is safe to
|
|
232
|
+
// show a non-technical user.
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
const ERR = {
|
|
236
|
+
NOT_INSTALLED: 'not_installed',
|
|
237
|
+
DOCKER_UNAVAILABLE: 'docker_unavailable',
|
|
238
|
+
HOST_NOT_RUNNING: 'host_not_running',
|
|
239
|
+
AGENT_GROUP_MISSING: 'agent_group_missing',
|
|
240
|
+
CHANNEL_NOT_LOADED: 'channel_not_loaded',
|
|
241
|
+
WIRING_MISSING: 'wiring_missing',
|
|
242
|
+
CREDS_MISSING: 'creds_missing',
|
|
243
|
+
AUTH_FAILED: 'auth_failed',
|
|
244
|
+
INCOMPATIBLE_VERSION: 'incompatible_version',
|
|
245
|
+
SINGLE_CONNECTION: 'single_connection',
|
|
246
|
+
DELIVERY_OVERFLOW: 'delivery_overflow',
|
|
247
|
+
DELIVERY_EXPIRED: 'delivery_expired',
|
|
248
|
+
DELIVERY_CORRUPT: 'delivery_corrupt',
|
|
249
|
+
CONTAINER_START_FAILED: 'container_start_failed',
|
|
250
|
+
SEND_FAILED: 'send_failed',
|
|
251
|
+
TIMEOUT: 'timeout',
|
|
252
|
+
DISCONNECTED: 'disconnected',
|
|
253
|
+
RECONNECT_FAILED: 'reconnect_failed',
|
|
254
|
+
DUPLICATE: 'duplicate',
|
|
255
|
+
RUNTIME_CRASHED: 'runtime_crashed',
|
|
256
|
+
UNKNOWN: 'unknown',
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const _ERR_MESSAGES = {
|
|
260
|
+
[ERR.NOT_INSTALLED]:
|
|
261
|
+
'NanoClaw is not installed or could not be found. Set NANOCLAW_HOME to your NanoClaw checkout (or put `ncl` on your PATH).',
|
|
262
|
+
[ERR.DOCKER_UNAVAILABLE]:
|
|
263
|
+
'Docker is not available. NanoClaw runs each agent group in a container — start Docker Desktop / the Docker daemon (on Windows use WSL2).',
|
|
264
|
+
[ERR.HOST_NOT_RUNNING]:
|
|
265
|
+
'The NanoClaw host service is not running. Start it (e.g. `./nanoclaw.sh start`, launchd, or systemd) and try again.',
|
|
266
|
+
[ERR.AGENT_GROUP_MISSING]:
|
|
267
|
+
'The configured NanoClaw Agent Group was not found. Pick an existing group (NANOCLAW_AGENT_GROUP) or create one in NanoClaw.',
|
|
268
|
+
[ERR.CHANNEL_NOT_LOADED]:
|
|
269
|
+
'The NanoClaw `openagents` channel is not loaded. Install it (/add-openagents) and restart the NanoClaw host.',
|
|
270
|
+
[ERR.WIRING_MISSING]:
|
|
271
|
+
'This OpenAgents channel is not wired to the Agent Group yet. Approve the wiring in NanoClaw (creating wirings requires approval).',
|
|
272
|
+
[ERR.CREDS_MISSING]:
|
|
273
|
+
'NanoClaw is missing its provider credentials. Configure the agent group provider (e.g. Anthropic) in NanoClaw.',
|
|
274
|
+
[ERR.AUTH_FAILED]:
|
|
275
|
+
'Could not authenticate to the NanoClaw openagents channel (local secret missing or mismatched). Restart the NanoClaw host so a fresh secret is issued.',
|
|
276
|
+
[ERR.INCOMPATIBLE_VERSION]:
|
|
277
|
+
'This NanoClaw version is not compatible with the OpenAgents channel. Use a verified NanoClaw version, or upgrade the OpenAgents integration.',
|
|
278
|
+
[ERR.SINGLE_CONNECTION]:
|
|
279
|
+
'Another OpenAgents connector is already attached to this NanoClaw host. Only one connector per host is allowed; disconnect the other first.',
|
|
280
|
+
[ERR.DELIVERY_OVERFLOW]:
|
|
281
|
+
'A queued NanoClaw reply was dropped because the local outbox is full — it may not be recoverable.',
|
|
282
|
+
[ERR.DELIVERY_EXPIRED]:
|
|
283
|
+
'A queued NanoClaw reply expired before it could be delivered — it may not be recoverable.',
|
|
284
|
+
[ERR.DELIVERY_CORRUPT]:
|
|
285
|
+
'A corrupt NanoClaw outbox record was found and quarantined — a queued reply may not be recoverable.',
|
|
286
|
+
[ERR.CONTAINER_START_FAILED]:
|
|
287
|
+
'NanoClaw could not start the agent container. Check Docker and the NanoClaw host logs.',
|
|
288
|
+
[ERR.SEND_FAILED]: 'Could not deliver the message to NanoClaw. Retrying / check the host.',
|
|
289
|
+
[ERR.TIMEOUT]: 'NanoClaw did not reply in time. The agent may still be working — try again shortly.',
|
|
290
|
+
[ERR.DISCONNECTED]: 'Lost the connection to NanoClaw. Reconnecting…',
|
|
291
|
+
[ERR.RECONNECT_FAILED]: 'Could not reconnect to NanoClaw after several attempts.',
|
|
292
|
+
[ERR.DUPLICATE]: 'Duplicate message ignored.',
|
|
293
|
+
[ERR.RUNTIME_CRASHED]: 'The NanoClaw runtime exited unexpectedly.',
|
|
294
|
+
[ERR.UNKNOWN]: 'An unexpected error occurred talking to NanoClaw.',
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Classify a raw error/condition into a stable code + a safe user message.
|
|
299
|
+
* @param {Error|string|{code?:string}} err
|
|
300
|
+
* @param {{hint?:string}} [ctx]
|
|
301
|
+
* @returns {{code:string, userMessage:string, detail:string}}
|
|
302
|
+
*/
|
|
303
|
+
function classifyError(err, ctx = {}) {
|
|
304
|
+
const rawMsg = err == null ? '' : (err.message || String(err));
|
|
305
|
+
const lower = rawMsg.toLowerCase();
|
|
306
|
+
let code = (err && err.code && _ERR_MESSAGES[err.code]) ? err.code : null;
|
|
307
|
+
|
|
308
|
+
if (!code) {
|
|
309
|
+
if (/enoent|not found|no such file|cannot find|nanoclaw_home/.test(lower) && /ncl|nanoclaw|sock|home/.test(lower)) {
|
|
310
|
+
code = ERR.NOT_INSTALLED;
|
|
311
|
+
} else if (/docker/.test(lower) && /(not|cannot|unavailable|refused|daemon)/.test(lower)) {
|
|
312
|
+
code = ERR.DOCKER_UNAVAILABLE;
|
|
313
|
+
} else if (/econnrefused|host closed|ncl\.sock|control socket|not running/.test(lower)) {
|
|
314
|
+
code = ERR.HOST_NOT_RUNNING;
|
|
315
|
+
} else if (/agent group|agent_group/.test(lower) && /(missing|not found|unknown)/.test(lower)) {
|
|
316
|
+
code = ERR.AGENT_GROUP_MISSING;
|
|
317
|
+
} else if (/epipe|econnreset|socket hang up|disconnect|closed/.test(lower)) {
|
|
318
|
+
code = ERR.DISCONNECTED;
|
|
319
|
+
} else if (/timed out|timeout|etimedout/.test(lower)) {
|
|
320
|
+
code = ERR.TIMEOUT;
|
|
321
|
+
} else {
|
|
322
|
+
code = ERR.UNKNOWN;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
code,
|
|
327
|
+
userMessage: _ERR_MESSAGES[code] || _ERR_MESSAGES[ERR.UNKNOWN],
|
|
328
|
+
detail: redactSecrets(rawMsg, ctx.secrets || []),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = {
|
|
333
|
+
PROTOCOL_VERSION,
|
|
334
|
+
CHANNEL_TYPE,
|
|
335
|
+
ERR,
|
|
336
|
+
platformIdFor,
|
|
337
|
+
channelFromPlatformId,
|
|
338
|
+
makeMessageId,
|
|
339
|
+
shouldForwardInbound,
|
|
340
|
+
encodeFrame,
|
|
341
|
+
buildHello,
|
|
342
|
+
buildAck,
|
|
343
|
+
buildInbound,
|
|
344
|
+
buildCancel,
|
|
345
|
+
buildPing,
|
|
346
|
+
parseFrames,
|
|
347
|
+
redactSecrets,
|
|
348
|
+
classifyError,
|
|
349
|
+
};
|