pipane 0.1.6 → 0.1.8

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.
Files changed (58) hide show
  1. package/README.md +14 -3
  2. package/bin/pipane-rendezvous.js +21 -0
  3. package/bin/pipane.js +21 -1
  4. package/dist/build-info.json +5 -0
  5. package/dist/client/assets/__vite-browser-external-DtLbhLGx.js +1 -0
  6. package/dist/client/assets/app-runtime-Rw-O-AwW.js +1 -0
  7. package/dist/client/assets/backend-landing-page-B7SbyIXQ.js +1 -0
  8. package/dist/client/assets/index-C7_1ODks.js +2 -0
  9. package/dist/client/assets/index-DgI_gkjg.css +1 -0
  10. package/dist/client/assets/main-DEfSB8wO.js +2822 -0
  11. package/dist/client/assets/pairing-page-C0YByC2D.js +1 -0
  12. package/dist/client/assets/remote-backend-manager-DCSdS38m.js +1 -0
  13. package/dist/client/assets/rendezvous-trust-api-C2Zob5i4.js +4 -0
  14. package/dist/client/assets/webrtc-frame-transport-DGX5EO_A.js +1 -0
  15. package/dist/client/assets/ws-agent-adapter-DpkkZGPl.js +6 -0
  16. package/dist/client/index.html +2 -2
  17. package/dist/server/rendezvous/rendezvous-hub.js +426 -0
  18. package/dist/server/rendezvous/server.js +247 -0
  19. package/dist/server/rendezvous/trust-store.js +432 -0
  20. package/dist/server/server/auth-guard.js +61 -0
  21. package/dist/server/server/backend-connection-authorizer.js +29 -0
  22. package/dist/server/server/backend-identity.js +167 -0
  23. package/dist/server/server/backend-protocol-handler.js +132 -0
  24. package/dist/server/server/backend-trust-store.js +157 -0
  25. package/dist/server/server/backend-webrtc.js +245 -0
  26. package/dist/server/server/build-info.js +13 -0
  27. package/dist/server/server/conversation-file-access.js +97 -0
  28. package/dist/server/server/frame-connection.js +106 -0
  29. package/dist/server/server/frame-router.js +45 -0
  30. package/dist/server/server/ice-servers.js +24 -0
  31. package/dist/server/server/local-backend-api.js +389 -0
  32. package/dist/server/server/local-settings.js +17 -4
  33. package/dist/server/server/pi-rpc-protocol.js +407 -0
  34. package/dist/server/server/process-pool.js +27 -24
  35. package/dist/server/server/rendezvous-client.js +282 -0
  36. package/dist/server/server/rest-api.js +133 -176
  37. package/dist/server/server/server.js +167 -97
  38. package/dist/server/server/session-index.js +105 -28
  39. package/dist/server/server/session-jsonl.js +82 -5
  40. package/dist/server/server/session-path.js +145 -0
  41. package/dist/server/server/update-api.js +28 -0
  42. package/dist/server/server/update-check.js +33 -13
  43. package/dist/server/server/update-manager.js +233 -0
  44. package/dist/server/server/worktree-name.js +116 -31
  45. package/dist/server/server/ws-handler.js +267 -186
  46. package/dist/server/shared/backend-api.js +1 -0
  47. package/dist/server/shared/backend-protocol.js +164 -0
  48. package/dist/server/shared/data-channel-framing.js +137 -0
  49. package/dist/server/shared/node-trust-crypto.js +61 -0
  50. package/dist/server/shared/rendezvous-protocol.js +243 -0
  51. package/dist/server/shared/tool-runtime.js +1 -0
  52. package/dist/server/shared/trust-protocol.js +97 -0
  53. package/dist/server/shared/updates.js +4 -0
  54. package/dist/server/shared/ws-protocol.js +473 -1
  55. package/docs/protocol.md +129 -0
  56. package/package.json +21 -8
  57. package/dist/client/assets/index-Dl_wdLZH.css +0 -1
  58. package/dist/client/assets/index-hNqbnG06.js +0 -2482
