pi-sdk-web 0.2.3 → 0.2.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.
package/dist/server.js CHANGED
@@ -487,10 +487,68 @@ export class PiWebServer {
487
487
  await this.executeCommand(name, args);
488
488
  break;
489
489
  }
490
+ case "session":
491
+ // Read-only session info (TUI /session equivalent)
492
+ this.broadcastSessionInfo();
493
+ break;
490
494
  default:
491
495
  throw new Error(`Unsupported command: ${cmdType}`);
492
496
  }
493
497
  }
498
+ /** TUI /session equivalent: show session info as a modal (read-only). */
499
+ broadcastSessionInfo() {
500
+ try {
501
+ const stats = this.session.getSessionStats();
502
+ const sm = this.session.sessionManager;
503
+ const entries = sm.getEntries();
504
+ const model = this.session.model;
505
+ const lines = [];
506
+ lines.push("## Session Info", "");
507
+ const name = sm.getSessionName();
508
+ if (name)
509
+ lines.push(`**Name:** ${name}`);
510
+ lines.push(`**ID:** ${this.session.sessionId}`);
511
+ if (stats.sessionFile)
512
+ lines.push(`**File:** ${stats.sessionFile}`);
513
+ if (model)
514
+ lines.push(`**Model:** ${model.provider}/${model.id}`);
515
+ lines.push(`**Thinking:** ${this.session.thinkingLevel}`, "");
516
+ lines.push("### Messages");
517
+ lines.push(`- User: ${stats.userMessages}`);
518
+ lines.push(`- Assistant: ${stats.assistantMessages}`);
519
+ lines.push(`- Tool calls: ${stats.toolCalls}`);
520
+ lines.push(`- Tool results: ${stats.toolResults}`);
521
+ lines.push(`- Total: ${stats.totalMessages}`);
522
+ lines.push(`- Entries: ${entries.length}`, "");
523
+ const t = stats.tokens;
524
+ if (t) {
525
+ lines.push("### Tokens");
526
+ lines.push(`- Input: ${t.input}`);
527
+ lines.push(`- Output: ${t.output}`);
528
+ lines.push(`- Cache read: ${t.cacheRead}`);
529
+ lines.push(`- Cache write: ${t.cacheWrite}`);
530
+ lines.push(`- Total: ${t.total}`, "");
531
+ }
532
+ lines.push("### Cost");
533
+ lines.push(`$${stats.cost.toFixed(4)}`);
534
+ const cu = stats.contextUsage;
535
+ if (cu?.contextWindow) {
536
+ lines.push("", "### Context");
537
+ lines.push(`- ${cu.percent}% / ${cu.contextWindow} tokens`);
538
+ }
539
+ this.broadcast({
540
+ type: "extension_ui_request",
541
+ id: crypto.randomUUID(),
542
+ method: "notify",
543
+ title: "/session",
544
+ message: lines.join("\n"),
545
+ notifyType: "info",
546
+ });
547
+ }
548
+ catch {
549
+ // session info unavailable - skip
550
+ }
551
+ }
494
552
  /**
495
553
  * Execute an extension slash command (e.g. /ctx-status) by invoking its
496
554
  * registered handler with a command context.
@@ -8,6 +8,7 @@ const BUILTIN_COMMANDS = [
8
8
  { name: 'compact', description: 'Manually compact the session context', builtin: true, action: 'compact' },
9
9
  { name: 'reload', description: 'Reload session resources and extensions', builtin: true, action: 'reload' },
10
10
  { name: 'export', description: 'Export session to HTML (or .jsonl)', builtin: true, action: 'export' },
11
+ { name: 'session', description: 'Show session information', builtin: true, action: 'session' },
11
12
  { name: 'name', description: 'Set session display name', builtin: true, action: 'name' },
12
13
  { name: 'login', description: 'Configure provider authentication (not supported in web)', builtin: true, unsupported: true },
13
14
  { name: 'logout', description: 'Remove provider authentication (not supported in web)', builtin: true, unsupported: true },
@@ -1202,6 +1203,8 @@ class PiWebClient {
1202
1203
  } else if (cmd.action === 'export') {
1203
1204
  const path = (args || '').trim();
1204
1205
  this.send({ type: 'export', path: path });
1206
+ } else if (cmd.action === 'session') {
1207
+ this.send({ type: 'session' });
1205
1208
  } else if (cmd.action === 'name') {
1206
1209
  const newName = window.prompt('Set session display name:', '');
1207
1210
  if (newName && newName.trim()) {
@@ -1469,12 +1472,30 @@ class PiWebClient {
1469
1472
  }
1470
1473
 
1471
1474
  openExtensionNotify(req) {
1472
- this.openModal(req.title || 'Notification', 'extension-notify');
1473
- this.currentExtRequest = req;
1474
- this.modalSearch.style.display = 'none';
1475
- // Render as markdown (sanitized) so command outputs (/ctx-status etc.) look right
1476
- this.modalList.innerHTML = `<div class="modal-message body-text">${this.renderMarkdown(req.message || '')}</div>`;
1477
- // Close button in modal footer is enough
1475
+ // Command output notifications (title starts with "/", e.g. /ctx-status) keep
1476
+ // the modal. Other extension notifies are lightweight toasts (TUI shows
1477
+ // notify as a transient status message, not a dialog).
1478
+ if (req.title && String(req.title).startsWith('/')) {
1479
+ this.openModal(req.title || 'Notification', 'extension-notify');
1480
+ this.currentExtRequest = req;
1481
+ this.modalSearch.style.display = 'none';
1482
+ this.modalList.innerHTML = `<div class="modal-message body-text">${this.renderMarkdown(req.message || '')}</div>`;
1483
+ return;
1484
+ }
1485
+ this.showToast(req.message || '', req.notifyType);
1486
+ }
1487
+
1488
+ showToast(message, type) {
1489
+ let container = document.getElementById('toast-container');
1490
+ if (!container) return;
1491
+ const toast = document.createElement('div');
1492
+ toast.className = 'toast' + (type ? ` toast-${type}` : '');
1493
+ toast.textContent = message;
1494
+ container.appendChild(toast);
1495
+ setTimeout(() => {
1496
+ toast.classList.add('toast-hide');
1497
+ setTimeout(() => toast.remove(), 300);
1498
+ }, type === 'error' ? 8000 : 5000);
1478
1499
  }
1479
1500
 
1480
1501
  handleModels(models) {
@@ -1614,11 +1635,24 @@ class PiWebClient {
1614
1635
  ...((this.lastState && this.lastState.commands) || []),
1615
1636
  ...BUILTIN_COMMANDS,
1616
1637
  ];
1617
- const filtered = commands.filter((c) => {
1618
- const name = (c.name || '').replace(/^skill:/, '').toLowerCase();
1619
- const desc = (c.description || c.source || '').toLowerCase();
1620
- return name.includes(query) || desc.includes(query);
1621
- });
1638
+ const filtered = commands
1639
+ .filter((c) => {
1640
+ const name = (c.name || '').replace(/^skill:/, '').toLowerCase();
1641
+ const desc = (c.description || c.source || '').toLowerCase();
1642
+ return name.includes(query) || desc.includes(query);
1643
+ })
1644
+ .sort((a, b) => {
1645
+ // Exact-name match first, then name-prefix, then name-contains,
1646
+ // then description-contains (avoids unrelated commands flooding the list)
1647
+ const score = (c) => {
1648
+ const name = (c.name || '').replace(/^skill:/, '').toLowerCase();
1649
+ if (name === query) return 0;
1650
+ if (name.startsWith(query)) return 1;
1651
+ if (name.includes(query)) return 2;
1652
+ return 3;
1653
+ };
1654
+ return score(a) - score(b);
1655
+ });
1622
1656
 
1623
1657
  if (filtered.length === 0) {
1624
1658
  this.hideCommandMenu();
@@ -53,6 +53,7 @@
53
53
  <div id="modal-close">Close (Esc)</div>
54
54
  </div>
55
55
  </div>
56
+ <div id="toast-container"></div>
56
57
  <script src="vendor/marked.min.js"></script>
57
58
  <script src="app.js"></script>
58
59
  </body>
@@ -734,6 +734,44 @@ body {
734
734
  color: var(--text);
735
735
  }
736
736
 
737
+ /* Lightweight extension notifications (TUI shows notify as transient status) */
738
+ #toast-container {
739
+ position: fixed;
740
+ top: 12px;
741
+ right: 12px;
742
+ z-index: 200;
743
+ display: flex;
744
+ flex-direction: column;
745
+ gap: 8px;
746
+ max-width: 420px;
747
+ }
748
+
749
+ .toast {
750
+ background: var(--modal-bg);
751
+ border: 1px solid var(--border);
752
+ border-left: 3px solid var(--accent);
753
+ border-radius: 6px;
754
+ padding: 8px 12px;
755
+ color: var(--text);
756
+ font-size: 13px;
757
+ line-height: 1.5;
758
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
759
+ opacity: 1;
760
+ transition: opacity 0.3s;
761
+ }
762
+
763
+ .toast-warning {
764
+ border-left-color: var(--warning);
765
+ }
766
+
767
+ .toast-error {
768
+ border-left-color: var(--error);
769
+ }
770
+
771
+ .toast-hide {
772
+ opacity: 0;
773
+ }
774
+
737
775
  .modal-message {
738
776
  color: var(--text);
739
777
  font-size: 13px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sdk-web",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
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": {