interceptpilot-mcp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/package.json +1 -1
- package/src/daemon-client.js +46 -2
- package/src/daemon.js +45 -1
- package/src/index.js +46 -5
- package/src/mcp-server.js +2 -1
- package/src/package-version.js +16 -0
- package/src/protocol.js +5 -2
package/README.md
CHANGED
|
@@ -41,6 +41,8 @@ AI client
|
|
|
41
41
|
|
|
42
42
|
The MCP client starts the adapter. The adapter starts the local daemon when none is listening on the configured port; several adapters (several chats or clients) share one daemon and one key. If the extension disconnects, the daemon stays up and the tools return a safe error until the extension reconnects.
|
|
43
43
|
|
|
44
|
+
The daemon exits on its own after 10 minutes with no adapter and no extension connected. When you update the package, the newer adapter asks an older daemon to stop and starts a fresh one, so both always run the same version.
|
|
45
|
+
|
|
44
46
|
## Options
|
|
45
47
|
|
|
46
48
|
| Option | Meaning |
|
|
@@ -49,6 +51,7 @@ The MCP client starts the adapter. The adapter starts the local daemon when none
|
|
|
49
51
|
| `--port <PORT>` | Local daemon port. Default `37177`. |
|
|
50
52
|
| `--no-daemon-autostart` | Do not start the daemon automatically (debugging). |
|
|
51
53
|
| `daemon` | Run only the daemon: `npx -y interceptpilot-mcp daemon --port <PORT> --key <KEY>`. |
|
|
54
|
+
| `--version`, `--help` | Print the version or this usage. |
|
|
52
55
|
|
|
53
56
|
## Tools
|
|
54
57
|
|
|
@@ -100,6 +103,8 @@ What each tool exposes depends on the permissions you set in the extension. By d
|
|
|
100
103
|
|
|
101
104
|
**Port in use.** A healthy daemon on the port is reused by other adapters. If an old process is stuck, stop it or change the port in both the AI Bridge page and the client configuration.
|
|
102
105
|
|
|
106
|
+
**"An older interceptpilot-mcp daemon is running on port … and could not be replaced automatically."** A daemon from version 0.1.0 is still running; that version cannot be stopped remotely. End the `node` process that runs `interceptpilot-mcp daemon` (or restart the machine) and start the MCP client again. Later versions replace themselves without this step.
|
|
107
|
+
|
|
103
108
|
**Invalid or regenerated key.** Copy the configuration again from the AI Bridge page.
|
|
104
109
|
|
|
105
110
|
**Client using an old configuration.** Update the client configuration and restart the client. Permission changes happen in the extension and do not require a new key.
|
package/package.json
CHANGED
package/src/daemon-client.js
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
isClientResponse,
|
|
11
11
|
parseBridgeMessage
|
|
12
12
|
} from './protocol.js'
|
|
13
|
+
import { PACKAGE_VERSION, compareVersions } from './package-version.js'
|
|
13
14
|
import { startDaemonProcess as defaultStartDaemonProcess } from './process-manager.js'
|
|
14
15
|
|
|
15
16
|
const DEFAULT_REQUEST_TIMEOUT_MS = 5000
|
|
17
|
+
const DEFAULT_SHUTDOWN_TIMEOUT_MS = 1500
|
|
16
18
|
const DEFAULT_RETRY_DELAY_MS = 100
|
|
17
19
|
const DEFAULT_MAX_PENDING_REQUESTS = 25
|
|
18
20
|
const DEFAULT_MAX_CONNECT_ATTEMPTS = 30
|
|
@@ -22,6 +24,7 @@ const COMMAND_NOT_ALLOWED_MESSAGE = 'Command is not allowed.'
|
|
|
22
24
|
const TOO_MANY_PENDING_MESSAGE = 'Too many pending InterceptPilot requests.'
|
|
23
25
|
const DAEMON_NOT_RUNNING_MESSAGE = 'InterceptPilot daemon is not running. Start AI Bridge or run without --no-daemon-autostart.'
|
|
24
26
|
const DAEMON_START_FAILED_MESSAGE = 'Unable to start InterceptPilot daemon.'
|
|
27
|
+
const OUTDATED_DAEMON_MESSAGE = port => `An older interceptpilot-mcp daemon is running on port ${port} and could not be replaced automatically. End that node process (or restart the machine) and start the MCP client again.`
|
|
25
28
|
|
|
26
29
|
function createDefaultLogger() {
|
|
27
30
|
return {
|
|
@@ -63,6 +66,8 @@ export function createDaemonClientBridge(options = {}) {
|
|
|
63
66
|
maxPendingRequests = DEFAULT_MAX_PENDING_REQUESTS,
|
|
64
67
|
maxConnectAttempts = DEFAULT_MAX_CONNECT_ATTEMPTS,
|
|
65
68
|
retryDelayMs = DEFAULT_RETRY_DELAY_MS,
|
|
69
|
+
shutdownTimeoutMs = DEFAULT_SHUTDOWN_TIMEOUT_MS,
|
|
70
|
+
packageVersion = PACKAGE_VERSION,
|
|
66
71
|
idGenerator,
|
|
67
72
|
clientId = 'interceptpilot-mcp',
|
|
68
73
|
logger = createDefaultLogger(),
|
|
@@ -128,8 +133,9 @@ export function createDaemonClientBridge(options = {}) {
|
|
|
128
133
|
timer?.unref?.()
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
function connectAttempt(resolve, reject, attempt = 1, autostartTried = false) {
|
|
136
|
+
function connectAttempt(resolve, reject, attempt = 1, autostartTried = false, replaceTried = false) {
|
|
132
137
|
let settled = false
|
|
138
|
+
let replacing = false
|
|
133
139
|
const activeSocket = new WebSocketCtor(`ws://${host}:${port}`)
|
|
134
140
|
socket = activeSocket
|
|
135
141
|
|
|
@@ -142,7 +148,25 @@ export function createDaemonClientBridge(options = {}) {
|
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
function retryAfterAutostart() {
|
|
145
|
-
schedule(() => connectAttempt(resolve, reject, attempt + 1, true))
|
|
151
|
+
schedule(() => connectAttempt(resolve, reject, attempt + 1, true, replaceTried))
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// A daemon older than this adapter may not know newer commands. Ask it to
|
|
155
|
+
// stop and start a fresh one; a daemon too old to understand the request is
|
|
156
|
+
// kept, with a warning, so the user is not blocked.
|
|
157
|
+
function replaceOutdatedDaemon(daemonVersion) {
|
|
158
|
+
replacing = true
|
|
159
|
+
logger.info(`Replacing daemon ${daemonVersion || '(unknown version)'} with ${packageVersion}.`)
|
|
160
|
+
sendJson(activeSocket, { type: MESSAGE_TYPES.clientShutdown })
|
|
161
|
+
const timer = setTimeoutFn(() => {
|
|
162
|
+
if (settled) return
|
|
163
|
+
settled = true
|
|
164
|
+
replacing = false
|
|
165
|
+
connected = true
|
|
166
|
+
logger.warn(OUTDATED_DAEMON_MESSAGE(port))
|
|
167
|
+
resolve()
|
|
168
|
+
}, shutdownTimeoutMs)
|
|
169
|
+
timer?.unref?.()
|
|
146
170
|
}
|
|
147
171
|
|
|
148
172
|
addSocketListener(activeSocket, 'open', () => {
|
|
@@ -161,12 +185,20 @@ export function createDaemonClientBridge(options = {}) {
|
|
|
161
185
|
if (!message) return
|
|
162
186
|
|
|
163
187
|
if (message.type === MESSAGE_TYPES.clientHelloAck && message.ok === true && Number(message.version) === BRIDGE_PROTOCOL_VERSION) {
|
|
188
|
+
if (settled || replacing) return
|
|
189
|
+
const daemonVersion = String(message.packageVersion || '')
|
|
190
|
+
if (daemonAutostart && !replaceTried && compareVersions(daemonVersion, packageVersion) < 0) {
|
|
191
|
+
replaceOutdatedDaemon(daemonVersion)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
164
194
|
settled = true
|
|
165
195
|
connected = true
|
|
166
196
|
resolve()
|
|
167
197
|
return
|
|
168
198
|
}
|
|
169
199
|
|
|
200
|
+
if (message.type === MESSAGE_TYPES.clientShutdownAck) return
|
|
201
|
+
|
|
170
202
|
handleResponse(message)
|
|
171
203
|
})
|
|
172
204
|
|
|
@@ -200,6 +232,18 @@ export function createDaemonClientBridge(options = {}) {
|
|
|
200
232
|
})
|
|
201
233
|
|
|
202
234
|
addSocketListener(activeSocket, 'close', () => {
|
|
235
|
+
if (replacing && !settled) {
|
|
236
|
+
settled = true
|
|
237
|
+
socket = null
|
|
238
|
+
try {
|
|
239
|
+
startDaemonProcess({ port, key: connectionKey, logger })
|
|
240
|
+
} catch {
|
|
241
|
+
failStart(DAEMON_START_FAILED_MESSAGE)
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
schedule(() => connectAttempt(resolve, reject, attempt + 1, true, true))
|
|
245
|
+
return
|
|
246
|
+
}
|
|
203
247
|
if (!connected && !settled) {
|
|
204
248
|
settled = true
|
|
205
249
|
socket = null
|
package/src/daemon.js
CHANGED
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
validateClientHelloMessage,
|
|
15
15
|
validateHelloMessage
|
|
16
16
|
} from './protocol.js'
|
|
17
|
+
import { PACKAGE_VERSION } from './package-version.js'
|
|
17
18
|
|
|
18
19
|
const DEFAULT_REQUEST_TIMEOUT_MS = 5000
|
|
19
20
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 20000
|
|
21
|
+
const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000
|
|
20
22
|
const DEFAULT_MAX_PENDING_REQUESTS = 25
|
|
21
23
|
const NOT_CONNECTED_MESSAGE = 'InterceptPilot extension is not connected. Open InterceptPilot settings and start an AI Bridge session.'
|
|
22
24
|
const TIMEOUT_MESSAGE = 'InterceptPilot did not respond in time.'
|
|
@@ -62,6 +64,12 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
62
64
|
requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS,
|
|
63
65
|
heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
64
66
|
maxPendingRequests = DEFAULT_MAX_PENDING_REQUESTS,
|
|
67
|
+
// Exit when no MCP client has been connected for this long and the
|
|
68
|
+
// extension is not connected either. 0 disables it.
|
|
69
|
+
idleTimeoutMs = DEFAULT_IDLE_TIMEOUT_MS,
|
|
70
|
+
packageVersion = PACKAGE_VERSION,
|
|
71
|
+
onIdle = () => {},
|
|
72
|
+
onShutdown = () => {},
|
|
65
73
|
idGenerator,
|
|
66
74
|
logger = createDefaultLogger(),
|
|
67
75
|
setTimeoutFn = setTimeout,
|
|
@@ -79,6 +87,7 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
79
87
|
let extensionConnected = false
|
|
80
88
|
let requestSeq = 0
|
|
81
89
|
let heartbeatTimer = null
|
|
90
|
+
let idleTimer = null
|
|
82
91
|
|
|
83
92
|
function nextRequestId() {
|
|
84
93
|
if (idGenerator) return idGenerator()
|
|
@@ -104,6 +113,25 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
104
113
|
heartbeatTimer = null
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
function clearIdleTimer() {
|
|
117
|
+
if (!idleTimer) return
|
|
118
|
+
clearTimeoutFn(idleTimer)
|
|
119
|
+
idleTimer = null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function scheduleIdleCheck() {
|
|
123
|
+
clearIdleTimer()
|
|
124
|
+
if (!idleTimeoutMs || clients.size > 0 || isExtensionConnected()) return
|
|
125
|
+
idleTimer = setTimeoutFn(async () => {
|
|
126
|
+
idleTimer = null
|
|
127
|
+
if (clients.size > 0 || isExtensionConnected()) return
|
|
128
|
+
logger.info('No MCP client or extension connected; shutting down idle daemon.')
|
|
129
|
+
await close()
|
|
130
|
+
onIdle()
|
|
131
|
+
}, idleTimeoutMs)
|
|
132
|
+
idleTimer?.unref?.()
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
function rejectPendingForClient(clientSocket) {
|
|
108
136
|
for (const [extensionRequestId, request] of pending) {
|
|
109
137
|
if (request.clientSocket !== clientSocket) continue
|
|
@@ -131,6 +159,7 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
131
159
|
stopHeartbeat()
|
|
132
160
|
rejectAllPending(DISCONNECTED_MESSAGE)
|
|
133
161
|
logger.info('Extension disconnected.')
|
|
162
|
+
scheduleIdleCheck()
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
function detachClient(socket) {
|
|
@@ -138,6 +167,7 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
138
167
|
clients.delete(socket)
|
|
139
168
|
rejectPendingForClient(socket)
|
|
140
169
|
logger.info('MCP client disconnected.')
|
|
170
|
+
scheduleIdleCheck()
|
|
141
171
|
}
|
|
142
172
|
|
|
143
173
|
function acceptExtension(socket, message) {
|
|
@@ -156,6 +186,7 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
156
186
|
|
|
157
187
|
extensionSocket = socket
|
|
158
188
|
extensionConnected = true
|
|
189
|
+
clearIdleTimer()
|
|
159
190
|
sendJson(socket, createHelloAckMessage())
|
|
160
191
|
startHeartbeat()
|
|
161
192
|
logger.info('Extension connected.')
|
|
@@ -170,10 +201,20 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
170
201
|
}
|
|
171
202
|
|
|
172
203
|
clients.add(socket)
|
|
173
|
-
|
|
204
|
+
clearIdleTimer()
|
|
205
|
+
sendJson(socket, createClientHelloAckMessage(packageVersion))
|
|
174
206
|
logger.info('MCP client connected.')
|
|
175
207
|
}
|
|
176
208
|
|
|
209
|
+
// Only a client that passed the key check at hello can ask for this; a newer
|
|
210
|
+
// adapter uses it to replace an outdated daemon on the same port.
|
|
211
|
+
async function handleClientShutdown(socket) {
|
|
212
|
+
logger.info('Shutdown requested by an MCP client; stopping daemon.')
|
|
213
|
+
sendJson(socket, { type: MESSAGE_TYPES.clientShutdownAck, ok: true })
|
|
214
|
+
await close()
|
|
215
|
+
onShutdown()
|
|
216
|
+
}
|
|
217
|
+
|
|
177
218
|
function sendClientError(socket, id, code, message) {
|
|
178
219
|
sendJson(socket, createClientError(id, code, message))
|
|
179
220
|
}
|
|
@@ -262,6 +303,7 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
262
303
|
|
|
263
304
|
if (clients.has(socket)) {
|
|
264
305
|
if (message.type === MESSAGE_TYPES.clientCommand) handleClientCommand(socket, message)
|
|
306
|
+
else if (message.type === MESSAGE_TYPES.clientShutdown) handleClientShutdown(socket)
|
|
265
307
|
return
|
|
266
308
|
}
|
|
267
309
|
}
|
|
@@ -284,10 +326,12 @@ export function createInterceptPilotDaemon(options = {}) {
|
|
|
284
326
|
server.on('connection', handleSocket)
|
|
285
327
|
server.on('listening', () => logger.info(`Shared daemon listening on ${host}:${port}.`))
|
|
286
328
|
server.on('error', () => logger.error('Shared daemon WebSocket error.'))
|
|
329
|
+
scheduleIdleCheck()
|
|
287
330
|
return server
|
|
288
331
|
}
|
|
289
332
|
|
|
290
333
|
async function close() {
|
|
334
|
+
clearIdleTimer()
|
|
291
335
|
stopHeartbeat()
|
|
292
336
|
rejectAllPending(DISCONNECTED_MESSAGE)
|
|
293
337
|
if (extensionSocket) extensionSocket.close()
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,22 @@ import { BRIDGE_PORT } from './protocol.js'
|
|
|
7
7
|
import { createDaemonClientBridge } from './daemon-client.js'
|
|
8
8
|
import { createInterceptPilotDaemon } from './daemon.js'
|
|
9
9
|
import { createMcpServer } from './mcp-server.js'
|
|
10
|
+
import { PACKAGE_VERSION } from './package-version.js'
|
|
11
|
+
|
|
12
|
+
export const USAGE = `interceptpilot-mcp ${PACKAGE_VERSION}
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
interceptpilot-mcp --key <KEY> [--port <PORT>] [--no-daemon-autostart]
|
|
16
|
+
interceptpilot-mcp daemon --key <KEY> [--port <PORT>]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--key <KEY> Session key shown by the InterceptPilot AI Bridge page (required)
|
|
20
|
+
--port <PORT> Local daemon port (default ${BRIDGE_PORT})
|
|
21
|
+
--no-daemon-autostart Do not start the shared daemon automatically
|
|
22
|
+
--version Print the package version
|
|
23
|
+
--help Print this help
|
|
24
|
+
|
|
25
|
+
Copy the ready-made configuration from InterceptPilot > AI Bridge and paste it into your MCP client.`
|
|
10
26
|
|
|
11
27
|
export function parseCliArgs(argv = []) {
|
|
12
28
|
const options = {
|
|
@@ -14,7 +30,9 @@ export function parseCliArgs(argv = []) {
|
|
|
14
30
|
port: BRIDGE_PORT,
|
|
15
31
|
key: '',
|
|
16
32
|
daemonHost: '127.0.0.1',
|
|
17
|
-
daemonAutostart: true
|
|
33
|
+
daemonAutostart: true,
|
|
34
|
+
help: false,
|
|
35
|
+
version: false
|
|
18
36
|
}
|
|
19
37
|
|
|
20
38
|
const args = [...argv]
|
|
@@ -33,6 +51,10 @@ export function parseCliArgs(argv = []) {
|
|
|
33
51
|
i += 1
|
|
34
52
|
} else if (arg === '--no-daemon-autostart') {
|
|
35
53
|
options.daemonAutostart = false
|
|
54
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
55
|
+
options.help = true
|
|
56
|
+
} else if (arg === '--version' || arg === '-v') {
|
|
57
|
+
options.version = true
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
|
|
@@ -53,14 +75,17 @@ export async function createRuntime(options, factories = {}) {
|
|
|
53
75
|
createInterceptPilotDaemon: createInterceptPilotDaemonFn = createInterceptPilotDaemon,
|
|
54
76
|
createMcpServer: createMcpServerFn = createMcpServer,
|
|
55
77
|
StdioServerTransportCtor = StdioServerTransport,
|
|
56
|
-
logger = createStderrLogger()
|
|
78
|
+
logger = createStderrLogger(),
|
|
79
|
+
exit = code => process.exit(code)
|
|
57
80
|
} = factories
|
|
58
81
|
|
|
59
82
|
if (options.mode === 'daemon') {
|
|
60
83
|
const daemon = createInterceptPilotDaemonFn({
|
|
61
84
|
port: options.port,
|
|
62
85
|
expectedKey: options.key,
|
|
63
|
-
logger
|
|
86
|
+
logger,
|
|
87
|
+
onIdle: () => exit(0),
|
|
88
|
+
onShutdown: () => exit(0)
|
|
64
89
|
})
|
|
65
90
|
daemon.start()
|
|
66
91
|
return {
|
|
@@ -94,7 +119,21 @@ export async function createRuntime(options, factories = {}) {
|
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
export async function main(argv = process.argv.slice(2), factories = {}) {
|
|
97
|
-
const
|
|
122
|
+
const options = parseCliArgs(argv)
|
|
123
|
+
if (options.help) {
|
|
124
|
+
console.log(USAGE)
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
if (options.version) {
|
|
128
|
+
console.log(PACKAGE_VERSION)
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
if (!options.key) {
|
|
132
|
+
throw new Error(`--key is required.
|
|
133
|
+
|
|
134
|
+
${USAGE}`)
|
|
135
|
+
}
|
|
136
|
+
const runtime = await createRuntime(options, factories)
|
|
98
137
|
|
|
99
138
|
process.on('SIGINT', async () => {
|
|
100
139
|
await runtime.close()
|
|
@@ -106,7 +145,9 @@ export async function main(argv = process.argv.slice(2), factories = {}) {
|
|
|
106
145
|
|
|
107
146
|
const currentFile = fileURLToPath(import.meta.url)
|
|
108
147
|
if (process.argv[1] === currentFile) {
|
|
109
|
-
main().
|
|
148
|
+
main().then(runtime => {
|
|
149
|
+
if (!runtime) process.exit(0)
|
|
150
|
+
}).catch(error => {
|
|
110
151
|
console.error(`[interceptpilot-mcp] ${String(error?.message || 'Failed to start InterceptPilot MCP server.')}`)
|
|
111
152
|
process.exit(1)
|
|
112
153
|
})
|
package/src/mcp-server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import { PACKAGE_VERSION } from './package-version.js'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
4
5
|
import { getImportBundleExamples, getImportBundleSchema } from './import-bundle-schema.js'
|
|
@@ -658,7 +659,7 @@ export function createToolCallHandler({ bridge }) {
|
|
|
658
659
|
export function createMcpServer({ bridge }) {
|
|
659
660
|
const server = new McpServer({
|
|
660
661
|
name: 'interceptpilot-mcp',
|
|
661
|
-
version:
|
|
662
|
+
version: PACKAGE_VERSION
|
|
662
663
|
})
|
|
663
664
|
const handleToolCall = createToolCallHandler({ bridge })
|
|
664
665
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createRequire } from 'node:module'
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url)
|
|
4
|
+
|
|
5
|
+
export const PACKAGE_VERSION = String(require('../package.json').version || '0.0.0')
|
|
6
|
+
|
|
7
|
+
// Numeric semver compare on major.minor.patch; anything unparsable counts as 0.
|
|
8
|
+
export function compareVersions(a, b) {
|
|
9
|
+
const parse = value => String(value || '').split('.').slice(0, 3).map(part => Number.parseInt(part, 10) || 0)
|
|
10
|
+
const [left, right] = [parse(a), parse(b)]
|
|
11
|
+
for (let i = 0; i < 3; i += 1) {
|
|
12
|
+
const diff = (left[i] || 0) - (right[i] || 0)
|
|
13
|
+
if (diff !== 0) return diff < 0 ? -1 : 1
|
|
14
|
+
}
|
|
15
|
+
return 0
|
|
16
|
+
}
|
package/src/protocol.js
CHANGED
|
@@ -15,6 +15,8 @@ export const MESSAGE_TYPES = {
|
|
|
15
15
|
clientCommand: 'client:command',
|
|
16
16
|
clientResult: 'client:result',
|
|
17
17
|
clientError: 'client:error',
|
|
18
|
+
clientShutdown: 'client:shutdown',
|
|
19
|
+
clientShutdownAck: 'client:shutdown:ack',
|
|
18
20
|
ping: 'ping',
|
|
19
21
|
pong: 'pong'
|
|
20
22
|
}
|
|
@@ -65,11 +67,12 @@ export function createHelloAckMessage() {
|
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
export function createClientHelloAckMessage() {
|
|
70
|
+
export function createClientHelloAckMessage(packageVersion = '') {
|
|
69
71
|
return {
|
|
70
72
|
type: MESSAGE_TYPES.clientHelloAck,
|
|
71
73
|
ok: true,
|
|
72
|
-
version: BRIDGE_PROTOCOL_VERSION
|
|
74
|
+
version: BRIDGE_PROTOCOL_VERSION,
|
|
75
|
+
...(packageVersion ? { packageVersion: String(packageVersion) } : {})
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
|