@@ -0,0 +1,407 @@
1
+ import { StringDecoder } from "node:string_decoder";
2
+ const SUPPORTED_COMMAND_TYPES = [
3
+ "prompt",
4
+ "steer",
5
+ "abort",
6
+ "new_session",
7
+ "get_state",
8
+ "set_model",
9
+ "get_available_models",
10
+ "set_thinking_level",
11
+ "compact",
12
+ "get_session_stats",
13
+ "switch_session",
14
+ "fork",
15
+ "set_session_name",
16
+ "get_commands",
17
+ ];
18
+ class ValidationFailure extends Error {
19
+ constructor(path, message) {
20
+ super(`${path}: ${message}`);
21
+ }
22
+ }
23
+ function fail(path, message) {
24
+ throw new ValidationFailure(path, message);
25
+ }
26
+ function isRecord(value) {
27
+ return typeof value === "object" && value !== null && !Array.isArray(value);
28
+ }
29
+ function record(value, path) {
30
+ if (!isRecord(value))
31
+ fail(path, "expected an object");
32
+ return value;
33
+ }
34
+ function string(value, path, allowEmpty = true) {
35
+ if (typeof value !== "string" || (!allowEmpty && value.length === 0)) {
36
+ fail(path, allowEmpty ? "expected a string" : "expected a non-empty string");
37
+ }
38
+ return value;
39
+ }
40
+ function optionalString(value, path) {
41
+ if (value !== undefined)
42
+ string(value, path);
43
+ }
44
+ function boolean(value, path) {
45
+ if (typeof value !== "boolean")
46
+ fail(path, "expected a boolean");
47
+ return value;
48
+ }
49
+ function integer(value, path) {
50
+ if (!Number.isSafeInteger(value) || value < 0)
51
+ fail(path, "expected a non-negative safe integer");
52
+ return value;
53
+ }
54
+ function finiteNumber(value, path) {
55
+ if (typeof value !== "number" || !Number.isFinite(value))
56
+ fail(path, "expected a finite number");
57
+ return value;
58
+ }
59
+ function array(value, path) {
60
+ if (!Array.isArray(value))
61
+ fail(path, "expected an array");
62
+ return value;
63
+ }
64
+ function stringArray(value, path) {
65
+ array(value, path).forEach((item, index) => string(item, `${path}[${index}]`));
66
+ }
67
+ function thinkingLevel(value, path) {
68
+ if (!["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(String(value))) {
69
+ fail(path, "expected a supported thinking level");
70
+ }
71
+ }
72
+ function model(value, path) {
73
+ const source = record(value, path);
74
+ string(source.provider, `${path}.provider`, false);
75
+ string(source.id, `${path}.id`, false);
76
+ }
77
+ function agentMessage(value, path) {
78
+ const message = record(value, path);
79
+ string(message.role, `${path}.role`, false);
80
+ }
81
+ function validateState(value, path) {
82
+ const state = record(value, path);
83
+ if (state.model !== undefined && state.model !== null)
84
+ model(state.model, `${path}.model`);
85
+ thinkingLevel(state.thinkingLevel, `${path}.thinkingLevel`);
86
+ boolean(state.isStreaming, `${path}.isStreaming`);
87
+ boolean(state.isCompacting, `${path}.isCompacting`);
88
+ if (state.steeringMode !== "all" && state.steeringMode !== "one-at-a-time") {
89
+ fail(`${path}.steeringMode`, "expected all or one-at-a-time");
90
+ }
91
+ if (state.followUpMode !== "all" && state.followUpMode !== "one-at-a-time") {
92
+ fail(`${path}.followUpMode`, "expected all or one-at-a-time");
93
+ }
94
+ optionalString(state.sessionFile, `${path}.sessionFile`);
95
+ string(state.sessionId, `${path}.sessionId`);
96
+ optionalString(state.sessionName, `${path}.sessionName`);
97
+ boolean(state.autoCompactionEnabled, `${path}.autoCompactionEnabled`);
98
+ integer(state.messageCount, `${path}.messageCount`);
99
+ integer(state.pendingMessageCount, `${path}.pendingMessageCount`);
100
+ }
101
+ function validateSlashCommands(value, path) {
102
+ array(value, path).forEach((item, index) => {
103
+ const command = record(item, `${path}[${index}]`);
104
+ string(command.name, `${path}[${index}].name`, false);
105
+ optionalString(command.description, `${path}[${index}].description`);
106
+ if (!["extension", "prompt", "skill"].includes(String(command.source))) {
107
+ fail(`${path}[${index}].source`, "expected extension, prompt, or skill");
108
+ }
109
+ if (command.sourceInfo !== undefined)
110
+ record(command.sourceInfo, `${path}[${index}].sourceInfo`);
111
+ });
112
+ }
113
+ function validateSessionStats(value, path) {
114
+ const stats = record(value, path);
115
+ optionalString(stats.sessionFile, `${path}.sessionFile`);
116
+ string(stats.sessionId, `${path}.sessionId`, false);
117
+ for (const field of ["userMessages", "assistantMessages", "toolCalls", "toolResults", "totalMessages"]) {
118
+ integer(stats[field], `${path}.${field}`);
119
+ }
120
+ const tokens = record(stats.tokens, `${path}.tokens`);
121
+ for (const field of ["input", "output", "cacheRead", "cacheWrite", "total"]) {
122
+ integer(tokens[field], `${path}.tokens.${field}`);
123
+ }
124
+ if (finiteNumber(stats.cost, `${path}.cost`) < 0)
125
+ fail(`${path}.cost`, "expected a non-negative number");
126
+ if (stats.contextUsage !== undefined) {
127
+ const context = record(stats.contextUsage, `${path}.contextUsage`);
128
+ if (context.tokens !== null)
129
+ integer(context.tokens, `${path}.contextUsage.tokens`);
130
+ integer(context.contextWindow, `${path}.contextUsage.contextWindow`);
131
+ if (context.percent !== null && finiteNumber(context.percent, `${path}.contextUsage.percent`) < 0) {
132
+ fail(`${path}.contextUsage.percent`, "expected a non-negative number or null");
133
+ }
134
+ }
135
+ }
136
+ const SUPPORTED_COMMAND_SET = new Set(SUPPORTED_COMMAND_TYPES);
137
+ function commandType(value, path) {
138
+ const type = string(value, path, false);
139
+ if (!SUPPORTED_COMMAND_SET.has(type))
140
+ fail(path, `unexpected command '${type}'`);
141
+ return type;
142
+ }
143
+ function validateResponse(value) {
144
+ string(value.id, "$rpc.id", false);
145
+ const command = commandType(value.command, "$rpc.command");
146
+ const success = boolean(value.success, "$rpc.success");
147
+ if (!success) {
148
+ string(value.error, "$rpc.error", false);
149
+ return value;
150
+ }
151
+ switch (command) {
152
+ case "prompt":
153
+ case "steer":
154
+ case "abort":
155
+ case "set_thinking_level":
156
+ case "set_session_name":
157
+ break;
158
+ case "new_session":
159
+ case "switch_session": {
160
+ const data = record(value.data, "$rpc.data");
161
+ boolean(data.cancelled, "$rpc.data.cancelled");
162
+ break;
163
+ }
164
+ case "get_state":
165
+ validateState(value.data, "$rpc.data");
166
+ break;
167
+ case "set_model":
168
+ model(value.data, "$rpc.data");
169
+ break;
170
+ case "get_available_models": {
171
+ const data = record(value.data, "$rpc.data");
172
+ array(data.models, "$rpc.data.models").forEach((item, index) => model(item, `$rpc.data.models[${index}]`));
173
+ break;
174
+ }
175
+ case "compact":
176
+ record(value.data, "$rpc.data");
177
+ break;
178
+ case "get_session_stats":
179
+ validateSessionStats(value.data, "$rpc.data");
180
+ break;
181
+ case "fork": {
182
+ const data = record(value.data, "$rpc.data");
183
+ string(data.text, "$rpc.data.text");
184
+ boolean(data.cancelled, "$rpc.data.cancelled");
185
+ break;
186
+ }
187
+ case "get_commands": {
188
+ const data = record(value.data, "$rpc.data");
189
+ validateSlashCommands(data.commands, "$rpc.data.commands");
190
+ break;
191
+ }
192
+ }
193
+ return value;
194
+ }
195
+ function validateExtensionUiRequest(value) {
196
+ string(value.id, "$rpc.id", false);
197
+ const method = string(value.method, "$rpc.method", false);
198
+ switch (method) {
199
+ case "select":
200
+ string(value.title, "$rpc.title");
201
+ stringArray(value.options, "$rpc.options");
202
+ if (value.timeout !== undefined)
203
+ integer(value.timeout, "$rpc.timeout");
204
+ break;
205
+ case "confirm":
206
+ string(value.title, "$rpc.title");
207
+ string(value.message, "$rpc.message");
208
+ if (value.timeout !== undefined)
209
+ integer(value.timeout, "$rpc.timeout");
210
+ break;
211
+ case "input":
212
+ string(value.title, "$rpc.title");
213
+ optionalString(value.placeholder, "$rpc.placeholder");
214
+ if (value.timeout !== undefined)
215
+ integer(value.timeout, "$rpc.timeout");
216
+ break;
217
+ case "editor":
218
+ string(value.title, "$rpc.title");
219
+ optionalString(value.prefill, "$rpc.prefill");
220
+ break;
221
+ case "notify":
222
+ string(value.message, "$rpc.message");
223
+ if (value.notifyType !== undefined && !["info", "warning", "error"].includes(String(value.notifyType))) {
224
+ fail("$rpc.notifyType", "expected info, warning, or error");
225
+ }
226
+ break;
227
+ case "setStatus":
228
+ string(value.statusKey, "$rpc.statusKey", false);
229
+ optionalString(value.statusText, "$rpc.statusText");
230
+ break;
231
+ case "setWidget":
232
+ string(value.widgetKey, "$rpc.widgetKey", false);
233
+ if (value.widgetLines !== undefined)
234
+ stringArray(value.widgetLines, "$rpc.widgetLines");
235
+ if (value.widgetPlacement !== undefined && value.widgetPlacement !== "aboveEditor" && value.widgetPlacement !== "belowEditor") {
236
+ fail("$rpc.widgetPlacement", "expected aboveEditor or belowEditor");
237
+ }
238
+ break;
239
+ case "setTitle":
240
+ string(value.title, "$rpc.title");
241
+ break;
242
+ case "set_editor_text":
243
+ string(value.text, "$rpc.text");
244
+ break;
245
+ default:
246
+ fail("$rpc.method", `unknown extension UI method '${method}'`);
247
+ }
248
+ return value;
249
+ }
250
+ function validateEvent(value, type) {
251
+ switch (type) {
252
+ case "agent_start":
253
+ case "agent_settled":
254
+ case "turn_start":
255
+ break;
256
+ case "agent_end":
257
+ array(value.messages, "$rpc.messages").forEach((item, index) => agentMessage(item, `$rpc.messages[${index}]`));
258
+ boolean(value.willRetry, "$rpc.willRetry");
259
+ break;
260
+ case "turn_end":
261
+ agentMessage(value.message, "$rpc.message");
262
+ array(value.toolResults, "$rpc.toolResults").forEach((item, index) => agentMessage(item, `$rpc.toolResults[${index}]`));
263
+ break;
264
+ case "message_start":
265
+ case "message_end":
266
+ agentMessage(value.message, "$rpc.message");
267
+ break;
268
+ case "message_update":
269
+ agentMessage(value.message, "$rpc.message");
270
+ record(value.assistantMessageEvent, "$rpc.assistantMessageEvent");
271
+ break;
272
+ case "tool_execution_start":
273
+ string(value.toolCallId, "$rpc.toolCallId", false);
274
+ string(value.toolName, "$rpc.toolName", false);
275
+ record(value.args, "$rpc.args");
276
+ break;
277
+ case "tool_execution_update":
278
+ string(value.toolCallId, "$rpc.toolCallId", false);
279
+ string(value.toolName, "$rpc.toolName", false);
280
+ record(value.args, "$rpc.args");
281
+ record(value.partialResult, "$rpc.partialResult");
282
+ break;
283
+ case "tool_execution_end":
284
+ string(value.toolCallId, "$rpc.toolCallId", false);
285
+ string(value.toolName, "$rpc.toolName", false);
286
+ record(value.result, "$rpc.result");
287
+ boolean(value.isError, "$rpc.isError");
288
+ break;
289
+ case "queue_update":
290
+ stringArray(value.steering, "$rpc.steering");
291
+ stringArray(value.followUp, "$rpc.followUp");
292
+ break;
293
+ case "compaction_start":
294
+ if (!["manual", "threshold", "overflow"].includes(String(value.reason)))
295
+ fail("$rpc.reason", "expected a compaction reason");
296
+ break;
297
+ case "compaction_end":
298
+ if (!["manual", "threshold", "overflow"].includes(String(value.reason)))
299
+ fail("$rpc.reason", "expected a compaction reason");
300
+ if (value.result !== undefined && value.result !== null)
301
+ record(value.result, "$rpc.result");
302
+ boolean(value.aborted, "$rpc.aborted");
303
+ boolean(value.willRetry, "$rpc.willRetry");
304
+ optionalString(value.errorMessage, "$rpc.errorMessage");
305
+ break;
306
+ case "auto_retry_start":
307
+ integer(value.attempt, "$rpc.attempt");
308
+ integer(value.maxAttempts, "$rpc.maxAttempts");
309
+ integer(value.delayMs, "$rpc.delayMs");
310
+ string(value.errorMessage, "$rpc.errorMessage");
311
+ break;
312
+ case "auto_retry_end":
313
+ boolean(value.success, "$rpc.success");
314
+ integer(value.attempt, "$rpc.attempt");
315
+ optionalString(value.finalError, "$rpc.finalError");
316
+ break;
317
+ case "entry_appended":
318
+ record(value.entry, "$rpc.entry");
319
+ break;
320
+ case "session_info_changed":
321
+ optionalString(value.name, "$rpc.name");
322
+ break;
323
+ case "thinking_level_changed":
324
+ thinkingLevel(value.level, "$rpc.level");
325
+ break;
326
+ case "extension_error":
327
+ string(value.extensionPath, "$rpc.extensionPath");
328
+ string(value.event, "$rpc.event");
329
+ string(value.error, "$rpc.error");
330
+ break;
331
+ default:
332
+ fail("$rpc.type", `unknown event '${type}'`);
333
+ }
334
+ return value;
335
+ }
336
+ /** Validate one complete line emitted by Pi RPC before it reaches state mutation. */
337
+ export function decodePiRpcLine(line) {
338
+ let parsed;
339
+ try {
340
+ parsed = JSON.parse(line);
341
+ }
342
+ catch (error) {
343
+ return {
344
+ ok: false,
345
+ error: {
346
+ message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
347
+ line,
348
+ },
349
+ };
350
+ }
351
+ try {
352
+ const value = record(parsed, "$rpc");
353
+ const type = string(value.type, "$rpc.type", false);
354
+ if (type === "response")
355
+ return { ok: true, value: validateResponse(value) };
356
+ if (type === "extension_ui_request")
357
+ return { ok: true, value: validateExtensionUiRequest(value) };
358
+ return { ok: true, value: validateEvent(value, type) };
359
+ }
360
+ catch (error) {
361
+ return {
362
+ ok: false,
363
+ error: {
364
+ message: error instanceof Error ? error.message : String(error),
365
+ line,
366
+ },
367
+ };
368
+ }
369
+ }
370
+ /**
371
+ * Pi RPC is strict JSONL: only LF delimits records. Node readline also splits on
372
+ * U+2028/U+2029, so use this decoder for protocol-compliant framing.
373
+ */
374
+ export function attachStrictJsonlReader(stream, onLine) {
375
+ const decoder = new StringDecoder("utf8");
376
+ let buffer = "";
377
+ const consume = (chunk) => {
378
+ buffer += typeof chunk === "string" ? chunk : decoder.write(chunk);
379
+ while (true) {
380
+ const newline = buffer.indexOf("\n");
381
+ if (newline === -1)
382
+ break;
383
+ let line = buffer.slice(0, newline);
384
+ buffer = buffer.slice(newline + 1);
385
+ if (line.endsWith("\r"))
386
+ line = line.slice(0, -1);
387
+ if (line.length > 0)
388
+ onLine(line);
389
+ }
390
+ };
391
+ const end = () => {
392
+ buffer += decoder.end();
393
+ if (buffer.length > 0)
394
+ onLine(buffer.endsWith("\r") ? buffer.slice(0, -1) : buffer);
395
+ buffer = "";
396
+ };
397
+ stream.on("data", consume);
398
+ stream.on("end", end);
399
+ return () => {
400
+ stream.removeListener("data", consume);
401
+ stream.removeListener("end", end);
402
+ };
403
+ }
404
+ /** Build a correlated command line after static command typing has succeeded. */
405
+ export function encodePiRpcCommand(command, id) {
406
+ return `${JSON.stringify({ ...command, id })}\n`;
407
+ }
@@ -6,8 +6,8 @@
6
6
  * reserved atomically before control returns to asynchronous application code.
7
7
  */
8
8
  import { spawn } from "node:child_process";
9
- import * as readline from "node:readline";
10
9
  import { existsSync } from "node:fs";
10
+ import { attachStrictJsonlReader, decodePiRpcLine, encodePiRpcCommand, } from "./pi-rpc-protocol.js";
11
11
  /** Exclusive ownership of one pooled process. */
12
12
  export class RpcProcessLease {
13
13
  process;
@@ -114,35 +114,37 @@ export class ProcessPool {
114
114
  recentStderr.splice(0, recentStderr.length - 60);
115
115
  }
116
116
  });
