@timurproko/a1 0.1.8-dev.0b6d8cc → 0.1.8-dev.21b95d6

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.
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "unsupported",
8
- "builtAt": "2026-08-25T08:02:28.988Z",
8
+ "builtAt": "2026-08-25T08:21:01.149Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "7524bf568992517f50ed53196c861c5edeba0d7275633bb4735ab45bd5963a28",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-08-25T08:02:31.153Z",
8
+ "builtAt": "2026-08-25T08:20:57.618Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "29e22fe29de2828982bc67ef418c4adcaab1490281477b7bea592ec6fe621bcd",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-08-25T08:02:58.479Z",
8
+ "builtAt": "2026-08-25T08:21:59.118Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "fdbde2408643ffb897f5107d96ed40e8c8467505ef7daa64f86f9c2497c304ca",
11
+ "sha256": "82d4ad108c0e73f59b8bca207c751a7822e21045895c1453296fff6b649042ea",
12
12
  "size": 172544
13
13
  },
14
14
  "provenance": {
@@ -1476,14 +1476,16 @@ export class PiEngineAdapter {
1476
1476
  this.#upsertMessageBlock(event.message, "live");
1477
1477
  return;
1478
1478
  case "message_update": {
1479
- const block = this.#upsertMessageBlock(event.message, "live");
1480
- if (block && isRecord(event.assistantMessageEvent) && typeof event.assistantMessageEvent.delta === "string") {
1481
- this.#upsertTranscriptBlock({
1482
- ...block,
1483
- text: block.text.endsWith(event.assistantMessageEvent.delta)
1484
- ? block.text
1485
- : `${block.text}${event.assistantMessageEvent.delta}`,
1486
- });
1479
+ const delta = isRecord(event.assistantMessageEvent) && typeof event.assistantMessageEvent.delta === "string"
1480
+ ? event.assistantMessageEvent.delta
1481
+ : undefined;
1482
+ // The delta is folded in before the block is stored, so a chunk is one update to
1483
+ // one block rather than a store without the delta followed by a store with it.
1484
+ const blocks = this.#messageBlocks(event.message, "live", this.#transcript.length);
1485
+ for (const [index, block] of blocks.entries()) {
1486
+ this.#upsertTranscriptBlock(index === 0 && delta !== undefined && !block.text.endsWith(delta)
1487
+ ? { ...block, text: `${block.text}${delta}` }
1488
+ : block);
1487
1489
  }
1488
1490
  return;
1489
1491
  }
@@ -1638,7 +1640,13 @@ export class PiEngineAdapter {
1638
1640
  }
1639
1641
  }
1640
1642
  }
1641
- this.#setTranscript(blocks);
1643
+ // An authoritative rebuild restates most of what is already there. Reusing the block
1644
+ // that already says it keeps its revision, and with it the rows the shell rendered for
1645
+ // it — otherwise every turn that ends re-renders the whole session.
1646
+ this.#setTranscript(blocks.map(block => {
1647
+ const existing = this.#transcriptBlock(block.id);
1648
+ return existing !== undefined && sameBlockContent(existing, block) ? existing : block;
1649
+ }));
1642
1650
  }
1643
1651
  #upsertMessageBlock(message, status) {
1644
1652
  const blocks = this.#messageBlocks(message, status, this.#transcript.length);
@@ -1815,8 +1823,14 @@ export class PiEngineAdapter {
1815
1823
  }
1816
1824
  #upsertTranscriptBlock(block) {
1817
1825
  const index = this.#transcriptIndex.get(block.id);
