pi-sdk-web 0.3.5 → 0.3.7

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/dist/server.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { execFileSync } from "node:child_process";
12
12
  import { existsSync, readFileSync } from "node:fs";
13
- import { readFile } from "node:fs/promises";
13
+ import { readFile, unlink } from "node:fs/promises";
14
14
  import { createServer } from "node:http";
15
15
  import { dirname, join, resolve, sep } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
@@ -567,6 +567,21 @@ export class PiWebServer {
567
567
  await this.resumeSession(path);
568
568
  break;
569
569
  }
570
+ case "delete_session": {
571
+ const path = typeof data.path === "string" && data.path ? data.path : "";
572
+ if (!path)
573
+ throw new Error("Missing 'path'");
574
+ // Safety: only allow deleting known session files (never arbitrary paths)
575
+ const known = (await listSessions()).some((s) => s.path === path);
576
+ if (!known)
577
+ throw new Error("Not a known session file");
578
+ // Align with TUI: cannot delete the currently active session
579
+ if (this.session.sessionFile === path) {
580
+ throw new Error("Cannot delete the currently active session");
581
+ }
582
+ await unlink(path);
583
+ break;
584
+ }
570
585
  default:
571
586
  throw new Error(`Unsupported command: ${cmdType}`);
572
587
  }
@@ -678,16 +693,21 @@ export class PiWebServer {
678
693
  unsubscribe();
679
694
  }
680
695
  // Show command output as a modal (TUI-like). The session entry remains
681
- // for history; the modal is the transient presentation.
696
+ // for history; the modal is the transient presentation. Only entries
697
+ // carrying a text payload are shown as modals — entries without
698
+ // data.text are status traces (e.g. minimode-status) and stay
699
+ // file/event-stream only, matching TUI, which renders nothing for them.
682
700
  for (const entry of captured) {
683
701
  const data = entry.data;
684
- const message = data?.text ?? (data !== undefined ? JSON.stringify(data, null, 2) : "");
702
+ if (!data || typeof data.text !== "string" || data.text.trim().length === 0) {
703
+ continue;
704
+ }
685
705
  this.broadcast({
686
706
  type: "extension_ui_request",
687
707
  id: crypto.randomUUID(),
688
708
  method: "notify",
689
- title: data?.title ?? `/${name}`,
690
- message,
709
+ title: data.title ?? `/${name}`,
710
+ message: data.text,
691
711
  notifyType: "info",
692
712
  });
693
713
  }
@@ -554,6 +554,12 @@ class PiWebClient {
554
554
  }
555
555
 
556
556
  renderCustom(entry) {
557
+ // Status traces (appendEntry entries without data.text, e.g.
558
+ // minimode-status {mode, tools}) are file/event-stream only — TUI
559
+ // renders nothing for them, so neither do we.
560
+ if (entry.type === 'custom' && !(entry.data && typeof entry.data.text === 'string' && entry.data.text.trim().length > 0)) {
561
+ return;
562
+ }
557
563
  const customType = entry.customType || 'custom';
558
564
  const label = `[${customType}]`;
559
565
  const content = this.customEntryText(entry);
@@ -1630,12 +1636,35 @@ class PiWebClient {
1630
1636
  this.modalList.innerHTML = '<div class="modal-message">No other sessions available</div>';
1631
1637
  return;
1632
1638
  }
1639
+ this.resumeSessions = sessions;
1633
1640
  this.renderModalItems(
1634
1641
  sessions.map((s) => ({ name: s.name || s.id, desc: s.cwd, value: s.path })),
1635
1642
  (item) => {
1636
1643
  this.send({ type: 'resume', path: item.value });
1637
1644
  },
1638
1645
  );
1646
+ // Add delete buttons to each item (stopPropagation so clicks don't resume)
1647
+ this.modalList.querySelectorAll('.modal-item').forEach((el, i) => {
1648
+ const session = sessions[i];
1649
+ el.classList.add('resume-item');
1650
+ const del = document.createElement('span');
1651
+ del.className = 'resume-delete';
1652
+ del.title = 'Delete this session';
1653
+ del.textContent = '🗑';
1654
+ del.addEventListener('click', (e) => {
1655
+ e.stopPropagation();
1656
+ this.deleteSession(session);
1657
+ });
1658
+ el.appendChild(del);
1659
+ });
1660
+ }
1661
+
1662
+ deleteSession(session) {
1663
+ const label = session.name || session.id;
1664
+ if (!window.confirm(`Delete session "${label}"?\nThis cannot be undone.`)) return;
1665
+ this.send({ type: 'delete_session', path: session.path });
1666
+ // Refresh the list after delete (errors surface via toast/error message)
1667
+ setTimeout(() => this.send({ type: 'get_sessions' }), 300);
1639
1668
  }
1640
1669
 
1641
1670
  // ------------------------------------------------------------------
@@ -710,6 +710,29 @@ body {
710
710
  font-size: 13px;
711
711
  }
712
712
 
713
+ /* Resume picker items: delete icon pushed to the right, away from the path */
714
+ .modal-item.resume-item {
715
+ display: flex;
716
+ align-items: baseline;
717
+ }
718
+
719
+ .modal-item.resume-item .resume-delete {
720
+ margin-left: auto;
721
+ padding-left: 16px;
722
+ cursor: pointer;
723
+ opacity: 0.6;
724
+ }
725
+
726
+ .modal-item.resume-item .resume-delete:hover {
727
+ opacity: 1;
728
+ }
729
+
730
+ .modal-item.resume-item .modal-item-desc {
731
+ overflow: hidden;
732
+ text-overflow: ellipsis;
733
+ white-space: nowrap;
734
+ }
735
+
713
736
  .modal-item:hover {
714
737
  background: var(--accent-hover);
715
738
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sdk-web",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Browser Web access for Pi (AI coding agent) via the Pi SDK - standalone module, zero modification to Pi itself",
5
5
  "type": "module",
6
6
  "bin": {