@pi-unipi/btw 2.6.1 → 2.9.0

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.
Files changed (2) hide show
  1. package/extensions/btw.ts +34 -47
  2. package/package.json +12 -8
package/extensions/btw.ts CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  type ExtensionAPI,
27
27
  type ExtensionCommandContext,
28
28
  type ExtensionContext,
29
+ type ModelRuntime,
29
30
  type ResourceLoader,
30
31
  } from "@earendil-works/pi-coding-agent";
31
32
  import { type AssistantMessage, type Message, type ThinkingLevel as AiThinkingLevel, type UserMessage } from "@earendil-works/pi-ai";
@@ -161,6 +162,17 @@ function stripDynamicSystemPromptFooter(systemPrompt: string): string {
161
162
  .trim();
162
163
  }
163
164
 
165
+ /**
166
+ * Extract the canonical ModelRuntime from the extension-facing ModelRegistry
167
+ * facade. The SDK (>=0.84) exposes `modelRuntime` on AgentSession and expects
168
+ * it in CreateAgentSessionOptions; the extension context only hands us the
169
+ * ModelRegistry wrapper, whose `runtime` field is the same instance the
170
+ * parent session uses (agent-session wires `new ModelRegistry(this._modelRuntime)`).
171
+ */
172
+ function sessionModelRuntime(ctx: ExtensionCommandContext): ModelRuntime {
173
+ return (ctx.modelRegistry as unknown as { runtime: ModelRuntime }).runtime;
174
+ }
175
+
164
176
  function createBtwResourceLoader(
165
177
  ctx: ExtensionCommandContext,
166
178
  appendSystemPrompt: string[] = [BTW_SYSTEM_PROMPT],
@@ -175,7 +187,9 @@ function createBtwResourceLoader(
175
187
  getThemes: () => ({ themes: [], diagnostics: [] }),
176
188
  getAgentsFiles: () => ({ agentsFiles: [] }),
177
189
  getSystemPrompt: () => systemPrompt,
190
+ getSystemPromptSource: () => undefined,
178
191
  getAppendSystemPrompt: () => appendSystemPrompt,
192
+ getAppendSystemPromptSources: () => [],
179
193
  extendResources: () => {},
180
194
  reload: async () => {},
181
195
  };
@@ -606,9 +620,8 @@ function applyAssistantMessageToTranscript(
606
620
  message: AssistantMessage,
607
621
  streaming: boolean,
608
622
  ): void {
609
- const assistantMessage = message;
610
- const thinking = extractThinking(assistantMessage);
611
- const answer = extractMessageText(assistantMessage);
623
+ const thinking = extractThinking(message);
624
+ const answer = extractMessageText(message);
612
625
 
613
626
  if (thinking) {
614
627
  upsertTranscriptTextEntry(state, turnId, "thinking", thinking, streaming);
@@ -972,11 +985,6 @@ function getOverlayTitle(mode: BtwThreadMode): string {
972
985
 
973
986
  class BtwOverlayComponent extends Container implements Focusable {
974
987
  private readonly input: Input;
975
- private readonly transcript: Container;
976
- private readonly statusText: Text;
977
- private readonly modeText: Text;
978
- private readonly summaryText: Text;
979
- private readonly hintsText: Text;
980
988
  private readonly readTranscriptEntries: () => BtwTranscript;
981
989
  private readonly getStatus: () => string | null;
982
990
  private readonly getMode: () => BtwThreadMode;
@@ -1025,11 +1033,6 @@ class BtwOverlayComponent extends Container implements Focusable {
1025
1033
  this.onDismissCallback = onDismiss;
1026
1034
  this.onUnfocusCallback = onUnfocus;
1027
1035
 
1028
- this.modeText = new Text("", 1, 0);
1029
- this.summaryText = new Text("", 1, 0);
1030
- this.transcript = new Container();
1031
- this.statusText = new Text("", 1, 0);
1032
-
1033
1036
  this.input = new Input();
1034
1037
  this.input.onSubmit = (value) => {
1035
1038
  this.followTranscript = true;
@@ -1039,8 +1042,6 @@ class BtwOverlayComponent extends Container implements Focusable {
1039
1042
  this.onDismissCallback();
1040
1043
  };
1041
1044
 
1042
- this.hintsText = new Text("", 1, 0);
1043
-
1044
1045
  const originalHandleInput = this.input.handleInput.bind(this.input);
1045
1046
  this.input.handleInput = (data: string) => {
1046
1047
  if (keybindings.matches(data, "tui.select.cancel")) {
@@ -1092,14 +1093,14 @@ class BtwOverlayComponent extends Container implements Focusable {
1092
1093
  return;
1093
1094
  }
1094
1095
 
1095
- if (matchesKey(data, Key.pageUp)) {
1096
+ if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.ctrl("pageUp")) || matchesKey(data, Key.alt("pageUp"))) {
1096
1097
  this.followTranscript = false;
1097
1098
  this.transcriptScrollOffset = Math.max(0, this.transcriptScrollOffset - Math.max(1, this.transcriptViewportHeight - 1));
1098
1099
  this.tui.requestRender();
1099
1100
  return;
1100
1101
  }
1101
1102
 
1102
- if (matchesKey(data, Key.pageDown)) {
1103
+ if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("pageDown")) || matchesKey(data, Key.alt("pageDown"))) {
1103
1104
  this.transcriptScrollOffset += Math.max(1, this.transcriptViewportHeight - 1);
1104
1105
  this.tui.requestRender();
1105
1106
  return;
@@ -1182,30 +1183,18 @@ class BtwOverlayComponent extends Container implements Focusable {
1182
1183
  return this.input.getValue();
1183
1184
  }
1184
1185
 
1185
- getTranscriptEntries(): BtwTranscript {
1186
- return this.readTranscriptEntries().map((entry) => ({ ...entry }));
1187
- }
1188
-
1189
1186
  refresh(): void {
1190
1187
  this.modeTextValue = `${getOverlayTitle(this.getMode())} · hidden thread preserved`;
1191
- this.modeText.setText(this.modeTextValue);
1192
1188
  const entries = this.readTranscriptEntries();
1193
1189
  const exchanges = getCompletedExchangeCount(entries);
1194
1190
  const active = hasStreamingTranscriptEntry(entries) ? " · streaming" : " · idle";
1195
1191
  this.summaryTextValue = `${exchanges} exchange${exchanges === 1 ? "" : "s"}${active}`;
1196
- this.summaryText.setText(this.summaryTextValue);
1197
1192
 
1198
1193
  this.transcriptLines = buildOverlayTranscript(entries, this.theme);
1199
- this.transcript.clear();
1200
- for (const line of this.transcriptLines) {
1201
- this.transcript.addChild(new Text(line, 1, 0));
1202
- }
1203
1194
 
1204
1195
  const status = this.getStatus() ?? "Ready. Enter submits; Escape dismisses without clearing.";
1205
1196
  this.statusTextValue = status;
1206
- this.statusText.setText(this.statusTextValue);
1207
- this.hintsTextValue = "Enter submit · Alt+/ toggle focus · Escape dismiss · PgUp/PgDn scroll";
1208
- this.hintsText.setText(this.hintsTextValue);
1197
+ this.hintsTextValue = "Enter submit · Alt+/ toggle focus · Escape dismiss · PgUp/Ctrl+PgUp scroll up · PgDn/Ctrl+PgDn scroll down";
1209
1198
  this.tui.requestRender();
1210
1199
  }
1211
1200
  }
@@ -1364,7 +1353,7 @@ export default function (pi: ExtensionAPI) {
1364
1353
  const { session } = await createAgentSession({
1365
1354
  sessionManager: SessionManager.inMemory(),
1366
1355
  model: ctx.model,
1367
- modelRegistry: ctx.modelRegistry as AgentSession["modelRegistry"],
1356
+ modelRuntime: sessionModelRuntime(ctx),
1368
1357
  thinkingLevel: pi.getThinkingLevel() as SessionThinkingLevel,
1369
1358
  tools: ["read", "bash", "edit", "write"],
1370
1359
  resourceLoader: createBtwResourceLoader(ctx),
@@ -1498,7 +1487,7 @@ export default function (pi: ExtensionAPI) {
1498
1487
  });
1499
1488
  }
1500
1489
 
1501
- async function dispatchBtwCommand(name: string, args: string, ctx: ExtensionCommandContext): Promise<boolean> {
1490
+ async function dispatchBtwCommand(name: string, args: string, ctx: ExtensionCommandContext): Promise<void> {
1502
1491
  const trimmedArgs = args.trim();
1503
1492
 
1504
1493
  if (name === "btw") {
@@ -1506,7 +1495,7 @@ export default function (pi: ExtensionAPI) {
1506
1495
  if (!question) {
1507
1496
  await ensureBtwSession(ctx, pendingMode);
1508
1497
  await ensureOverlay(ctx);
1509
- return true;
1498
+ return;
1510
1499
  }
1511
1500
 
1512
1501
  if (pendingMode !== "contextual") {
@@ -1514,7 +1503,7 @@ export default function (pi: ExtensionAPI) {
1514
1503
  }
1515
1504
 
1516
1505
  await runBtw(ctx, question, save, "contextual");
1517
- return true;
1506
+ return;
1518
1507
  }
1519
1508
 
1520
1509
  if (name === "btw:tangent") {
@@ -1526,11 +1515,11 @@ export default function (pi: ExtensionAPI) {
1526
1515
  if (!question) {
1527
1516
  await ensureBtwSession(ctx, "tangent");
1528
1517
  await ensureOverlay(ctx);
1529
- return true;
1518
+ return;
1530
1519
  }
1531
1520
 
1532
1521
  await runBtw(ctx, question, save, "tangent");
1533
- return true;
1522
+ return;
1534
1523
  }
1535
1524
 
1536
1525
  if (name === "btw:new") {
@@ -1544,20 +1533,20 @@ export default function (pi: ExtensionAPI) {
1544
1533
  await ensureOverlay(ctx);
1545
1534
  notify(ctx, "Started a fresh BTW thread.", "info");
1546
1535
  }
1547
- return true;
1536
+ return;
1548
1537
  }
1549
1538
 
1550
1539
  if (name === "btw:clear") {
1551
1540
  await resetThread(ctx);
1552
1541
  dismissOverlay();
1553
1542
  notify(ctx, "Cleared BTW thread.", "info");
1554
- return true;
1543
+ return;
1555
1544
  }
1556
1545
 
1557
1546
  if (name === "btw:inject") {
1558
1547
  if (pendingThread.length === 0) {
1559
1548
  notify(ctx, "No BTW thread to inject.", "warning");
1560
- return true;
1549
+ return;
1561
1550
  }
1562
1551
 
1563
1552
  setOverlayStatus("⏳ injecting into the main session...", ctx);
@@ -1579,13 +1568,13 @@ export default function (pi: ExtensionAPI) {
1579
1568
  setOverlayStatus("Inject failed. Thread preserved for retry or summarize.", ctx);
1580
1569
  notify(ctx, error instanceof Error ? error.message : String(error), "error");
1581
1570
  }
1582
- return true;
1571
+ return;
1583
1572
  }
1584
1573
 
1585
1574
  if (name === "btw:summarize") {
1586
1575
  if (pendingThread.length === 0) {
1587
1576
  notify(ctx, "No BTW thread to summarize.", "warning");
1588
- return true;
1577
+ return;
1589
1578
  }
1590
1579
 
1591
1580
  setOverlayStatus("⏳ summarizing...", ctx);
@@ -1608,10 +1597,8 @@ export default function (pi: ExtensionAPI) {
1608
1597
  setOverlayStatus("Summarize failed. Thread preserved for retry or injection.", ctx);
1609
1598
  notify(ctx, error instanceof Error ? error.message : String(error), "error");
1610
1599
  }
1611
- return true;
1600
+ return;
1612
1601
  }
1613
-
1614
- return false;
1615
1602
  }
1616
1603
 
1617
1604
  function parseOverlayBtwCommand(value: string): { name: string; args: string } | null {
@@ -1817,7 +1804,7 @@ export default function (pi: ExtensionAPI) {
1817
1804
 
1818
1805
  async function getBtwHandoffThread(
1819
1806
  ctx: ExtensionCommandContext,
1820
- ): Promise<{ sessionRuntime: BtwSessionRuntime | null; thread: BtwHandoffExchange[] }> {
1807
+ ): Promise<{ thread: BtwHandoffExchange[] }> {
1821
1808
  const sessionRuntime = activeBtwSession ?? (await ensureBtwSession(ctx, pendingMode));
1822
1809
  const thread = sessionRuntime ? extractBtwHandoffThread(sessionRuntime) : [];
1823
1810
  const resolvedThread = thread.length > 0 ? thread : getPendingThreadForHandoff();
@@ -1826,7 +1813,7 @@ export default function (pi: ExtensionAPI) {
1826
1813
  throw new Error("No BTW thread available for handoff.");
1827
1814
  }
1828
1815
 
1829
- return { sessionRuntime, thread: resolvedThread };
1816
+ return { thread: resolvedThread };
1830
1817
  }
1831
1818
 
1832
1819
  async function summarizeThread(ctx: ExtensionCommandContext, thread: BtwHandoffExchange[]): Promise<string> {
@@ -1843,7 +1830,7 @@ export default function (pi: ExtensionAPI) {
1843
1830
  const { session } = await createAgentSession({
1844
1831
  sessionManager: SessionManager.inMemory(),
1845
1832
  model,
1846
- modelRegistry: ctx.modelRegistry as AgentSession["modelRegistry"],
1833
+ modelRuntime: sessionModelRuntime(ctx),
1847
1834
  thinkingLevel: "off",
1848
1835
  tools: [],
1849
1836
  resourceLoader: createBtwResourceLoader(ctx, [BTW_SUMMARIZE_SYSTEM_PROMPT]),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/btw",
3
- "version": "2.6.1",
4
- "description": "A pi extension for parallel side conversations with /unipi:btw part of the Unipi suite",
3
+ "version": "2.9.0",
4
+ "description": "A pi extension for parallel side conversations with /unipi:btw \u2014 part of the Unipi suite",
5
5
  "type": "module",
6
6
  "main": "extensions/btw.ts",
7
7
  "license": "MIT",
@@ -32,16 +32,20 @@
32
32
  "access": "public"
33
33
  },
34
34
  "peerDependencies": {
35
- "@earendil-works/pi-ai": "^0.80.0",
36
- "@earendil-works/pi-coding-agent": "^0.80.0",
37
- "@earendil-works/pi-tui": "^0.80.0"
35
+ "@earendil-works/pi-ai": "^0.84.0",
36
+ "@earendil-works/pi-coding-agent": "^0.84.0",
37
+ "@earendil-works/pi-tui": "^0.84.0"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "2.6.1"
40
+ "@pi-unipi/core": "2.9.0"
41
41
  },
42
42
  "pi": {
43
- "extensions": [],
44
- "skills": [],
43
+ "extensions": [
44
+ "./extensions/btw.ts"
45
+ ],
46
+ "skills": [
47
+ "./skills"
48
+ ],
45
49
  "prompts": [],
46
50
  "themes": []
47
51
  }