@remits/remits-cli 0.1.162 → 0.1.164
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 +4 -0
- package/index.js +41 -20
- package/lib/local-ai.js +46 -6
- package/lib/websocket-retry.js +67 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -139,6 +139,10 @@ remits-cli install --skills --overwrite true
|
|
|
139
139
|
- The service groups sessions by `baseUrl` so one websocket connection can service multiple authenticated accounts on the same Remits environment.
|
|
140
140
|
- Websocket subscriptions are deduplicated by user topic. If multiple authenticated accounts share the same websocket topic, the listener subscribes once and maps that topic back to all related accounts.
|
|
141
141
|
- The daemon keeps STOMP heartbeats enabled and runs a maintenance watchdog that recreates unhealthy websocket connections.
|
|
142
|
+
- Terminal server authentication/authorization errors suspend WebSocket retries and show
|
|
143
|
+
`authentication_required`. Reauthenticate to replace the managed client's credentials; the watchdog
|
|
144
|
+
does not revive rejected credentials. Transient failures use jittered exponential backoff up to
|
|
145
|
+
60 seconds. Test watchers keep HTTP polling; restart local-AI workers after reauthentication.
|
|
142
146
|
- If the machine sleeps, the network drops, or auth sessions change while the daemon is already running, the service now attempts to reconnect and resubscribe automatically once connectivity returns.
|
|
143
147
|
- Support tickets are the primary unit of dispatched work. A serving agent launches one fresh worker process per routed ticket.
|
|
144
148
|
- Agent sessions register themselves with `remits-cli agent register` or `remits-cli agent serve`; `serve` also starts the local supervisor.
|
package/index.js
CHANGED
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
## Table of Contents
|
|
5
5
|
|
|
6
6
|
- L22 Runtime Bootstrap And Shared State
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
7
|
+
- L125 Sessions, Account Resolution, And Production Guards
|
|
8
|
+
- L631 Local State, Workspaces, And Verification Evidence
|
|
9
|
+
- L2669 Account Repos, Guide Sync, And Platform Repo
|
|
10
|
+
- L3509 Component Discovery And HTTP Logging
|
|
11
|
+
- L4277 Skill Delivery And TOC Resolution
|
|
12
|
+
- L4592 Auth And Component Staging
|
|
13
|
+
- L5224 Component Summaries, Status, And Sync Gates
|
|
14
|
+
- L7548 Branches, Promotion, Commit, And Test Runs
|
|
15
|
+
- L9802 Tokens, Tools, Verification, And Config
|
|
16
|
+
- L11319 Service Dashboard And WebSocket Listener
|
|
17
|
+
- L13829 Agent And Ticket Workflows
|
|
18
|
+
- L18090 Help, Auto Update, And Command Dispatch
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
/*
|
|
@@ -32,6 +32,7 @@ const axios = require('axios');
|
|
|
32
32
|
const openModule = require('open');
|
|
33
33
|
const { Client } = require('@stomp/stompjs');
|
|
34
34
|
const WebSocket = require('ws');
|
|
35
|
+
const { installWebsocketRetry } = require('./lib/websocket-retry');
|
|
35
36
|
const YAML = require('yaml');
|
|
36
37
|
|
|
37
38
|
Object.assign(global, { WebSocket });
|
|
@@ -4160,7 +4161,7 @@ const WORKLOAD_BUSY_MAX_RETRIES = 6;
|
|
|
4160
4161
|
* How long to wait before retrying a request the platform refused as `WORKLOAD_BUSY`, or null when the
|
|
4161
4162
|
* failure is anything else (or the retries are spent).
|
|
4162
4163
|
*
|
|
4163
|
-
* The platform admits only a few heavyweight requests
|
|
4164
|
+
* The platform admits only a few heavyweight requests at once and answers the rest with HTTP 429
|
|
4164
4165
|
* BEFORE doing any work, so repeating the identical request is safe — which is not true of any other
|
|
4165
4166
|
* failure, and is why this matches on the code rather than on the status alone. The wait starts at the
|
|
4166
4167
|
* server's Retry-After and grows, with jitter so several refused agents do not come back in step.
|
|
@@ -4186,7 +4187,7 @@ async function postRidingOutBusy(api, endpoint, payload) {
|
|
|
4186
4187
|
} catch (err) {
|
|
4187
4188
|
const delayMs = workloadBusyRetryDelayMs(err, attempt, maxRetries, Math.random);
|
|
4188
4189
|
if (delayMs == null) throw err;
|
|
4189
|
-
console.error('Platform
|
|
4190
|
+
console.error('Platform is busy with ' + (err.response.data.workload || 'other work') +
|
|
4190
4191
|
'; retrying in ' + Math.round(delayMs / 1000) + 's (' + (attempt + 1) + '/' + maxRetries + ').');
|
|
4191
4192
|
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
4192
4193
|
}
|
|
@@ -9144,7 +9145,16 @@ function watchWebsocket(baseUrl, topicId, taskId, options = {}) {
|
|
|
9144
9145
|
const emit = options.stderr ? console.error : console.log;
|
|
9145
9146
|
const client = new Client({
|
|
9146
9147
|
brokerURL: webSocketUrl(baseUrl),
|
|
9147
|
-
connectHeaders: {
|
|
9148
|
+
connectHeaders: {
|
|
9149
|
+
'X-Auth-Token': options.token || '',
|
|
9150
|
+
'x-remits-ws-label': 'cli-test',
|
|
9151
|
+
'x-remits-client-kind': 'cli',
|
|
9152
|
+
'x-remits-tab-id': 'cli:' + process.pid + ':test:' + crypto.randomUUID(),
|
|
9153
|
+
'x-remits-origin': 'remits-cli://test',
|
|
9154
|
+
'x-remits-path': 'test/watch',
|
|
9155
|
+
'x-remits-location': normalizeBaseUrl(baseUrl),
|
|
9156
|
+
'x-remits-broker': webSocketUrl(baseUrl)
|
|
9157
|
+
},
|
|
9148
9158
|
reconnectDelay: 3000,
|
|
9149
9159
|
heartbeatIncoming: 10000,
|
|
9150
9160
|
heartbeatOutgoing: 10000,
|
|
@@ -9176,6 +9186,7 @@ function watchWebsocket(baseUrl, topicId, taskId, options = {}) {
|
|
|
9176
9186
|
});
|
|
9177
9187
|
};
|
|
9178
9188
|
|
|
9189
|
+
installWebsocketRetry(client, { onBlocked: code => emit('[ws] Authentication required (' + code + '); HTTP polling continues.') });
|
|
9179
9190
|
client.activate();
|
|
9180
9191
|
return () => {
|
|
9181
9192
|
client.deactivate();
|
|
@@ -9228,7 +9239,7 @@ function testRunOptionsFromFlags(flags = {}) {
|
|
|
9228
9239
|
// How long a later shard of a parallel run waits for a free suite slot before the launch gives up.
|
|
9229
9240
|
const SHARD_ADMISSION_WAIT_MS = 30 * 60 * 1000;
|
|
9230
9241
|
|
|
9231
|
-
// A start the platform refused because every suite slot
|
|
9242
|
+
// A start the platform refused because every suite slot is taken (safe to repeat: no work began).
|
|
9232
9243
|
function shardStartBusy(err) {
|
|
9233
9244
|
const response = err && err.response;
|
|
9234
9245
|
return !!(response && response.status === 429 && response.data && response.data.code === 'WORKLOAD_BUSY');
|
|
@@ -9340,7 +9351,7 @@ async function testCommand(flags) {
|
|
|
9340
9351
|
};
|
|
9341
9352
|
// Shards are started one after another (each start returns at once) and then run side by side on the platform.
|
|
9342
9353
|
const shardStarts = [];
|
|
9343
|
-
// The platform admits only a few suites
|
|
9354
|
+
// The platform admits only a few suites at once and refuses the rest as WORKLOAD_BUSY. Once one
|
|
9344
9355
|
// of our shards is running it holds a slot it will give back, so a later shard keeps waiting for one (bounded)
|
|
9345
9356
|
// instead of giving up after the usual retries and leaving a group that can never finish.
|
|
9346
9357
|
const shardWaitDeadline = Date.now() + SHARD_ADMISSION_WAIT_MS;
|
|
@@ -9354,7 +9365,7 @@ async function testCommand(flags) {
|
|
|
9354
9365
|
const data = err && err.response && err.response.data;
|
|
9355
9366
|
if (shardStarts.length && shardStartBusy(err) && Date.now() < shardWaitDeadline) {
|
|
9356
9367
|
console.error('Shard ' + (shardStarts.length + 1) + ' of ' + runOptions.parallel + ' is waiting for a free suite slot ' +
|
|
9357
|
-
'(
|
|
9368
|
+
'(the platform runs a limited number of suites at once; the shards already running free them).');
|
|
9358
9369
|
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
9359
9370
|
continue;
|
|
9360
9371
|
}
|
|
@@ -13538,6 +13549,7 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13538
13549
|
discardWebsocketOnCommFailure: true,
|
|
13539
13550
|
connectHeaders: {
|
|
13540
13551
|
'x-remits-ws-label': 'remits-cli',
|
|
13552
|
+
'x-remits-client-kind': 'cli',
|
|
13541
13553
|
'x-remits-ws-scope': headerScope,
|
|
13542
13554
|
'x-remits-user-id': headerUserIds.length === 1 ? headerUserIds[0] : '',
|
|
13543
13555
|
'x-remits-account-id': headerAccountIds.length === 1 ? headerAccountIds[0] : '',
|
|
@@ -13643,7 +13655,7 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13643
13655
|
ensureRuntimeWebsocketEntry(normalizedBaseUrl);
|
|
13644
13656
|
runtimeState.websocket[normalizedBaseUrl] = {
|
|
13645
13657
|
...runtimeState.websocket[normalizedBaseUrl],
|
|
13646
|
-
status: 'disconnected',
|
|
13658
|
+
status: client.remitsAuthBlocked ? 'authentication_required' : 'disconnected',
|
|
13647
13659
|
disconnectedAt: new Date().toISOString()
|
|
13648
13660
|
};
|
|
13649
13661
|
console.log('[listener] Disconnected from', normalizedBaseUrl, '- will reconnect');
|
|
@@ -13673,7 +13685,7 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13673
13685
|
ensureRuntimeWebsocketEntry(normalizedBaseUrl);
|
|
13674
13686
|
runtimeState.websocket[normalizedBaseUrl] = {
|
|
13675
13687
|
...runtimeState.websocket[normalizedBaseUrl],
|
|
13676
|
-
status: 'disconnected',
|
|
13688
|
+
status: client.remitsAuthBlocked ? 'authentication_required' : 'disconnected',
|
|
13677
13689
|
disconnectedAt: new Date().toISOString(),
|
|
13678
13690
|
lastError: detail
|
|
13679
13691
|
};
|
|
@@ -13700,6 +13712,12 @@ function createManagedWebsocketClient(baseUrl, urlSessions) {
|
|
|
13700
13712
|
};
|
|
13701
13713
|
|
|
13702
13714
|
managedClient.client = client;
|
|
13715
|
+
installWebsocketRetry(client, { onBlocked: code => {
|
|
13716
|
+
runtimeState.websocket[normalizedBaseUrl] = {
|
|
13717
|
+
...runtimeState.websocket[normalizedBaseUrl], status: 'authentication_required', lastError: code
|
|
13718
|
+
};
|
|
13719
|
+
updateRuntimeServiceState();
|
|
13720
|
+
} });
|
|
13703
13721
|
return managedClient;
|
|
13704
13722
|
}
|
|
13705
13723
|
|
|
@@ -13745,6 +13763,9 @@ async function runManagedWebsocketWatchdog() {
|
|
|
13745
13763
|
if (!managedClient || !managedClient.client) {
|
|
13746
13764
|
continue;
|
|
13747
13765
|
}
|
|
13766
|
+
// A session-file change replaces the client. The watchdog must not revive rejected credentials
|
|
13767
|
+
// or defeat the normal transport backoff while it is waiting to reconnect.
|
|
13768
|
+
if (managedClient.client.remitsAuthBlocked || managedClient.client.remitsRetryPending) continue;
|
|
13748
13769
|
const state = ensureRuntimeWebsocketEntry(managedClient.baseUrl);
|
|
13749
13770
|
const socket = managedClient.client.webSocket;
|
|
13750
13771
|
const readyState = socket && typeof socket.readyState === 'number' ? socket.readyState : null;
|
package/lib/local-ai.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require('node:fs');
|
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const crypto = require('node:crypto');
|
|
8
8
|
const { spawn } = require('node:child_process');
|
|
9
|
+
const { installWebsocketRetry } = require('./websocket-retry');
|
|
9
10
|
|
|
10
11
|
const MAX_BYTES = 8 * 1024 * 1024;
|
|
11
12
|
const AGENTS = ['manual', 'codex', 'claude', 'gemini'];
|
|
@@ -354,6 +355,13 @@ function describe(state) {
|
|
|
354
355
|
return parts.length ? ` (${parts.join('; ')})` : '';
|
|
355
356
|
}
|
|
356
357
|
|
|
358
|
+
// HTTP polling recovers what a WebSocket notification missed, renews worker presence (it expires after 90
|
|
359
|
+
// seconds) and notices cancelled requests. With a live socket and nothing in flight, notifications carry
|
|
360
|
+
// delivery and a slow poll only renews presence; otherwise poll fast. Idle `ai serve` processes had been
|
|
361
|
+
// the largest share of all requests on the CLI node (2026-10-23: one request every 2s each, around the clock).
|
|
362
|
+
const FAST_POLL_MS = 2000;
|
|
363
|
+
const IDLE_POLL_MS = 30000;
|
|
364
|
+
|
|
357
365
|
class Supervisor {
|
|
358
366
|
constructor({ api, dir, worker, agent, model, log = console.error, run = runNative, clock = Date.now, onStop }) {
|
|
359
367
|
Object.assign(this, { api, dir, worker, agent, model, log, run, clock, onStop });
|
|
@@ -361,14 +369,23 @@ class Supervisor {
|
|
|
361
369
|
this.busySessions = new Set();
|
|
362
370
|
this.stopping = false;
|
|
363
371
|
this.polling = false;
|
|
372
|
+
this.socketReady = false;
|
|
364
373
|
this.lastContact = clock();
|
|
365
374
|
}
|
|
366
375
|
|
|
376
|
+
nextPollDelayMs() {
|
|
377
|
+
return this.socketReady && this.active.size === 0 ? IDLE_POLL_MS : FAST_POLL_MS;
|
|
378
|
+
}
|
|
379
|
+
|
|
367
380
|
async register() { return this.api('register', { ...this.worker, agent: this.agent }); }
|
|
368
381
|
|
|
369
382
|
async tick() {
|
|
370
|
-
if (this.
|
|
383
|
+
if (this.stopping) return;
|
|
384
|
+
// A wakeup during a poll gets ONE follow-up poll: that poll's answer may predate the notification,
|
|
385
|
+
// and the next scheduled poll can be 30 seconds away.
|
|
386
|
+
if (this.polling) { this.pollAgain = true; return; }
|
|
371
387
|
this.polling = true;
|
|
388
|
+
this.pollAgain = false;
|
|
372
389
|
try {
|
|
373
390
|
const work = await this.api('work');
|
|
374
391
|
this.lastContact = this.clock();
|
|
@@ -402,6 +419,7 @@ class Supervisor {
|
|
|
402
419
|
this.onStop?.();
|
|
403
420
|
}
|
|
404
421
|
} finally { this.polling = false; }
|
|
422
|
+
if (this.pollAgain && !this.stopping) await this.tick();
|
|
405
423
|
}
|
|
406
424
|
|
|
407
425
|
async handle(summary, task) {
|
|
@@ -565,7 +583,15 @@ async function command({ subcommand, flags, context, root, axios, Client, WebSoc
|
|
|
565
583
|
console.error(`Local AI serving ${baseUrl} account ${accountId} ${dataMode}: ${agent}, worker ${worker.workerId}, capacity ${maxConcurrent}. Ctrl-C to stop.`);
|
|
566
584
|
process.once('SIGINT', stop);
|
|
567
585
|
process.once('SIGTERM', stop);
|
|
568
|
-
|
|
586
|
+
const schedule = () => {
|
|
587
|
+
if (supervisor.stopping) return;
|
|
588
|
+
timer = setTimeout(async () => {
|
|
589
|
+
try { await supervisor.tick(); } finally { schedule(); }
|
|
590
|
+
}, supervisor.nextPollDelayMs());
|
|
591
|
+
};
|
|
592
|
+
// A socket change re-paces at once, so losing the socket never leaves a 30-second gap.
|
|
593
|
+
const repace = () => { clearTimeout(timer); schedule(); };
|
|
594
|
+
schedule();
|
|
569
595
|
const topic = session.websocketTopic || String(session.user.uuid || '').replace(/-/g, '');
|
|
570
596
|
if (topic) {
|
|
571
597
|
socket = new Client({
|
|
@@ -577,23 +603,37 @@ async function command({ subcommand, flags, context, root, axios, Client, WebSoc
|
|
|
577
603
|
connectHeaders: { 'X-Auth-Token': session.token,
|
|
578
604
|
'x-remits-ws-label': 'remits-cli-ai', 'x-remits-user-id': String(session.user.id) },
|
|
579
605
|
onConnect: () => {
|
|
606
|
+
supervisor.socketReady = true;
|
|
607
|
+
repace();
|
|
580
608
|
socket.subscribe('/topic/messages/' + topic, (message) => {
|
|
581
609
|
try {
|
|
582
610
|
const event = JSON.parse(message.body);
|
|
583
611
|
if (event.type === 'cli_ai_request' && event.payload?.workerId === worker.workerId &&
|
|
584
|
-
String(event.payload.accountId) === String(accountId) && event.payload.dataMode === dataMode)
|
|
612
|
+
String(event.payload.accountId) === String(accountId) && event.payload.dataMode === dataMode) {
|
|
613
|
+
// A request that starts here needs the fast poll at once, to notice a cancellation.
|
|
614
|
+
supervisor.tick().finally(repace);
|
|
615
|
+
}
|
|
585
616
|
} catch (_) { /* HTTP polling is authoritative. */ }
|
|
586
617
|
});
|
|
587
618
|
supervisor.tick();
|
|
588
619
|
},
|
|
589
|
-
|
|
620
|
+
onWebSocketClose: () => { supervisor.socketReady = false; repace(); },
|
|
621
|
+
onStompError: () => {
|
|
622
|
+
supervisor.socketReady = false;
|
|
623
|
+
repace();
|
|
624
|
+
console.error('Local AI WebSocket unavailable; authenticated polling continues.');
|
|
625
|
+
}
|
|
590
626
|
});
|
|
627
|
+
installWebsocketRetry(socket, { onBlocked: code => {
|
|
628
|
+
console.error(`Local AI WebSocket authentication required (${code}); reauthenticate and restart the worker.`);
|
|
629
|
+
} });
|
|
591
630
|
socket.activate();
|
|
592
631
|
}
|
|
593
632
|
await supervisor.tick();
|
|
594
633
|
await stopped;
|
|
595
634
|
} finally {
|
|
596
|
-
|
|
635
|
+
if (supervisor) supervisor.stopping = true;
|
|
636
|
+
clearTimeout(timer);
|
|
597
637
|
process.removeListener('SIGINT', stop);
|
|
598
638
|
process.removeListener('SIGTERM', stop);
|
|
599
639
|
if (socket) await socket.deactivate({ force: true });
|
|
@@ -602,5 +642,5 @@ async function command({ subcommand, flags, context, root, axios, Client, WebSoc
|
|
|
602
642
|
}
|
|
603
643
|
}
|
|
604
644
|
|
|
605
|
-
module.exports = { command, help, Supervisor, runNative, nativeCommand, consumeEvent, promptFor, parseDecision,
|
|
645
|
+
module.exports = { command, help, Supervisor, FAST_POLL_MS, IDLE_POLL_MS, runNative, nativeCommand, consumeEvent, promptFor, parseDecision,
|
|
606
646
|
validateDecision, decisionSchema, offeredTools, continuation, assertTextRequest, stateDirectory, acquireLock, read, write, identifier };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The server sends stable, credential-free codes. A transport close code alone (including 1002)
|
|
4
|
+
// cannot distinguish expired credentials from a transient broker failure.
|
|
5
|
+
const TERMINAL_CODES = new Set(['credentials_missing', 'credentials_rejected', 'credentials_malformed',
|
|
6
|
+
'session_required', 'topic_denied', 'destination_denied', 'recipient_denied']);
|
|
7
|
+
|
|
8
|
+
function terminalCode(frame) {
|
|
9
|
+
const headers = frame?.headers || {};
|
|
10
|
+
const code = headers['x-remits-error-code'];
|
|
11
|
+
return TERMINAL_CODES.has(code) ? code : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function installWebsocketRetry(client, { onBlocked = () => {}, random = Math.random,
|
|
15
|
+
setTimer = setTimeout, clearTimer = clearTimeout } = {}) {
|
|
16
|
+
const base = Math.max(1, Number(client.reconnectDelay) || 5000);
|
|
17
|
+
let failures = 0, wait = 0, timer, release;
|
|
18
|
+
const cancelWait = () => {
|
|
19
|
+
if (timer) clearTimer(timer);
|
|
20
|
+
timer = null;
|
|
21
|
+
if (release) release();
|
|
22
|
+
release = null;
|
|
23
|
+
client.remitsRetryPending = false;
|
|
24
|
+
};
|
|
25
|
+
const before = client.beforeConnect;
|
|
26
|
+
client.beforeConnect = async () => {
|
|
27
|
+
if (client.remitsAuthBlocked) { await client.deactivate(); return; }
|
|
28
|
+
if (wait > 0) {
|
|
29
|
+
client.remitsRetryPending = true;
|
|
30
|
+
await new Promise(resolve => { release = resolve; timer = setTimer(cancelWait, wait); });
|
|
31
|
+
// A stop/replacement may have resolved the wait; never read or send credentials afterwards.
|
|
32
|
+
if (!client.active || client.remitsAuthBlocked) return;
|
|
33
|
+
}
|
|
34
|
+
client.remitsRetryPending = false;
|
|
35
|
+
if (before) await before();
|
|
36
|
+
};
|
|
37
|
+
const connected = client.onConnect;
|
|
38
|
+
client.onConnect = frame => {
|
|
39
|
+
failures = 0; wait = 0; client.remitsRetryPending = false;
|
|
40
|
+
if (connected) connected(frame);
|
|
41
|
+
};
|
|
42
|
+
const closed = client.onWebSocketClose;
|
|
43
|
+
client.onWebSocketClose = event => {
|
|
44
|
+
const ceiling = Math.min(60000, base * Math.pow(2, Math.min(++failures, 5)));
|
|
45
|
+
// STOMP already waits base ms. Add jitter/backoff before its next CONNECT using public APIs.
|
|
46
|
+
wait = Math.max(0, Math.floor(ceiling / 2 + random() * ceiling / 2) - base);
|
|
47
|
+
client.remitsRetryPending = !client.remitsAuthBlocked;
|
|
48
|
+
if (closed) closed(event);
|
|
49
|
+
};
|
|
50
|
+
const error = client.onStompError;
|
|
51
|
+
client.onStompError = frame => {
|
|
52
|
+
const code = terminalCode(frame);
|
|
53
|
+
if (code) {
|
|
54
|
+
client.remitsAuthBlocked = code;
|
|
55
|
+
cancelWait();
|
|
56
|
+
// deactivate changes active synchronously, before STOMP schedules the close reconnect.
|
|
57
|
+
Promise.resolve(client.deactivate({ force: true })).catch(() => {});
|
|
58
|
+
}
|
|
59
|
+
if (error) error(frame);
|
|
60
|
+
if (code) onBlocked(code);
|
|
61
|
+
};
|
|
62
|
+
const deactivate = client.deactivate.bind(client);
|
|
63
|
+
client.deactivate = options => { cancelWait(); return deactivate(options); };
|
|
64
|
+
return client;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { installWebsocketRetry, terminalCode };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remits/remits-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.164",
|
|
4
4
|
"description": "Local CLI for auth, component sync, and live test execution against Remits",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"index.js",
|
|
14
14
|
"lib/sync-operation.js",
|
|
15
15
|
"lib/local-ai.js",
|
|
16
|
+
"lib/websocket-retry.js",
|
|
16
17
|
"README.md",
|
|
17
18
|
"scripts/prepare-index-toc.js",
|
|
18
19
|
"skills/remits-cli/SKILL.md",
|