pi-sdk-web 0.3.2 → 0.3.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
@@ -278,6 +278,7 @@ export class PiWebServer {
278
278
  cwd: this.formatCwd(this.session.sessionManager.getCwd()),
279
279
  gitBranch: this.getGitBranch(this.session.sessionManager.getCwd()),
280
280
  commands: this.getCommands(),
281
+ tools: this.getActiveTools(),
281
282
  };
282
283
  try {
283
284
  state.sessionStats = this.session.getSessionStats();
@@ -352,6 +353,19 @@ export class PiWebServer {
352
353
  return [];
353
354
  }
354
355
  }
356
+ /** Tools currently exposed to the LLM (name + description). */
357
+ getActiveTools() {
358
+ try {
359
+ const active = new Set(this.session.getActiveToolNames());
360
+ return this.session
361
+ .getAllTools()
362
+ .filter((t) => active.has(t.name))
363
+ .map((t) => ({ name: t.name, description: t.description }));
364
+ }
365
+ catch {
366
+ return [];
367
+ }
368
+ }
355
369
  handleClientMessage(ws, raw) {
356
370
  let data;
357
371
  try {
@@ -208,7 +208,7 @@ class PiWebClient {
208
208
  }
209
209
 
210
210
  // Loaded resources
211
- this.renderLoadedResources(state.commands);
211
+ this.renderLoadedResources(state.commands, state.tools);
212
212
 
213
213
  // First footer line: pwd (branch) • session name
214
214
  const pwd = state.cwd || '';
@@ -220,56 +220,81 @@ class PiWebClient {
220
220
  this.renderStats(state.sessionStats, state);
221
221
  }
222
222
 
223
- renderLoadedResources(commands) {
223
+ renderLoadedResources(commands, tools) {
224
224
  if (!this.loadedResourcesEl) return;
225
- if (!commands || commands.length === 0) {
226
- this.loadedResourcesEl.innerHTML = '';
227
- return;
228
- }
225
+ this.loadedResourcesEl.innerHTML = '';
229
226
 
230
- const groups = { skill: [], prompt: [], extension: [] };
231
- for (const cmd of commands) {
232
- const source = cmd.source;
233
- if (source === 'skill') groups.skill.push(cmd);
234
- else if (source === 'prompt') groups.prompt.push(cmd);
235
- else groups.extension.push(cmd);
236
- }
227
+ // Loaded Resources block (Skills/Prompts/Extensions)
228
+ if (commands && commands.length > 0) {
229
+ const groups = { skill: [], prompt: [], extension: [] };
230
+ for (const cmd of commands) {
231
+ const source = cmd.source;
232
+ if (source === 'skill') groups.skill.push(cmd);
233
+ else if (source === 'prompt') groups.prompt.push(cmd);
234
+ else groups.extension.push(cmd);
235
+ }
237
236
 
238
- const div = document.createElement('div');
239
- div.className = 'resources-block';
240
- div.dataset.expanded = 'false';
237
+ const div = document.createElement('div');
238
+ div.className = 'resources-block';
239
+ div.dataset.expanded = 'false';
241
240
 
242
- const header = document.createElement('div');
243
- header.className = 'resources-header';
244
- header.textContent = 'Loaded Resources (click to expand)';
241
+ const header = document.createElement('div');
242
+ header.className = 'resources-header';
243
+ header.textContent = 'Loaded Resources (click to expand)';
245
244
 
246
- const body = document.createElement('div');
247
- body.className = 'resources-body';
248
- body.style.display = 'none';
245
+ const body = document.createElement('div');
246
+ body.className = 'resources-body';
247
+ body.style.display = 'none';
249
248
 
250
- const sections = [];
251
- if (groups.skill.length > 0) {
252
- sections.push(this.resourceSection('Skills', groups.skill.map((c) => c.name)));
253
- }
254
- if (groups.prompt.length > 0) {
255
- sections.push(this.resourceSection('Prompts', groups.prompt.map((c) => c.name)));
256
- }
257
- if (groups.extension.length > 0) {
258
- sections.push(this.resourceSection('Extensions', groups.extension.map((c) => c.name)));
249
+ const sections = [];
250
+ if (groups.skill.length > 0) {
251
+ sections.push(this.resourceSection('Skills', groups.skill.map((c) => c.name)));
252
+ }
253
+ if (groups.prompt.length > 0) {
254
+ sections.push(this.resourceSection('Prompts', groups.prompt.map((c) => c.name)));
255
+ }
256
+ if (groups.extension.length > 0) {
257
+ sections.push(this.resourceSection('Extensions', groups.extension.map((c) => c.name)));
258
+ }
259
+ body.innerHTML = sections.join('');
260
+
261
+ div.appendChild(header);
262
+ div.appendChild(body);
263
+ div.addEventListener('click', () => {
264
+ const expanded = div.dataset.expanded === 'true';
265
+ div.dataset.expanded = expanded ? 'false' : 'true';
266
+ body.style.display = expanded ? 'none' : 'block';
267
+ header.textContent = expanded ? 'Loaded Resources (click to expand)' : 'Loaded Resources (click to collapse)';
268
+ });
269
+ this.loadedResourcesEl.appendChild(div);
259
270
  }
260
- body.innerHTML = sections.join('');
261
271
 
262
- div.appendChild(header);
263
- div.appendChild(body);
264
- div.addEventListener('click', () => {
265
- const expanded = div.dataset.expanded === 'true';
266
- div.dataset.expanded = expanded ? 'false' : 'true';
267
- body.style.display = expanded ? 'none' : 'block';
268
- header.textContent = expanded ? 'Loaded Resources (click to expand)' : 'Loaded Resources (click to collapse)';
269
- });
272
+ // Separate Tools block below Loaded Resources (web enhancement)
273
+ if (tools && tools.length > 0) {
274
+ const div = document.createElement('div');
275
+ div.className = 'resources-block';
276
+ div.dataset.expanded = 'false';
277
+ div.style.marginTop = '6px';
270
278
 
271
- this.loadedResourcesEl.innerHTML = '';
272
- this.loadedResourcesEl.appendChild(div);
279
+ const header = document.createElement('div');
280
+ header.className = 'resources-header';
281
+ header.textContent = 'Tools (click to expand)';
282
+
283
+ const body = document.createElement('div');
284
+ body.className = 'resources-body';
285
+ body.style.display = 'none';
286
+ body.innerHTML = this.resourceSection('', tools.map((t) => t.name));
287
+
288
+ div.appendChild(header);
289
+ div.appendChild(body);
290
+ div.addEventListener('click', () => {
291
+ const expanded = div.dataset.expanded === 'true';
292
+ div.dataset.expanded = expanded ? 'false' : 'true';
293
+ body.style.display = expanded ? 'none' : 'block';
294
+ header.textContent = expanded ? 'Tools (click to expand)' : 'Tools (click to collapse)';
295
+ });
296
+ this.loadedResourcesEl.appendChild(div);
297
+ }
273
298
  }
274
299
 
275
300
  resourceSection(name, items) {
@@ -804,12 +829,14 @@ class PiWebClient {
804
829
  const widgetsEl = document.getElementById('widgets');
805
830
  if (!widgetsEl) return;
806
831
  const key = req.widgetKey || 'widget';
832
+ const wasAtBottom = this.wasAtBottom();
807
833
 
808
834
  if (req.widgetLines === undefined || req.widgetLines === null) {
809
835
  // Clear this widget
810
836
  const el = widgetsEl.querySelector(`[data-widget-key="${CSS.escape(key)}"]`);
811
837
  if (el) el.remove();
812
838
  if (widgetsEl.children.length === 0) widgetsEl.style.display = 'none';
839
+ if (wasAtBottom) this.scrollToBottom();
813
840
  return;
814
841
  }
815
842
 
@@ -829,6 +856,8 @@ class PiWebClient {
829
856
  el.querySelector('.widget-title').textContent = key;
830
857
  el.querySelector('.widget-body').textContent = (req.widgetLines || []).join('\n');
831
858
  widgetsEl.style.display = 'block';
859
+ // Layout change: keep pinned to bottom if already there
860
+ if (wasAtBottom) this.scrollToBottom();
832
861
  }
833
862
 
834
863
  // ------------------------------------------------------------------
@@ -1401,18 +1430,28 @@ class PiWebClient {
1401
1430
  renderStatusItem(req) {
1402
1431
  const el = document.getElementById('ext-status');
1403
1432
  if (!el) return;
1433
+ const wasAtBottom = this.wasAtBottom();
1404
1434
  if (req.statusText === undefined || req.statusText === null) {
1405
1435
  delete this.extStatus[req.statusKey];
1406
1436
  } else {
1407
1437
  this.extStatus[req.statusKey] = req.statusText;
1408
1438
  }
1409
1439
  this.updateExtStatusDisplay();
1440
+ if (wasAtBottom) this.scrollToBottom();
1410
1441
  }
1411
1442
 
1412
1443
  applyExtStatusSnapshot(snapshot) {
1413
1444
  if (!snapshot) return;
1445
+ const wasAtBottom = this.wasAtBottom();
1414
1446
  this.extStatus = Object.assign({}, snapshot);
1415
1447
  this.updateExtStatusDisplay();
1448
+ if (wasAtBottom) this.scrollToBottom();
1449
+ }
1450
+
1451
+ /** Whether the view is currently pinned to the bottom (before layout changes). */
1452
+ wasAtBottom() {
1453
+ const scroller = document.getElementById('scroll-view');
1454
+ return !!scroller && scroller.scrollTop + scroller.clientHeight >= scroller.scrollHeight - 2;
1416
1455
  }
1417
1456
 
1418
1457
  updateExtStatusDisplay() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sdk-web",
3
- "version": "0.3.2",
3
+ "version": "0.3.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": {