tau-coding-agent 0.1.6 → 0.2.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/README.md +15 -12
- package/extensions/answer.ts +129 -79
- package/extensions/branch-term/README.md +7 -0
- package/extensions/{branch-term.ts → branch-term/index.ts} +113 -104
- package/extensions/btw.ts +17 -2
- package/extensions/caffeinate/README.md +5 -0
- package/extensions/caffeinate/index.ts +144 -0
- package/extensions/fast.ts +292 -0
- package/extensions/ghostty.ts +214 -211
- package/extensions/git-diff-stats.ts +124 -84
- package/extensions/git-pr-status.ts +274 -208
- package/extensions/insights.ts +109 -191
- package/extensions/loop.ts +149 -145
- package/extensions/memory.ts +138 -86
- package/extensions/notify.ts +14 -26
- package/extensions/openai-verbosity.ts +108 -42
- package/extensions/review/fix.ts +15 -6
- package/extensions/review/git.ts +93 -103
- package/extensions/review/index.ts +60 -62
- package/extensions/review/interrupt.ts +117 -0
- package/extensions/review/message-queue.ts +45 -12
- package/extensions/review/models.ts +79 -3
- package/extensions/review/prompts.ts +41 -38
- package/extensions/review/review.ts +161 -166
- package/extensions/review/runner.ts +139 -147
- package/extensions/review/runtime.ts +173 -120
- package/extensions/review/triage.ts +41 -44
- package/extensions/sandbox/bash.ts +775 -0
- package/extensions/sandbox/command.ts +605 -0
- package/extensions/sandbox/config.ts +764 -0
- package/extensions/sandbox/index.ts +132 -3338
- package/extensions/sandbox/permissions/dialog.ts +118 -0
- package/extensions/sandbox/permissions/filesystem.ts +559 -0
- package/extensions/sandbox/permissions/mach-lookup.ts +186 -0
- package/extensions/sandbox/permissions/network.ts +164 -0
- package/extensions/sandbox/permissions/unsandboxed.ts +270 -0
- package/extensions/sandbox/runtime.ts +616 -0
- package/extensions/stash.ts +28 -15
- package/extensions/subagent/README.md +73 -0
- package/extensions/subagent/index.ts +822 -0
- package/extensions/subagent/interrupt.ts +117 -0
- package/extensions/subagent/permissions.ts +101 -0
- package/extensions/subagent/rpc.ts +177 -0
- package/extensions/tool-display-mode.ts +267 -64
- package/extensions/usage/index.ts +249 -356
- package/extensions/usage/openrouter.ts +10 -2
- package/extensions/websearch/README.md +12 -47
- package/extensions/websearch/config.ts +5 -2
- package/extensions/websearch/index.ts +94 -109
- package/extensions/websearch/output.ts +51 -0
- package/extensions/websearch/providers/anthropic.pi.ts +27 -44
- package/extensions/websearch/providers/gemini.browser.ts +68 -77
- package/extensions/websearch/providers/gemini.pi.ts +17 -17
- package/extensions/websearch/providers/openai-codex.pi.ts +145 -7
- package/extensions/websearch/providers/pi-model.shared.ts +33 -39
- package/extensions/websearch/providers/shared.ts +87 -2
- package/extensions/websearch/types.ts +0 -3
- package/extensions/worktree.ts +132 -172
- package/package.json +10 -5
- package/skills/browser-tools/SKILL.md +29 -234
- package/skills/browser-tools/references/cookies.md +36 -0
- package/skills/browser-tools/references/interaction.md +90 -0
- package/skills/browser-tools/references/logging.md +34 -0
- package/skills/git-clean-history/SKILL.md +6 -6
- package/skills/git-commit/SKILL.md +5 -3
- package/skills/github-pull-request/SKILL.md +60 -0
- package/skills/github-pull-request/references/create.md +40 -0
- package/skills/github-pull-request/references/stewardship.md +60 -0
- package/skills/oracle/SKILL.md +3 -3
- package/skills/oracle/scripts/oracle +10 -1
- package/skills/sentry/SKILL.md +15 -185
- package/skills/sentry/references/events.md +79 -0
- package/skills/sentry/references/issues.md +62 -0
- package/skills/sentry/references/logs.md +46 -0
- package/skills/update-changelog/SKILL.md +27 -121
- package/skills/web-design/SKILL.md +16 -105
- package/themes/tau-dark.json +4 -0
- package/extensions/openai-fast.ts +0 -229
- package/extensions/websearch/providers/openai-codex.browser.ts +0 -77
- package/extensions/websearch/providers/openai-codex.shared.ts +0 -123
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import {
|
|
3
|
+
getKeybindings,
|
|
4
|
+
isKeyRelease,
|
|
5
|
+
type TuiMainScreen,
|
|
6
|
+
type Component,
|
|
7
|
+
type TUI,
|
|
8
|
+
} from "@earendil-works/pi-tui";
|
|
9
|
+
|
|
10
|
+
interface InterruptRequest {
|
|
11
|
+
sessionKey: string;
|
|
12
|
+
cancel?: boolean;
|
|
13
|
+
active: boolean;
|
|
14
|
+
confirming?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Each extension owns its guard; optional peers contribute work through the event bus. */
|
|
18
|
+
export function createInterruptGuard(
|
|
19
|
+
pi: ExtensionAPI,
|
|
20
|
+
ctx: ExtensionContext,
|
|
21
|
+
tui: TUI & Partial<Pick<TuiMainScreen, "getFocusedComponent">>,
|
|
22
|
+
isWorking: () => boolean,
|
|
23
|
+
cancel: () => void,
|
|
24
|
+
isPromptActive: () => boolean,
|
|
25
|
+
) {
|
|
26
|
+
const sessionKey =
|
|
27
|
+
ctx.sessionManager.getSessionFile() ?? `session:${ctx.sessionManager.getSessionId()}`;
|
|
28
|
+
let closed = false;
|
|
29
|
+
let pending:
|
|
30
|
+
| { controller: AbortController; editor: Component; completion: Promise<void> }
|
|
31
|
+
| undefined;
|
|
32
|
+
const unsubscribeWork = pi.events.on("tau:interrupt", (data) => {
|
|
33
|
+
const request = data as InterruptRequest;
|
|
34
|
+
if (request.sessionKey !== sessionKey || closed) return;
|
|
35
|
+
if (pending) request.confirming = true;
|
|
36
|
+
if (!isWorking()) return;
|
|
37
|
+
request.active = true;
|
|
38
|
+
if (request.cancel) cancel();
|
|
39
|
+
});
|
|
40
|
+
const unsubscribeChanged = pi.events.on("tau:work-changed", (data) => {
|
|
41
|
+
if ((data as { sessionKey: string }).sessionKey === sessionKey && pending && !hasWork())
|
|
42
|
+
pending.controller.abort();
|
|
43
|
+
});
|
|
44
|
+
const unsubscribeInput = ctx.ui.onTerminalInput((data) => {
|
|
45
|
+
if (isKeyRelease(data) || !getKeybindings().matches(data, "app.interrupt")) return;
|
|
46
|
+
const editor = tui.getFocusedComponent?.();
|
|
47
|
+
// A dismissed native dialog restores focus before its promise settles. Swallow a rapid
|
|
48
|
+
// extra Escape in that gap instead of accidentally interrupting the parent.
|
|
49
|
+
if (pending) return editor === pending.editor ? { consume: true } : undefined;
|
|
50
|
+
if (
|
|
51
|
+
closed ||
|
|
52
|
+
isPromptActive() ||
|
|
53
|
+
!editor ||
|
|
54
|
+
!("getText" in editor) ||
|
|
55
|
+
!("setText" in editor) ||
|
|
56
|
+
("isShowingAutocomplete" in editor &&
|
|
57
|
+
typeof editor.isShowingAutocomplete === "function" &&
|
|
58
|
+
editor.isShowingAutocomplete()) ||
|
|
59
|
+
!hasWork()
|
|
60
|
+
)
|
|
61
|
+
return;
|
|
62
|
+
const current = {
|
|
63
|
+
controller: new AbortController(),
|
|
64
|
+
editor,
|
|
65
|
+
completion: Promise.resolve(),
|
|
66
|
+
};
|
|
67
|
+
pending = current;
|
|
68
|
+
current.completion = (async () => {
|
|
69
|
+
try {
|
|
70
|
+
const request: {
|
|
71
|
+
run: (signal: AbortSignal) => Promise<void>;
|
|
72
|
+
signal: AbortSignal;
|
|
73
|
+
result?: Promise<unknown>;
|
|
74
|
+
} = {
|
|
75
|
+
signal: current.controller.signal,
|
|
76
|
+
async run(signal) {
|
|
77
|
+
const confirmed = await ctx.ui.confirm("Cancel all ongoing work?", "", { signal });
|
|
78
|
+
if (!confirmed || closed || signal.aborted || !hasWork()) return;
|
|
79
|
+
pi.events.emit("tau:interrupt", { sessionKey, active: false, cancel: true });
|
|
80
|
+
// Reuse the editor's native interrupt, including queue restoration, Bash and retries.
|
|
81
|
+
editor.handleInput?.(data);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
// Keep queued approvals blocked until the confirmed cancellation has been applied.
|
|
85
|
+
pi.events.emit("subagent:permission", request);
|
|
86
|
+
await (request.result ?? request.run(request.signal));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (!closed && !current.controller.signal.aborted)
|
|
89
|
+
ctx.ui.notify(`Could not confirm cancellation: ${String(error)}`, "warning");
|
|
90
|
+
} finally {
|
|
91
|
+
if (pending === current) pending = undefined;
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
return { consume: true };
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
refresh() {
|
|
99
|
+
if (!closed) pi.events.emit("tau:work-changed", { sessionKey });
|
|
100
|
+
},
|
|
101
|
+
async dispose() {
|
|
102
|
+
closed = true;
|
|
103
|
+
unsubscribeInput();
|
|
104
|
+
unsubscribeWork();
|
|
105
|
+
unsubscribeChanged();
|
|
106
|
+
pending?.controller.abort();
|
|
107
|
+
pi.events.emit("tau:work-changed", { sessionKey });
|
|
108
|
+
await pending?.completion;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
function hasWork(): boolean {
|
|
113
|
+
const request: InterruptRequest = { sessionKey, active: false };
|
|
114
|
+
pi.events.emit("tau:interrupt", request);
|
|
115
|
+
return request.active;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
/** Optional integration for sharing the parent's approval and confirmation queue. */
|
|
4
|
+
interface PermissionRequest {
|
|
5
|
+
run: (signal: AbortSignal) => Promise<unknown>;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
result?: Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function registerPermissions(pi: ExtensionAPI) {
|
|
11
|
+
let queue = new PermissionQueue();
|
|
12
|
+
pi.events.on("subagent:permission", (data) => {
|
|
13
|
+
const request = data as PermissionRequest;
|
|
14
|
+
request.result = queue.run(request.run, request.signal);
|
|
15
|
+
});
|
|
16
|
+
pi.on("ui_prompt_start", () => queue.setPromptActive(true));
|
|
17
|
+
pi.on("ui_prompt_end", () => queue.setPromptActive(false));
|
|
18
|
+
return {
|
|
19
|
+
get active() {
|
|
20
|
+
return queue.active;
|
|
21
|
+
},
|
|
22
|
+
run<T>(run: (signal: AbortSignal) => Promise<T>, signal: AbortSignal) {
|
|
23
|
+
return queue.run(run, signal);
|
|
24
|
+
},
|
|
25
|
+
async reset() {
|
|
26
|
+
await queue.close();
|
|
27
|
+
queue = new PermissionQueue();
|
|
28
|
+
},
|
|
29
|
+
close: () => queue.close(),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Cancel queued requests independently; stopping one child must not wait for another child's user. */
|
|
34
|
+
class PermissionQueue {
|
|
35
|
+
private readonly controller = new AbortController();
|
|
36
|
+
private readonly pending: (() => Promise<void>)[] = [];
|
|
37
|
+
private current?: Promise<void>;
|
|
38
|
+
private promptActive = false;
|
|
39
|
+
|
|
40
|
+
get active(): boolean {
|
|
41
|
+
return this.promptActive || this.current !== undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
run<T>(run: (signal: AbortSignal) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
45
|
+
const combined = signal
|
|
46
|
+
? AbortSignal.any([signal, this.controller.signal])
|
|
47
|
+
: this.controller.signal;
|
|
48
|
+
return new Promise<T>((resolve, reject) => {
|
|
49
|
+
if (combined.aborted) {
|
|
50
|
+
reject(combined.reason);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const start = async () => {
|
|
54
|
+
try {
|
|
55
|
+
combined.throwIfAborted();
|
|
56
|
+
resolve(await run(combined));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
reject(error);
|
|
59
|
+
} finally {
|
|
60
|
+
combined.removeEventListener("abort", cancel);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const cancel = () => {
|
|
64
|
+
const index = this.pending.indexOf(start);
|
|
65
|
+
if (index === -1) return;
|
|
66
|
+
this.pending.splice(index, 1);
|
|
67
|
+
combined.removeEventListener("abort", cancel);
|
|
68
|
+
reject(combined.reason);
|
|
69
|
+
};
|
|
70
|
+
combined.addEventListener("abort", cancel, { once: true });
|
|
71
|
+
this.pending.push(start);
|
|
72
|
+
this.advance();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setPromptActive(active: boolean): void {
|
|
77
|
+
this.promptActive = active;
|
|
78
|
+
this.advance();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async close(): Promise<void> {
|
|
82
|
+
this.controller.abort();
|
|
83
|
+
await this.current;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private advance(): void {
|
|
87
|
+
if (this.current || this.promptActive || this.controller.signal.aborted) return;
|
|
88
|
+
const start = this.pending.shift();
|
|
89
|
+
if (!start) return;
|
|
90
|
+
let begin!: () => void;
|
|
91
|
+
this.current = new Promise<void>((resolve, reject) => {
|
|
92
|
+
begin = () => {
|
|
93
|
+
void start().then(resolve, reject);
|
|
94
|
+
};
|
|
95
|
+
}).finally(() => {
|
|
96
|
+
this.current = undefined;
|
|
97
|
+
this.advance();
|
|
98
|
+
});
|
|
99
|
+
begin();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
+
import type {
|
|
3
|
+
JsonAgentSessionEvent,
|
|
4
|
+
RpcCommand,
|
|
5
|
+
RpcExtensionUIRequest,
|
|
6
|
+
RpcExtensionUIResponse,
|
|
7
|
+
RpcResponse,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
export type ChildEvent = JsonAgentSessionEvent | RpcExtensionUIRequest;
|
|
11
|
+
|
|
12
|
+
/** Own the native RPC process, its LF-delimited protocol, and all pending requests through close. */
|
|
13
|
+
export class SubagentProcess {
|
|
14
|
+
private readonly process: ChildProcessWithoutNullStreams;
|
|
15
|
+
private readonly pending = new Map<
|
|
16
|
+
string,
|
|
17
|
+
{
|
|
18
|
+
command: RpcCommand["type"];
|
|
19
|
+
resolve: (response: RpcResponse) => void;
|
|
20
|
+
reject: (error: Error) => void;
|
|
21
|
+
timer: ReturnType<typeof setTimeout>;
|
|
22
|
+
}
|
|
23
|
+
>();
|
|
24
|
+
private sequence = 0;
|
|
25
|
+
private buffer = "";
|
|
26
|
+
private stderr = "";
|
|
27
|
+
private failure?: Error;
|
|
28
|
+
private stopping = false;
|
|
29
|
+
private closed = false;
|
|
30
|
+
private escalation?: ReturnType<typeof setTimeout>;
|
|
31
|
+
private shutdownRequest?: Promise<void>;
|
|
32
|
+
readonly completion: Promise<void>;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
cwd: string,
|
|
36
|
+
args: string[],
|
|
37
|
+
env: NodeJS.ProcessEnv,
|
|
38
|
+
onEvent: (event: ChildEvent) => void,
|
|
39
|
+
onClose: (error?: Error) => void,
|
|
40
|
+
) {
|
|
41
|
+
this.process = spawn("pi", ["--mode", "rpc", ...args], {
|
|
42
|
+
cwd,
|
|
43
|
+
env,
|
|
44
|
+
detached: true,
|
|
45
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
46
|
+
});
|
|
47
|
+
this.process.stdout.setEncoding("utf8");
|
|
48
|
+
this.process.stderr.setEncoding("utf8");
|
|
49
|
+
this.process.stdout.on("data", (chunk: string) => {
|
|
50
|
+
try {
|
|
51
|
+
this.buffer += chunk;
|
|
52
|
+
let newline: number;
|
|
53
|
+
while ((newline = this.buffer.indexOf("\n")) !== -1) {
|
|
54
|
+
const line = this.buffer.slice(0, newline).replace(/\r$/, "");
|
|
55
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
56
|
+
if (!line) continue;
|
|
57
|
+
if (Buffer.byteLength(line) > 32 * 1024 * 1024)
|
|
58
|
+
throw new Error("Subagent RPC record exceeds 32 MiB");
|
|
59
|
+
const event = JSON.parse(line) as RpcResponse | ChildEvent;
|
|
60
|
+
if (!event || typeof event !== "object" || typeof event.type !== "string") {
|
|
61
|
+
throw new Error("Invalid subagent RPC record");
|
|
62
|
+
}
|
|
63
|
+
if (event.type === "response") {
|
|
64
|
+
const request = event.id ? this.pending.get(event.id) : undefined;
|
|
65
|
+
if (!request) continue;
|
|
66
|
+
if (event.command !== request.command || typeof event.success !== "boolean")
|
|
67
|
+
throw new Error("Invalid subagent RPC acknowledgement");
|
|
68
|
+
clearTimeout(request.timer);
|
|
69
|
+
this.pending.delete(event.id!);
|
|
70
|
+
if (event.success) request.resolve(event);
|
|
71
|
+
else request.reject(new Error(event.error));
|
|
72
|
+
} else if (!this.stopping) {
|
|
73
|
+
onEvent(event);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (Buffer.byteLength(this.buffer) > 32 * 1024 * 1024)
|
|
77
|
+
throw new Error("Subagent RPC record exceeds 32 MiB");
|
|
78
|
+
} catch (error) {
|
|
79
|
+
this.fail(error);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
this.process.stderr.on("data", (chunk: string) => {
|
|
83
|
+
this.stderr = (this.stderr + chunk).slice(-8192);
|
|
84
|
+
});
|
|
85
|
+
this.process.on("error", (error) => this.fail(error));
|
|
86
|
+
this.process.stdin.on("error", (error) => this.fail(error));
|
|
87
|
+
this.completion = new Promise<void>((resolve) => {
|
|
88
|
+
this.process.once("close", (code, signal) => {
|
|
89
|
+
this.closed = true;
|
|
90
|
+
clearTimeout(this.escalation);
|
|
91
|
+
const error =
|
|
92
|
+
this.failure ??
|
|
93
|
+
new Error(
|
|
94
|
+
`Subagent exited (${signal ?? code}).${this.stderr.trim() ? ` ${this.stderr.trim()}` : ""}`,
|
|
95
|
+
);
|
|
96
|
+
for (const request of this.pending.values()) {
|
|
97
|
+
clearTimeout(request.timer);
|
|
98
|
+
request.reject(error);
|
|
99
|
+
}
|
|
100
|
+
this.pending.clear();
|
|
101
|
+
try {
|
|
102
|
+
onClose(this.stopping && !this.failure ? undefined : error);
|
|
103
|
+
} finally {
|
|
104
|
+
resolve();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async request(command: RpcCommand): Promise<RpcResponse> {
|
|
111
|
+
if (this.closed || this.stopping) throw this.failure ?? new Error("Subagent has stopped");
|
|
112
|
+
return this.send(command);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
respond(response: RpcExtensionUIResponse): void {
|
|
116
|
+
if (!this.closed && !this.stopping) this.write(response);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async stop(): Promise<void> {
|
|
120
|
+
if (!this.closed && !this.stopping) {
|
|
121
|
+
this.stopping = true;
|
|
122
|
+
let terminating = false;
|
|
123
|
+
const terminate = () => {
|
|
124
|
+
if (terminating || this.closed) return;
|
|
125
|
+
terminating = true;
|
|
126
|
+
clearTimeout(this.escalation);
|
|
127
|
+
// Pi's SIGTERM handler also stops separately detached Bash children.
|
|
128
|
+
this.signal("SIGTERM");
|
|
129
|
+
this.escalation = setTimeout(() => this.signal("SIGKILL"), 1000);
|
|
130
|
+
};
|
|
131
|
+
this.escalation = setTimeout(terminate, 250);
|
|
132
|
+
this.shutdownRequest = (
|
|
133
|
+
this.failure
|
|
134
|
+
? Promise.resolve()
|
|
135
|
+
: this.send({ type: "clear_queue" }).then(() => this.send({ type: "abort" }))
|
|
136
|
+
).then(terminate, terminate);
|
|
137
|
+
}
|
|
138
|
+
await this.completion;
|
|
139
|
+
await this.shutdownRequest;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private send(command: RpcCommand): Promise<RpcResponse> {
|
|
143
|
+
if (this.closed) return Promise.reject(this.failure ?? new Error("Subagent has stopped"));
|
|
144
|
+
const id = String(++this.sequence);
|
|
145
|
+
return new Promise<RpcResponse>((resolve, reject) => {
|
|
146
|
+
const timer = setTimeout(
|
|
147
|
+
() =>
|
|
148
|
+
this.fail(new Error(`Subagent did not acknowledge ${command.type} within 30 seconds`)),
|
|
149
|
+
30_000,
|
|
150
|
+
);
|
|
151
|
+
this.pending.set(id, { command: command.type, resolve, reject, timer });
|
|
152
|
+
this.write({ ...command, id });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private write(message: RpcCommand | RpcExtensionUIResponse): void {
|
|
157
|
+
try {
|
|
158
|
+
this.process.stdin.write(`${JSON.stringify(message)}\n`);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
this.fail(error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private fail(error: unknown): void {
|
|
165
|
+
this.failure ??= error instanceof Error ? error : new Error(String(error));
|
|
166
|
+
void this.stop();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private signal(signal: NodeJS.Signals): void {
|
|
170
|
+
if (this.closed) return;
|
|
171
|
+
try {
|
|
172
|
+
if (this.process.pid) process.kill(-this.process.pid, signal);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if ((error as NodeJS.ErrnoException).code !== "ESRCH") this.process.kill(signal);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|