wave-code 1.0.8 → 1.0.10
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/bin/wave-code.js +21 -0
- package/dist/contexts/useChat.js +39 -18
- package/dist/stdio/jsonRpcConnection.d.ts +10 -1
- package/dist/stdio/jsonRpcConnection.js +9 -1
- package/dist/stdio/stdioServer.d.ts +8 -0
- package/dist/stdio/stdioServer.js +16 -1
- package/package.json +2 -2
- package/src/contexts/useChat.tsx +42 -20
- package/src/stdio/jsonRpcConnection.ts +19 -1
- package/src/stdio/stdioServer.ts +17 -0
package/bin/wave-code.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { readFileSync, writeSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
// `wave -v` / `wave --version` must be fast: editors probe the installed CLI
|
|
8
|
+
// version on every launch (e.g. the desktop app's auto-update check). Loading
|
|
9
|
+
// the full app graph (wave-agent-sdk, ink, highlight.js, ...) just to print
|
|
10
|
+
// the version takes 2-3s+ on a warm machine and can exceed callers' probe
|
|
11
|
+
// timeouts on cold starts (AV scan of freshly installed files), which they
|
|
12
|
+
// misread as "CLI missing/corrupt" → spurious re-installs. Print the version
|
|
13
|
+
// straight from package.json and exit before touching the app graph.
|
|
14
|
+
const versionArgs = ["-v", "--version"];
|
|
15
|
+
if (process.argv.slice(2).some((a) => versionArgs.includes(a))) {
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const packageJson = JSON.parse(
|
|
18
|
+
readFileSync(path.resolve(__dirname, "../package.json"), "utf-8"),
|
|
19
|
+
);
|
|
20
|
+
writeSync(1, `${packageJson.version}\n`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
// Import and start the CLI
|
|
4
25
|
import("../dist/index.js")
|
|
5
26
|
.then(async ({ main }) => {
|
package/dist/contexts/useChat.js
CHANGED
|
@@ -14,6 +14,21 @@ export const useChat = () => {
|
|
|
14
14
|
}
|
|
15
15
|
return context;
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* Snapshot a SDK message for consumer state. The SDK mutates its internal
|
|
19
|
+
* message blocks in-place BEFORE firing the delta callback (it writes the full
|
|
20
|
+
* accumulated value to the shared block, then computes the chunk delta by
|
|
21
|
+
* slicing the new value). A consumer that pushed the SDK message object by
|
|
22
|
+
* live reference would read the already-updated block and append the delta
|
|
23
|
+
* again — the first delta is double-counted ("LetLet me think..."), affecting
|
|
24
|
+
* reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
|
|
25
|
+
* The clone must be at least one layer deep (message + blocks) so the in-place
|
|
26
|
+
* block mutation never leaks into consumer state.
|
|
27
|
+
*/
|
|
28
|
+
const snapshotMessage = (message) => ({
|
|
29
|
+
...message,
|
|
30
|
+
blocks: message.blocks.map((block) => ({ ...block })),
|
|
31
|
+
});
|
|
17
32
|
/**
|
|
18
33
|
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
19
34
|
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
@@ -322,7 +337,7 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
322
337
|
// the incremental callbacks in initializeAgent below.
|
|
323
338
|
const refreshMessages = useCallback(() => {
|
|
324
339
|
if (!isExpandedRef.current && agentRef.current) {
|
|
325
|
-
const msgs =
|
|
340
|
+
const msgs = agentRef.current.messages.map(snapshotMessage);
|
|
326
341
|
setMessages(msgs);
|
|
327
342
|
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
328
343
|
}
|
|
@@ -357,7 +372,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
357
372
|
const last = msgs[msgs.length - 1];
|
|
358
373
|
if (!last || last.role !== "user")
|
|
359
374
|
return;
|
|
360
|
-
setMessages((prev) => prev.some((m) => m.id === last.id)
|
|
375
|
+
setMessages((prev) => prev.some((m) => m.id === last.id)
|
|
376
|
+
? prev
|
|
377
|
+
: [...prev, snapshotMessage(last)]);
|
|
361
378
|
},
|
|
362
379
|
onAssistantMessageAdded: (messageId) => {
|
|
363
380
|
if (isExpandedRef.current || !agentRef.current)
|
|
@@ -365,7 +382,9 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
365
382
|
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
366
383
|
if (!msg)
|
|
367
384
|
return;
|
|
368
|
-
setMessages((prev) => prev.some((m) => m.id === messageId)
|
|
385
|
+
setMessages((prev) => prev.some((m) => m.id === messageId)
|
|
386
|
+
? prev
|
|
387
|
+
: [...prev, snapshotMessage(msg)]);
|
|
369
388
|
},
|
|
370
389
|
onAssistantContentUpdated: (params) => {
|
|
371
390
|
if (isExpandedRef.current)
|
|
@@ -388,19 +407,20 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
388
407
|
if (isExpandedRef.current)
|
|
389
408
|
return;
|
|
390
409
|
setMessages((prev) => {
|
|
391
|
-
// Append to the
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
: m
|
|
403
|
-
|
|
410
|
+
// Append to the LAST message only if it is an assistant message
|
|
411
|
+
// (the current turn's in-flight reply). If the last message is a
|
|
412
|
+
// user message, the error belongs BELOW it — create a new
|
|
413
|
+
// assistant message instead of polluting the stale assistant from
|
|
414
|
+
// an earlier turn (which surfaced the error above the latest
|
|
415
|
+
// user message and accumulated it there).
|
|
416
|
+
const last = prev[prev.length - 1];
|
|
417
|
+
if (last && last.role === "assistant") {
|
|
418
|
+
return prev.map((m, idx) => idx === prev.length - 1
|
|
419
|
+
? {
|
|
420
|
+
...m,
|
|
421
|
+
blocks: [...m.blocks, { type: "error", content: error }],
|
|
422
|
+
}
|
|
423
|
+
: m);
|
|
404
424
|
}
|
|
405
425
|
return [
|
|
406
426
|
...prev,
|
|
@@ -562,9 +582,10 @@ export const ChatProvider = ({ children, bypassPermissions, permissionMode: init
|
|
|
562
582
|
};
|
|
563
583
|
agent.setWorktreeSession(session);
|
|
564
584
|
}
|
|
565
|
-
// Get initial state
|
|
585
|
+
// Get initial state — snapshot the SDK messages (never hold live
|
|
586
|
+
// references; see snapshotMessage)
|
|
566
587
|
setSessionId(agent.sessionId);
|
|
567
|
-
setMessages(agent.messages);
|
|
588
|
+
setMessages(agent.messages.map(snapshotMessage));
|
|
568
589
|
setIsLoading(agent.isLoading);
|
|
569
590
|
setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
|
|
570
591
|
setIsCommandRunning(agent.isCommandRunning);
|
|
@@ -9,13 +9,22 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { Readable, Writable } from "stream";
|
|
11
11
|
import { AgentBridge } from "./agentBridge.js";
|
|
12
|
+
export interface JsonRpcConnectionOptions {
|
|
13
|
+
/** Called when the input stream reaches EOF (parent closed the pipe). Only
|
|
14
|
+
* stdio mode passes it — it must exit the process when its client dies;
|
|
15
|
+
* daemon mode never passes it (a detached client must not kill the daemon,
|
|
16
|
+
* see daemonServer.ts). */
|
|
17
|
+
onClose?: () => void;
|
|
18
|
+
}
|
|
12
19
|
export declare class JsonRpcConnection {
|
|
13
20
|
private input;
|
|
14
21
|
private output;
|
|
15
22
|
private bridge;
|
|
16
23
|
private rl;
|
|
17
24
|
private started;
|
|
18
|
-
|
|
25
|
+
private stoppedByOwner;
|
|
26
|
+
private onClose?;
|
|
27
|
+
constructor(input: Readable, output: Writable, bridge: AgentBridge, options?: JsonRpcConnectionOptions);
|
|
19
28
|
start(): void;
|
|
20
29
|
stop(): void;
|
|
21
30
|
handleLine(line: string): Promise<void>;
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
import readline from "readline";
|
|
11
11
|
import { PARSE_ERROR, INVALID_REQUEST, INTERNAL_ERROR, isRequest, isNotification, } from "./protocol.js";
|
|
12
12
|
export class JsonRpcConnection {
|
|
13
|
-
constructor(input, output, bridge) {
|
|
13
|
+
constructor(input, output, bridge, options = {}) {
|
|
14
14
|
this.input = input;
|
|
15
15
|
this.output = output;
|
|
16
16
|
this.bridge = bridge;
|
|
17
17
|
this.started = false;
|
|
18
|
+
this.stoppedByOwner = false;
|
|
19
|
+
this.onClose = options.onClose;
|
|
18
20
|
}
|
|
19
21
|
start() {
|
|
20
22
|
if (this.started)
|
|
@@ -39,9 +41,15 @@ export class JsonRpcConnection {
|
|
|
39
41
|
});
|
|
40
42
|
this.rl.on("close", () => {
|
|
41
43
|
this.started = false;
|
|
44
|
+
// stdin EOF from the owning client — not a deliberate stop(). stdio
|
|
45
|
+
// mode reacts by exiting the process (its parent is gone); daemon mode
|
|
46
|
+
// has no onClose so a detached client reset never kills the daemon.
|
|
47
|
+
if (!this.stoppedByOwner)
|
|
48
|
+
this.onClose?.();
|
|
42
49
|
});
|
|
43
50
|
}
|
|
44
51
|
stop() {
|
|
52
|
+
this.stoppedByOwner = true;
|
|
45
53
|
this.rl?.close();
|
|
46
54
|
this.rl = undefined;
|
|
47
55
|
this.started = false;
|
|
@@ -19,6 +19,14 @@ export declare class StdioServer {
|
|
|
19
19
|
get agentBridge(): AgentBridge;
|
|
20
20
|
start(): void;
|
|
21
21
|
stop(): void;
|
|
22
|
+
/**
|
|
23
|
+
* The owning client closed stdin (EOF) — it's gone, so this process must
|
|
24
|
+
* follow it. destroyAll() saves each session's transcript and drains
|
|
25
|
+
* auto-memory first; a timeout bounds it so a stuck agent can't orphan the
|
|
26
|
+
* exit. This is the real-world "parent died" path: without it the process
|
|
27
|
+
* lingers forever whenever an agent keeps event-loop handles alive.
|
|
28
|
+
*/
|
|
29
|
+
private onConnectionClosed;
|
|
22
30
|
handleLine(line: string): Promise<void>;
|
|
23
31
|
sendResponse(id: number | string | null, result?: unknown, error?: {
|
|
24
32
|
code: number;
|
|
@@ -13,7 +13,7 @@ export class StdioServer {
|
|
|
13
13
|
...options.bridgeOptions,
|
|
14
14
|
emit: (method, params, sessionId) => this.sendNotification(method, params, sessionId),
|
|
15
15
|
});
|
|
16
|
-
this.conn = new JsonRpcConnection(options.input ?? process.stdin, options.output ?? process.stdout, this.bridge);
|
|
16
|
+
this.conn = new JsonRpcConnection(options.input ?? process.stdin, options.output ?? process.stdout, this.bridge, { onClose: () => this.onConnectionClosed() });
|
|
17
17
|
}
|
|
18
18
|
get agentBridge() {
|
|
19
19
|
return this.bridge;
|
|
@@ -24,6 +24,21 @@ export class StdioServer {
|
|
|
24
24
|
stop() {
|
|
25
25
|
this.conn.stop();
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The owning client closed stdin (EOF) — it's gone, so this process must
|
|
29
|
+
* follow it. destroyAll() saves each session's transcript and drains
|
|
30
|
+
* auto-memory first; a timeout bounds it so a stuck agent can't orphan the
|
|
31
|
+
* exit. This is the real-world "parent died" path: without it the process
|
|
32
|
+
* lingers forever whenever an agent keeps event-loop handles alive.
|
|
33
|
+
*/
|
|
34
|
+
async onConnectionClosed() {
|
|
35
|
+
const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
36
|
+
await Promise.race([
|
|
37
|
+
this.bridge.destroyAll(),
|
|
38
|
+
new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)),
|
|
39
|
+
]);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
27
42
|
handleLine(line) {
|
|
28
43
|
return this.conn.handleLine(line);
|
|
29
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wave-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "CLI-based code assistant powered by AI, built with React and Ink",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"wrap-ansi": "^10.0.0",
|
|
44
44
|
"yargs": "^17.7.2",
|
|
45
45
|
"zod": "^3.23.8",
|
|
46
|
-
"wave-agent-sdk": "1.0.
|
|
46
|
+
"wave-agent-sdk": "1.0.10"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/react": "^19.1.8",
|
package/src/contexts/useChat.tsx
CHANGED
|
@@ -168,6 +168,22 @@ interface StreamingUpdateParams {
|
|
|
168
168
|
stage: "streaming" | "end";
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Snapshot a SDK message for consumer state. The SDK mutates its internal
|
|
173
|
+
* message blocks in-place BEFORE firing the delta callback (it writes the full
|
|
174
|
+
* accumulated value to the shared block, then computes the chunk delta by
|
|
175
|
+
* slicing the new value). A consumer that pushed the SDK message object by
|
|
176
|
+
* live reference would read the already-updated block and append the delta
|
|
177
|
+
* again — the first delta is double-counted ("LetLet me think..."), affecting
|
|
178
|
+
* reasoning and text content alike. See docs/specs/core/stream-content-updates.md.
|
|
179
|
+
* The clone must be at least one layer deep (message + blocks) so the in-place
|
|
180
|
+
* block mutation never leaks into consumer state.
|
|
181
|
+
*/
|
|
182
|
+
const snapshotMessage = (message: Message): Message => ({
|
|
183
|
+
...message,
|
|
184
|
+
blocks: message.blocks.map((block) => ({ ...block })),
|
|
185
|
+
});
|
|
186
|
+
|
|
171
187
|
/**
|
|
172
188
|
* Window-concat throttle for pure-delta streaming updates: chunks arriving
|
|
173
189
|
* within the cooldown window are merged so no delta is lost (a dropped delta
|
|
@@ -605,7 +621,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
605
621
|
// the incremental callbacks in initializeAgent below.
|
|
606
622
|
const refreshMessages = useCallback(() => {
|
|
607
623
|
if (!isExpandedRef.current && agentRef.current) {
|
|
608
|
-
const msgs =
|
|
624
|
+
const msgs = agentRef.current.messages.map(snapshotMessage);
|
|
609
625
|
setMessages(msgs);
|
|
610
626
|
setLatestTotalTokens(extractLatestTotalTokens(msgs));
|
|
611
627
|
}
|
|
@@ -656,7 +672,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
656
672
|
const last = msgs[msgs.length - 1];
|
|
657
673
|
if (!last || last.role !== "user") return;
|
|
658
674
|
setMessages((prev) =>
|
|
659
|
-
prev.some((m) => m.id === last.id)
|
|
675
|
+
prev.some((m) => m.id === last.id)
|
|
676
|
+
? prev
|
|
677
|
+
: [...prev, snapshotMessage(last)],
|
|
660
678
|
);
|
|
661
679
|
},
|
|
662
680
|
onAssistantMessageAdded: (messageId: string) => {
|
|
@@ -664,7 +682,9 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
664
682
|
const msg = agentRef.current.messages.find((m) => m.id === messageId);
|
|
665
683
|
if (!msg) return;
|
|
666
684
|
setMessages((prev) =>
|
|
667
|
-
prev.some((m) => m.id === messageId)
|
|
685
|
+
prev.some((m) => m.id === messageId)
|
|
686
|
+
? prev
|
|
687
|
+
: [...prev, snapshotMessage(msg)],
|
|
668
688
|
);
|
|
669
689
|
},
|
|
670
690
|
onAssistantContentUpdated: (params) => {
|
|
@@ -683,21 +703,22 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
683
703
|
onErrorBlockAdded: (error: string) => {
|
|
684
704
|
if (isExpandedRef.current) return;
|
|
685
705
|
setMessages((prev) => {
|
|
686
|
-
// Append to the
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
706
|
+
// Append to the LAST message only if it is an assistant message
|
|
707
|
+
// (the current turn's in-flight reply). If the last message is a
|
|
708
|
+
// user message, the error belongs BELOW it — create a new
|
|
709
|
+
// assistant message instead of polluting the stale assistant from
|
|
710
|
+
// an earlier turn (which surfaced the error above the latest
|
|
711
|
+
// user message and accumulated it there).
|
|
712
|
+
const last = prev[prev.length - 1];
|
|
713
|
+
if (last && last.role === "assistant") {
|
|
714
|
+
return prev.map((m, idx) =>
|
|
715
|
+
idx === prev.length - 1
|
|
716
|
+
? {
|
|
717
|
+
...m,
|
|
718
|
+
blocks: [...m.blocks, { type: "error", content: error }],
|
|
719
|
+
}
|
|
720
|
+
: m,
|
|
721
|
+
);
|
|
701
722
|
}
|
|
702
723
|
return [
|
|
703
724
|
...prev,
|
|
@@ -889,9 +910,10 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|
|
889
910
|
agent.setWorktreeSession(session);
|
|
890
911
|
}
|
|
891
912
|
|
|
892
|
-
// Get initial state
|
|
913
|
+
// Get initial state — snapshot the SDK messages (never hold live
|
|
914
|
+
// references; see snapshotMessage)
|
|
893
915
|
setSessionId(agent.sessionId);
|
|
894
|
-
setMessages(agent.messages);
|
|
916
|
+
setMessages(agent.messages.map(snapshotMessage));
|
|
895
917
|
setIsLoading(agent.isLoading);
|
|
896
918
|
setLatestTotalTokens(extractLatestTotalTokens(agent.messages));
|
|
897
919
|
setIsCommandRunning(agent.isCommandRunning);
|
|
@@ -22,15 +22,28 @@ import {
|
|
|
22
22
|
isNotification,
|
|
23
23
|
} from "./protocol.js";
|
|
24
24
|
|
|
25
|
+
export interface JsonRpcConnectionOptions {
|
|
26
|
+
/** Called when the input stream reaches EOF (parent closed the pipe). Only
|
|
27
|
+
* stdio mode passes it — it must exit the process when its client dies;
|
|
28
|
+
* daemon mode never passes it (a detached client must not kill the daemon,
|
|
29
|
+
* see daemonServer.ts). */
|
|
30
|
+
onClose?: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
25
33
|
export class JsonRpcConnection {
|
|
26
34
|
private rl: readline.Interface | undefined;
|
|
27
35
|
private started = false;
|
|
36
|
+
private stoppedByOwner = false;
|
|
37
|
+
private onClose?: () => void;
|
|
28
38
|
|
|
29
39
|
constructor(
|
|
30
40
|
private input: Readable,
|
|
31
41
|
private output: Writable,
|
|
32
42
|
private bridge: AgentBridge,
|
|
33
|
-
|
|
43
|
+
options: JsonRpcConnectionOptions = {},
|
|
44
|
+
) {
|
|
45
|
+
this.onClose = options.onClose;
|
|
46
|
+
}
|
|
34
47
|
|
|
35
48
|
start(): void {
|
|
36
49
|
if (this.started) return;
|
|
@@ -58,10 +71,15 @@ export class JsonRpcConnection {
|
|
|
58
71
|
|
|
59
72
|
this.rl.on("close", () => {
|
|
60
73
|
this.started = false;
|
|
74
|
+
// stdin EOF from the owning client — not a deliberate stop(). stdio
|
|
75
|
+
// mode reacts by exiting the process (its parent is gone); daemon mode
|
|
76
|
+
// has no onClose so a detached client reset never kills the daemon.
|
|
77
|
+
if (!this.stoppedByOwner) this.onClose?.();
|
|
61
78
|
});
|
|
62
79
|
}
|
|
63
80
|
|
|
64
81
|
stop(): void {
|
|
82
|
+
this.stoppedByOwner = true;
|
|
65
83
|
this.rl?.close();
|
|
66
84
|
this.rl = undefined;
|
|
67
85
|
this.started = false;
|
package/src/stdio/stdioServer.ts
CHANGED
|
@@ -30,6 +30,7 @@ export class StdioServer {
|
|
|
30
30
|
options.input ?? process.stdin,
|
|
31
31
|
options.output ?? process.stdout,
|
|
32
32
|
this.bridge,
|
|
33
|
+
{ onClose: () => this.onConnectionClosed() },
|
|
33
34
|
);
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -45,6 +46,22 @@ export class StdioServer {
|
|
|
45
46
|
this.conn.stop();
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/**
|
|
50
|
+
* The owning client closed stdin (EOF) — it's gone, so this process must
|
|
51
|
+
* follow it. destroyAll() saves each session's transcript and drains
|
|
52
|
+
* auto-memory first; a timeout bounds it so a stuck agent can't orphan the
|
|
53
|
+
* exit. This is the real-world "parent died" path: without it the process
|
|
54
|
+
* lingers forever whenever an agent keeps event-loop handles alive.
|
|
55
|
+
*/
|
|
56
|
+
private async onConnectionClosed(): Promise<void> {
|
|
57
|
+
const SHUTDOWN_TIMEOUT_MS = 5_000;
|
|
58
|
+
await Promise.race([
|
|
59
|
+
this.bridge.destroyAll(),
|
|
60
|
+
new Promise((resolve) => setTimeout(resolve, SHUTDOWN_TIMEOUT_MS)),
|
|
61
|
+
]);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
handleLine(line: string): Promise<void> {
|
|
49
66
|
return this.conn.handleLine(line);
|
|
50
67
|
}
|