pi-web 0.13.5 → 0.14.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.
@@ -25,7 +25,9 @@ export class RpcSession {
25
25
  try {
26
26
  this.opts.onEvent(JSON.parse(line));
27
27
  }
28
- catch { }
28
+ catch {
29
+ // malformed JSON line — skip
30
+ }
29
31
  }
30
32
  idx = this.buffer.indexOf('\n');
31
33
  }
@@ -56,9 +58,13 @@ export class RpcSession {
56
58
  try {
57
59
  this.proc.kill('SIGKILL');
58
60
  }
59
- catch { }
61
+ catch {
62
+ // process already gone
63
+ }
60
64
  }, 2000);
61
65
  }
62
- catch { }
66
+ catch {
67
+ // process already gone
68
+ }
63
69
  }
64
70
  }
@@ -1,6 +1,6 @@
1
1
  import { createServer } from 'node:http';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { createReadStream, existsSync, readFileSync, statSync, unlinkSync, } from 'node:fs';
3
+ import { createReadStream, existsSync, readFileSync, statSync, unlinkSync } from 'node:fs';
4
4
  import { readdir } from 'node:fs/promises';
5
5
  import { basename, dirname, extname, isAbsolute, join, normalize, relative, resolve, } from 'node:path';
6
6
  import { homedir } from 'node:os';
@@ -29,9 +29,7 @@ function getArg(name) {
29
29
  if (pair)
30
30
  return pair.slice(flag.length);
31
31
  const idx = process.argv.indexOf(`--${name}`);
32
- if (idx !== -1 &&
33
- process.argv[idx + 1] &&
34
- !process.argv[idx + 1].startsWith('--'))
32
+ if (idx !== -1 && process.argv[idx + 1] && !process.argv[idx + 1].startsWith('--'))
35
33
  return process.argv[idx + 1];
36
34
  return undefined;
37
35
  }
@@ -44,8 +42,8 @@ function parseAgent(value) {
44
42
  }
45
43
  function getAgentCommand(agent) {
46
44
  return agent === 'omp'
47
- ? { command: 'npx', args: ['-y', '@oh-my-pi/pi-coding-agent@latest'] }
48
- : { command: 'npx', args: ['-y', '@mariozechner/pi-coding-agent@latest'] };
45
+ ? { command: 'npx', args: ['-y', '@earendil-works/pi-coding-agent@latest'] }
46
+ : { command: 'npx', args: ['-y', '@earendil-works/pi-coding-agent@latest'] };
49
47
  }
50
48
  const AGENT = parseAgent(getArg('agent'));
51
49
  const PORT = parseInt(getArg('port') || '8192', 10);
@@ -55,11 +53,9 @@ const IDLE_SESSION_TTL_MS = 60_000;
55
53
  const isWatchMode = process.argv.includes('--watch') ||
56
54
  process.execArgv.some((arg) => arg === '--watch' || arg.startsWith('--watch-'));
57
55
  const isDev = isWatchMode;
58
- const distDirCandidates = [
59
- join(__dirname, 'dist'),
60
- join(__dirname, '..', '..', 'dist'),
61
- ];
62
- const distDir = distDirCandidates.find((candidate) => existsSync(join(candidate, 'index.html'))) ?? distDirCandidates[0];
56
+ const distDirCandidates = [join(__dirname, 'dist'), join(__dirname, '..', '..', 'dist')];
57
+ const distDir = distDirCandidates.find((candidate) => existsSync(join(candidate, 'index.html'))) ??
58
+ distDirCandidates[0];
63
59
  const htmlPath = join(distDir, 'index.html');
64
60
  const htmlCache = isDev || !existsSync(htmlPath) ? null : readFileSync(htmlPath, 'utf-8');
65
61
  const HOME_DIR = resolve(homedir() || '/');
