pi-sdk-web 0.4.2 → 0.4.3

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
@@ -433,6 +433,9 @@ export class PiWebServer {
433
433
  if (!command)
434
434
  throw new Error("Missing 'command'");
435
435
  const excludeFromContext = data.excludeFromContext === true;
436
+ // Correlation id for streaming: Pi emits bash_execution_update
437
+ // events carrying this id while the command runs (onChunk).
438
+ const id = typeof data.id === "string" && data.id ? data.id : `bash-${Date.now()}`;
436
439
  // Let extensions intercept/enhance the command (same as Pi RPC/TUI):
437
440
  // user_bash handlers may provide a result or operations for execution.
438
441
  const eventResult = await this.session.extensionRunner.emitUserBash({
@@ -443,14 +446,15 @@ export class PiWebServer {
443
446
  });
444
447
  if (eventResult?.result) {
445
448
  this.session.recordBashResult(command, eventResult.result, { excludeFromContext });
446
- this.broadcast({ type: "bash_result", command, data: eventResult.result });
449
+ this.broadcast({ type: "bash_result", id, command, data: eventResult.result });
447
450
  break;
448
451
  }
449
452
  const result = await this.session.executeBash(command, undefined, {
450
453
  excludeFromContext,
454
+ id,
451
455
  operations: eventResult?.operations,
452
456
  });
453
- this.broadcast({ type: "bash_result", command, data: result });
457
+ this.broadcast({ type: "bash_result", id, command, data: result });
454
458
  break;
455
459
  }
456
460
  case "cycle_model":
@@ -213,6 +213,10 @@ class PiWebClient {
213
213
 
214
214
  // Tool call rendering state: map toolCallId -> element
215
215
  this.toolEls = new Map();
216
+ // Bash streaming blocks: map bash id -> element
217
+ this.bashEls = new Map();
218
+ this.pendingBashId = null;
219
+ this.pendingBashCommand = '';
216
220
  // Tool execution timers: map toolCallId -> interval id
217
221
  this.toolTimers = new Map();
218
222
 
@@ -913,6 +917,14 @@ class PiWebClient {
913
917
  // Extension custom entries (e.g. magic-context /ctx-status) arrive live
914
918
  this.renderEntry(ev.entry);
915
919
  break;
920
+ case 'bash_execution_update':
921
+ // Streaming bash output (e.g. !! running a long-lived program).
922
+ // Append the delta to the matching tool block in real time; the
923
+ // final bash_result finalizes it (exit code, truncation info).
924
+ if (typeof ev.delta === 'string' && ev.delta.length > 0) {
925
+ this.appendBashChunk(ev.id, ev.delta);
926
+ }
927
+ break;
916
928
  default:
917
929
  break;
918
930
  }
@@ -1310,11 +1322,39 @@ class PiWebClient {
1310
1322
  this.toolEls.delete(ev.toolCallId);
1311
1323
  }
1312
1324
 
1325
+ appendBashChunk(id, delta) {
1326
+ // Find or create the tool block for this bash run (keyed by the id the
1327
+ // client sent with the bash message, e.g. bash-<timestamp>).
1328
+ const key = id || this.pendingBashId;
1329
+ if (!key) return;
1330
+ let div = this.bashEls.get(key);
1331
+ if (!div) {
1332
+ div = this.createToolBlock('bash', { command: this.pendingBashCommand || '' }, key);
1333
+ div.className = 'tool-block pending';
1334
+ this.bashEls.set(key, div);
1335
+ }
1336
+ // Append the delta to the full text and re-apply the preview (respects
1337
+ // the collapsed/expanded state of the block).
1338
+ const full = (div.dataset.fullOutput || '') + delta;
1339
+ div.dataset.fullOutput = full;
1340
+ this.applyToolPreview(div);
1341
+ if (this.wasAtBottom()) this.scrollToBottom();
1342
+ }
1343
+
1313
1344
  renderBashResult(data) {
1314
- const div = this.createToolBlock('bash', { command: data.command }, 'bash-' + Date.now());
1345
+ const id = data.id;
1315
1346
  const result = data.data || {};
1347
+ let div = (id && this.bashEls.get(id)) || null;
1348
+ if (!div) {
1349
+ div = this.createToolBlock('bash', { command: data.command }, id || 'bash-' + Date.now());
1350
+ if (id) this.bashEls.set(id, div);
1351
+ }
1316
1352
  const output = result.output || '';
1317
- this.setToolOutput(div, output);
1353
+ // Streaming blocks have the full text already (appended incrementally);
1354
+ // for non-streaming runs set it now.
1355
+ if (!(div.dataset.fullOutput && div.dataset.fullOutput.length > 0)) {
1356
+ this.setToolOutput(div, output);
1357
+ }
1318
1358
 
1319
1359
  const isError = result.exitCode !== undefined && result.exitCode !== 0;
1320
1360
  div.className = isError ? 'tool-block error' : 'tool-block success';
@@ -1334,6 +1374,10 @@ class PiWebClient {
1334
1374
  }
1335
1375
 
1336
1376
  this.scrollToBottom();
1377
+ // Streaming done: drop the block from the live map (no more deltas).
1378
+ if (id) this.bashEls.delete(id);
1379
+ this.pendingBashId = null;
1380
+ this.pendingBashCommand = '';
1337
1381
  }
1338
1382
 
1339
1383
  resultText(result) {
@@ -1420,7 +1464,10 @@ class PiWebClient {
1420
1464
  const isExcluded = text.startsWith('!!');
1421
1465
  const command = (isExcluded ? text.slice(2) : text.slice(1)).trim();
1422
1466
  if (command) {
1423
- this.send({ type: 'bash', command: command, excludeFromContext: isExcluded });
1467
+ const id = 'bash-' + Date.now();
1468
+ this.send({ type: 'bash', command: command, excludeFromContext: isExcluded, id: id });
1469
+ this.pendingBashId = id;
1470
+ this.pendingBashCommand = command;
1424
1471
  }
1425
1472
  } else if (text.startsWith('/')) {
1426
1473
  // Skill commands (/skill:name args) go through prompt expansion in Pi -
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sdk-web",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
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": {