1818
- if (index !== undefined)
1826
+ if (index !== undefined) {
1827
+ const existing = this.#transcript[index];
1828
+ // Nothing to tell the shell about a block that repeats itself, and keeping the
1829
+ // revision keeps the rows it already rendered.
1830
+ if (existing !== undefined && sameBlockContent(existing, block))
1831
+ return;
1819
1832
  this.#transcript[index] = block;
1833
+ }
1820
1834
  else {
1821
1835
  this.#transcriptIndex.set(block.id, this.#transcript.length);
1822
1836
  this.#transcript.push(block);
@@ -2388,6 +2402,36 @@ function compactResourceLabel(path) {
2388
2402
  const segments = path.replaceAll("\\", "/").split("/").filter(Boolean);
2389
2403
  return segments.at(-1) ?? path;
2390
2404
  }
2405
+ /**
2406
+ * Whether two blocks say the same thing. A block that says what it already said is not a
2407
+ * new revision: the shell renders a block once per revision, so bumping one it did not
2408
+ * need re-renders it for nothing.
2409
+ */
2410
+ function sameBlockContent(left, right) {
2411
+ return left.kind === right.kind
2412
+ && left.status === right.status
2413
+ && left.title === right.title
2414
+ && left.text === right.text
2415
+ && sameValue(left.payload, right.payload);
2416
+ }
2417
+ function sameValue(left, right) {
2418
+ if (left === right)
2419
+ return true;
2420
+ if (typeof left !== typeof right || left === null || right === null)
2421
+ return false;
2422
+ if (Array.isArray(left) || Array.isArray(right)) {
2423
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
2424
+ return false;
2425
+ return left.every((value, index) => sameValue(value, right[index]));
2426
+ }
2427
+ if (typeof left !== "object")
2428
+ return false;
2429
+ const leftKeys = Object.keys(left);
2430
+ const rightRecord = right;
2431
+ if (leftKeys.length !== Object.keys(rightRecord).length)
2432
+ return false;
2433
+ return leftKeys.every(key => Object.hasOwn(rightRecord, key) && sameValue(left[key], rightRecord[key]));
2434
+ }
2391
2435
  function textFromContent(content) {
2392
2436
  if (typeof content === "string")
2393
2437
  return content;
@@ -39,6 +39,12 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
39
39
  readonly onDequeue?: () => void;
40
40
  }, startup?: PiShellHeaderOptions, agentDir?: string, extensionRenderers?: PiShellExtensionRendererResolver);
41
41
  update(view: OwnedUiSessionViewModel): void;
42
+ /**
43
+ * Applies one block: its component is created or updated in place and the order grows
44
+ * only when the block is new. A streamed chunk costs one component update rather than a
45
+ * walk of the whole transcript.
46
+ */
47
+ applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
42
48
  render(width: number): readonly string[];
43
49
  layoutRoot(): PiTuiLayoutNode;
44
50
  transcriptComponent(id: string): PiShellTranscriptComponentPort | undefined;
@@ -71,6 +71,25 @@ export class OwnedUiSessionShellRoot {
71
71
  this.editor.setThinkingLevel(view.thinkingLevel);
72
72
  this.invalidate();
73
73
  }
74
+ /**
75
+ * Applies one block: its component is created or updated in place and the order grows
76
+ * only when the block is new. A streamed chunk costs one component update rather than a
77
+ * walk of the whole transcript.
78
+ */
79
+ applyTranscriptBlock(block) {
80
+ this.#blocksById.set(block.id, block);
81
+ const component = this.#transcript.get(block.id);
82
+ if (component === undefined) {
83
+ const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers);
84
+ created.setExpanded(this.#toolsExpanded);
85
+ this.#transcript.set(block.id, created);
86
+ this.#transcriptOrder.push(block.id);
87
+ }
88
+ else if (component.revision !== block.revision) {
89
+ component.update(block);
90
+ }
91
+ this.invalidate();
92
+ }
74
93
  render(width) {
75
94
  const queued = this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
76
95
  return [
@@ -794,9 +813,12 @@ export class OwnedUiSessionShell {
794
813
  });
795
814
  this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
796
815
  this.#unsubscribe = this.backend.onEvent(event => {
797
- // One view read per event: the model is built by the backend, and building it
798
- // twice per streamed chunk is what made a long session cost more per chunk.
799
- const view = this.#syncView();
816
+ // A streamed chunk names one block, and touching only that block is what keeps the
817
+ // cost of a chunk the same in a long session as in a new one. Everything else
818
+ // resynchronizes the view, which is cheap next to re-reading the transcript.
819
+ const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.sessionGeneration
820
+ ? this.#syncBlock(event.block)
821
+ : this.#syncView();
800
822
  if (view.lifecycle === "ready" && this.#compactionQueue.length > 0)
801
823
  void this.#flushCompactionQueue();
802
824
  if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
@@ -1415,6 +1437,18 @@ export class OwnedUiSessionShell {
1415
1437
  this.#extensionBridge.dispose();
1416
1438
  await this.runtime.dispose();
1417
1439
  }
1440
+ /**
1441
+ * Applies one transcript block without re-reading the session. The listeners still hear
1442
+ * the view they would have heard, so nothing downstream can tell the difference.
1443
+ */
1444
+ #syncBlock(block) {
1445
+ this.root.applyTranscriptBlock(block);
1446
+ this.runtime.requestRender();
1447
+ const view = this.view();
1448
+ for (const listener of this.#listeners)
1449
+ listener(view);
1450
+ return view;
1451
+ }
1418
1452
  #syncView() {
1419
1453
  const view = this.view();
1420
1454
  if (this.backend.sessionGeneration !== this.#sessionGeneration) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.0b6d8cc",
3
+ "version": "0.1.8-dev.21b95d6",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.13.0",