@@ -208,16 +204,12 @@ const server = createServer((req, res) => {
208
204
  .replace(/^(\.\.[/\\])+/, '')
209
205
  .replace(/^[/\\]+/, '');
210
206
  const filePath = resolve(join(distDir, safePath));
211
- if (isWithinRoot(filePath, distDir) &&
212
- existsSync(filePath) &&
213
- statSync(filePath).isFile()) {
207
+ if (isWithinRoot(filePath, distDir) && existsSync(filePath) && statSync(filePath).isFile()) {
214
208
  serveFile(filePath, res);
215
209
  return;
216
210
  }
217
211
  const acceptsHtml = (req.headers.accept ?? '').includes('text/html');
218
- if (req.method === 'GET' &&
219
- !url.pathname.startsWith('/api/') &&
220
- acceptsHtml) {
212
+ if (req.method === 'GET' && !url.pathname.startsWith('/api/') && acceptsHtml) {
221
213
  if (!existsSync(htmlPath)) {
222
214
  res.writeHead(503, { 'Content-Type': 'text/plain' });
223
215
  res.end('frontend not built. run: npm run build');
@@ -339,9 +331,9 @@ function detachSocket(ws) {
339
331
  cleanupIfIdle(current);
340
332
  }
341
333
  function registerDiscoveredSessionKey(managed, event) {
342
- if (event?.type !== 'response' || event?.command !== 'get_state')
334
+ if (event.type !== 'response' || event.command !== 'get_state' || !event.success)
343
335
  return;
344
- const sessionPath = event?.data?.sessionFile;
336
+ const sessionPath = event.data?.sessionFile;
345
337
  if (typeof sessionPath !== 'string' || sessionPath.length === 0)
346
338
  return;
347
339
  const key = buildSessionKey(managed.cwd, basename(sessionPath));
@@ -356,8 +348,6 @@ function deriveModelSupportsImages(model) {
356
348
  return input.includes('image');
357
349
  }
358
350
  function updateSessionModelCapability(managed, event) {
359
- if (!event || typeof event !== 'object')
360
- return;
361
351
  if (event.type === 'model_changed') {
362
352
  const supports = deriveModelSupportsImages(event.model);
363
353
  if (supports != null)
@@ -365,7 +355,7 @@ function updateSessionModelCapability(managed, event) {
365
355
  return;
366
356
  }
367
357
  if (event.type === 'response') {
368
- if (event.command === 'get_state') {
358
+ if (event.command === 'get_state' && event.success) {
369
359
  const supports = deriveModelSupportsImages(event.data?.model);
370
360
  if (supports != null)
371
361
  managed.currentModelSupportsImages = supports;
@@ -379,9 +369,7 @@ function updateSessionModelCapability(managed, event) {
379
369
  }
380
370
  }
381
371
  function createManagedSession(cwd, sessionFile, initialKey) {
382
- const sessionPath = sessionFile
383
- ? getSessionFilePath(cwd, sessionFile, AGENT)
384
- : undefined;
372
+ const sessionPath = sessionFile ? getSessionFilePath(cwd, sessionFile, AGENT) : undefined;
385
373
  let managed = null;
386
374
  const rpc = new RpcSession({
387
375
  piCmd: AGENT_CMD,
@@ -390,11 +378,11 @@ function createManagedSession(cwd, sessionFile, initialKey) {
390
378
  onEvent: (event) => {
391
379
  if (!managed)
392
380
  return;
393
- if (event?.type === 'agent_start') {
381
+ if (event.type === 'agent_start') {
394
382
  managed.isAgentRunning = true;
395
383
  clearIdleCleanupTimer(managed);
396
384
  }
397
- if (event?.type === 'agent_end') {
385
+ if (event.type === 'agent_end') {
398
386
  managed.isAgentRunning = false;
399
387
  cleanupIfIdle(managed);
400
388
  }
@@ -447,9 +435,7 @@ wss.on('connection', (ws) => {
447
435
  return;
448
436
  }
449
437
  if (msg.type === 'start_session') {
450
- const cwd = typeof msg.cwd === 'string' && msg.cwd.trim().length > 0
451
- ? resolve(msg.cwd)
452
- : HOME_DIR;
438
+ const cwd = typeof msg.cwd === 'string' && msg.cwd.trim().length > 0 ? resolve(msg.cwd) : HOME_DIR;
453
439
  const sessionFile = typeof msg.sessionFile === 'string' && msg.sessionFile.length > 0
454
440
  ? basename(msg.sessionFile)
455
441
  : null;
@@ -479,13 +465,9 @@ wss.on('connection', (ws) => {
479
465
  return;
480
466
  }
481
467
  const command = msg.command;
482
- const isPromptLikeCommand = command?.type === 'prompt' ||
483
- command?.type === 'steer' ||
484
- command?.type === 'follow_up';
485
- const hasImages = Array.isArray(command?.images) && command.images.length > 0;
486
- if (isPromptLikeCommand &&
487
- hasImages &&
488
- managed.currentModelSupportsImages === false) {
468
+ const isPromptLikeCommand = command.type === 'prompt' || command.type === 'steer' || command.type === 'follow_up';
469
+ const hasImages = 'images' in command && Array.isArray(command.images) && command.images.length > 0;
470
+ if (isPromptLikeCommand && hasImages && managed.currentModelSupportsImages === false) {
489
471
  sendToSocket(ws, {
490
472
  type: 'error',
491
473
  message: 'selected model does not support file attachments',
@@ -14,9 +14,7 @@ function cwdToSessionDir(cwd, agent) {
14
14
  if (normalisedCwd === HOME_DIR ||
15
15
  normalisedCwd.startsWith(`${HOME_DIR}/`) ||
16
16
  normalisedCwd.startsWith(`${HOME_DIR}\\`)) {
17
- const relative = normalisedCwd
18
- .slice(HOME_DIR.length)
19
- .replace(/^[/\\]/, '');
17
+ const relative = normalisedCwd.slice(HOME_DIR.length).replace(/^[/\\]/, '');
20
18
  return `-${relative.replace(/[/\\:]/g, '-')}`;
21
19
  }
22
20
  }
@@ -54,13 +52,17 @@ export async function listSessions(opts) {
54
52
  if (info)
55
53
  results.push(info);
56
54
  }
57
- catch { }
55
+ catch {
56
+ // unreadable session file — skip
57
+ }
58
58
  }
59
59
  if (results.length >= limit)
60
60
  break;
61
61
  }
62
62
  }
63
- catch { }
63
+ catch {
64
+ // session directory does not exist or is unreadable
65
+ }
64
66
  results.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
65
67
  return results.slice(0, limit);
66
68
  }
@@ -105,7 +107,9 @@ export async function readSessionMessages(filePath) {
105
107
  usage: msg.usage,
106
108
  });
107
109
  }
108
- catch { }
110
+ catch {
111
+ // malformed JSON line — skip
112
+ }
109
113
  }
110
114
  }
111
115
  finally {
@@ -163,7 +167,9 @@ async function readSessionHeader(filePath) {
163
167
  }
164
168
  }
165
169
  }
166
- catch { }
170
+ catch {
171
+ // malformed JSON line — skip
172
+ }
167
173
  }
168
174
  }
169
175
  finally {
@@ -173,10 +179,10 @@ async function readSessionHeader(filePath) {
173
179
  if (!header)
174
180
  return null;
175
181
  return {
176
- id: header.id,
182
+ id: typeof header.id === 'string' ? header.id : '',
177
183
  file: basename(filePath),
178
- cwd: header.cwd || '',
179
- timestamp: header.timestamp || '',
184
+ cwd: typeof header.cwd === 'string' ? header.cwd : '',
185
+ timestamp: typeof header.timestamp === 'string' ? header.timestamp : '',
180
186
  firstPrompt,
181
187
  messageCount,
182
188
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};