117
- const rl = readline.createInterface({ input: child.stdout, terminal: false });
118
117
  const proc = {
119
118
  id: procId,
120
119
  cwd,
121
120
  process: child,
122
- rl,
121
+ stopReadingStdout: () => { },
123
122
  pendingRequests: new Map(),
124
123
  requestId: 0,
125
124
  lastResponseTime: Date.now(),
126
125
  recentStderr,
127
126
  };
128
- rl.on("line", (line) => {
129
- let data;
130
- try {
131
- data = JSON.parse(line);
132
- }
133
- catch {
127
+ proc.stopReadingStdout = attachStrictJsonlReader(child.stdout, (line) => {
128
+ const decoded = decodePiRpcLine(line);
129
+ if (!decoded.ok) {
130
+ console.warn(`[pool] pi#${proc.id} ignored invalid RPC output: ${decoded.error.message}`);
134
131
  return;
135
132
  }
136
- if (data.type === "response") {
137
- if (data.id && proc.pendingRequests.has(data.id)) {
138
- const pending = proc.pendingRequests.get(data.id);
139
- proc.pendingRequests.delete(data.id);
140
- proc.lastResponseTime = Date.now();
141
- pending.resolve(data);
133
+ const message = decoded.value;
134
+ if (message.type === "response") {
135
+ const pending = message.id ? proc.pendingRequests.get(message.id) : undefined;
136
+ if (!pending || !message.id)
137
+ return;
138
+ proc.pendingRequests.delete(message.id);
139
+ proc.lastResponseTime = Date.now();
140
+ if (message.command !== pending.command) {
141
+ pending.reject(new Error(`Mismatched RPC response for ${pending.command}: received ${message.command}`));
142
+ return;
142
143
  }
144
+ pending.resolve(message);
143
145
  return;
144
146
  }
145
- this.emitEvent(proc, data);
147
+ this.emitEvent(proc, message);
146
148
  });
147
149
  child.on("error", (err) => {
148
150
  console.error(`[pool] pi#${proc.id} spawn error: ${err.message}`);
@@ -178,6 +180,7 @@ export class ProcessPool {
178
180
  lease.markReleased();
179
181
  this.leases.delete(proc);
180
182
  this.decommissioning.delete(proc);
183
+ proc.stopReadingStdout();
181
184
  for (const pending of proc.pendingRequests.values())
182
185
  pending.reject(error);
183
186
  proc.pendingRequests.clear();
@@ -301,7 +304,6 @@ export class ProcessPool {
301
304
  }
302
305
  const timeout = timeoutMs ?? this.rpcTimeout;
303
306
  const id = `req_${++proc.requestId}`;
304
- const fullCommand = { ...command, id };
305
307
  return new Promise((resolve, reject) => {
306
308
  const timer = setTimeout(() => {
307
309
  proc.pendingRequests.delete(id);
@@ -311,22 +313,23 @@ export class ProcessPool {
311
313
  reject(new Error(`Timeout waiting for RPC response to ${command.type}`));
312
314
  }, timeout);
313
315
  proc.pendingRequests.set(id, {
314
- resolve: (data) => {
316
+ command: command.type,
317
+ resolve: (response) => {
315
318
  clearTimeout(timer);
316
- resolve(data);
319
+ resolve(response);
317
320
  },
318
- reject: (err) => {
321
+ reject: (error) => {
319
322
  clearTimeout(timer);
320
- reject(err);
323
+ reject(error);
321
324
  },
322
325
  });
323
- proc.process.stdin.write(JSON.stringify(fullCommand) + "\n");
326
+ proc.process.stdin.write(encodePiRpcCommand(command, id));
324
327
  });
325
328
  }
326
329
  async sendRpcChecked(proc, command) {
327
330
  const response = await this.sendRpc(proc, command);
328
- if (!response?.success) {
329
- throw new Error(response?.error || `RPC command failed: ${command.type}`);
331
+ if (!response.success) {
332
+ throw new Error(response.error || `RPC command failed: ${command.type}`);
330
333
  }
331
334
  return response;
332
335
  }