@timurproko/a1 0.1.8-dev.b449b5c → 0.1.8-dev.c64be0e
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/README.md +22 -17
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/src/cli/dispatch.js +53 -18
- package/dist/src/cli/packages.d.ts +9 -1
- package/dist/src/cli/packages.js +24 -20
- package/dist/src/foundation/pi-component-adapter/index.d.ts +1 -1
- package/dist/src/foundation/pi-component-adapter/index.js +1 -1
- package/dist/src/foundation/pi-component-adapter/shell-presenters-transcript.js +2 -1
- package/dist/src/foundation/pi-engine-adapter/adapter.d.ts +7 -0
- package/dist/src/foundation/pi-engine-adapter/adapter.js +193 -39
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.d.ts +6 -0
- package/dist/src/foundation/pi-owned-ui-integration/session-shell.js +223 -13
- package/dist/src/foundation/release/index.d.ts +0 -1
- package/dist/src/foundation/release/index.js +0 -1
- package/dist/src/foundation/release/update.js +13 -3
- package/docs/ci-release-runbook.md +1 -1
- package/package.json +2 -1
- package/dist/src/foundation/release/development-preview-release.d.ts +0 -49
- package/dist/src/foundation/release/development-preview-release.js +0 -114
|
@@ -11,6 +11,12 @@ import { createPiRuntimeIntegration } from "./runtime-integration.js";
|
|
|
11
11
|
import { PiSessionCommandIntegration } from "./session-integration.js";
|
|
12
12
|
import { PiSettingsIntegration } from "./settings-integration.js";
|
|
13
13
|
const execFileAsync = promisify(execFile);
|
|
14
|
+
/**
|
|
15
|
+
* Engine events delivered before the queue hands the event loop a turn. Small enough
|
|
16
|
+
* that a streaming burst never holds input, large enough that an ordinary turn is one
|
|
17
|
+
* batch.
|
|
18
|
+
*/
|
|
19
|
+
const EVENT_DELIVERY_BATCH = 16;
|
|
14
20
|
const DEFAULT_SURFACE = {
|
|
15
21
|
columns: 100,
|
|
16
22
|
rows: 32,
|
|
@@ -53,6 +59,8 @@ export class PiEngineAdapter {
|
|
|
53
59
|
#activeCommandIds = [];
|
|
54
60
|
#completedCommands = new Map();
|
|
55
61
|
#transcript = [];
|
|
62
|
+
#transcriptIndex = new Map();
|
|
63
|
+
#transcriptSnapshot;
|
|
56
64
|
#messageBlockIds = new WeakMap();
|
|
57
65
|
#messageFallbackIds = new Map();
|
|
58
66
|
#toolBlockIds = new Map();
|
|
@@ -61,6 +69,8 @@ export class PiEngineAdapter {
|
|
|
61
69
|
#eventQueue = [];
|
|
62
70
|
#eventQueueProcessing;
|
|
63
71
|
#droppedEventCount = 0;
|
|
72
|
+
#agentRunActive = false;
|
|
73
|
+
#statusKind = null;
|
|
64
74
|
#sessionCommands;
|
|
65
75
|
#gitBranch = null;
|
|
66
76
|
#extensionUi;
|
|
@@ -143,7 +153,7 @@ export class PiEngineAdapter {
|
|
|
143
153
|
if (this.#disposed || updates.length === 0)
|
|
144
154
|
return;
|
|
145
155
|
const packages = updates.map(name => `- ${name}`).join("\n");
|
|
146
|
-
this.#addDiagnostic("info", "package-updates", `Package updates are available. Run pi update --extensions\nPackages:\n${packages}`, true);
|
|
156
|
+
this.#addDiagnostic("info", "package-updates", `Package updates are available. Run ${PRODUCT_IDENTITY.commandName} pi update --extensions\nPackages:\n${packages}`, true);
|
|
147
157
|
this.#emitView();
|
|
148
158
|
}
|
|
149
159
|
onEvent(listener) {
|
|
@@ -278,6 +288,7 @@ export class PiEngineAdapter {
|
|
|
278
288
|
id: `extension-${resources.length}`,
|
|
279
289
|
sourcePath: extension.path,
|
|
280
290
|
resolvedPath: extension.resolvedPath,
|
|
291
|
+
sourceInfo: extensionSourceSummary(extension.sourceInfo),
|
|
281
292
|
loaded: true,
|
|
282
293
|
hidden: extension.hidden === true,
|
|
283
294
|
diagnostic: null,
|
|
@@ -373,9 +384,10 @@ export class PiEngineAdapter {
|
|
|
373
384
|
}
|
|
374
385
|
const extensionCommands = this.#session?.extensionRunner?.getRegisteredCommands?.();
|
|
375
386
|
if (Array.isArray(extensionCommands)) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
387
|
+
const registered = extensionCommands.filter(isRecord);
|
|
388
|
+
for (const command of registered) {
|
|
389
|
+
const name = stringProperty(command, "invocationName") ?? stringProperty(command, "name");
|
|
390
|
+
if (!name || usedNames.has(name) || isPiPrefixedCompatibilityAlias(command, registered))
|
|
379
391
|
continue;
|
|
380
392
|
commands.push({ name, description: stringProperty(command, "description") ?? "Extension command", source: "extension" });
|
|
381
393
|
usedNames.add(name);
|
|
@@ -724,7 +736,7 @@ export class PiEngineAdapter {
|
|
|
724
736
|
sessionId: this.#sessionId,
|
|
725
737
|
revision: this.#viewRevision,
|
|
726
738
|
lifecycle: this.#lifecycle,
|
|
727
|
-
transcript: this.#
|
|
739
|
+
transcript: this.#transcriptSnapshot ??= Object.freeze([...this.#transcript]),
|
|
728
740
|
editor: { ...this.#editor, queuedSubmissions: [...this.#editor.queuedSubmissions] },
|
|
729
741
|
status: {
|
|
730
742
|
...this.#status,
|
|
@@ -1350,6 +1362,8 @@ export class PiEngineAdapter {
|
|
|
1350
1362
|
submitEnabled: true,
|
|
1351
1363
|
};
|
|
1352
1364
|
this.#status = { ...this.#status, workingMessage: null, badges: [] };
|
|
1365
|
+
this.#statusKind = null;
|
|
1366
|
+
this.#agentRunActive = false;
|
|
1353
1367
|
this.#activeModel = readModel(session.model);
|
|
1354
1368
|
this.#reconcileActiveModelAvailability();
|
|
1355
1369
|
this.#thinkingLevel = readThinkingLevel(session.thinkingLevel);
|
|
@@ -1402,28 +1416,76 @@ export class PiEngineAdapter {
|
|
|
1402
1416
|
this.#emitView();
|
|
1403
1417
|
}
|
|
1404
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Replaces the transcript and the index that finds a block by its identifier. Every
|
|
1421
|
+
* lookup goes through the index, so streaming a chunk costs the same in a long session
|
|
1422
|
+
* as in a new one.
|
|
1423
|
+
*/
|
|
1424
|
+
#setTranscript(blocks) {
|
|
1425
|
+
this.#transcript = blocks;
|
|
1426
|
+
this.#transcriptIndex.clear();
|
|
1427
|
+
for (const [index, block] of blocks.entries())
|
|
1428
|
+
this.#transcriptIndex.set(block.id, index);
|
|
1429
|
+
this.#transcriptSnapshot = undefined;
|
|
1430
|
+
}
|
|
1431
|
+
#transcriptBlock(id) {
|
|
1432
|
+
const index = this.#transcriptIndex.get(id);
|
|
1433
|
+
return index === undefined ? undefined : this.#transcript[index];
|
|
1434
|
+
}
|
|
1435
|
+
/** Shows the state named by `kind`, which becomes the state a later end can clear. */
|
|
1436
|
+
#enterWorkState(kind, message) {
|
|
1437
|
+
const wasBusy = this.#lifecycle === "busy";
|
|
1438
|
+
this.#statusKind = kind;
|
|
1439
|
+
this.#lifecycle = "busy";
|
|
1440
|
+
this.#status = { ...this.#status, workingMessage: message };
|
|
1441
|
+
if (!wasBusy)
|
|
1442
|
+
this.#emitEvent({ type: "session-lifecycle", lifecycle: "busy", reason: null });
|
|
1443
|
+
this.#emitEvent({ type: "status", status: this.#status });
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* Ends one named state. A state the shell is not in is left alone, so a finished
|
|
1447
|
+
* compaction or retry cannot clear the working state it never replaced. While the run
|
|
1448
|
+
* continues, ending either of those returns to working rather than to idle.
|
|
1449
|
+
*/
|
|
1450
|
+
#endWorkState(kind) {
|
|
1451
|
+
if (this.#statusKind !== kind)
|
|
1452
|
+
return;
|
|
1453
|
+
if (this.#agentRunActive) {
|
|
1454
|
+
this.#enterWorkState("working", "Working...");
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
this.#leaveWorkStates();
|
|
1458
|
+
}
|
|
1459
|
+
/** Leaves every work state and reports the session idle. */
|
|
1460
|
+
#leaveWorkStates() {
|
|
1461
|
+
this.#statusKind = null;
|
|
1462
|
+
this.#lifecycle = "ready";
|
|
1463
|
+
this.#status = { ...this.#status, workingMessage: null };
|
|
1464
|
+
this.#emitEvent({ type: "session-lifecycle", lifecycle: "ready", reason: null });
|
|
1465
|
+
this.#emitEvent({ type: "status", status: this.#status });
|
|
1466
|
+
}
|
|
1405
1467
|
#handlePiEvent(event) {
|
|
1406
1468
|
if (!isRecord(event) || typeof event.type !== "string")
|
|
1407
1469
|
return;
|
|
1408
1470
|
switch (event.type) {
|
|
1409
1471
|
case "agent_start":
|
|
1410
|
-
this.#
|
|
1411
|
-
this.#
|
|
1412
|
-
this.#emitEvent({ type: "session-lifecycle", lifecycle: "busy", reason: null });
|
|
1413
|
-
this.#emitEvent({ type: "status", status: this.#status });
|
|
1472
|
+
this.#agentRunActive = true;
|
|
1473
|
+
this.#enterWorkState("working", "Working...");
|
|
1414
1474
|
return;
|
|
1415
1475
|
case "message_start":
|
|
1416
1476
|
this.#upsertMessageBlock(event.message, "live");
|
|
1417
1477
|
return;
|
|
1418
1478
|
case "message_update": {
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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);
|
|
1427
1489
|
}
|
|
1428
1490
|
return;
|
|
1429
1491
|
}
|
|
@@ -1452,11 +1514,18 @@ export class PiEngineAdapter {
|
|
|
1452
1514
|
if (finalMessages.length > 0)
|
|
1453
1515
|
this.#rebuildTranscript(finalMessages, "finalized");
|
|
1454
1516
|
else
|
|
1455
|
-
this.#
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1517
|
+
this.#setTranscript(this.#transcript.map(block => block.status === "live" ? { ...block, status: "finalized" } : block));
|
|
1518
|
+
// Ending a turn leaves the working state, as the recorded pinned baseline does, but
|
|
1519
|
+
// it leaves only that state: a compaction or retry being shown outlives the turn
|
|
1520
|
+
// that ended under it. Settlement ends the run, and with it every state — the
|
|
1521
|
+
// engine ends a turn for each continuation it makes and settles once.
|
|
1522
|
+
if (event.type === "agent_settled") {
|
|
1523
|
+
this.#agentRunActive = false;
|
|
1524
|
+
this.#leaveWorkStates();
|
|
1525
|
+
}
|
|
1526
|
+
else if (this.#statusKind === null || this.#statusKind === "working") {
|
|
1527
|
+
this.#leaveWorkStates();
|
|
1528
|
+
}
|
|
1460
1529
|
this.#emitView();
|
|
1461
1530
|
return;
|
|
1462
1531
|
}
|
|
@@ -1472,20 +1541,16 @@ export class PiEngineAdapter {
|
|
|
1472
1541
|
return;
|
|
1473
1542
|
}
|
|
1474
1543
|
case "auto_retry_start":
|
|
1475
|
-
this.#
|
|
1476
|
-
this.#status = { ...this.#status, workingMessage: "Retrying…" };
|
|
1477
|
-
this.#emitEvent({ type: "status", status: this.#status });
|
|
1544
|
+
this.#enterWorkState("retry", "Retrying…");
|
|
1478
1545
|
return;
|
|
1479
1546
|
case "auto_retry_end":
|
|
1480
|
-
|
|
1481
|
-
this.#lifecycle = "ready";
|
|
1482
|
-
this.#status = { ...this.#status, workingMessage: null };
|
|
1483
|
-
this.#emitEvent({ type: "status", status: this.#status });
|
|
1547
|
+
this.#endWorkState("retry");
|
|
1484
1548
|
return;
|
|
1485
1549
|
case "compaction_start":
|
|
1486
|
-
this.#
|
|
1487
|
-
|
|
1488
|
-
|
|
1550
|
+
this.#enterWorkState("compaction", "Compacting…");
|
|
1551
|
+
return;
|
|
1552
|
+
case "compaction_end":
|
|
1553
|
+
this.#endWorkState("compaction");
|
|
1489
1554
|
return;
|
|
1490
1555
|
case "thinking_level_changed":
|
|
1491
1556
|
this.#thinkingLevel = readThinkingLevel(event.level);
|
|
@@ -1575,7 +1640,13 @@ export class PiEngineAdapter {
|
|
|
1575
1640
|
}
|
|
1576
1641
|
}
|
|
1577
1642
|
}
|
|
1578
|
-
|
|
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
|
+
}));
|
|
1579
1650
|
}
|
|
1580
1651
|
#upsertMessageBlock(message, status) {
|
|
1581
1652
|
const blocks = this.#messageBlocks(message, status, this.#transcript.length);
|
|
@@ -1660,7 +1731,7 @@ export class PiEngineAdapter {
|
|
|
1660
1731
|
: this.#toolBlockIds.get(toolCallId) ?? `tool-${toolCallId}`;
|
|
1661
1732
|
if (toolCallId !== undefined)
|
|
1662
1733
|
this.#toolBlockIds.set(toolCallId, blockId);
|
|
1663
|
-
const existing = this.#
|
|
1734
|
+
const existing = this.#transcriptBlock(blockId);
|
|
1664
1735
|
const existingPayload = isRecord(existing?.payload) ? existing.payload : undefined;
|
|
1665
1736
|
return [{
|
|
1666
1737
|
id: blockId,
|
|
@@ -1751,11 +1822,20 @@ export class PiEngineAdapter {
|
|
|
1751
1822
|
});
|
|
1752
1823
|
}
|
|
1753
1824
|
#upsertTranscriptBlock(block) {
|
|
1754
|
-
const index = this.#
|
|
1755
|
-
if (index
|
|
1825
|
+
const index = this.#transcriptIndex.get(block.id);
|
|
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;
|
|
1756
1832
|
this.#transcript[index] = block;
|
|
1757
|
-
|
|
1833
|
+
}
|
|
1834
|
+
else {
|
|
1835
|
+
this.#transcriptIndex.set(block.id, this.#transcript.length);
|
|
1758
1836
|
this.#transcript.push(block);
|
|
1837
|
+
}
|
|
1838
|
+
this.#transcriptSnapshot = undefined;
|
|
1759
1839
|
this.#emitEvent({ type: "transcript-block", block });
|
|
1760
1840
|
}
|
|
1761
1841
|
#messageBlockId(message, fallbackIndex, status, occurrence) {
|
|
@@ -1773,7 +1853,7 @@ export class PiEngineAdapter {
|
|
|
1773
1853
|
}
|
|
1774
1854
|
}
|
|
1775
1855
|
else {
|
|
1776
|
-
id = [...cached].reverse().find(candidate => this.#
|
|
1856
|
+
id = [...cached].reverse().find(candidate => this.#transcriptBlock(candidate)?.status === "live");
|
|
1777
1857
|
if (id === undefined && status === "finalized")
|
|
1778
1858
|
id = cached.at(-1);
|
|
1779
1859
|
if (id === undefined) {
|
|
@@ -1786,7 +1866,7 @@ export class PiEngineAdapter {
|
|
|
1786
1866
|
return id;
|
|
1787
1867
|
}
|
|
1788
1868
|
#nextBlockRevision(id) {
|
|
1789
|
-
const existing = this.#
|
|
1869
|
+
const existing = this.#transcriptBlock(id);
|
|
1790
1870
|
if (existing)
|
|
1791
1871
|
return existing.revision + 1;
|
|
1792
1872
|
this.#nextBlockSequence += 1;
|
|
@@ -1847,6 +1927,7 @@ export class PiEngineAdapter {
|
|
|
1847
1927
|
}
|
|
1848
1928
|
async #processEventQueue() {
|
|
1849
1929
|
try {
|
|
1930
|
+
let deliveredSinceYield = 0;
|
|
1850
1931
|
while (this.#eventQueue.length > 0) {
|
|
1851
1932
|
const event = this.#eventQueue.shift();
|
|
1852
1933
|
if (!event)
|
|
@@ -1859,6 +1940,14 @@ export class PiEngineAdapter {
|
|
|
1859
1940
|
this.#recordDiagnostic("warning", "event-listener", error instanceof Error ? error.message : String(error), true);
|
|
1860
1941
|
}
|
|
1861
1942
|
}
|
|
1943
|
+
deliveredSinceYield += 1;
|
|
1944
|
+
// A microtask chain runs to exhaustion before the loop turns, so a streaming
|
|
1945
|
+
// burst would hold typed input, pointer reports, and timed indicators until it
|
|
1946
|
+
// drained. Yielding on a macrotask hands those their turn between batches.
|
|
1947
|
+
if (deliveredSinceYield >= EVENT_DELIVERY_BATCH && this.#eventQueue.length > 0) {
|
|
1948
|
+
deliveredSinceYield = 0;
|
|
1949
|
+
await new Promise(resolve => { setImmediate(resolve); });
|
|
1950
|
+
}
|
|
1862
1951
|
}
|
|
1863
1952
|
}
|
|
1864
1953
|
finally {
|
|
@@ -2245,11 +2334,26 @@ function extensionResourceDiagnostic(index, sourcePath, diagnostic) {
|
|
|
2245
2334
|
id: `extension-diagnostic-${index}`,
|
|
2246
2335
|
sourcePath,
|
|
2247
2336
|
resolvedPath: null,
|
|
2337
|
+
sourceInfo: null,
|
|
2248
2338
|
loaded: false,
|
|
2249
2339
|
hidden: false,
|
|
2250
2340
|
diagnostic,
|
|
2251
2341
|
};
|
|
2252
2342
|
}
|
|
2343
|
+
function extensionSourceSummary(value) {
|
|
2344
|
+
if (!isRecord(value))
|
|
2345
|
+
return null;
|
|
2346
|
+
const source = stringProperty(value, "source");
|
|
2347
|
+
const scope = value.scope;
|
|
2348
|
+
const origin = value.origin;
|
|
2349
|
+
const baseDir = value.baseDir;
|
|
2350
|
+
if (!source
|
|
2351
|
+
|| (scope !== "user" && scope !== "project" && scope !== "temporary")
|
|
2352
|
+
|| (origin !== "package" && origin !== "top-level")
|
|
2353
|
+
|| (baseDir !== undefined && typeof baseDir !== "string"))
|
|
2354
|
+
return null;
|
|
2355
|
+
return { source, scope, origin, baseDir: baseDir ?? null };
|
|
2356
|
+
}
|
|
2253
2357
|
function collectionResult(value, key) {
|
|
2254
2358
|
if (!isRecord(value))
|
|
2255
2359
|
return { values: [], diagnostics: [] };
|
|
@@ -2274,10 +2378,60 @@ function stringProperty(value, key) {
|
|
|
2274
2378
|
const item = value[key];
|
|
2275
2379
|
return typeof item === "string" && item.length > 0 ? item : undefined;
|
|
2276
2380
|
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Some ecosystem extensions retain a `pi-<name>` slash-command alias beside
|
|
2383
|
+
* their unprefixed command. A1 presents the product-neutral command once while
|
|
2384
|
+
* leaving Pi's runner free to accept the compatibility alias when typed.
|
|
2385
|
+
*/
|
|
2386
|
+
function isPiPrefixedCompatibilityAlias(command, commands) {
|
|
2387
|
+
const name = stringProperty(command, "name");
|
|
2388
|
+
if (!name?.startsWith("pi-") || name.length === 3)
|
|
2389
|
+
return false;
|
|
2390
|
+
const canonicalName = name.slice(3);
|
|
2391
|
+
const description = stringProperty(command, "description");
|
|
2392
|
+
const sourcePath = extensionCommandSourcePath(command);
|
|
2393
|
+
return commands.some(candidate => candidate !== command
|
|
2394
|
+
&& stringProperty(candidate, "name") === canonicalName
|
|
2395
|
+
&& stringProperty(candidate, "description") === description
|
|
2396
|
+
&& extensionCommandSourcePath(candidate) === sourcePath);
|
|
2397
|
+
}
|
|
2398
|
+
function extensionCommandSourcePath(command) {
|
|
2399
|
+
return isRecord(command) ? stringProperty(command.sourceInfo, "path") : undefined;
|
|
2400
|
+
}
|
|
2277
2401
|
function compactResourceLabel(path) {
|
|
2278
2402
|
const segments = path.replaceAll("\\", "/").split("/").filter(Boolean);
|
|
2279
2403
|
return segments.at(-1) ?? path;
|
|
2280
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
|
+
}
|
|
2281
2435
|
function textFromContent(content) {
|
|
2282
2436
|
if (typeof content === "string")
|
|
2283
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;
|