pipane 0.1.2 → 0.1.4

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.
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { spawn } from "node:child_process";
16
16
  import * as readline from "node:readline";
17
+ import { existsSync } from "node:fs";
17
18
  export class ProcessPool {
18
19
  /** All live processes, keyed by cwd */
19
20
  pools = new Map();
@@ -51,6 +52,9 @@ export class ProcessPool {
51
52
  * Does not wait for readiness — call waitForReady() separately.
52
53
  */
53
54
  spawn(cwd) {
55
+ if (!existsSync(cwd)) {
56
+ throw new Error(`Cannot spawn pi process: directory does not exist: ${cwd}`);
57
+ }
54
58
  const procId = ++this.nextProcId;
55
59
  console.log(`[pool] Spawning pi process #${procId} (cwd: ${cwd})...`);
56
60
  // Strip NODE_ENV from the child environment so tools spawned by pi
@@ -96,6 +100,24 @@ export class ProcessPool {
96
100
  pending.resolve(data);
97
101
  }
98
102
  });
103
+ child.on("error", (err) => {
104
+ console.error(`[pool] pi#${proc.id} spawn error: ${err.message}`);
105
+ // Remove from pool
106
+ const poolForCwd = this.pools.get(cwd);
107
+ if (poolForCwd) {
108
+ const idx = poolForCwd.indexOf(proc);
109
+ if (idx !== -1)
110
+ poolForCwd.splice(idx, 1);
111
+ if (poolForCwd.length === 0)
112
+ this.pools.delete(cwd);
113
+ }
114
+ // Reject any pending requests
115
+ for (const [, pending] of proc.pendingRequests) {
116
+ pending.reject(new Error(`pi process #${proc.id} failed to spawn: ${err.message}`));
117
+ }
118
+ proc.pendingRequests.clear();
119
+ this.onProcessExit?.(proc);
120
+ });
99
121
  child.on("exit", (code) => {
100
122
  console.log(`[pool] pi#${proc.id} exited (code ${code})`);
101
123
  // Remove from pool
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { WebSocket } from "ws";
13
13
  import { copyFile } from "node:fs/promises";
14
+ import { existsSync } from "node:fs";
14
15
  import path from "node:path";
15
16
  import { URL } from "node:url";
16
17
  import { getAgentDir } from "@mariozechner/pi-coding-agent";
@@ -559,7 +560,8 @@ export class WsHandler {
559
560
  const newFilename = `${timestamp}_${newId}.jsonl`;
560
561
  const newSessionPath = path.join(sessionsDir, newFilename);
561
562
  await copyFile(sessionPath, newSessionPath);
562
- const cwd = getSessionCwd(sessionPath) || this.defaultCwd;
563
+ const forkCwd = getSessionCwd(sessionPath);
564
+ const cwd = (forkCwd && existsSync(forkCwd)) ? forkCwd : this.defaultCwd;
563
565
  const proc = await this.acquireProcess(cwd);
564
566
  await this.pool.waitForReady(proc);
565
567
  this.busyProcesses.add(proc);
@@ -632,7 +634,8 @@ export class WsHandler {
632
634
  const existing = this.lifecycle.getAttachedProcess(sessionPath);
633
635
  if (existing)
634
636
  return existing;
635
- const cwd = getSessionCwd(sessionPath) || this.defaultCwd;
637
+ const sessionCwd = getSessionCwd(sessionPath);
638
+ const cwd = (sessionCwd && existsSync(sessionCwd)) ? sessionCwd : this.defaultCwd;
636
639
  const proc = await this.acquireProcess(cwd);
637
640
  this.busyProcesses.add(proc);
638
641
  this.lifecycle.attach(sessionPath, proc);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipane",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "A clean web interface for the pi coding agent — chat UI with real-time tool calls, streaming output, session management, and model picker",
5
5
  "private": false,
6
6
  "type": "module",